Fixed a design flaw in luci.model.uci
[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.load_config( config )
73         self.beenthere = { }
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.load_config( config )
112         self.beenthere = { }
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.load_config( config )
127         self.beenthere = { }
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