* luci/libs/uvl: more sensitive checking of error reasons in evaluation of option...
[project/luci.git] / libs / uvl / luasrc / uvl / dependencies.lua
1 --[[
2
3 UCI Validation Layer - Dependency helper
4 (c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
5 (c) 2008 Steven Barth <steven@midlink.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11         http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 ]]--
16
17 local uvl = require "luci.uvl"
18 local ERR = require "luci.uvl.errors"
19 local util = require "luci.util"
20 local table = require "table"
21
22 local type, unpack = type, unpack
23 local ipairs, pairs = ipairs, pairs
24
25 module "luci.uvl.dependencies"
26
27
28
29 function _parse_reference( r, c, s, o )
30         local ref  = { }
31         local vars = {
32                 config  = c,
33                 section = s,
34                 option  = o
35         }
36
37         for v in r:gmatch("[^.]+") do
38                 ref[#ref+1] = (v:gsub( "%$(.+)", vars ))
39         end
40
41         if #ref < 2 then
42                 table.insert(ref, 1, s or '$section')
43         end
44         if #ref < 3 then
45                 table.insert(ref, 1, c or '$config')
46         end
47
48         return ref
49 end
50
51 function _serialize_dependency( dep, v )
52         local str
53
54         for k, v in util.spairs( dep,
55                 function(a,b)
56                         a = ( type(dep[a]) ~= "boolean" and "_" or "" ) .. a
57                         b = ( type(dep[b]) ~= "boolean" and "_" or "" ) .. b
58                         return a < b
59                 end
60         ) do
61                 str = ( str and str .. " and " or "" ) .. k ..
62                         ( type(v) ~= "boolean" and "=" .. v or "" )
63         end
64
65         return str
66 end
67
68 function check( self, object, nodeps )
69
70         local derr = ERR.DEPENDENCY(object)
71
72         if not self.depseen[object:cid()] then
73                 self.depseen[object:cid()] = true
74         else
75                 return false, derr:child(ERR.DEP_RECURSIVE(object))
76         end
77
78         if object:scheme('depends') then
79                 local ok    = true
80                 local valid = false
81
82                 for _, dep in ipairs(object:scheme('depends')) do
83                         local subcondition = true
84                         local score        = 0
85
86                         for k, v in util.spairs(
87                                 dep, function(a, b) return type(dep[a]) == "string" end
88                         ) do
89                                 -- XXX: better error
90                                 local ref = _parse_reference( k, unpack(object.cref) )
91
92                                 if not ref then
93                                         return false, derr:child(ERR.SME_BADDEP(object,k))
94                                 end
95
96                                 local option = uvl.option( self, object.c, unpack(ref) )
97
98                                 valid, err = self:_validate_option( option, true )
99                                 if valid then
100                                         if not (
101                                                 ( type(v) == "boolean" and option:value() ) or
102                                                 ( ref[3] and option:value() ) == v
103                                         ) then
104                                                 subcondition = false
105
106                                                 local depstr = _serialize_dependency( dep, v )
107                                                 derr:child(
108                                                         type(v) == "boolean"
109                                                                 and ERR.DEP_NOVALUE(option, depstr)
110                                                                 or  ERR.DEP_NOTEQUAL(option, {depstr, v}),
111                                                         score
112                                                 )
113
114                                                 --break
115                                         else
116                                                 score = score + ( type(v) == "boolean" and 1 or 10 )
117                                         end
118                                 else
119                                         subcondition = false
120
121                                         local depstr = _serialize_dependency( dep, v )
122                                         derr:child(ERR.DEP_NOTVALID(option, depstr):child(err))
123
124                                         break
125                                 end
126                         end
127
128                         if subcondition then
129                                 ok = true
130                                 break
131                         else
132                                 ok = false
133                         end
134                 end
135
136                 if not ok then
137                         return false, derr
138                 end
139         else
140                 return true
141         end
142
143         if object:scheme("type") == "enum" and
144            object:scheme("enum_depends")[object:value()]
145         then
146                 local ok    = true
147                 local valid = false
148                 local enum  = object:enum()
149                 local eerr  = ERR.DEP_BADENUM(enum)
150
151                 for _, dep in ipairs(enum:scheme('enum_depends')[object:value()]) do
152                         local subcondition = true
153                         for k, v in pairs(dep) do
154                                 -- XXX: better error
155                                 local ref = _parse_reference( k, unpack(object.cref) )
156
157                                 if not ref then
158                                         return false, derr:child(eerr:child(ERR.SME_BADDEP(enum,k)))
159                                 end
160
161                                 local option = luci.uvl.option( self, object.c, unpack(ref) )
162
163                                 valid, err = self:_validate_option( option, true )
164                                 if valid then
165                                         if not (
166                                                 ( type(v) == "boolean" and object.config[ref[2]][ref[3]] ) or
167                                                 ( ref[3] and object:config() ) == v
168                                         ) then
169                                                 subcondition = false
170
171                                                 local depstr = _serialize_dependency( dep, v )
172                                                 eerr:child(
173                                                         type(v) == "boolean"
174                                                                 and ERR.DEP_NOVALUE(option, depstr)
175                                                                 or  ERR.DEP_NOTEQUAL(option, {depstr, v})
176                                                 )
177
178                                                 break
179                                         end
180                                 else
181                                         subcondition = false
182
183                                         local depstr = _serialize_dependency( dep, v )
184                                         eerr:child(ERR.DEP_NOTVALID(option, depstr):child(err))
185
186                                         break
187                                 end
188                         end
189
190                         if subcondition then
191                                 return true
192                         else
193                                 ok = false
194                         end
195                 end
196
197                 if not ok then
198                         return false, derr:child(eerr)
199                 end
200         end
201
202         return true
203 end