I think the most straightforward solution is to encode the graph structure in some dumb tables and use a constructor function to convert those tables into your preferred internal graph data structure. The constructor can also do error handling in a straightforward manner, since it can look at all the input (as opposed to something localized like your ref function).
Separating the input representation from the final result also lets you have more then one kind of representation. For example:
local graph_1 = adjacency_list {
a = {'b'},
b = {'a', 'c'},
c = {'a'},
}
local graph_2 = list_of_edges {
{'a', 'b'},
{'b', 'a'},
{'b', 'c'},
{'c', 'a'},
}
local graph_2 = adjacency_matrix {
{0, 1, 0},
{1, 0, 1},
{1, 0, 0},
}