X-Git-Url: https://git.archive.openwrt.org/?a=blobdiff_plain;f=libs%2Fuvl%2Fluasrc%2Fuvl.lua;h=aa3aeb361d170ee41aec29136365c277156c19fe;hb=1d08361bea91c11ba2f82f3ab3004c067ebabc85;hp=9cbc546da248189050320407629424f6d757ae4f;hpb=23a4d764d939552e0bf5b22d9262d7ad005f1aa9;p=project%2Fluci.git diff --git a/libs/uvl/luasrc/uvl.lua b/libs/uvl/luasrc/uvl.lua index 9cbc546da..aa3aeb361 100644 --- a/libs/uvl/luasrc/uvl.lua +++ b/libs/uvl/luasrc/uvl.lua @@ -19,14 +19,22 @@ 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") TYPE_SECTION = 0x01 TYPE_VARIABLE = 0x02 TYPE_ENUM = 0x03 +STRICT_UNKNOWN_SECTIONS = true +STRICT_UNKNOWN_OPTIONS = true +STRICT_EXTERNAL_VALIDATORS = true + local default_schemedir = "/etc/scheme" + local function _assert( condition, fmt, ... ) if not condition then return assert( nil, string.format( fmt, ... ) ) @@ -38,52 +46,144 @@ 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.log = luci.uvl.loghelper self.datatypes = luci.uvl.datatypes 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.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 + err = self.log.config_error( config, err ) + break + end + end + end + return ok, err + end - if not scheme then - return false, "No scheme found" + 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 ) + return self:_validate_section( sect ) + end + ) + if not ok then return false, err end end - for k, v in pairs( config ) do - local ok, err = self:validate_section( config, k, scheme ) + if STRICT_UNKNOWN_SECTIONS then + for k, v in pairs(co) do + if not self.beenthere[config..'.'..k] then + return false, self.log.config_error( config, + "Section '" .. config .. '.' .. co[k]['.type'] .. + "' not found in scheme" ) + end + end + end - if not ok then - return ok, err + 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 +function UVL.validate_section( self, config, section ) + self.uci.load_config( config ) + self.beenthere = { } + + 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.load_config( config ) + self.beenthere = { } + + 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, self.log.section_error( section, 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, err + 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 +197,56 @@ 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, "Option '" .. option:cid() .. + "' not found in scheme" end - if not scheme then - return false, "No scheme found" - 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 - local sv = scheme.variables[section] - if not sv then return false, "Requested section not found in scheme" 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(luci.util.keys(option:option().values),", ") .. + " }" + end + end - sv = sv[option] - if not sv then return false, "Requested option not found in scheme" end + 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 + end - if not ( config[section] and config[section][option] ) and sv.required then - return false, "Mandatory variable doesn't have a value" - end + if not nodeps then + return luci.uvl.dependencies.check( self, option ) + 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" - end - else - return false, "Unknown datatype '" .. sv.type .. "' encountered" + local ok, err = luci.uvl.validation.check( self, option ) + if not ok and STRICT_EXTERNAL_VALIDATORS then + return false, self.log.validator_error( option, err ) end end @@ -194,6 +317,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 @@ -219,16 +347,22 @@ function UVL._read_scheme_parts( self, scheme, schemes ) if k ~= "name" and k ~= "package" and k:sub(1,1) ~= "." then if k:match("^depends") then s["depends"] = _assert( - self:_read_depency( v2, s["depends"] ), + self:_read_dependency( v2, s["depends"] ), "Section '%s' in scheme '%s' has malformed " .. - "depency specification in '%s'", + "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 end end + + s.dynamic = s.dynamic or false + s.unique = s.unique or false + s.required = s.required or false end end end @@ -238,7 +372,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 ) @@ -254,27 +388,32 @@ 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_depency( v, t["depends"] ), - "Variable '%s' in scheme '%s' has malformed " .. - "depency 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.datatype = t.datatype or "string" + t.required = t.required or false end end end @@ -327,8 +466,8 @@ function UVL._read_scheme_parts( self, scheme, schemes ) return self end --- Read a depency specification -function UVL._read_depency( self, value, deps ) +-- Read a dependency specification +function UVL._read_dependency( self, value, deps ) local parts = luci.util.split( value, "%s*,%s*", nil, true ) local condition = { } @@ -357,22 +496,24 @@ end -- Read a validator specification function UVL._read_validator( self, value, validators ) - local validator - - if value and value:match("/") and self.datatypes.file(value) then - validator = value - else - validator = self:_resolve_function( value ) - end + if value then + local validator - if validator then - if not validators then - validators = { validator } - else - table.insert( validators, validator ) + if value:match("^exec:") then + validator = value:gsub("^exec:","") + elseif value:match("^lua:") then + validator = self:_resolve_function( (value:gsub("^lua:","") ) ) end - return validators + if validator then + if not validators then + validators = { validator } + else + table.insert( validators, validator ) + end + + return validators + end end end @@ -385,7 +526,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 @@ -399,3 +540,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