lua-users home
lua-l archive

[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