* luci/libs: uvl: fix handling of boolean options, better error descriptions, impleme...
authorJo-Philipp Wich <jow@openwrt.org>
Sun, 17 Aug 2008 15:31:48 +0000 (15:31 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Sun, 17 Aug 2008 15:31:48 +0000 (15:31 +0000)
libs/uvl/luasrc/uvl.lua
libs/uvl/luasrc/uvl/dependencies.lua
libs/uvl/luasrc/uvl/loghelper.lua [new file with mode: 0644]

index 33ee79b79bd1fa18ab3f2e46f7a00c96f4855934..d778cfc3af301e149ce065dafaa5cd9ce64319cd 100644 (file)
@@ -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 '<nil>' ) ..
                                        "' 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 '<nil>', scheme or '<nil>', 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
index c5d0734e4c1b4c60b765e4bb158335108804ecd8..217f2958899d53071607eee6b0ca8d0c97ad9244 100644 (file)
@@ -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 (file)
index 0000000..9ec57f1
--- /dev/null
@@ -0,0 +1,59 @@
+--[[
+
+UCI Validation Layer - Logging utilities
+(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.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