9cbc546da248189050320407629424f6d757ae4f
[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 -- helper function to construct identifiers for given elements
155 local function _id( c, t )
156 if c == TYPE_SECTION then
157 return string.format(
158 "section '%s.%s'",
159 scheme, t.name or '?' )
160 elseif c == TYPE_VARIABLE then
161 return string.format(
162 "variable '%s.%s.%s'",
163 scheme, t.section or '?.?', t.name or '?' )
164 elseif c == TYPE_ENUM then
165 return string.format(
166 "enum '%s.%s.%s'",
167 scheme, t.variable or '?.?.?', t.value or '?' )
168 end
169 end
170
171 -- helper function to check for required fields
172 local function _req( c, t, r )
173 for i, v in ipairs(r) do
174 _assert( t[v], "Missing required field '%s' in %s", v, _id(c, t) )
175 end
176 end
177
178 -- helper function to validate references
179 local function _ref( c, t )
180 local k
181 if c == TYPE_SECTION then
182 k = "package"
183 elseif c == TYPE_VARIABLE then
184 k = "section"
185 elseif c == TYPE_ENUM then
186 k = "variable"
187 end
188
189 local r = luci.util.split( t[k], "." )
190 r[1] = ( #r[1] > 0 and r[1] or scheme )
191
192 _assert( #r == c, "Malformed %s reference in %s", k, _id(c, t) )
193
194 return r
195 end
196
197 -- Step 1: get all sections
198 for i, conf in ipairs( schemes ) do
199 for k, v in pairs( conf ) do
200 if v['.type'] == 'section' then
201
202 _req( TYPE_SECTION, v, { "name", "package" } )
203
204 local r = _ref( TYPE_SECTION, v )
205
206 self.packages[r[1]] =
207 self.packages[r[1]] or {
208 ["sections"] = { };
209 ["variables"] = { };
210 }
211
212 local p = self.packages[r[1]]
213 p.sections[v.name] = p.sections[v.name] or { }
214 p.variables[v.name] = p.variables[v.name] or { }
215
216 local s = p.sections[v.name]
217
218 for k, v2 in pairs(v) do
219 if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then
220 if k:match("^depends") then
221 s["depends"] = _assert(
222 self:_read_depency( v2, s["depends"] ),
223 "Section '%s' in scheme '%s' has malformed " ..
224 "depency specification in '%s'",
225 v.name or '<nil>', scheme or '<nil>', k
226 )
227 else
228 s[k] = v2
229 end
230 end
231 end
232 end
233 end
234 end
235
236 -- Step 2: get all variables
237 for i, conf in ipairs( schemes ) do
238 for k, v in pairs( conf ) do
239 if v['.type'] == "variable" then
240
241 _req( TYPE_VARIABLE, v, { "name", "type", "section" } )
242
243 local r = _ref( TYPE_VARIABLE, v )
244
245 local p = _assert( self.packages[r[1]],
246 "Variable '%s' in scheme '%s' references unknown package '%s'",
247 v.name, scheme, r[1] )
248
249 local s = _assert( p.variables[r[2]],
250 "Variable '%s' in scheme '%s' references unknown section '%s'",
251 v.name, scheme, r[2] )
252
253 s[v.name] = s[v.name] or { }
254
255 local t = s[v.name]
256
257 for k, v in pairs(v) do
258 if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then
259 if k:match("^depends") then
260 t["depends"] = _assert(
261 self:_read_depency( v, t["depends"] ),
262 "Variable '%s' in scheme '%s' has malformed " ..
263 "depency specification in '%s'",
264 v.name, scheme, k
265 )
266 elseif k:match("^validator") then
267 t["validators"] = _assert(
268 self:_read_validator( v, t["validators"] ),
269 "Variable '%s' in scheme '%s' has malformed " ..
270 "validator specification in '%s'",
271 v.name, scheme, k
272 )
273 else
274 t[k] = v
275 end
276 end
277 end
278 end
279 end
280 end
281
282 -- Step 3: get all enums
283 for i, conf in ipairs( schemes ) do
284 for k, v in pairs( conf ) do
285 if v['.type'] == "enum" then
286
287 _req( TYPE_ENUM, v, { "value", "variable" } )
288
289 local r = _ref( TYPE_ENUM, v )
290
291 local p = _assert( self.packages[r[1]],
292 "Enum '%s' in scheme '%s' references unknown package '%s'",
293 v.value, scheme, r[1] )
294
295 local s = _assert( p.variables[r[2]],
296 "Enum '%s' in scheme '%s' references unknown section '%s'",
297 v.value, scheme, r[2] )
298
299 local t = _assert( s[r[3]],
300 "Enum '%s' in scheme '%s', section '%s' references " ..
301 "unknown variable '%s'",
302 v.value, scheme, r[2], r[3] )
303
304 _assert( t.type == "enum",
305 "Enum '%s' in scheme '%s', section '%s' references " ..
306 "variable '%s' with non enum type '%s'",
307 v.value, scheme, r[2], r[3], t.type )
308
309 if not t.values then
310 t.values = { [v.value] = v.title or v.value }
311 else
312 t.values[v.value] = v.title or v.value
313 end
314
315 if v.default then
316 _assert( not t.default,
317 "Enum '%s' in scheme '%s', section '%s' redeclares " ..
318 "the default value of variable '%s'",
319 v.value, scheme, r[2], v.variable )
320
321 t.default = v.value
322 end
323 end
324 end
325 end
326
327 return self
328 end
329
330 -- Read a depency specification
331 function UVL._read_depency( self, value, deps )
332 local parts = luci.util.split( value, "%s*,%s*", nil, true )
333 local condition = { }
334
335 for i, val in ipairs(parts) do
336 local k, v = unpack(luci.util.split( val, "%s*=%s*", nil, true ))
337
338 if k and (
339 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
340 k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or
341 k:match("^%$?[a-zA-Z0-9_]+$")
342 ) then
343 condition[k] = v or true
344 else
345 return nil
346 end
347 end
348
349 if not deps then
350 deps = { condition }
351 else
352 table.insert( deps, condition )
353 end
354
355 return deps
356 end
357
358 -- Read a validator specification
359 function UVL._read_validator( self, value, validators )
360 local validator
361
362 if value and value:match("/") and self.datatypes.file(value) then
363 validator = value
364 else
365 validator = self:_resolve_function( value )
366 end
367
368 if validator then
369 if not validators then
370 validators = { validator }
371 else
372 table.insert( validators, validator )
373 end
374
375 return validators
376 end
377 end
378
379 -- Resolve given path
380 function UVL._resolve_function( self, value )
381 local path = luci.util.split(value, ".")
382
383 for i=1, #path-1 do
384 local stat, mod = pcall(require, table.concat(path, ".", 1, i))
385 if stat and mod then
386 for j=i+1, #path-1 do
387 if not type(mod) == "table" then
388 break;
389 end
390 mod = mod[path[j]]
391 if not mod then
392 break
393 end
394 end
395 mod = type(mod) == "table" and mod[path[#path]] or nil
396 if type(mod) == "function" then
397 return mod
398 end
399 end
400 end
401 end