[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: validating Lua data
- From: Norman Ramsey <nr@...>
- Date: Fri, 29 Dec 2006 23:32:01 -0500
All these suggestions are good ones.
But I think the real intellectual work lies in the design
of the schema language. How, for example, should I specify a table
in which
field track is optional but if present contains
a list of which each element is
a table containing the required keys
'lat', which is a number, and
'lon', which is a number, and
the optional key 'ele', which is a number, and
the optional key 'time' which is a time
where
a time is a string which can be be validated with the function
is_iso_8601_time
???
I've attached something that suits my simple needs; the example above
is
require 'validate'
local ty = validate.type
local time = ty.by_function(is_iso_8601_time, 'time', 'is_iso_8601_time')
return ty.table {
track = ty.optional(ty.list(ty.table {
lat = ty.number,
lon = ty.number,
ele = ty.optional(ty.number),
time = ty.optional(ty.time),
}))}
Norman
local luatype, getmetatable, pairs, tostring, table, string =
type, getmetatable, pairs, tostring, table, string
local function eprintf(...) return io.stderr:write(string.format(...)) end
local function eprintf(...) end
module 'validate'
--[[ How to use:
1. Construct a type tau using the functions in table validate.type
2. Calling validate.validate(tau, x) returns
true -- if validation is successful
false, expected, got
-- if unsuccessful, a description of what we expected
-- and what we actually found
Here's a synopsis of the ways to construct a type:
tau ::= nil | boolean | number | string | userdata
| table(t, exact) -- type for each field in t, exact if no extra keys allowed
| list(tau) -- keys 1 to #x validate against tau
| optional(tau) -- validates against tau or is nil
| eq(v) -- equal to value v
| having_metatable(meta)
| union(tau, tau) -- either of two types
| inter(tau, tau) -- both of two types (e.g., table and list)
| by_function(f, expected, fname) -- validate using named function
-- e.g., by_function(is_date, 'date', 'is_date')
]]
local function basetype(s)
return function(x)
eprintf('checking basetype %s against value of type %s\n', s, luatype(x))
if luatype(x) == s then
return true
else
return false, s, luatype(x)
end
end
end
type = { ['nil'] = basetype 'nil', boolean = basetype 'boolean',
number = basetype 'number', string = basetype 'string',
userdata = basetype 'userdata' }
function type.with_metatable(m)
return function(x)
local meta = getmetatable(x)
if meta == m then
return true
else
local badmeta =
meta and 'having metatable ' .. tostring(meta) or 'with no metatable'
return false, 'having metatable ' .. tostring(m), badmeta
end
end
end
function type.union(t1, t2)
return function(x)
eprintf('checking union t1 against value of type %s\n', luatype(x))
local ok1, ex1, x1 = t1(x)
if ok1 then return true end
eprintf('checking union t2\n')
local ok2, ex2, x2 = t2(x)
if ok2 then return true end
local expected
if t1 == type['nil'] then expected = 'optional ' .. ex2
elseif t2 == type['nil'] then expected = 'optional ' .. ex1
else expected = table.concat { '(', ex1, ') or (', ex2, ')' }
end
return false, expected, x2
end
end
function type.optional(t)
return type.union(type['nil'], t)
end
function type.inter(t1, t2)
return function(x)
eprintf('checking intersection\n')
local ok1, ex1, x1 = t1(x,context)
if not ok1 then return false, ex1, x1 end
local ok2, ex2, x2 = t2(x)
if not ok2 then return false, ex2, x2 end
return true
end
end
function type.list(t)
return function(x)
eprintf('checking list\n')
if luatype(x) == 'table' then
for i = 1, #x do
local ok, ex, bad = t(x[i])
if not ok then
return false, 'list of ' .. ex, 'list containing a ' .. bad
end
end
eprintf('done checking list\n')
return true
else
return false, 'list', luatype(x)
end
end
end
local function contents(x)
local t = { }
local function short(v)
local s = tostring(v)
if string.len(s) > 10 then
s = string.sub(s, 1, 7) .. '...'
end
return s
end
for k, v in pairs(x) do table.insert(t, tostring(k) .. ' = ' .. short(v)) end
return ' (table is { ' .. table.concat(t, ', ') .. ' })'
end
function type.table(tbl, exact)
return function(x)
eprintf('checking table\n')
if luatype(x) == 'table' then
for k, t in pairs(tbl) do
eprintf('checking field %s\n', tostring(k))
local ok, ex, bad = t(x[k])
if not ok then
return false,
'table containing ' .. ex .. ' in field ' .. tostring(k),
'table containing ' .. bad .. ' in field ' .. tostring(k) .. contents(x)
end
end
if exact then
for k in pairs(x) do
if tbl[k] ~= nil then
return false, 'table not containing field ' .. tostring(k),
'table with ' .. luatype(x[k]) .. ' in field ' .. tostring(k)
end
end
end
eprintf('done checking table\n')
return true
else
return false, 'table', luatype(x)
end
end
end
function type.eq(v)
return function(x)
if x == v then
return true
else
return false, tostring(v), 'the value ' .. tostring(x)
end
end
end
function type.by_function(f, expected, fname)
fname = fname or tostring(f)
return function(x)
eprintf('Checking function %s\n', fname)
local ok, expected2, got = f(x)
if f(x) then
return true
else
return false, expected or expected2 or 'validated by function ' .. fname,
'a ' .. (got or luatype(x)) .. ' that function ' .. fname .. ' would not accept'
end
end
end
function validate(t, x) return t(x) end