4894d300673c64f2b035b0c57ce8cc93798e6e40
[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
24 TYPE_SECTION = 0x01
25 TYPE_VARIABLE = 0x02
26 TYPE_ENUM = 0x03
27
28
29 local default_schemedir = "/etc/scheme"
30 local function _assert( condition, fmt, ... )
31 if not condition then
32 return assert( nil, string.format( fmt, ... ) )
33 else
34 return condition
35 end
36 end
37
38 UVL = luci.util.class()
39
40 function UVL.__init__( self, schemedir )
41
42 self.schemedir = schemedir or default_schemedir
43 self.packages = { }
44 self.uci = luci.model.uci
45 self.datatypes = luci.uvl.datatypes
46 end
47
48 --- Validate given configuration.
49 -- @param config Name of the configuration to validate
50 -- @param scheme Scheme to validate against (optional)
51 -- @return Boolean indicating weather the given config validates
52 -- @return String containing the reason for errors (if any)
53 function UVL.validate( self, config, scheme )
54
55 if not scheme then
56 return false, "No scheme found"
57 end
58
59 for k, v in pairs( config ) do
60 local ok, err = self:validate_section( config, k, scheme )
61
62 if not ok then
63 return ok, err
64 end
65 end
66
67 return true, nil
68 end
69
70 --- Validate given section of given configuration.
71 -- @param config Name of the configuration to validate
72 -- @param section Key of the section to validate
73 -- @param scheme Scheme to validate against
74 -- @return Boolean indicating weather the given config validates
75 -- @return String containing the reason for errors (if any)
76 function UVL.validate_section( self, config, section, scheme )
77
78 if not scheme then
79 return false, "No scheme found"
80 end
81
82 for k, v in pairs( config[section] ) do
83 local ok, err = self:validate_option( config, section, k, scheme )
84
85 if not ok then
86 return ok, err
87 end
88 end
89
90 return true, nil
91 end
92
93 --- Validate given option within section of given configuration.
94 -- @param config Name of the configuration to validate
95 -- @param section Key of the section to validate
96 -- @param option Name of the option to validate
97 -- @param scheme Scheme to validate against
98 -- @return Boolean indicating weather the given config validates
99 -- @return String containing the reason for errors (if any)
100 function UVL.validate_option( self, config, section, option, scheme )
101
102 if type(config) == "string" then
103 config = { ["variables"] = { [section] = { [option] = config } } }
104 end
105
106 if not scheme then
107 return false, "No scheme found"
108 end
109
110 local sv = scheme.variables[section]
111 if not sv then return false, "Requested section not found in scheme" end
112
113 sv = sv[option]
114 if not sv then return false, "Requested option not found in scheme" end
115
116 if not ( config[section] and config[section][option] ) and sv.required then
117 return false, "Mandatory variable doesn't have a value"
118 end
119
120 if sv.type then
121 if self.datatypes[sv.type] then
122 if not self.datatypes[sv.type]( config[section][option] ) then
123 return false, "Value of given option doesn't validate"
124 end
125 else
126 return false, "Unknown datatype '" .. sv.type .. "' encountered"
127 end
128 end
129
130 return true, nil
131 end
132
133 --- Find all parts of given scheme and construct validation tree
134 -- @param scheme Name of the scheme to parse
135 -- @return Parsed scheme
136 function UVL.read_scheme( self, scheme )
137 local schemes = { }
138
139 for i, file in ipairs( luci.fs.glob(self.schemedir .. '/*/' .. scheme) ) do
140 _assert( luci.fs.access(file), "Can't access file '%s'", file )
141
142 self.uci.set_confdir( luci.fs.dirname(file) )
143 self.uci.load( luci.fs.basename(file) )
144
145 table.insert( schemes, self.uci.get_all( luci.fs.basename(file) ) )
146 end
147
148 return self:_read_scheme_parts( scheme, schemes )
149 end
150
151 -- Process all given parts and construct validation tree
152 function UVL._read_scheme_parts( self, scheme, schemes )
153
154 local stbl = { }
155
156 -- helper function to construct identifiers for given elements
157 local function _id( c, t )
158 if c == TYPE_SECTION then
159 return string.format(
160 "section '%s.%s'",
161 scheme, t.name or '?' )
162 elseif c == TYPE_VARIABLE then
163 return string.format(
164 "variable '%s.%s.%s'",
165 scheme, t.section or '?.?', t.name or '?' )
166 elseif c == TYPE_ENUM then
167 return string.format(
168 "enum '%s.%s.%s'",
169 scheme, t.variable or '?.?.?', t.value or '?' )
170 end
171 end
172
173 -- helper function to check for required fields
174 local function _req( c, t, r )
175 for i, v in ipairs(r) do
176 _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
177 end
178 end
179
180 -- helper function to validate references
181 local function _ref( c, t )
182 local k
183 if c == TYPE_SECTION then
184 k = "package"
185 elseif c == TYPE_VARIABLE then
186 k = "section"
187 elseif c == TYPE_ENUM then
188 k = "variable"
189 end
190
191 local r = luci.util.split( t[k], "." )
192 r[1] = ( #r[1] > 0 and r[1] or scheme )
193
194 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
195
196 return r
197 end
198
199 -- Step 1: get all sections
200 for i, conf in ipairs( schemes ) do
201 for k, v in pairs( conf ) do
202 if v['.type'] == 'section' then
203
204 _req( TYPE_SECTION, v, { "name", "package" } )
205
206 local r = _ref( TYPE_SECTION, v )
207
208 stbl.packages[r[1]] =
209 stbl.packages[r[1]] or {
210 ["sections"] = { };
211 ["variables"] = { };
212 }
213
214 local p = stbl.packages[r[1]]
215 p.sections[v.name] = p.sections[v.name] or { }
216 p.variables[v.name] = p.variables[v.name] or { }
217
218 local s = p.sections[v.name]
219
220 for k, v in pairs(v) do
221 if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
222 if k:match("^depends") then
223 s["depends"] = _assert(
224 self:_read_depency( v, s["depends"] ),
225 "Section '%s' in scheme '%s' has malformed " ..
226 "depency specification in '%s'",
227 v.name, scheme, k
228 )
229 else
230 s[k] = v
231 end
232 end
233 end
234 end
235 end
236 end
237
238 -- Step 2: get all variables
239 for i, conf in ipairs( schemes ) do
240 for k, v in pairs( conf ) do
241 if v['.type'] == "variable" then
242
243 _req( TYPE_VARIABLE, v, { "name", "type", "section" } )
244
245 local r = _ref( TYPE_VARIABLE, v )
246
247 local p = _assert( stbl.packages[r[1]],
248 "Variable '%s' in scheme '%s' references unknown package '%s'",
249 v.name, scheme, r[1] )
250
251 local s = _assert( p.variables[r[2]],
252 "Variable '%s' in scheme '%s' references unknown section '%s'",
253 v.name, scheme, r[2] )
254
255 s[v.name] = s[v.name] or { }
256
257 local t = s[v.name]
258
259 for k, v in pairs(v) do
260 if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
261 if k:match("^depends") then
262 t["depends"] = _assert(
263 self:_read_depency( v, t["depends"] ),
264 "Variable '%s' in scheme '%s' has malformed " ..
265 "depency specification in '%s'",
266 v.name, scheme, k
267 )
268 elseif k:match("^validator") then
269 t["validators"] = _assert(
270 self:_read_validator( v, t["validators"] ),
271 "Variable '%s' in scheme '%s' has malformed " ..
272 "validator specification in '%s'",
273 v.name, scheme, k
274 )
275 else
276 t[k] = v
277 end
278 end
279 end
280 end
281 end
282 end
283
284 -- Step 3: get all enums
285 for i, conf in ipairs( schemes ) do
286 for k, v in pairs( conf ) do
287 if v['.type'] == "enum" then
288
289 _req( TYPE_ENUM, v, { "value", "variable" } )
290
291 local r = _ref( TYPE_ENUM, v )
292
293 local p = _assert( stbl.packages[r[1]],
294 "Enum '%s' in scheme '%s' references unknown package '%s'",
295 v.value, scheme, r[1] )
296
297 local s = _assert( p.variables[r[2]],
298 "Enum '%s' in scheme '%s' references unknown section '%s'",
299 v.value, scheme, r[2] )
300
301 local t = _assert( s[r[3]],
302 "Enum '%s' in scheme '%s', section '%s' references " ..
303 "unknown variable '%s'",
304 v.value, scheme, r[2], r[3] )
305
306 _assert( t.type == "enum",
307 "Enum '%s' in scheme '%s', section '%s' references " ..
308 "variable '%s' with non enum type '%s'",
309 v.value, scheme, r[2], r[3], t.type )
310
311 if not t.values then
312 t.values = { [v.value] = v.title or v.value }
313 else
314 t.values[v.value] = v.title or v.value
315 end
316
317 if v.default then
318 _assert( not t.default,
319 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
320 "the default value of variable '%s'",
321 v.value, scheme, r[2], v.variable )
322
323 t.default = v.value
324 end
325 end
326 end
327 end
328
329 return stbl
330 end
331
332 -- Read a depency specification
333 function UVL._read_depency( self, value, deps )
334 local parts = luci.util.split( value, "%s*;%s*" )
335 local condition = { }
336
337 for i, val in ipairs(parts) do
338 local k, v = unpack(luci.util.split( val, "%s*=%s*" ))
339
340 if k and (
341 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$")
342 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
343 k:match("^%$?[a-zA-Z0-9_]+$") or
344 ) then
345 condition[k] = v or true
346 else
347 return nil
348 end
349 end
350
351 if not deps then
352 deps = { condition }
353 else
354 table.insert( deps, condition )
355 end
356
357 return deps
358 end
359
360 -- Read a validator specification
361 function UVL._read_validator( self, value, validators )
362 local validator
363
364 if value and value:match("/") and self.datatypes.file(value) then
365 validator = value
366 else
367 validator = self:_resolve_function( value )
368 end
369
370 if validator then
371 if not validators then
372 validators = { validator }
373 else
374 table.insert( validators, validator )
375 end
376
377 return validators
378 end
379 end
380
381 -- Resolve given path
382 function UVL._resolve_function( self, value )
383 local path = luci.util.split(value, ".")
384
385 for i=1, #path-1 do
386 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
387 if stat and mod then
388 for j=i+1, #path-1 do
389 if not type(mod) == "table" then
390 break;
391 end
392 mod = mod[path[j]]
393 if not mod then
394 break
395 end
396 end
397 mod = type(mod) == "table" and mod[path[#path]] or nil
398 if type(mod) == "function" then
399 return mod
400 end
401 end
402 end
403 end