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 )
154 -- helper function to construct identifiers for given elements
155 local function _id( c, t )
156 if c == TYPE_SECTION then
157 return string.format(
159 scheme, t.name or '?' )
160 elseif c == TYPE_VARIABLE then
161 return string.format(
162 "variable '%s.%s.%s'",
163 scheme, t.section or '?.?', t.name or '?' )
164 elseif c == TYPE_ENUM then
165 return string.format(
167 scheme, t.variable or '?.?.?', t.value or '?' )
171 -- helper function to check for required fields
172 local function _req( c, t, r )
173 for i, v in ipairs(r) do
174 _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
178 -- helper function to validate references
179 local function _ref( c, t )
181 if c == TYPE_SECTION then
183 elseif c == TYPE_VARIABLE then
185 elseif c == TYPE_ENUM then
189 local r = luci.util.split( t[k], "." )
190 r[1] = ( #r[1] > 0 and r[1] or scheme )
192 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
197 -- Step 1: get all sections
198 for i, conf in ipairs( schemes ) do
199 for k, v in pairs( conf ) do
200 if v['.type'] == 'section' then
202 _req( TYPE_SECTION, v, { "name", "package" } )
204 local r = _ref( TYPE_SECTION, v )
206 self.packages[r[1]] =
207 self.packages[r[1]] or {
212 local p = self.packages[r[1]]
213 p.sections[v.name] = p.sections[v.name] or { }
214 p.variables[v.name] = p.variables[v.name] or { }
216 local s = p.sections[v.name]
218 for k, v2 in pairs(v) do
219 if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
220 if k:match("^depends") then
221 s["depends"] = _assert(
222 self:_read_depency( v2, s["depends"] ),
223 "Section '%s' in scheme '%s' has malformed " ..
224 "depency specification in '%s'",
225 v.name or '<nil>', scheme or '<nil>', k
236 -- Step 2: get all variables
237 for i, conf in ipairs( schemes ) do
238 for k, v in pairs( conf ) do
239 if v['.type'] == "variable" then
241 _req( TYPE_VARIABLE, v, { "name", "type", "section" } )
243 local r = _ref( TYPE_VARIABLE, v )
245 local p = _assert( self.packages[r[1]],
246 "Variable '%s' in scheme '%s' references unknown package '%s'",
247 v.name, scheme, r[1] )
249 local s = _assert( p.variables[r[2]],
250 "Variable '%s' in scheme '%s' references unknown section '%s'",
251 v.name, scheme, r[2] )
253 s[v.name] = s[v.name] or { }
257 for k, v in pairs(v) do
258 if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
259 if k:match("^depends") then
260 t["depends"] = _assert(
261 self:_read_depency( v, t["depends"] ),
262 "Variable '%s' in scheme '%s' has malformed " ..
263 "depency specification in '%s'",
266 elseif k:match("^validator") then
267 t["validators"] = _assert(
268 self:_read_validator( v, t["validators"] ),
269 "Variable '%s' in scheme '%s' has malformed " ..
270 "validator specification in '%s'",
282 -- Step 3: get all enums
283 for i, conf in ipairs( schemes ) do
284 for k, v in pairs( conf ) do
285 if v['.type'] == "enum" then
287 _req( TYPE_ENUM, v, { "value", "variable" } )
289 local r = _ref( TYPE_ENUM, v )
291 local p = _assert( self.packages[r[1]],
292 "Enum '%s' in scheme '%s' references unknown package '%s'",
293 v.value, scheme, r[1] )
295 local s = _assert( p.variables[r[2]],
296 "Enum '%s' in scheme '%s' references unknown section '%s'",
297 v.value, scheme, r[2] )
299 local t = _assert( s[r[3]],
300 "Enum '%s' in scheme '%s', section '%s' references " ..
301 "unknown variable '%s'",
302 v.value, scheme, r[2], r[3] )
304 _assert( t.type == "enum",
305 "Enum '%s' in scheme '%s', section '%s' references " ..
306 "variable '%s' with non enum type '%s'",
307 v.value, scheme, r[2], r[3], t.type )
310 t.values = { [v.value] = v.title or v.value }
312 t.values[v.value] = v.title or v.value
316 _assert( not t.default,
317 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
318 "the default value of variable '%s'",
319 v.value, scheme, r[2], v.variable )
330 -- Read a depency specification
331 function UVL._read_depency( self, value, deps )
332 local parts = luci.util.split( value, "%s*,%s*", nil, true )
333 local condition = { }
335 for i, val in ipairs(parts) do
336 local k, v = unpack(luci.util.split( val, "%s*=%s*", nil, true ))
339 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
340 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
341 k:match("^%$?[a-zA-Z0-9_]+$")
343 condition[k] = v or true
352 table.insert( deps, condition )
358 -- Read a validator specification
359 function UVL._read_validator( self, value, validators )
362 if value and value:match("/") and self.datatypes.file(value) then
365 validator = self:_resolve_function( value )
369 if not validators then
370 validators = { validator }
372 table.insert( validators, validator )
379 -- Resolve given path
380 function UVL._resolve_function( self, value )
381 local path = luci.util.split(value, ".")
384 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
386 for j=i+1, #path-1 do
387 if not type(mod) == "table" then
395 mod = type(mod) == "table" and mod[path[#path]] or nil
396 if type(mod) == "function" then