* 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