* luci/libs: uvl - cleanup round #2, reinitroduce validate_section() and validate_opt...
[project/luci.git] / libs / uvl / luasrc / uvl.lua
1 --[[
2
3 UCI Validation Layer - Main Library
4 (c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
5 (c) 2008 Steven Barth <steven@midlink.org>
6
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
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 ]]--
16
17 module( "luci.uvl", package.seeall )
18
19 require("luci.fs")
20 require("luci.util")
21 require("luci.model.uci")
22 require("luci.uvl.datatypes")
23 --require("luci.uvl.validation")
24 require("luci.uvl.dependencies")
25
26 TYPE_SECTION = 0x01
27 TYPE_VARIABLE = 0x02
28 TYPE_ENUM = 0x03
29
30 STRICT_UNKNOWN_SECTIONS = true
31 STRICT_UNKNOWN_OPTIONS = true
32
33
34 local default_schemedir = "/etc/scheme"
35
36 local function _assert( condition, fmt, ... )
37 if not condition then
38 return assert( nil, string.format( fmt, ... ) )
39 else
40 return condition
41 end
42 end
43
44 UVL = luci.util.class()
45
46 function UVL.__init__( self, schemedir )
47 self.schemedir = schemedir or default_schemedir
48 self.packages = { }
49 self.beenthere = { }
50 self.uci = luci.model.uci
51 self.datatypes = luci.uvl.datatypes
52 end
53
54 function UVL._keys( self, tbl )
55 local keys = { }
56 if tbl then
57 for k, _ in luci.util.kspairs(tbl) do
58 table.insert( keys, k )
59 end
60 end
61 return keys
62 end
63
64
65 --- Validate given configuration.
66 -- @param config Name of the configuration to validate
67 -- @param scheme Scheme to validate against (optional)
68 -- @return Boolean indicating weather the given config validates
69 -- @return String containing the reason for errors (if any)
70 function UVL.validate( self, config )
71
72 self.uci.set_confdir( self.uci.confdir_default )
73 self.uci.load( config )
74
75 local co = self.uci.get_all( config )
76
77 local function _uci_foreach( type, func )
78 local ok, err
79 for k, v in pairs(co) do
80 if co[k]['.type'] == type then
81 ok, err = func( k, v )
82 if not ok then break end
83 end
84 end
85 return ok, err
86 end
87
88 for k, v in pairs( self.packages[config].sections ) do
89 local ok, err = _uci_foreach( k,
90 function(s)
91 local sect = luci.uvl.section( self, co, k, config, s )
92 return self:_validate_section( sect )
93 end
94 )
95 if not ok then return false, err end
96 end
97
98 if STRICT_UNKNOWN_SECTIONS then
99 for k, v in pairs(co) do
100 if not self.beenthere[config..'.'..k] then
101 return false, "Section '" .. config .. '.' .. co[k]['.type'] ..
102 "' not found in scheme"
103 end
104 end
105 end
106
107 return true, nil
108 end
109
110 function UVL.validate_section( self, config, section )
111 self.uci.set_confdir( self.uci.confdir_default )
112 self.uci.load( config )
113
114 local co = self.uci.get_all( config )
115 if co[section] then
116 return self:_validate_section( luci.uvl.section(
117 self, co, co[section]['.type'], config, section
118 ) )
119 else
120 return false, "Section '" .. config .. '.' .. section ..
121 "' not found in config. Nothing to do."
122 end
123 end
124
125 function UVL.validate_option( self, config, section, option )
126 self.uci.set_confdir( self.uci.confdir_default )
127 self.uci.load( config )
128
129 local co = self.uci.get_all( config )
130 if co[section] and co[section][option] then
131 return self:_validate_option( luci.uvl.option(
132 self, co, co[section]['.type'], config, section, option
133 ) )
134 else
135 return false, "Option '" ..
136 config .. '.' .. section .. '.' .. option ..
137 "' not found in config. Nothing to do."
138 end
139 end
140
141 --- Validate given section of given configuration.
142 -- @param config Name of the configuration to validate
143 -- @param section Key of the section to validate
144 -- @param scheme Scheme to validate against
145 -- @return Boolean indicating weather the given config validates
146 -- @return String containing the reason for errors (if any)
147 function UVL._validate_section( self, section )
148
149 if section:values() then
150
151 for _, v in ipairs(section:variables()) do
152 local ok, err = self:_validate_option( v )
153
154 if not ok then
155 return ok, err
156 end
157 end
158
159 local ok, err = luci.uvl.dependencies.check( self, section )
160
161 if not ok then
162 return false, "All possible dependencies failed"
163 end
164 else
165 print( "Error, scheme section '" .. section:sid() .. "' not found in data" )
166 end
167
168 if STRICT_UNKNOWN_OPTIONS and not section:section().dynamic then
169 for k, v in pairs(section:values()) do
170 if k:sub(1,1) ~= "." and not self.beenthere[
171 section:cid() .. '.' .. k
172 ] then
173 return false, "Option '" .. section:sid() .. '.' .. k ..
174 "' not found in scheme"
175 end
176 end
177 end
178
179 return true, nil
180 end
181
182 --- Validate given option within section of given configuration.
183 -- @param config Name of the configuration to validate
184 -- @param section Key of the section to validate
185 -- @param option Name of the option to validate
186 -- @param scheme Scheme to validate against
187 -- @return Boolean indicating weather the given config validates
188 -- @return String containing the reason for errors (if any)
189 function UVL._validate_option( self, option, nodeps )
190
191 if not option:option() and
192 not ( option:section() and option:section().dynamic )
193 then
194 return false, "Requested option '" .. option:sid() ..
195 "' not found in scheme"
196 end
197
198 if option:option() then
199 if option:option().required and not option:value() then
200 return false, "Mandatory variable '" .. option:cid() ..
201 "' doesn't have a value"
202 end
203
204 if option:option().type == "enum" and option:value() then
205 if not option:option().values or
206 not option:option().values[option:value()]
207 then
208 return false, "Value '" .. ( option:value() or '<nil>' ) ..
209 "' of given option '" .. option:cid() ..
210 "' is not defined in enum { " ..
211 table.concat(self:_keys(option:option().values),", ") ..
212 " }"
213 end
214 end
215
216 if option:option().datatype and option:value() then
217 if self.datatypes[option:option().datatype] then
218 if not self.datatypes[option:option().datatype](
219 option:value()
220 ) then
221 return false, "Value '" .. ( option:value() or '<nil>' ) ..
222 "' of given option '" .. option:cid() ..
223 "' doesn't validate as datatype '" ..
224 option:option().datatype .. "'"
225 end
226 else
227 return false, "Unknown datatype '" ..
228 option:option().datatype .. "' encountered"
229 end
230 end
231
232 if not nodeps then
233 return luci.uvl.dependencies.check( self, option )
234 end
235 end
236
237 return true, nil
238 end
239
240 --- Find all parts of given scheme and construct validation tree
241 -- @param scheme Name of the scheme to parse
242 -- @return Parsed scheme
243 function UVL.read_scheme( self, scheme )
244 local schemes = { }
245
246 for i, file in ipairs( luci.fs.glob(self.schemedir .. '/*/' .. scheme) ) do
247 _assert( luci.fs.access(file), "Can't access file '%s'", file )
248
249 self.uci.set_confdir( luci.fs.dirname(file) )
250 self.uci.load( luci.fs.basename(file) )
251
252 table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
253 end
254
255 return self:_read_scheme_parts( scheme, schemes )
256 end
257
258 -- Process all given parts and construct validation tree
259 function UVL._read_scheme_parts( self, scheme, schemes )
260
261 -- helper function to construct identifiers for given elements
262 local function _id( c, t )
263 if c == TYPE_SECTION then
264 return string.format(
265 "section '%s.%s'",
266 scheme, t.name or '?' )
267 elseif c == TYPE_VARIABLE then
268 return string.format(
269 "variable '%s.%s.%s'",
270 scheme, t.section or '?.?', t.name or '?' )
271 elseif c == TYPE_ENUM then
272 return string.format(
273 "enum '%s.%s.%s'",
274 scheme, t.variable or '?.?.?', t.value or '?' )
275 end
276 end
277
278 -- helper function to check for required fields
279 local function _req( c, t, r )
280 for i, v in ipairs(r) do
281 _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
282 end
283 end
284
285 -- helper function to validate references
286 local function _ref( c, t )
287 local k
288 if c == TYPE_SECTION then
289 k = "package"
290 elseif c == TYPE_VARIABLE then
291 k = "section"
292 elseif c == TYPE_ENUM then
293 k = "variable"
294 end
295
296 local r = luci.util.split( t[k], "." )
297 r[1] = ( #r[1] > 0 and r[1] or scheme )
298
299 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
300
301 return r
302 end
303
304 -- Step 1: get all sections
305 for i, conf in ipairs( schemes ) do
306 for k, v in pairs( conf ) do
307 if v['.type'] == 'section' then
308
309 _req( TYPE_SECTION, v, { "name", "package" } )
310
311 local r = _ref( TYPE_SECTION, v )
312
313 self.packages[r[1]] =
314 self.packages[r[1]] or {
315 ["sections"] = { };
316 ["variables"] = { };
317 }
318
319 local p = self.packages[r[1]]
320 p.sections[v.name] = p.sections[v.name] or { }
321 p.variables[v.name] = p.variables[v.name] or { }
322
323 local s = p.sections[v.name]
324
325 for k, v2 in pairs(v) do
326 if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
327 if k:match("^depends") then
328 s["depends"] = _assert(
329 self:_read_dependency( v2, s["depends"] ),
330 "Section '%s' in scheme '%s' has malformed " ..
331 "dependency specification in '%s'",
332 v.name or '<nil>', scheme or '<nil>', k
333 )
334 else
335 s[k] = v2
336 end
337 end
338 end
339 end
340 end
341 end
342
343 -- Step 2: get all variables
344 for i, conf in ipairs( schemes ) do
345 for k, v in pairs( conf ) do
346 if v['.type'] == "variable" then
347
348 _req( TYPE_VARIABLE, v, { "name", "section" } )
349
350 local r = _ref( TYPE_VARIABLE, v )
351
352 local p = _assert( self.packages[r[1]],
353 "Variable '%s' in scheme '%s' references unknown package '%s'",
354 v.name, scheme, r[1] )
355
356 local s = _assert( p.variables[r[2]],
357 "Variable '%s' in scheme '%s' references unknown section '%s'",
358 v.name, scheme, r[2] )
359
360 s[v.name] = s[v.name] or { }
361
362 local t = s[v.name]
363
364 for k, v in pairs(v) do
365 if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
366 if k:match("^depends") then
367 t["depends"] = _assert(
368 self:_read_dependency( v, t["depends"] ),
369 "Variable '%s' in scheme '%s' has malformed " ..
370 "dependency specification in '%s'",
371 v.name, scheme, k
372 )
373 elseif k:match("^validator") then
374 t["validators"] = _assert(
375 self:_read_validator( v, t["validators"] ),
376 "Variable '%s' in scheme '%s' has malformed " ..
377 "validator specification in '%s'",
378 v.name, scheme, k
379 )
380 else
381 t[k] = v
382 end
383 end
384 end
385 end
386 end
387 end
388
389 -- Step 3: get all enums
390 for i, conf in ipairs( schemes ) do
391 for k, v in pairs( conf ) do
392 if v['.type'] == "enum" then
393
394 _req( TYPE_ENUM, v, { "value", "variable" } )
395
396 local r = _ref( TYPE_ENUM, v )
397
398 local p = _assert( self.packages[r[1]],
399 "Enum '%s' in scheme '%s' references unknown package '%s'",
400 v.value, scheme, r[1] )
401
402 local s = _assert( p.variables[r[2]],
403 "Enum '%s' in scheme '%s' references unknown section '%s'",
404 v.value, scheme, r[2] )
405
406 local t = _assert( s[r[3]],
407 "Enum '%s' in scheme '%s', section '%s' references " ..
408 "unknown variable '%s'",
409 v.value, scheme, r[2], r[3] )
410
411 _assert( t.type == "enum",
412 "Enum '%s' in scheme '%s', section '%s' references " ..
413 "variable '%s' with non enum type '%s'",
414 v.value, scheme, r[2], r[3], t.type )
415
416 if not t.values then
417 t.values = { [v.value] = v.title or v.value }
418 else
419 t.values[v.value] = v.title or v.value
420 end
421
422 if v.default then
423 _assert( not t.default,
424 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
425 "the default value of variable '%s'",
426 v.value, scheme, r[2], v.variable )
427
428 t.default = v.value
429 end
430 end
431 end
432 end
433
434 return self
435 end
436
437 -- Read a dependency specification
438 function UVL._read_dependency( self, value, deps )
439 local parts = luci.util.split( value, "%s*,%s*", nil, true )
440 local condition = { }
441
442 for i, val in ipairs(parts) do
443 local k, v = unpack(luci.util.split( val, "%s*=%s*", nil, true ))
444
445 if k and (
446 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
447 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
448 k:match("^%$?[a-zA-Z0-9_]+$")
449 ) then
450 condition[k] = v or true
451 else
452 return nil
453 end
454 end
455
456 if not deps then
457 deps = { condition }
458 else
459 table.insert( deps, condition )
460 end
461
462 return deps
463 end
464
465 -- Read a validator specification
466 function UVL._read_validator( self, value, validators )
467 local validator
468
469 if value and value:match("/") and self.datatypes.file(value) then
470 validator = value
471 else
472 validator = self:_resolve_function( value )
473 end
474
475 if validator then
476 if not validators then
477 validators = { validator }
478 else
479 table.insert( validators, validator )
480 end
481
482 return validators
483 end
484 end
485
486 -- Resolve given path
487 function UVL._resolve_function( self, value )
488 local path = luci.util.split(value, ".")
489
490 for i=1, #path-1 do
491 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
492 if stat and mod then
493 for j=i+1, #path-1 do
494 if not type(mod) == "table" then
495 break
496 end
497 mod = mod[path[j]]
498 if not mod then
499 break
500 end
501 end
502 mod = type(mod) == "table" and mod[path[#path]] or nil
503 if type(mod) == "function" then
504 return mod
505 end
506 end
507 end
508 end
509
510
511 section = luci.util.class()
512
513 function section.__init__(self, scheme, co, st, c, s)
514 self.csection = co[s]
515 self.ssection = scheme.packages[c].sections[st]
516 self.cref = { c, s }
517 self.sref = { c, st }
518 self.scheme = scheme
519 self.config = co
520 self.type = luci.uvl.TYPE_SECTION
521 end
522
523 function section.cid(self)
524 return ( self.cref[1] or '?' ) .. '.' .. ( self.cref[2] or '?' )
525 end
526
527 function section.sid(self)
528 return ( self.sref[1] or '?' ) .. '.' .. ( self.sref[2] or '?' )
529 end
530
531 function section.values(self)
532 return self.csection
533 end
534
535 function section.section(self)
536 return self.ssection
537 end
538
539 function section.variables(self)
540 local v = { }
541 if self.scheme.packages[self.sref[1]].variables[self.sref[2]] then
542 for o, _ in pairs(
543 self.scheme.packages[self.sref[1]].variables[self.sref[2]]
544 ) do
545 table.insert( v, luci.uvl.option(
546 self.scheme, self.config, self.sref[2],
547 self.cref[1], self.cref[2], o
548 ) )
549 end
550 end
551 return v
552 end
553
554
555 option = luci.util.class()
556
557 function option.__init__(self, scheme, co, st, c, s, o)
558 self.coption = co[s] and co[s][o] or nil
559 self.soption = scheme.packages[c].variables[st][o]
560 self.cref = { c, s, o }
561 self.sref = { c, st, o }
562 self.scheme = scheme
563 self.config = co
564 self.type = luci.uvl.TYPE_OPTION
565 end
566
567 function option.cid(self)
568 return ( self.cref[1] or '?' ) .. '.' ..
569 ( self.cref[2] or '?' ) .. '.' ..
570 ( self.cref[3] or '?' )
571 end
572
573 function option.sid(self)
574 return ( self.sref[1] or '?' ) .. '.' ..
575 ( self.sref[2] or '?' ) .. '.' ..
576 ( self.sref[3] or '?' )
577 end
578
579 function option.value(self)
580 return self.coption
581 end
582
583 function option.option(self)
584 return self.soption
585 end
586
587 function option.section(self)
588 return self.scheme.packages[self.sref[1]].sections[self.sref[2]]
589 end