From ed5c3eacf09c7936587f2b021622abd4148b47a0 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sun, 17 Aug 2008 15:31:48 +0000 Subject: * luci/libs: uvl: fix handling of boolean options, better error descriptions, implement "required" and "unique" scheme properties for sections --- libs/uvl/luasrc/uvl.lua | 71 +++++++++++++++++++++++------------- libs/uvl/luasrc/uvl/dependencies.lua | 17 +++++---- libs/uvl/luasrc/uvl/loghelper.lua | 59 ++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 33 deletions(-) create mode 100644 libs/uvl/luasrc/uvl/loghelper.lua (limited to 'libs') diff --git a/libs/uvl/luasrc/uvl.lua b/libs/uvl/luasrc/uvl.lua index 33ee79b79..d778cfc3a 100644 --- a/libs/uvl/luasrc/uvl.lua +++ b/libs/uvl/luasrc/uvl.lua @@ -19,6 +19,7 @@ module( "luci.uvl", package.seeall ) require("luci.fs") require("luci.util") require("luci.model.uci") +require("luci.uvl.loghelper") require("luci.uvl.datatypes") --require("luci.uvl.validation") require("luci.uvl.dependencies") @@ -48,19 +49,10 @@ function UVL.__init__( self, schemedir ) self.packages = { } self.beenthere = { } self.uci = luci.model.uci + self.log = luci.uvl.loghelper self.datatypes = luci.uvl.datatypes 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 @@ -68,24 +60,29 @@ end -- @return Boolean indicating weather the given config validates -- @return String containing the reason for errors (if any) function UVL.validate( self, config ) - self.uci.load_config( config ) self.beenthere = { } local co = self.uci.get_all( config ) + local sc = { } local function _uci_foreach( type, func ) local ok, err for k, v in pairs(co) do if co[k]['.type'] == type then + sc[type] = sc[type] + 1 ok, err = func( k, v ) - if not ok then break end + if not ok then + err = self.log.config_error( config, err ) + break + end end end return ok, err end for k, v in pairs( self.packages[config].sections ) do + sc[k] = 0 local ok, err = _uci_foreach( k, function(s) local sect = luci.uvl.section( self, co, k, config, s ) @@ -98,12 +95,25 @@ function UVL.validate( self, config ) if STRICT_UNKNOWN_SECTIONS then for k, v in pairs(co) do if not self.beenthere[config..'.'..k] then - return false, "Section '" .. config .. '.' .. co[k]['.type'] .. - "' not found in scheme" + return false, self.log.config_error( config, + "Section '" .. config .. '.' .. co[k]['.type'] .. + "' not found in scheme" ) end end end + for _, k in ipairs(luci.util.keys(sc)) do + local s = self.packages[config].sections[k] + + if s.required and sc[k] == 0 then + return false, self.log.config_error( config, + 'Required section "' .. k .. '" not found in config' ) + elseif s.unique and sc[k] > 1 then + return false, self.log.config_error( config, + 'Unique section "' .. k .. '" occurs multiple times in config' ) + end + end + return true, nil end @@ -152,14 +162,14 @@ function UVL._validate_section( self, section ) local ok, err = self:_validate_option( v ) if not ok then - return ok, err + return ok, self.log.section_error( section, err ) end end local ok, err = luci.uvl.dependencies.check( self, section ) if not ok then - return false, "All possible dependencies failed" + return false, err end else print( "Error, scheme section '" .. section:sid() .. "' not found in data" ) @@ -191,7 +201,7 @@ function UVL._validate_option( self, option, nodeps ) if not option:option() and not ( option:section() and option:section().dynamic ) then - return false, "Requested option '" .. option:sid() .. + return false, "Option '" .. option:cid() .. "' not found in scheme" end @@ -208,7 +218,7 @@ function UVL._validate_option( self, option, nodeps ) return false, "Value '" .. ( option:value() or '' ) .. "' of given option '" .. option:cid() .. "' is not defined in enum { " .. - table.concat(self:_keys(option:option().values),", ") .. + table.concat(luci.util.keys(option:option().values),", ") .. " }" end end @@ -301,6 +311,11 @@ function UVL._read_scheme_parts( self, scheme, schemes ) return r end + -- helper function to read bools + local function _bool( v ) + return ( v == "true" or v == "yes" or v == "on" or v == "1" ) + end + -- Step 1: get all sections for i, conf in ipairs( schemes ) do for k, v in pairs( conf ) do @@ -331,6 +346,8 @@ function UVL._read_scheme_parts( self, scheme, schemes ) "dependency specification in '%s'", v.name or '', scheme or '', k ) + elseif k == "dynamic" or k == "unique" or k == "required" then + s[k] = _bool(v2) else s[k] = v2 end @@ -361,27 +378,31 @@ function UVL._read_scheme_parts( self, scheme, schemes ) local t = s[v.name] - for k, v in pairs(v) do + for k, v2 in pairs(v) do if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then if k:match("^depends") then t["depends"] = _assert( - self:_read_dependency( v, t["depends"] ), - "Variable '%s' in scheme '%s' has malformed " .. - "dependency specification in '%s'", - v.name, scheme, k + self:_read_dependency( v2, t["depends"] ), + 'Invalid reference "%s" in "%s.%s.%s"', + v2, v.name, scheme, k ) elseif k:match("^validator") then t["validators"] = _assert( - self:_read_validator( v, t["validators"] ), + self:_read_validator( v2, t["validators"] ), "Variable '%s' in scheme '%s' has malformed " .. "validator specification in '%s'", v.name, scheme, k ) + elseif k == "required" then + t[k] = _bool(v2) else - t[k] = v + t[k] = v2 end end end + + t.type = t.type or "variable" + t.required = t.required or false end end end diff --git a/libs/uvl/luasrc/uvl/dependencies.lua b/libs/uvl/luasrc/uvl/dependencies.lua index c5d0734e4..217f29588 100644 --- a/libs/uvl/luasrc/uvl/dependencies.lua +++ b/libs/uvl/luasrc/uvl/dependencies.lua @@ -61,7 +61,10 @@ function check( self, object, nodeps ) if item.depends then local ok = false - local valid, err + local valid, err = false, + string.format( 'In dependency check for %s "%s":', + ( object.type == luci.uvl.TYPE_SECTION and "section" or "option" ), + object:cid() ) for _, dep in ipairs(item.depends) do local subcondition = true @@ -82,23 +85,21 @@ function check( self, object, nodeps ) ref[1], ref[2], ref[3] ) - valid, err = self:_validate_option( option, true ) + valid, err2 = self:_validate_option( option, true ) if valid then if not ( ( type(v) == "boolean" and object.config[ref[2]][ref[3]] ) or ( ref[3] and object.config[ref[2]][ref[3]] ) == v ) then subcondition = false - err = type(v) ~= "boolean" - and "Option '" .. table.concat( ref, "." ) .. - "' doesn't match requested value '" .. v .. '"' - or "Option '" .. table.concat( ref, "." ) .. - "' has no value" - + err = err .. "\n" .. + self.log.dump_dependency( dep, ref, v ) break end else subcondition = false + err = err .. "\n" .. + self.log.dump_dependency( dep, ref, nil, err2 ) break end end diff --git a/libs/uvl/luasrc/uvl/loghelper.lua b/libs/uvl/luasrc/uvl/loghelper.lua new file mode 100644 index 000000000..9ec57f132 --- /dev/null +++ b/libs/uvl/luasrc/uvl/loghelper.lua @@ -0,0 +1,59 @@ +--[[ + +UCI Validation Layer - Logging utilities +(c) 2008 Jo-Philipp Wich +(c) 2008 Steven Barth + +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.loghelper", package.seeall ) + +function config_error( config, message ) + return string.format( + 'Error in config "%s":\n%s', + config, message or "Unknown error" + ) +end + +function section_error( section, message ) + return string.format( + 'Error in section "%s":\n%s', + section:cid(), message or "Unknown error" + ) +end + +function dump_dependency( dep, ref, v, e ) + local str = nil + + for k, v in luci.util.spairs( dep, + function(a,b) + a = ( type(dep[a]) ~= "boolean" and "_" or "" ) .. a + b = ( type(dep[b]) ~= "boolean" and "_" or "" ) .. b + return a < b + end + ) do + str = ( str and str .. " and " or "Dependency (" ) .. k .. + ( type(v) ~= "boolean" and "=" .. v or "" ) + end + + str = string.format( + '%s) failed:\n\t%s', + str, e or string.format( + 'Option "%s" %s', + table.concat( ref, "." ), ( + type(v) == "boolean" + and "has no value" or 'is not equal "' .. v .. '"' + ) + ) + ) + + return str +end -- cgit v1.2.3