* luci/libs: uvl: Major rewrite of internal element handling, reworked error model...
[project/luci.git] / libs / uvl / luasrc / uvl / dependencies.lua
index 217f295..8f96046 100644 (file)
@@ -16,100 +16,183 @@ $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
-
+local ERR = luci.uvl.errors
 
 function _parse_reference( r, c, s, o )
        local ref  = { }
        local vars = {
-               config  = c,
-               section = s,
-               option  = o
+               config  = ( c or '$config'  ),
+               section = ( s or '$section' ),
+               option  = ( o or '$option'  )
        }
 
        for i, v in ipairs(luci.util.split(r,".")) do
-               table.insert( ref, (v:gsub( "%$(.+)", function(n) return vars[n] end )) )
+               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
-               ref = nil
+       if c or s then
+               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
+                       ref = nil
+               end
+       else
+               if #ref == 1 then
+                       ref = { '$config', '$section', ref[1] }
+               elseif #ref == 2 then
+                       ref = { '$config', unpack(ref) }
+               elseif #ref ~= 3 then
+                       ref = nil
+               end
        end
 
        return ref
 end
 
+function _serialize_dependency( dep, v )
+       local str
+
+       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 "" ) .. k ..
+                       ( type(v) ~= "boolean" and "=" .. v or "" )
+       end
+
+       return str
+end
+
 function check( self, object, nodeps )
 
+       local derr = ERR.DEPENDENCY(object)
+
        if not self.beenthere[object:cid()] then
                self.beenthere[object:cid()] = true
        else
-               return false, "Recursive dependency for '" .. object:sid() .. "' found"
+               return false, derr:child(ERR.DEP_RECURSIVE(object))
        end
 
-       local item = object.type == luci.uvl.TYPE_SECTION
-               and object:section() or object:option()
+       if object:scheme('depends') then
+               local ok    = true
+               local valid = false
+
+               for _, dep in ipairs(object:scheme('depends')) do
+                       local subcondition = true
+                       for k, v in pairs(dep) do
+                               -- XXX: better error
+                               local ref = _parse_reference( k, unpack(object.cref) )
+
+                               if not ref then
+                                       return false, derr:child(ERR.SME_BADDEP(object,k))
+                               end
+
+                               local option = luci.uvl.option( self, object.c, unpack(ref) )
+
+                               valid, err = self:_validate_option( option, true )
+                               if valid then
+                                       if not (
+                                               ( type(v) == "boolean" and option:value() ) or
+                                               ( ref[3] and option:value() ) == v
+                                       ) then
+                                               subcondition = false
+
+                                               local depstr = _serialize_dependency( dep, v )
+                                               derr:child(
+                                                       type(v) == "boolean"
+                                                               and ERR.DEP_NOVALUE(option, depstr)
+                                                               or  ERR.DEP_NOTEQUAL(option, {depstr, v})
+                                               )
+
+                                               break
+                                       end
+                               else
+                                       subcondition = false
+
+                                       local depstr = _serialize_dependency( dep, v )
+                                       derr:child(ERR.DEP_NOTVALID(option, depstr):child(err))
+
+                                       break
+                               end
+                       end
+
+                       if subcondition then
+                               ok = true
+                               break
+                       else
+                               ok = false
+                       end
+               end
 
-       if item.depends then
-               local ok = false
-               local valid, err = false,
-                       string.format( 'In dependency check for %s "%s":',
-                               ( object.type == luci.uvl.TYPE_SECTION and "section" or "option" ),
-                               object:cid() )
+               if not ok then
+                       return false, derr
+               end
+       else
+               return true
+       end
 
-               for _, dep in ipairs(item.depends) do
+       if object:scheme("type") == "enum" and
+          object:scheme("enum_depends")[object:value()]
+       then
+               local ok    = true
+               local valid = false
+               local enum  = object:enum()
+               local eerr  = ERR.DEP_BADENUM(enum)
+
+               for _, dep in ipairs(enum:scheme('enum_depends')[object:value()]) do
                        local subcondition = true
                        for k, v in pairs(dep) do
                                -- XXX: better error
                                local ref = _parse_reference( k, unpack(object.cref) )
 
                                if not ref then
-                                       return false, "Ambiguous dependency reference '" .. k ..
-                                               "' for object '" .. object:sid() .. "' given"
+                                       return false, derr:child(eerr:child(ERR.SME_BADDEP(enum,k)))
                                end
 
-                               local option = luci.uvl.option(
-                                       self, object.config,
-                                       object.config[ref[2]]
-                                               and object.config[ref[2]]['.type']
-                                               or  object.sref[2],
-                                       ref[1], ref[2], ref[3]
-                               )
+                               local option = luci.uvl.option( self, object.c, unpack(ref) )
 
-                               valid, err2 = self:_validate_option( option, true )
+                               valid, err = 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
+                                               ( ref[3] and object:config() ) == v
                                        ) then
                                                subcondition = false
-                                               err = err .. "\n" ..
-                                                       self.log.dump_dependency( dep, ref, v )
+
+                                               local depstr = _serialize_dependency( dep, v )
+                                               eerr:child(
+                                                       type(v) == "boolean"
+                                                               and ERR.DEP_NOVALUE(option, depstr)
+                                                               or  ERR.DEP_NOTEQUAL(option, {depstr, v})
+                                               )
+
                                                break
                                        end
                                else
                                        subcondition = false
-                                       err = err .. "\n" ..
-                                               self.log.dump_dependency( dep, ref, nil, err2 )
+
+                                       local depstr = _serialize_dependency( dep, v )
+                                       eerr:child(ERR.DEP_NOTVALID(option, depstr):child(err))
+
                                        break
                                end
                        end
 
                        if subcondition then
                                return true
+                       else
+                               ok = false
                        end
                end
 
-               return false, err
+               if not ok then
+                       return false, derr:child(eerr)
+               end
        end
 
        return true