lua-users home
lua-l archive

Re: validating Lua data

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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

AltStyle によって変換されたページ (->オリジナル) /