* luci/libs: more UVL hacking, needs to be rewritten later
[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
31 local default_schemedir = "/etc/scheme"
32
33 local function _assert( condition, fmt, ... )
34         if not condition then
35                 return assert( nil, string.format( fmt, ... ) )
36         else
37                 return condition
38         end
39 end
40
41 UVL = luci.util.class()
42
43 function UVL.__init__( self, schemedir )
44
45         self.schemedir  = schemedir or default_schemedir
46         self.packages   = { }
47         self.beenthere  = { }
48         self.uci                = luci.model.uci
49         self.datatypes  = luci.uvl.datatypes
50 end
51
52
53 function UVL._scheme_section( self, uci, c, s )
54         if self.packages[c] and uci[s] then
55                 return self.packages[c].sections[uci[s][".type"]]
56         end
57 end
58
59 function UVL._scheme_option( self, uci, c, s, o )
60         if self.packages[c] and uci[s] and uci[s][o] then
61                 return self.packages[c].variables[uci[s][".type"]][o]
62         elseif self.packages[c] and self.packages[c].variables[s] then
63                 return self.packages[c].variables[s][o]
64         end
65 end
66
67 function UVL._keys( self, tbl )
68         local keys = { }
69         if tbl then
70                 for k, _ in luci.util.kspairs(tbl) do
71                         table.insert( keys, k )
72                 end
73         end
74         return keys
75 end
76
77
78 --- Validate given configuration.
79 -- @param config        Name of the configuration to validate
80 -- @param scheme        Scheme to validate against (optional)
81 -- @return                      Boolean indicating weather the given config validates
82 -- @return                      String containing the reason for errors (if any)
83 function UVL.validate( self, config )
84
85         self.uci.set_confdir( self.uci.confdir_default )
86         self.uci.load( config )
87
88         local co = self.uci.get_all( config )
89
90         local function _uci_foreach( type, func )
91                 for k, v in pairs(co) do
92                         if co[k]['.type'] == type then
93                                 func( k, v )
94                         end
95                 end
96         end
97
98         luci.util.dumptable(co)
99
100
101
102         for k, v in pairs( self.packages[config].sections ) do
103                 _uci_foreach( k,
104                         function(s)
105                                 local ok, err = self:validate_section( config, s, co )
106
107                                 if not ok then
108                                         return ok, err
109                                 end
110                         end
111                 )
112         end
113
114         return true, nil
115 end
116
117 --- Validate given section of given configuration.
118 -- @param config        Name of the configuration to validate
119 -- @param section       Key of the section to validate
120 -- @param scheme        Scheme to validate against
121 -- @return                      Boolean indicating weather the given config validates
122 -- @return                      String containing the reason for errors (if any)
123 function UVL.validate_section( self, config, section, co, nodeps )
124
125         if not co then
126                 self.uci.set_confdir( self.uci.confdir_default )
127                 self.uci.load( config )
128                 co = uci.get_all( config )
129         end
130
131         local cs     = co[section]
132         local scheme = self:_scheme_section( co, config, section )
133
134         if cs then
135                 --luci.util.dumptable(cs)
136
137
138                 for k, v in pairs(self.packages[config].variables[cs[".type"]]) do
139                         if k:sub(1,1) ~= "." then
140                                 local ok, err = self:validate_option( config, section, k, co, false, cs[".type"] )
141
142                                 if not ok then
143                                         print("ERR", err)
144                                         return ok, err
145                                 end
146                         end
147                 end
148
149                 --local dep_ok = nodeps or luci.uvl.dependencies.check_dependency( self, co, config, section )
150                 --print( "DEP: ", dep_ok )
151
152                 --print( "Validate section: ", config .. '.' .. section, nodeps and '(without depencies)' or '' )
153
154                 local ok, err = luci.uvl.dependencies.check_dependency(
155                         self, co, config, section, nil, true, cs[".type"]
156                 )
157
158                 if ok then
159                         --print("Validated section!\n\n")
160                         return true
161                 else
162                         print("ERR", "All possible dependencies failed. (Last error was: " .. err .. ")")
163                         return false, "All possible dependencies failed"
164                 end
165         else
166                 print( "Error, scheme section '" .. section .. "' not found in data" )
167         end
168
169         return true, nil
170 end
171
172 --- Validate given option within section of given configuration.
173 -- @param config        Name of the configuration to validate
174 -- @param section       Key of the section to validate
175 -- @param option        Name of the option to validate
176 -- @param scheme        Scheme to validate against
177 -- @return                      Boolean indicating weather the given config validates
178 -- @return                      String containing the reason for errors (if any)
179 function UVL.validate_option( self, config, section, option, co, nodeps, section2 )
180
181         if not co then
182                 self.uci.set_confdir( self.uci.confdir_default )
183                 self.uci.load( config )
184                 co = uci.get_all( config )
185         end
186
187         local cs = co[section]
188         local sv = self:_scheme_option( co, config, section, option ) or
189                 self:_scheme_option( co, config, section2, option )
190
191         --print("VOPT", config, section, option )
192
193         if not sv then
194                 return false, "Requested option '" ..
195                         config .. '.' .. ( section or section2 ) .. '.' .. option ..
196                         "' not found in scheme"
197         end
198
199         if sv.required and not cs[option] then
200                 return false, "Mandatory variable '" ..
201                         config .. '.' .. section .. '.' .. option ..
202                         "' doesn't have a value"
203         end
204
205         if sv.type == "enum" and cs[option] then
206                 if not sv.values or not sv.values[cs[option]] then
207                         return false, "Value '" .. ( cs[option] or '<nil>' ) .. "' of given option '" ..
208                                 config .. "." .. section .. "." .. option ..
209                                 "' is not defined in enum { " ..
210                                 table.concat(self:_keys(sv.values),", ") .. " }"
211                 end
212         end
213
214         if sv.datatype and cs[option] then
215                 if self.datatypes[sv.datatype] then
216                         if not self.datatypes[sv.datatype]( cs[option] ) then
217                                 return false, "Value '" .. ( cs[option] or '<nil>' ) .. "' of given option '" ..
218                                         config .. "." .. ( section or section2 ) .. "." .. option ..
219                                         "' doesn't validate as datatype '" .. sv.datatype .. "'"
220                         end
221                 else
222                         return false, "Unknown datatype '" .. sv.datatype .. "' encountered"
223                 end
224         end
225
226         if not nodeps then
227                 return luci.uvl.dependencies.check_dependency(
228                         self, co, config, section, option, nil, section2
229                 )
230         end
231
232         return true, nil
233 end
234
235 --- Find all parts of given scheme and construct validation tree
236 -- @param scheme        Name of the scheme to parse
237 -- @return                      Parsed scheme
238 function UVL.read_scheme( self, scheme )
239         local schemes = { }
240
241         for i, file in ipairs( luci.fs.glob(self.schemedir .. '/*/' .. scheme) ) do
242                 _assert( luci.fs.access(file), "Can't access file '%s'", file )
243
244                 self.uci.set_confdir( luci.fs.dirname(file) )
245                 self.uci.load( luci.fs.basename(file) )
246
247                 table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
248         end
249
250         return self:_read_scheme_parts( scheme, schemes )
251 end
252
253 -- Process all given parts and construct validation tree
254 function UVL._read_scheme_parts( self, scheme, schemes )
255
256         -- helper function to construct identifiers for given elements
257         local function _id( c, t )
258                 if c == TYPE_SECTION then
259                         return string.format(
260                                 "section '%s.%s'",
261                                         scheme, t.name or '?' )
262                 elseif c == TYPE_VARIABLE then
263                         return string.format(
264                                 "variable '%s.%s.%s'",
265                                         scheme, t.section or '?.?', t.name or '?' )
266                 elseif c == TYPE_ENUM then
267                         return string.format(
268                                 "enum '%s.%s.%s'",
269                                         scheme, t.variable or '?.?.?', t.value or '?' )
270                 end
271         end
272
273         -- helper function to check for required fields
274         local function _req( c, t, r )
275                 for i, v in ipairs(r) do
276                         _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
277                 end
278         end
279
280         -- helper function to validate references
281         local function _ref( c, t )
282                 local k
283                 if c == TYPE_SECTION then
284                         k = "package"
285                 elseif c == TYPE_VARIABLE then
286                         k = "section"
287                 elseif c == TYPE_ENUM then
288                         k = "variable"
289                 end
290
291                 local r = luci.util.split( t[k], "." )
292                 r[1] = ( #r[1] > 0 and r[1] or scheme )
293
294                 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
295
296                 return r
297         end
298
299         -- Step 1: get all sections
300         for i, conf in ipairs( schemes ) do
301                 for k, v in pairs( conf ) do
302                         if v['.type'] == 'section' then
303
304                                 _req( TYPE_SECTION, v, { "name", "package" } )
305
306                                 local r = _ref( TYPE_SECTION, v )
307
308                                 self.packages[r[1]] =
309                                         self.packages[r[1]] or {
310                                                 ["sections"]  = { };
311                                                 ["variables"] = { };
312                                         }
313
314                                 local p = self.packages[r[1]]
315                                           p.sections[v.name]  = p.sections[v.name]  or { }
316                                           p.variables[v.name] = p.variables[v.name] or { }
317
318                                 local s = p.sections[v.name]
319
320                                 for k, v2 in pairs(v) do
321                                         if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
322                                                 if k:match("^depends") then
323                                                         s["depends"] = _assert(
324                                                                 self:_read_depency( v2, s["depends"] ),
325                                                                 "Section '%s' in scheme '%s' has malformed " ..
326                                                                 "depency specification in '%s'",
327                                                                 v.name or '<nil>', scheme or '<nil>', k
328                                                         )
329                                                 else
330                                                         s[k] = v2
331                                                 end
332                                         end
333                                 end
334                         end
335                 end
336         end
337
338         -- Step 2: get all variables
339         for i, conf in ipairs( schemes ) do
340                 for k, v in pairs( conf ) do
341                         if v['.type'] == "variable" then
342
343                                 _req( TYPE_VARIABLE, v, { "name", "section" } )
344
345                                 local r = _ref( TYPE_VARIABLE, v )
346
347                                 local p = _assert( self.packages[r[1]],
348                                         "Variable '%s' in scheme '%s' references unknown package '%s'",
349                                         v.name, scheme, r[1] )
350
351                                 local s = _assert( p.variables[r[2]],
352                                         "Variable '%s' in scheme '%s' references unknown section '%s'",
353                                         v.name, scheme, r[2] )
354
355                                 s[v.name] = s[v.name] or { }
356
357                                 local t = s[v.name]
358
359                                 for k, v in pairs(v) do
360                                         if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
361                                                 if k:match("^depends") then
362                                                         t["depends"] = _assert(
363                                                                 self:_read_depency( v, t["depends"] ),
364                                                                 "Variable '%s' in scheme '%s' has malformed " ..
365                                                                 "depency specification in '%s'",
366                                                                 v.name, scheme, k
367                                                         )
368                                                 elseif k:match("^validator") then
369                                                         t["validators"] = _assert(
370                                                                 self:_read_validator( v, t["validators"] ),
371                                                                 "Variable '%s' in scheme '%s' has malformed " ..
372                                                                 "validator specification in '%s'",
373                                                                 v.name, scheme, k
374                                                         )
375                                                 else
376                                                         t[k] = v
377                                                 end
378                                         end
379                                 end
380                         end
381                 end
382         end
383
384         -- Step 3: get all enums
385         for i, conf in ipairs( schemes ) do
386                 for k, v in pairs( conf ) do
387                         if v['.type'] == "enum" then
388
389                                 _req( TYPE_ENUM, v, { "value", "variable" } )
390
391                                 local r = _ref( TYPE_ENUM, v )
392
393                                 local p = _assert( self.packages[r[1]],
394                                         "Enum '%s' in scheme '%s' references unknown package '%s'",
395                                         v.value, scheme, r[1] )
396
397                                 local s = _assert( p.variables[r[2]],
398                                         "Enum '%s' in scheme '%s' references unknown section '%s'",
399                                         v.value, scheme, r[2] )
400
401                                 local t = _assert( s[r[3]],
402                                         "Enum '%s' in scheme '%s', section '%s' references " ..
403                                         "unknown variable '%s'",
404                                         v.value, scheme, r[2], r[3] )
405
406                                 _assert( t.type == "enum",
407                                         "Enum '%s' in scheme '%s', section '%s' references " ..
408                                         "variable '%s' with non enum type '%s'",
409                                         v.value, scheme, r[2], r[3], t.type )
410
411                                 if not t.values then
412                                         t.values = { [v.value] = v.title or v.value }
413                                 else
414                                         t.values[v.value] = v.title or v.value
415                                 end
416
417                                 if v.default then
418                                         _assert( not t.default,
419                                                 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
420                                                 "the default value of variable '%s'",
421                                                 v.value, scheme, r[2], v.variable )
422
423                                         t.default = v.value
424                                 end
425                         end
426                 end
427         end
428
429         return self
430 end
431
432 -- Read a depency specification
433 function UVL._read_depency( self, value, deps )
434         local parts     = luci.util.split( value, "%s*,%s*", nil, true )
435         local condition = { }
436
437         for i, val in ipairs(parts) do
438                 local k, v = unpack(luci.util.split( val, "%s*=%s*", nil, true ))
439
440                 if k and (
441                         k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
442                         k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
443                         k:match("^%$?[a-zA-Z0-9_]+$")
444                 ) then
445                         condition[k] = v or true
446                 else
447                         return nil
448                 end
449         end
450
451         if not deps then
452                 deps = { condition }
453         else
454                 table.insert( deps, condition )
455         end
456
457         return deps
458 end
459
460 -- Read a validator specification
461 function UVL._read_validator( self, value, validators )
462         local validator
463
464         if value and value:match("/") and self.datatypes.file(value) then
465                 validator = value
466         else
467                 validator = self:_resolve_function( value )
468         end
469
470         if validator then
471                 if not validators then
472                         validators = { validator }
473                 else
474                         table.insert( validators, validator )
475                 end
476
477                 return validators
478         end
479 end
480
481 -- Resolve given path
482 function UVL._resolve_function( self, value )
483         local path = luci.util.split(value, ".")
484
485         for i=1, #path-1 do
486                 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
487                 if stat and mod then
488                         for j=i+1, #path-1 do
489                                 if not type(mod) == "table" then
490                                         break;
491                                 end
492                                 mod = mod[path[j]]
493                                 if not mod then
494                                         break
495                                 end
496                         end
497                         mod = type(mod) == "table" and mod[path[#path]] or nil
498                         if type(mod) == "function" then
499                                 return mod
500                         end
501                 end
502         end
503 end