* luci/libs: uvl: implement scheme loading on demand, add inline documentation
[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
18 --- UVL - UCI Validation Layer
19 -- @class module
20 -- @cstyle instance
21
22 module( "luci.uvl", package.seeall )
23
24 require("luci.fs")
25 require("luci.util")
26 require("luci.model.uci")
27 require("luci.uvl.loghelper")
28 require("luci.uvl.datatypes")
29 require("luci.uvl.validation")
30 require("luci.uvl.dependencies")
31
32
33 TYPE_SECTION = 0x01
34 TYPE_VARIABLE = 0x02
35 TYPE_ENUM = 0x03
36
37 --- Boolean; default true;
38 -- treat sections found in config but not in scheme as error
39 STRICT_UNKNOWN_SECTIONS = true
40
41 --- Boolean; default true;
42 -- treat options found in config but not in scheme as error
43 STRICT_UNKNOWN_OPTIONS = true
44
45 --- Boolean; default true;
46 -- treat failed external validators as error
47 STRICT_EXTERNAL_VALIDATORS = true
48
49
50 local default_schemedir = "/etc/scheme"
51
52 local function _assert( condition, fmt, ... )
53 if not condition then
54 return assert( nil, string.format( fmt, ... ) )
55 else
56 return condition
57 end
58 end
59
60
61 --- Object constructor
62 -- @class function
63 -- @name UVL
64 -- @param schemedir Path to the scheme directory (optional)
65 -- @return Instance object
66 UVL = luci.util.class()
67
68 function UVL.__init__( self, schemedir )
69 self.schemedir = schemedir or default_schemedir
70 self.packages = { }
71 self.beenthere = { }
72 self.uci = luci.model.uci
73 self.log = luci.uvl.loghelper
74 self.datatypes = luci.uvl.datatypes
75 end
76
77
78 --- Validate given configuration.
79 -- @param config Name of the configuration to validate
80 -- @return Boolean indicating whether the given config validates
81 -- @return String containing the reason for errors (if any)
82 function UVL.validate( self, config )
83
84 if not self.packages[config] then
85 local ok, err = pcall( self.read_scheme, self, config )
86 if not ok then
87 return false, self.log.scheme_error( config, err )
88 end
89 end
90
91 self.uci.load_config( config )
92 self.beenthere = { }
93
94 local co = self.uci.get_all( config )
95 local sc = { }
96
97 local function _uci_foreach( type, func )
98 local ok, err
99 for k, v in pairs(co) do
100 if co[k]['.type'] == type then
101 sc[type] = sc[type] + 1
102 ok, err = func( k, v )
103 if not ok then
104 err = self.log.config_error( config, err )
105 break
106 end
107 end
108 end
109 return ok, err
110 end
111
112 for k, v in pairs( self.packages[config].sections ) do
113 sc[k] = 0
114 local ok, err = _uci_foreach( k,
115 function(s)
116 local sect = luci.uvl.section( self, co, k, config, s )
117 return self:_validate_section( sect )
118 end
119 )
120 if not ok then return false, err end
121 end
122
123 if STRICT_UNKNOWN_SECTIONS then
124 for k, v in pairs(co) do
125 if not self.beenthere[config..'.'..k] then
126 return false, self.log.config_error( config,
127 "Section '" .. config .. '.' .. co[k]['.type'] ..
128 "' not found in scheme" )
129 end
130 end
131 end
132
133 for _, k in ipairs(luci.util.keys(sc)) do
134 local s = self.packages[config].sections[k]
135
136 if s.required and sc[k] == 0 then
137 return false, self.log.config_error( config,
138 'Required section "' .. k .. '" not found in config' )
139 elseif s.unique and sc[k] > 1 then
140 return false, self.log.config_error( config,
141 'Unique section "' .. k .. '" occurs multiple times in config' )
142 end
143 end
144
145 return true, nil
146 end
147
148 --- Validate given config section.
149 -- @param config Name of the configuration to validate
150 -- @param section Name of the section to validate
151 -- @return Boolean indicating whether the given config validates
152 -- @return String containing the reason for errors (if any)
153 function UVL.validate_section( self, config, section )
154
155 if not self.packages[config] then
156 local ok, err = pcall( self.read_scheme, self, config )
157 if not ok then
158 return false, self.log.scheme_error( config, err )
159 end
160 end
161
162 self.uci.load_config( config )
163 self.beenthere = { }
164
165 local co = self.uci.get_all( config )
166 if co[section] then
167 return self:_validate_section( luci.uvl.section(
168 self, co, co[section]['.type'], config, section
169 ) )
170 else
171 return false, "Section '" .. config .. '.' .. section ..
172 "' not found in config. Nothing to do."
173 end
174 end
175
176 --- Validate given config option.
177 -- @param config Name of the configuration to validate
178 -- @param section Name of the section to validate
179 -- @param option Name of the option to validate
180 -- @return Boolean indicating whether the given config validates
181 -- @return String containing the reason for errors (if any)
182 function UVL.validate_option( self, config, section, option )
183
184 if not self.packages[config] then
185 local ok, err = pcall( self.read_scheme, self, config )
186 if not ok then
187 return false, self.log.scheme_error( config, err )
188 end
189 end
190
191 self.uci.load_config( config )
192 self.beenthere = { }
193
194 local co = self.uci.get_all( config )
195 if co[section] and co[section][option] then
196 return self:_validate_option( luci.uvl.option(
197 self, co, co[section]['.type'], config, section, option
198 ) )
199 else
200 return false, "Option '" ..
201 config .. '.' .. section .. '.' .. option ..
202 "' not found in config. Nothing to do."
203 end
204 end
205
206
207 function UVL._validate_section( self, section )
208
209 if section:values() then
210
211 for _, v in ipairs(section:variables()) do
212 local ok, err = self:_validate_option( v )
213
214 if not ok then
215 return ok, self.log.section_error( section, err )
216 end
217 end
218
219 local ok, err = luci.uvl.dependencies.check( self, section )
220
221 if not ok then
222 return false, err
223 end
224 else
225 print( "Error, scheme section '" .. section:sid() .. "' not found in data" )
226 end
227
228 if STRICT_UNKNOWN_OPTIONS and not section:section().dynamic then
229 for k, v in pairs(section:values()) do
230 if k:sub(1,1) ~= "." and not self.beenthere[
231 section:cid() .. '.' .. k
232 ] then
233 return false, "Option '" .. section:sid() .. '.' .. k ..
234 "' not found in scheme"
235 end
236 end
237 end
238
239 return true, nil
240 end
241
242 function UVL._validate_option( self, option, nodeps )
243
244 if not option:option() and
245 not ( option:section() and option:section().dynamic )
246 then
247 return false, "Option '" .. option:cid() ..
248 "' not found in scheme"
249 end
250
251 if option:option() then
252 if option:option().required and not option:value() then
253 return false, "Mandatory variable '" .. option:cid() ..
254 "' doesn't have a value"
255 end
256
257 if option:option().type == "enum" and option:value() then
258 if not option:option().values or
259 not option:option().values[option:value()]
260 then
261 return false, "Value '" .. ( option:value() or '<nil>' ) ..
262 "' of given option '" .. option:cid() ..
263 "' is not defined in enum { " ..
264 table.concat(luci.util.keys(option:option().values),", ") ..
265 " }"
266 end
267 end
268
269 if option:option().datatype and option:value() then
270 if self.datatypes[option:option().datatype] then
271 if not self.datatypes[option:option().datatype](
272 option:value()
273 ) then
274 return false, "Value '" .. ( option:value() or '<nil>' ) ..
275 "' of given option '" .. option:cid() ..
276 "' doesn't validate as datatype '" ..
277 option:option().datatype .. "'"
278 end
279 else
280 return false, "Unknown datatype '" ..
281 option:option().datatype .. "' encountered"
282 end
283 end
284
285 if not nodeps then
286 return luci.uvl.dependencies.check( self, option )
287 end
288
289 local ok, err = luci.uvl.validation.check( self, option )
290 if not ok and STRICT_EXTERNAL_VALIDATORS then
291 return false, self.log.validator_error( option, err )
292 end
293 end
294
295 return true, nil
296 end
297
298 --- Find all parts of given scheme and construct validation tree.
299 -- This is normally done on demand, so you don't have to call this function
300 -- by yourself.
301 -- @param scheme Name of the scheme to parse
302 function UVL.read_scheme( self, scheme )
303 local schemes = { }
304 local files = luci.fs.glob(self.schemedir .. '/*/' .. scheme)
305
306 if files then
307 for i, file in ipairs( files ) do
308 _assert( luci.fs.access(file), "Can't access file '%s'", file )
309
310 self.uci.set_confdir( luci.fs.dirname(file) )
311 self.uci.load( luci.fs.basename(file) )
312
313 table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
314 end
315
316 return self:_read_scheme_parts( scheme, schemes )
317 else
318 error(
319 'Can\'t find scheme "' .. scheme ..
320 '" in "' .. self.schemedir .. '"'
321 )
322 end
323 end
324
325 -- Process all given parts and construct validation tree
326 function UVL._read_scheme_parts( self, scheme, schemes )
327
328 -- helper function to construct identifiers for given elements
329 local function _id( c, t )
330 if c == TYPE_SECTION then
331 return string.format(
332 "section '%s.%s'",
333 scheme, t.name or '?' )
334 elseif c == TYPE_VARIABLE then
335 return string.format(
336 "variable '%s.%s.%s'",
337 scheme, t.section or '?.?', t.name or '?' )
338 elseif c == TYPE_ENUM then
339 return string.format(
340 "enum '%s.%s.%s'",
341 scheme, t.variable or '?.?.?', t.value or '?' )
342 end
343 end
344
345 -- helper function to check for required fields
346 local function _req( c, t, r )
347 for i, v in ipairs(r) do
348 _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
349 end
350 end
351
352 -- helper function to validate references
353 local function _ref( c, t )
354 local k
355 if c == TYPE_SECTION then
356 k = "package"
357 elseif c == TYPE_VARIABLE then
358 k = "section"
359 elseif c == TYPE_ENUM then
360 k = "variable"
361 end
362
363 local r = luci.util.split( t[k], "." )
364 r[1] = ( #r[1] > 0 and r[1] or scheme )
365
366 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
367
368 return r
369 end
370
371 -- helper function to read bools
372 local function _bool( v )
373 return ( v == "true" or v == "yes" or v == "on" or v == "1" )
374 end
375
376 -- Step 1: get all sections
377 for i, conf in ipairs( schemes ) do
378 for k, v in pairs( conf ) do
379 if v['.type'] == 'section' then
380
381 _req( TYPE_SECTION, v, { "name", "package" } )
382
383 local r = _ref( TYPE_SECTION, v )
384
385 self.packages[r[1]] =
386 self.packages[r[1]] or {
387 ["sections"] = { };
388 ["variables"] = { };
389 }
390
391 local p = self.packages[r[1]]
392 p.sections[v.name] = p.sections[v.name] or { }
393 p.variables[v.name] = p.variables[v.name] or { }
394
395 local s = p.sections[v.name]
396
397 for k, v2 in pairs(v) do
398 if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
399 if k:match("^depends") then
400 s["depends"] = _assert(
401 self:_read_dependency( v2, s["depends"] ),
402 "Section '%s' in scheme '%s' has malformed " ..
403 "dependency specification in '%s'",
404 v.name or '<nil>', scheme or '<nil>', k
405 )
406 elseif k == "dynamic" or k == "unique" or k == "required" then
407 s[k] = _bool(v2)
408 else
409 s[k] = v2
410 end
411 end
412 end
413
414 s.dynamic = s.dynamic or false
415 s.unique = s.unique or false
416 s.required = s.required or false
417 end
418 end
419 end
420
421 -- Step 2: get all variables
422 for i, conf in ipairs( schemes ) do
423 for k, v in pairs( conf ) do
424 if v['.type'] == "variable" then
425
426 _req( TYPE_VARIABLE, v, { "name", "section" } )
427
428 local r = _ref( TYPE_VARIABLE, v )
429
430 local p = _assert( self.packages[r[1]],
431 "Variable '%s' in scheme '%s' references unknown package '%s'",
432 v.name, scheme, r[1] )
433
434 local s = _assert( p.variables[r[2]],
435 "Variable '%s' in scheme '%s' references unknown section '%s'",
436 v.name, scheme, r[2] )
437
438 s[v.name] = s[v.name] or { }
439
440 local t = s[v.name]
441
442 for k, v2 in pairs(v) do
443 if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
444 if k:match("^depends") then
445 t["depends"] = _assert(
446 self:_read_dependency( v2, t["depends"] ),
447 'Invalid reference "%s" in "%s.%s.%s"',
448 v2, v.name, scheme, k
449 )
450 elseif k:match("^validator") then
451 t["validators"] = _assert(
452 self:_read_validator( v2, t["validators"] ),
453 "Variable '%s' in scheme '%s' has malformed " ..
454 "validator specification in '%s'",
455 v.name, scheme, k
456 )
457 elseif k == "required" then
458 t[k] = _bool(v2)
459 else
460 t[k] = v2
461 end
462 end
463 end
464
465 t.type = t.type or "variable"
466 t.datatype = t.datatype or "string"
467 t.required = t.required or false
468 end
469 end
470 end
471
472 -- Step 3: get all enums
473 for i, conf in ipairs( schemes ) do
474 for k, v in pairs( conf ) do
475 if v['.type'] == "enum" then
476
477 _req( TYPE_ENUM, v, { "value", "variable" } )
478
479 local r = _ref( TYPE_ENUM, v )
480
481 local p = _assert( self.packages[r[1]],
482 "Enum '%s' in scheme '%s' references unknown package '%s'",
483 v.value, scheme, r[1] )
484
485 local s = _assert( p.variables[r[2]],
486 "Enum '%s' in scheme '%s' references unknown section '%s'",
487 v.value, scheme, r[2] )
488
489 local t = _assert( s[r[3]],
490 "Enum '%s' in scheme '%s', section '%s' references " ..
491 "unknown variable '%s'",
492 v.value, scheme, r[2], r[3] )
493
494 _assert( t.type == "enum",
495 "Enum '%s' in scheme '%s', section '%s' references " ..
496 "variable '%s' with non enum type '%s'",
497 v.value, scheme, r[2], r[3], t.type )
498
499 if not t.values then
500 t.values = { [v.value] = v.title or v.value }
501 else
502 t.values[v.value] = v.title or v.value
503 end
504
505 if v.default then
506 _assert( not t.default,
507 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
508 "the default value of variable '%s'",
509 v.value, scheme, r[2], v.variable )
510
511 t.default = v.value
512 end
513 end
514 end
515 end
516
517 return self
518 end
519
520 -- Read a dependency specification
521 function UVL._read_dependency( self, value, deps )
522 local parts = luci.util.split( value, "%s*,%s*", nil, true )
523 local condition = { }
524
525 for i, val in ipairs(parts) do
526 local k, v = unpack(luci.util.split( val, "%s*=%s*", nil, true ))
527
528 if k and (
529 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
530 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
531 k:match("^%$?[a-zA-Z0-9_]+$")
532 ) then
533 condition[k] = v or true
534 else
535 return nil
536 end
537 end
538
539 if not deps then
540 deps = { condition }
541 else
542 table.insert( deps, condition )
543 end
544
545 return deps
546 end
547
548 -- Read a validator specification
549 function UVL._read_validator( self, value, validators )
550 if value then
551 local validator
552
553 if value:match("^exec:") then
554 validator = value:gsub("^exec:","")
555 elseif value:match("^lua:") then
556 validator = self:_resolve_function( (value:gsub("^lua:","") ) )
557 end
558
559 if validator then
560 if not validators then
561 validators = { validator }
562 else
563 table.insert( validators, validator )
564 end
565
566 return validators
567 end
568 end
569 end
570
571 -- Resolve given path
572 function UVL._resolve_function( self, value )
573 local path = luci.util.split(value, ".")
574
575 for i=1, #path-1 do
576 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
577 if stat and mod then
578 for j=i+1, #path-1 do
579 if not type(mod) == "table" then
580 break
581 end
582 mod = mod[path[j]]
583 if not mod then
584 break
585 end
586 end
587 mod = type(mod) == "table" and mod[path[#path]] or nil
588 if type(mod) == "function" then
589 return mod
590 end
591 end
592 end
593 end
594
595
596 --- Object representation of a scheme/config section.
597 -- @class module
598 -- @cstyle instance
599 -- @name luci.uvl.section
600
601 --- Section instance constructor.
602 -- @class function
603 -- @name section
604 -- @param scheme Scheme instance
605 -- @param co Configuration data
606 -- @param st Section type
607 -- @param c Configuration name
608 -- @param s Section name
609 -- @return Section instance
610 section = luci.util.class()
611
612 function section.__init__(self, scheme, co, st, c, s)
613 self.csection = co[s]
614 self.ssection = scheme.packages[c].sections[st]
615 self.cref = { c, s }
616 self.sref = { c, st }
617 self.scheme = scheme
618 self.config = co
619 self.type = luci.uvl.TYPE_SECTION
620 end
621
622 --- Get the config path of this section.
623 -- @return String containing the identifier
624 function section.cid(self)
625 return ( self.cref[1] or '?' ) .. '.' .. ( self.cref[2] or '?' )
626 end
627
628 --- Get the scheme path of this section.
629 -- @return String containing the identifier
630 function section.sid(self)
631 return ( self.sref[1] or '?' ) .. '.' .. ( self.sref[2] or '?' )
632 end
633
634 --- Get all configuration values within this section.
635 -- @return Table containing the values
636 function section.values(self)
637 return self.csection
638 end
639
640 --- Get the associated section information in scheme.
641 -- @return Table containing the scheme properties
642 function section.section(self)
643 return self.ssection
644 end
645
646 --- Get all option objects associated with this section.
647 -- @return Table containing all associated luci.uvl.option instances
648 function section.variables(self)
649 local v = { }
650 if self.scheme.packages[self.sref[1]].variables[self.sref[2]] then
651 for o, _ in pairs(
652 self.scheme.packages[self.sref[1]].variables[self.sref[2]]
653 ) do
654 table.insert( v, luci.uvl.option(
655 self.scheme, self.config, self.sref[2],
656 self.cref[1], self.cref[2], o
657 ) )
658 end
659 end
660 return v
661 end
662
663
664 --- Object representation of a scheme/config option.
665 -- @class module
666 -- @cstyle instance
667 -- @name luci.uvl.option
668
669 --- Section instance constructor.
670 -- @class function
671 -- @name option
672 -- @param scheme Scheme instance
673 -- @param co Configuration data
674 -- @param st Section type
675 -- @param c Configuration name
676 -- @param s Section name
677 -- @param o Option name
678 -- @return Option instance
679 option = luci.util.class()
680
681 function option.__init__(self, scheme, co, st, c, s, o)
682 self.coption = co[s] and co[s][o] or nil
683 self.soption = scheme.packages[c].variables[st][o]
684 self.cref = { c, s, o }
685 self.sref = { c, st, o }
686 self.scheme = scheme
687 self.config = co
688 self.type = luci.uvl.TYPE_OPTION
689 end
690
691 --- Get the config path of this option.
692 -- @return String containing the identifier
693 function option.cid(self)
694 return ( self.cref[1] or '?' ) .. '.' ..
695 ( self.cref[2] or '?' ) .. '.' ..
696 ( self.cref[3] or '?' )
697 end
698
699 --- Get the scheme path of this option.
700 -- @return String containing the identifier
701 function option.sid(self)
702 return ( self.sref[1] or '?' ) .. '.' ..
703 ( self.sref[2] or '?' ) .. '.' ..
704 ( self.sref[3] or '?' )
705 end
706
707 --- Get the value of this option.
708 -- @return The associated configuration value
709 function option.value(self)
710 return self.coption
711 end
712
713 --- Get the associated option information in scheme.
714 -- @return Table containing the scheme properties
715 function option.option(self)
716 return self.soption
717 end
718
719 --- Get the associated section information in scheme.
720 -- @return Table containing the scheme properties
721 function option.section(self)
722 return self.scheme.packages[self.sref[1]].sections[self.sref[2]]
723 end