diff options
-rw-r--r-- | libs/uvl/luasrc/uvl.lua | 168 | ||||
-rw-r--r-- | libs/uvl/luasrc/uvl/dependencies.lua | 130 |
2 files changed, 265 insertions, 33 deletions
diff --git a/libs/uvl/luasrc/uvl.lua b/libs/uvl/luasrc/uvl.lua index 9cbc546da..35e903f20 100644 --- a/libs/uvl/luasrc/uvl.lua +++ b/libs/uvl/luasrc/uvl.lua @@ -20,6 +20,8 @@ require("luci.fs") require("luci.util") require("luci.model.uci") require("luci.uvl.datatypes") +--require("luci.uvl.validation") +require("luci.uvl.dependencies") TYPE_SECTION = 0x01 TYPE_VARIABLE = 0x02 @@ -27,6 +29,7 @@ TYPE_ENUM = 0x03 local default_schemedir = "/etc/scheme" + local function _assert( condition, fmt, ... ) if not condition then return assert( nil, string.format( fmt, ... ) ) @@ -41,29 +44,73 @@ function UVL.__init__( self, schemedir ) self.schemedir = schemedir or default_schemedir self.packages = { } + self.beenthere = { } self.uci = luci.model.uci self.datatypes = luci.uvl.datatypes end + +function UVL._scheme_section( self, uci, c, s ) + if self.packages[c] and uci[s] then + return self.packages[c].sections[uci[s][".type"]] + end +end + +function UVL._scheme_option( self, uci, c, s, o ) + if self.packages[c] and uci[s] and uci[s][o] then + return self.packages[c].variables[uci[s][".type"]][o] + elseif self.packages[c] and self.packages[c].variables[s] then + return self.packages[c].variables[s][o] + end +end + +function UVL._keys( self, tbl ) + local keys = { } + if tbl then + for k, _ in luci.util.kspairs(tbl) do + table.insert( keys, k ) + end + end + return keys +end + + --- Validate given configuration. -- @param config Name of the configuration to validate -- @param scheme Scheme to validate against (optional) -- @return Boolean indicating weather the given config validates -- @return String containing the reason for errors (if any) -function UVL.validate( self, config, scheme ) +function UVL.validate( self, config ) - if not scheme then - return false, "No scheme found" - end + self.uci.set_confdir( self.uci.confdir_default ) + self.uci.load( config ) - for k, v in pairs( config ) do - local ok, err = self:validate_section( config, k, scheme ) + local co = self.uci.get_all( config ) - if not ok then - return ok, err + local function _uci_foreach( type, func ) + for k, v in pairs(co) do + if co[k]['.type'] == type then + func( k, v ) + end end end + luci.util.dumptable(co) + + + + for k, v in pairs( self.packages[config].sections ) do + _uci_foreach( k, + function(s) + local ok, err = self:validate_section( config, s, co ) + + if not ok then + return ok, err + end + end + ) + end + return true, nil end @@ -73,18 +120,50 @@ end -- @param scheme Scheme to validate against -- @return Boolean indicating weather the given config validates -- @return String containing the reason for errors (if any) -function UVL.validate_section( self, config, section, scheme ) +function UVL.validate_section( self, config, section, co, nodeps ) - if not scheme then - return false, "No scheme found" + if not co then + self.uci.set_confdir( self.uci.confdir_default ) + self.uci.load( config ) + co = uci.get_all( config ) end - for k, v in pairs( config[section] ) do - local ok, err = self:validate_option( config, section, k, scheme ) + local cs = co[section] + local scheme = self:_scheme_section( co, config, section ) + + if cs then + --luci.util.dumptable(cs) - if not ok then - return ok, err + + for k, v in pairs(self.packages[config].variables[cs[".type"]]) do + if k:sub(1,1) ~= "." then + local ok, err = self:validate_option( config, section, k, co, false, cs[".type"] ) + + if not ok then + print("ERR", err) + return ok, err + end + end end + + --local dep_ok = nodeps or luci.uvl.dependencies.check_dependency( self, co, config, section ) + --print( "DEP: ", dep_ok ) + + --print( "Validate section: ", config .. '.' .. section, nodeps and '(without depencies)' or '' ) + + local ok, err = luci.uvl.dependencies.check_dependency( + self, co, config, section, nil, true, cs[".type"] + ) + + if ok then + --print("Validated section!\n\n") + return true + else + print("ERR", "All possible dependencies failed. (Last error was: " .. err .. ")") + return false, "All possible dependencies failed" + end + else + print( "Error, scheme section '" .. section .. "' not found in data" ) end return true, nil @@ -97,36 +176,59 @@ end -- @param scheme Scheme to validate against -- @return Boolean indicating weather the given config validates -- @return String containing the reason for errors (if any) -function UVL.validate_option( self, config, section, option, scheme ) +function UVL.validate_option( self, config, section, option, co, nodeps, section2 ) - if type(config) == "string" then - config = { ["variables"] = { [section] = { [option] = config } } } + if not co then + self.uci.set_confdir( self.uci.confdir_default ) + self.uci.load( config ) + co = uci.get_all( config ) end - if not scheme then - return false, "No scheme found" - end + local cs = co[section] + local sv = self:_scheme_option( co, config, section, option ) or + self:_scheme_option( co, config, section2, option ) + + --print("VOPT", config, section, option ) - local sv = scheme.variables[section] - if not sv then return false, "Requested section not found in scheme" end + if not sv then + return false, "Requested option '" .. + config .. '.' .. ( section or section2 ) .. '.' .. option .. + "' not found in scheme" + end - sv = sv[option] - if not sv then return false, "Requested option not found in scheme" end + if sv.required and not cs[option] then + return false, "Mandatory variable '" .. + config .. '.' .. section .. '.' .. option .. + "' doesn't have a value" + end - if not ( config[section] and config[section][option] ) and sv.required then - return false, "Mandatory variable doesn't have a value" + if sv.type == "enum" and cs[option] then + if not sv.values or not sv.values[cs[option]] then + return false, "Value '" .. ( cs[option] or '<nil>' ) .. "' of given option '" .. + config .. "." .. section .. "." .. option .. + "' is not defined in enum { " .. + table.concat(self:_keys(sv.values),", ") .. " }" + end end - if sv.type then - if self.datatypes[sv.type] then - if not self.datatypes[sv.type]( config[section][option] ) then - return false, "Value of given option doesn't validate" + if sv.datatype and cs[option] then + if self.datatypes[sv.datatype] then + if not self.datatypes[sv.datatype]( cs[option] ) then + return false, "Value '" .. ( cs[option] or '<nil>' ) .. "' of given option '" .. + config .. "." .. ( section or section2 ) .. "." .. option .. + "' doesn't validate as datatype '" .. sv.datatype .. "'" end else - return false, "Unknown datatype '" .. sv.type .. "' encountered" + return false, "Unknown datatype '" .. sv.datatype .. "' encountered" end end + if not nodeps then + return luci.uvl.dependencies.check_dependency( + self, co, config, section, option, nil, section2 + ) + end + return true, nil end @@ -238,7 +340,7 @@ function UVL._read_scheme_parts( self, scheme, schemes ) for k, v in pairs( conf ) do if v['.type'] == "variable" then - _req( TYPE_VARIABLE, v, { "name", "type", "section" } ) + _req( TYPE_VARIABLE, v, { "name", "section" } ) local r = _ref( TYPE_VARIABLE, v ) diff --git a/libs/uvl/luasrc/uvl/dependencies.lua b/libs/uvl/luasrc/uvl/dependencies.lua new file mode 100644 index 000000000..b8d728657 --- /dev/null +++ b/libs/uvl/luasrc/uvl/dependencies.lua @@ -0,0 +1,130 @@ +--[[ + +UCI Validation Layer - Main Library +(c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net> +(c) 2008 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ + +]]-- + +module( "luci.uvl.dependencies", package.seeall ) + +local function _assert( condition, fmt, ... ) + if not condition then + return assert( nil, string.format( fmt, ... ) ) + else + return condition + end +end + + +function _parse_reference( r, c, s, o ) + local ref = { } + local vars = { + config = c, + section = s, + option = o + } + + for i, v in ipairs(luci.util.split(r,".")) do + table.insert( ref, (v:gsub( "%$(.+)", function(n) return vars[n] end )) ) + end + + if #ref == 1 and c and s then + ref = { c, s, ref[1] } + elseif #ref == 2 and c then + ref = { c, unpack(ref) } + elseif #ref ~= 3 then + print("INVALID REFERENCE: "..#ref, c, s, o) + ref = nil + end + + return ref +end + +function check_dependency( self, uci, conf, sect, optn, nodeps, section2 ) + +-- print( "Depency check: ", conf .. '.' .. sect .. ( optn and '.' .. optn or '' ) ) + + local key = conf .. '.' .. sect .. ( optn and '.' .. optn or '' ) + if not self.beenthere[key] then + self.beenthere[key] = true + else + print("CIRCULAR DEPENDENCY!") + return false, "Recursive depency detected" + end + + -- check for config + if not self.packages[conf] then self:read_scheme(conf) end + local item = self.packages[conf] + + -- check for section + if sect then + item = _assert( self:_scheme_section( uci, conf, sect ) or self:_scheme_section( uci, conf, section2 ), + "Unknown section '%s' in scheme '%s' requested", + sect or '<nil>', conf or '<nil>' ) + + -- check for option + if optn then + item = _assert( self:_scheme_option( uci, conf, sect, optn ) or + self:_scheme_option( uci, conf, section2, optn ), + "Unknown variable '%s' in scheme '%s', section '%s' requested", + optn or '<nil>', conf or '<nil>', sect or '<nil>' ) + end + end + + if item.depends then + local ok = false + local valid, err + + for _, dep in ipairs(item.depends) do + --print("DEP:",luci.util.serialize_data(dep)) + + local subcondition = true + + for k, v in pairs(dep) do + -- XXX: better error + local ref = _assert( _parse_reference(k,conf,sect,optn), + "Ambiguous dependency reference '" .. k .. "' given" ) + + -- XXX: true -> nodeps + valid, err = self:validate_option(ref[1], ref[2], ref[3], uci, true, section2) + if valid then + --print("CHK:",uci[ref[2]][ref[3]],v,unpack(ref)) + if not ( + ( type(v) == "boolean" and uci[ref[2]][ref[3]] ) or + ( ref[3] and uci[ref[2]][ref[3]] ) == v + ) then + subcondition = false + err = type(v) ~= "boolean" + and "Option '" .. table.concat( ref, "." ) .. "' doesn't match requested type '" .. v .. '"' + or "Option '" .. table.concat( ref, "." ) .. "' has no value" + + break + end + else + subcondition = false + break + end + end + + if subcondition then +-- print( " -> Success (condition matched)\n" ) + return true + end + end + +-- print( " -> Failed\n" ) + return false, err + end + +-- print( " -> Success (no depends)\n" ) + return true +end |