3 UCI Validation Layer - Main Library
4 (c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
5 (c) 2008 Steven Barth <steven@midlink.org>
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
11 http://www.apache.org/licenses/LICENSE-2.0
17 module( "luci.uvl", package.seeall )
21 require("luci.model.uci")
22 require("luci.uvl.datatypes")
29 local default_schemedir = "/etc/scheme"
30 local function _assert( condition, fmt, ... )
32 return assert( nil, string.format( fmt, ... ) )
38 UVL = luci.util.class()
40 function UVL.__init__( self, schemedir )
42 self.schemedir = schemedir or default_schemedir
44 self.uci = luci.model.uci
45 self.datatypes = luci.uvl.datatypes
48 --- Validate given configuration.
49 -- @param config Name of the configuration to validate
50 -- @param scheme Scheme to validate against (optional)
51 -- @return Boolean indicating weather the given config validates
52 -- @return String containing the reason for errors (if any)
53 function UVL.validate( self, config, scheme )
56 return false, "No scheme found"
59 for k, v in pairs( config ) do
60 local ok, err = self:validate_section( config, k, scheme )
70 --- Validate given section of given configuration.
71 -- @param config Name of the configuration to validate
72 -- @param section Key of the section to validate
73 -- @param scheme Scheme to validate against
74 -- @return Boolean indicating weather the given config validates
75 -- @return String containing the reason for errors (if any)
76 function UVL.validate_section( self, config, section, scheme )
79 return false, "No scheme found"
82 for k, v in pairs( config[section] ) do
83 local ok, err = self:validate_option( config, section, k, scheme )
93 --- Validate given option within section of given configuration.
94 -- @param config Name of the configuration to validate
95 -- @param section Key of the section to validate
96 -- @param option Name of the option to validate
97 -- @param scheme Scheme to validate against
98 -- @return Boolean indicating weather the given config validates
99 -- @return String containing the reason for errors (if any)
100 function UVL.validate_option( self, config, section, option, scheme )
102 if type(config) == "string" then
103 config = { ["variables"] = { [section] = { [option] = config } } }
107 return false, "No scheme found"
110 local sv = scheme.variables[section]
111 if not sv then return false, "Requested section not found in scheme" end
114 if not sv then return false, "Requested option not found in scheme" end
116 if not ( config[section] and config[section][option] ) and sv.required then
117 return false, "Mandatory variable doesn't have a value"
121 if self.datatypes[sv.type] then
122 if not self.datatypes[sv.type]( config[section][option] ) then
123 return false, "Value of given option doesn't validate"
126 return false, "Unknown datatype '" .. sv.type .. "' encountered"
133 --- Find all parts of given scheme and construct validation tree
134 -- @param scheme Name of the scheme to parse
135 -- @return Parsed scheme
136 function UVL.read_scheme( self, scheme )
139 for i, file in ipairs( luci.fs.glob(self.schemedir .. '/*/' .. scheme) ) do
140 _assert( luci.fs.access(file), "Can't access file '%s'", file )
142 self.uci.set_confdir( luci.fs.dirname(file) )
143 self.uci.load( luci.fs.basename(file) )
145 table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
148 return self:_read_scheme_parts( scheme, schemes )
151 -- Process all given parts and construct validation tree
152 function UVL._read_scheme_parts( self, scheme, schemes )
156 -- helper function to construct identifiers for given elements
157 local function _id( c, t )
158 if c == TYPE_SECTION then
159 return string.format(
161 scheme, t.name or '?' )
162 elseif c == TYPE_VARIABLE then
163 return string.format(
164 "variable '%s.%s.%s'",
165 scheme, t.section or '?.?', t.name or '?' )
166 elseif c == TYPE_ENUM then
167 return string.format(
169 scheme, t.variable or '?.?.?', t.value or '?' )
173 -- helper function to check for required fields
174 local function _req( c, t, r )
175 for i, v in ipairs(r) do
176 _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
180 -- helper function to validate references
181 local function _ref( c, t )
183 if c == TYPE_SECTION then
185 elseif c == TYPE_VARIABLE then
187 elseif c == TYPE_ENUM then
191 local r = luci.util.split( t[k], "." )
192 r[1] = ( #r[1] > 0 and r[1] or scheme )
194 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
199 -- Step 1: get all sections
200 for i, conf in ipairs( schemes ) do
201 for k, v in pairs( conf ) do
202 if v['.type'] == 'section' then
204 _req( TYPE_SECTION, v, { "name", "package" } )
206 local r = _ref( TYPE_SECTION, v )
208 stbl.packages[r[1]] =
209 stbl.packages[r[1]] or {
214 local p = stbl.packages[r[1]]
215 p.sections[v.name] = p.sections[v.name] or { }
216 p.variables[v.name] = p.variables[v.name] or { }
218 local s = p.sections[v.name]
220 for k, v in pairs(v) do
221 if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
222 if k:match("^depends") then
223 s["depends"] = _assert(
224 self:_read_depency( v, s["depends"] ),
225 "Section '%s' in scheme '%s' has malformed " ..
226 "depency specification in '%s'",
238 -- Step 2: get all variables
239 for i, conf in ipairs( schemes ) do
240 for k, v in pairs( conf ) do
241 if v['.type'] == "variable" then
243 _req( TYPE_VARIABLE, v, { "name", "type", "section" } )
245 local r = _ref( TYPE_VARIABLE, v )
247 local p = _assert( stbl.packages[r[1]],
248 "Variable '%s' in scheme '%s' references unknown package '%s'",
249 v.name, scheme, r[1] )
251 local s = _assert( p.variables[r[2]],
252 "Variable '%s' in scheme '%s' references unknown section '%s'",
253 v.name, scheme, r[2] )
255 s[v.name] = s[v.name] or { }
259 for k, v in pairs(v) do
260 if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
261 if k:match("^depends") then
262 t["depends"] = _assert(
263 self:_read_depency( v, t["depends"] ),
264 "Variable '%s' in scheme '%s' has malformed " ..
265 "depency specification in '%s'",
268 elseif k:match("^validator") then
269 t["validators"] = _assert(
270 self:_read_validator( v, t["validators"] ),
271 "Variable '%s' in scheme '%s' has malformed " ..
272 "validator specification in '%s'",
284 -- Step 3: get all enums
285 for i, conf in ipairs( schemes ) do
286 for k, v in pairs( conf ) do
287 if v['.type'] == "enum" then
289 _req( TYPE_ENUM, v, { "value", "variable" } )
291 local r = _ref( TYPE_ENUM, v )
293 local p = _assert( stbl.packages[r[1]],
294 "Enum '%s' in scheme '%s' references unknown package '%s'",
295 v.value, scheme, r[1] )
297 local s = _assert( p.variables[r[2]],
298 "Enum '%s' in scheme '%s' references unknown section '%s'",
299 v.value, scheme, r[2] )
301 local t = _assert( s[r[3]],
302 "Enum '%s' in scheme '%s', section '%s' references " ..
303 "unknown variable '%s'",
304 v.value, scheme, r[2], r[3] )
306 _assert( t.type == "enum",
307 "Enum '%s' in scheme '%s', section '%s' references " ..
308 "variable '%s' with non enum type '%s'",
309 v.value, scheme, r[2], r[3], t.type )
312 t.values = { [v.value] = v.title or v.value }
314 t.values[v.value] = v.title or v.value
318 _assert( not t.default,
319 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
320 "the default value of variable '%s'",
321 v.value, scheme, r[2], v.variable )
332 -- Read a depency specification
333 function UVL._read_depency( self, value, deps )
334 local parts = luci.util.split( value, "%s*;%s*" )
335 local condition = { }
337 for i, val in ipairs(parts) do
338 local k, v = unpack(luci.util.split( val, "%s*=%s*" ))
341 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$")
342 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
343 k:match("^%$?[a-zA-Z0-9_]+$") or
345 condition[k] = v or true
354 table.insert( deps, condition )
360 -- Read a validator specification
361 function UVL._read_validator( self, value, validators )
364 if value and value:match("/") and self.datatypes.file(value) then
367 validator = self:_resolve_function( value )
371 if not validators then
372 validators = { validator }
374 table.insert( validators, validator )
381 -- Resolve given path
382 function UVL._resolve_function( self, value )
383 local path = luci.util.split(value, ".")
386 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
388 for j=i+1, #path-1 do
389 if not type(mod) == "table" then
397 mod = type(mod) == "table" and mod[path[#path]] or nil
398 if type(mod) == "function" then