X-Git-Url: http://git.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fuvl%2Fluasrc%2Fuvl.lua;h=da5784e59900bda1864b203517917912b05a02bd;hp=4894d300673c64f2b035b0c57ce8cc93798e6e40;hb=ef0be82b31e47b1c7e3b8efa6d798656036aa7b2;hpb=7726e737252026683d10f5d85a32798f975ef437 diff --git a/libs/uvl/luasrc/uvl.lua b/libs/uvl/luasrc/uvl.lua index 4894d30067..da5784e599 100644 --- a/libs/uvl/luasrc/uvl.lua +++ b/libs/uvl/luasrc/uvl.lua @@ -20,13 +20,19 @@ 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 TYPE_ENUM = 0x03 +STRICT_UNKNOWN_SECTIONS = true +STRICT_UNKNOWN_OPTIONS = true + local default_schemedir = "/etc/scheme" + local function _assert( condition, fmt, ... ) if not condition then return assert( nil, string.format( fmt, ... ) ) @@ -38,52 +44,135 @@ end UVL = luci.util.class() 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._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 ) + + self.uci.set_confdir( self.uci.confdir_default ) + self.uci.load( config ) - if not scheme then - return false, "No scheme found" + local co = self.uci.get_all( config ) + + local function _uci_foreach( type, func ) + local ok, err + for k, v in pairs(co) do + if co[k]['.type'] == type then + ok, err = func( k, v ) + if not ok then break end + end + end + return ok, err end - for k, v in pairs( config ) do - local ok, err = self:validate_section( config, k, scheme ) + for k, v in pairs( self.packages[config].sections ) do + local ok, err = _uci_foreach( k, + function(s) + local sect = luci.uvl.section( self, co, k, config, s ) + return self:_validate_section( sect ) + end + ) + if not ok then return false, err end + end - if not ok then - return ok, err + 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" + end end end return true, nil end +function UVL.validate_section( self, config, section ) + self.uci.set_confdir( self.uci.confdir_default ) + self.uci.load( config ) + + local co = self.uci.get_all( config ) + if co[section] then + return self:_validate_section( luci.uvl.section( + self, co, co[section]['.type'], config, section + ) ) + else + return false, "Section '" .. config .. '.' .. section .. + "' not found in config. Nothing to do." + end +end + +function UVL.validate_option( self, config, section, option ) + self.uci.set_confdir( self.uci.confdir_default ) + self.uci.load( config ) + + local co = self.uci.get_all( config ) + if co[section] and co[section][option] then + return self:_validate_option( luci.uvl.option( + self, co, co[section]['.type'], config, section, option + ) ) + else + return false, "Option '" .. + config .. '.' .. section .. '.' .. option .. + "' not found in config. Nothing to do." + end +end + --- Validate given section of given configuration. -- @param config Name of the configuration to validate -- @param section Key of the section to validate -- @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, section ) - if not scheme then - return false, "No scheme found" - end + if section:values() then + + for _, v in ipairs(section:variables()) do + local ok, err = self:_validate_option( v ) + + if not ok then + return ok, err + end + end - for k, v in pairs( config[section] ) do - local ok, err = self:validate_option( config, section, k, scheme ) + local ok, err = luci.uvl.dependencies.check( self, section ) if not ok then - return ok, err + return false, "All possible dependencies failed" + end + else + print( "Error, scheme section '" .. section:sid() .. "' not found in data" ) + end + + if STRICT_UNKNOWN_OPTIONS and not section:section().dynamic then + for k, v in pairs(section:values()) do + if k:sub(1,1) ~= "." and not self.beenthere[ + section:cid() .. '.' .. k + ] then + return false, "Option '" .. section:sid() .. '.' .. k .. + "' not found in scheme" + end end end @@ -97,33 +186,51 @@ 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, option, nodeps ) - if type(config) == "string" then - config = { ["variables"] = { [section] = { [option] = config } } } + if not option:option() and + not ( option:section() and option:section().dynamic ) + then + return false, "Requested option '" .. option:sid() .. + "' not found in scheme" end - if not scheme then - return false, "No scheme found" - end - - local sv = scheme.variables[section] - if not sv then return false, "Requested section not found in scheme" end - - sv = sv[option] - if not sv then return false, "Requested option not found in scheme" end + if option:option() then + if option:option().required and not option:value() then + return false, "Mandatory variable '" .. option:cid() .. + "' 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" - end + if option:option().type == "enum" and option:value() then + if not option:option().values or + not option:option().values[option:value()] + then + return false, "Value '" .. ( option:value() or '' ) .. + "' of given option '" .. option:cid() .. + "' is not defined in enum { " .. + table.concat(self:_keys(option:option().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 option:option().datatype and option:value() then + if self.datatypes[option:option().datatype] then + if not self.datatypes[option:option().datatype]( + option:value() + ) then + return false, "Value '" .. ( option:value() or '' ) .. + "' of given option '" .. option:cid() .. + "' doesn't validate as datatype '" .. + option:option().datatype .. "'" + end + else + return false, "Unknown datatype '" .. + option:option().datatype .. "' encountered" end - else - return false, "Unknown datatype '" .. sv.type .. "' encountered" + end + + if not nodeps then + return luci.uvl.dependencies.check( self, option ) end end @@ -151,8 +258,6 @@ end -- Process all given parts and construct validation tree function UVL._read_scheme_parts( self, scheme, schemes ) - local stbl = { } - -- helper function to construct identifiers for given elements local function _id( c, t ) if c == TYPE_SECTION then @@ -205,29 +310,29 @@ function UVL._read_scheme_parts( self, scheme, schemes ) local r = _ref( TYPE_SECTION, v ) - stbl.packages[r[1]] = - stbl.packages[r[1]] or { + self.packages[r[1]] = + self.packages[r[1]] or { ["sections"] = { }; ["variables"] = { }; } - local p = stbl.packages[r[1]] + local p = self.packages[r[1]] p.sections[v.name] = p.sections[v.name] or { } p.variables[v.name] = p.variables[v.name] or { } local s = p.sections[v.name] - for k, v in pairs(v) do + for k, v2 in pairs(v) do if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then if k:match("^depends") then s["depends"] = _assert( - self:_read_depency( v, s["depends"] ), + self:_read_dependency( v2, s["depends"] ), "Section '%s' in scheme '%s' has malformed " .. - "depency specification in '%s'", - v.name, scheme, k + "dependency specification in '%s'", + v.name or '', scheme or '', k ) else - s[k] = v + s[k] = v2 end end end @@ -240,11 +345,11 @@ 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 ) - local p = _assert( stbl.packages[r[1]], + local p = _assert( self.packages[r[1]], "Variable '%s' in scheme '%s' references unknown package '%s'", v.name, scheme, r[1] ) @@ -260,9 +365,9 @@ function UVL._read_scheme_parts( self, scheme, schemes ) if k ~= "name" and k ~= "section" and k:sub(1,1) ~= "." then if k:match("^depends") then t["depends"] = _assert( - self:_read_depency( v, t["depends"] ), + self:_read_dependency( v, t["depends"] ), "Variable '%s' in scheme '%s' has malformed " .. - "depency specification in '%s'", + "dependency specification in '%s'", v.name, scheme, k ) elseif k:match("^validator") then @@ -290,7 +395,7 @@ function UVL._read_scheme_parts( self, scheme, schemes ) local r = _ref( TYPE_ENUM, v ) - local p = _assert( stbl.packages[r[1]], + local p = _assert( self.packages[r[1]], "Enum '%s' in scheme '%s' references unknown package '%s'", v.value, scheme, r[1] ) @@ -326,21 +431,21 @@ function UVL._read_scheme_parts( self, scheme, schemes ) end end - return stbl + return self end --- Read a depency specification -function UVL._read_depency( self, value, deps ) - local parts = luci.util.split( value, "%s*;%s*" ) +-- Read a dependency specification +function UVL._read_dependency( self, value, deps ) + local parts = luci.util.split( value, "%s*,%s*", nil, true ) local condition = { } for i, val in ipairs(parts) do - local k, v = unpack(luci.util.split( val, "%s*=%s*" )) + local k, v = unpack(luci.util.split( val, "%s*=%s*", nil, true )) if k and ( - k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") + k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or k:match("^%$?[a-zA-Z0-9_]+%.%$?[a-zA-Z0-9_]+$") or - k:match("^%$?[a-zA-Z0-9_]+$") or + k:match("^%$?[a-zA-Z0-9_]+$") ) then condition[k] = v or true else @@ -387,7 +492,7 @@ function UVL._resolve_function( self, value ) if stat and mod then for j=i+1, #path-1 do if not type(mod) == "table" then - break; + break end mod = mod[path[j]] if not mod then @@ -401,3 +506,84 @@ function UVL._resolve_function( self, value ) end end end + + +section = luci.util.class() + +function section.__init__(self, scheme, co, st, c, s) + self.csection = co[s] + self.ssection = scheme.packages[c].sections[st] + self.cref = { c, s } + self.sref = { c, st } + self.scheme = scheme + self.config = co + self.type = luci.uvl.TYPE_SECTION +end + +function section.cid(self) + return ( self.cref[1] or '?' ) .. '.' .. ( self.cref[2] or '?' ) +end + +function section.sid(self) + return ( self.sref[1] or '?' ) .. '.' .. ( self.sref[2] or '?' ) +end + +function section.values(self) + return self.csection +end + +function section.section(self) + return self.ssection +end + +function section.variables(self) + local v = { } + if self.scheme.packages[self.sref[1]].variables[self.sref[2]] then + for o, _ in pairs( + self.scheme.packages[self.sref[1]].variables[self.sref[2]] + ) do + table.insert( v, luci.uvl.option( + self.scheme, self.config, self.sref[2], + self.cref[1], self.cref[2], o + ) ) + end + end + return v +end + + +option = luci.util.class() + +function option.__init__(self, scheme, co, st, c, s, o) + self.coption = co[s] and co[s][o] or nil + self.soption = scheme.packages[c].variables[st][o] + self.cref = { c, s, o } + self.sref = { c, st, o } + self.scheme = scheme + self.config = co + self.type = luci.uvl.TYPE_OPTION +end + +function option.cid(self) + return ( self.cref[1] or '?' ) .. '.' .. + ( self.cref[2] or '?' ) .. '.' .. + ( self.cref[3] or '?' ) +end + +function option.sid(self) + return ( self.sref[1] or '?' ) .. '.' .. + ( self.sref[2] or '?' ) .. '.' .. + ( self.sref[3] or '?' ) +end + +function option.value(self) + return self.coption +end + +function option.option(self) + return self.soption +end + +function option.section(self) + return self.scheme.packages[self.sref[1]].sections[self.sref[2]] +end