Comparison

util/jsonschema.lua @ 12579:ca6a43fe0231 0.12

util.jsonschema: Fix validation to not assume presence of "type" field MattJ reported a curious issue where validation did not work as expected. Primarily that the "type" field was expected to be mandatory, and thus leaving it out would result in no checks being performed. This was likely caused by misreading during initial development. Spent some time testing against https://github.com/json-schema-org/JSON-Schema-Test-Suite.git and discovered a multitude of issues, far too many to bother splitting into separate commits. More than half of them fail. Many because of features not implemented, which have been marked NYI. For example, some require deep comparisons e.g. when objects or arrays are present in enums fields. Some because of quirks with how Lua differs from JavaScript, e.g. no distinct array or object types. Tests involving fractional floating point numbers. We're definitely not going to follow references to remote resources. Or deal with UTF-16 sillyness. One test asserted that 1.0 is an integer, where Lua 5.3+ will disagree.
author Kim Alvefur <zash@zash.se>
date Fri, 08 Jul 2022 14:38:23 +0200
parent 12500:88e1b94105ae
child 12759:ec54fe0003d5
comparison
equal deleted inserted replaced
12557:ee5b061588ea 12579:ca6a43fe0231
1 -- This file is generated from teal-src/util/jsonschema.lua
2
1 local m_type = math.type or function (n) 3 local m_type = math.type or function (n)
2 return n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float"; 4 return n % 1 == 0 and n <= 9007199254740992 and n >= -9007199254740992 and "integer" or "float";
3 end; 5 end;
4 local json = require("util.json") 6 local json = require("util.json")
5 local null = json.null; 7 local null = json.null;
8 10
9 local json_type_name = json.json_type_name 11 local json_type_name = json.json_type_name
10 12
11 local schema_t = {} 13 local schema_t = {}
12 14
13 local json_schema_object = {xml_t = {}} 15 local json_schema_object = { xml_t = {} }
14
15 local type_validators = {}
16 16
17 local function simple_validate(schema, data) 17 local function simple_validate(schema, data)
18 if schema == "object" and type(data) == "table" then 18 if schema == nil then
19 return true
20 elseif schema == "object" and type(data) == "table" then
19 return type(data) == "table" and (next(data) == nil or type((next(data, nil))) == "string") 21 return type(data) == "table" and (next(data) == nil or type((next(data, nil))) == "string")
20 elseif schema == "array" and type(data) == "table" then 22 elseif schema == "array" and type(data) == "table" then
21 return type(data) == "table" and (next(data) == nil or type((next(data, nil))) == "number") 23 return type(data) == "table" and (next(data) == nil or type((next(data, nil))) == "number")
22 elseif schema == "integer" then 24 elseif schema == "integer" then
23 return m_type(data) == schema 25 return m_type(data) == schema
24 elseif schema == "null" then 26 elseif schema == "null" then
25 return data == null 27 return data == null
28 elseif type(schema) == "table" then
29 for _, one in ipairs(schema) do
30 if simple_validate(one, data) then
31 return true
32 end
33 end
34 return false
26 else 35 else
27 return type(data) == schema 36 return type(data) == schema
28 end 37 end
29 end 38 end
30 39
31 type_validators.string = function(schema, data) 40 local complex_validate
32
33 if type(data) == "string" then
34 if schema.maxLength and #data > schema.maxLength then
35 return false
36 end
37 if schema.minLength and #data < schema.minLength then
38 return false
39 end
40 return true
41 end
42 return false
43 end
44
45 type_validators.number = function(schema, data)
46 if schema.multipleOf and data % schema.multipleOf ~= 0 then
47 return false
48 end
49
50 if schema.maximum and not (data <= schema.maximum) then
51 return false
52 end
53
54 if schema.exclusiveMaximum and not (data < schema.exclusiveMaximum) then
55 return false
56 end
57
58 if schema.minimum and not (data >= schema.minimum) then
59 return false
60 end
61
62 if schema.exclusiveMinimum and not (data > schema.exclusiveMinimum) then
63 return false
64 end
65
66 return true
67 end
68
69 type_validators.integer = type_validators.number
70 41
71 local function validate(schema, data, root) 42 local function validate(schema, data, root)
72 if type(schema) == "boolean" then 43 if type(schema) == "boolean" then
73 return schema 44 return schema
74 end 45 else
75 if type(schema) == "string" then 46 return complex_validate(schema, data, root)
76 return simple_validate(schema, data) 47 end
77 end 48 end
78 if type(schema) == "table" then 49
79 if root == nil then 50 function complex_validate(schema, data, root)
80 root = schema 51
81 end 52 if root == nil then
82 if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then 53 root = schema
83 local referenced = pointer.resolve(root, schema["$ref"]:sub(2)) 54 end
84 if referenced ~= nil then 55
85 return validate(referenced, data, root) 56 if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then
86 end 57 local referenced = pointer.resolve(root, schema["$ref"]:sub(2))
87 end 58 if referenced ~= nil and referenced ~= root and referenced ~= schema then
88 59 if not validate(referenced, data, root) then
89 if schema.allOf then
90 for _, sub in ipairs(schema.allOf) do
91 if not validate(sub, data, root) then
92 return false
93 end
94 end
95 return true
96 end
97
98 if schema.oneOf then
99 local valid = 0
100 for _, sub in ipairs(schema.oneOf) do
101 if validate(sub, data, root) then
102 valid = valid + 1
103 end
104 end
105 return valid == 1
106 end
107
108 if schema.anyOf then
109 for _, sub in ipairs(schema.anyOf) do
110 if validate(sub, data, root) then
111 return true
112 end
113 end
114 return false
115 end
116
117 if schema["not"] then
118 if validate(schema["not"], data, root) then
119 return false 60 return false
120 end 61 end
121 end 62 end
122 63 end
123 if schema["if"] then 64
124 if validate(schema["if"], data, root) then 65 if not simple_validate(schema.type, data) then
125 if schema["then"] then 66 return false
126 return validate(schema["then"], data, root) 67 end
127 end 68
128 else 69 if schema.type == "object" then
129 if schema["else"] then 70 if type(data) == "table" then
130 return validate(schema["else"], data, root) 71
131 end 72 for k in pairs(data) do
132 end 73 if not (type(k) == "string") then
133 end 74 return false
134 75 end
135 if schema.const ~= nil and schema.const ~= data then 76 end
136 return false 77 end
137 end 78 end
138 79
139 if schema["enum"] ~= nil then 80 if schema.type == "array" then
140 for _, v in ipairs(schema["enum"]) do 81 if type(data) == "table" then
141 if v == data then 82
142 return true 83 for i in pairs(data) do
143 end 84 if not (math.type(i) == "integer") then
144 end 85 return false
145 return false 86 end
146 end 87 end
147 88 end
148 if schema.type then 89 end
149 if not simple_validate(schema.type, data) then 90
91 if schema["enum"] ~= nil then
92 local match = false
93 for _, v in ipairs(schema["enum"]) do
94 if v == data then
95
96 match = true
97 break
98 end
99 end
100 if not match then
101 return false
102 end
103 end
104
105 if type(data) == "string" then
106 if schema.maxLength and #data > schema.maxLength then
107 return false
108 end
109 if schema.minLength and #data < schema.minLength then
110 return false
111 end
112 end
113
114 if type(data) == "number" then
115 if schema.multipleOf and (data == 0 or data % schema.multipleOf ~= 0) then
116 return false
117 end
118
119 if schema.maximum and not (data <= schema.maximum) then
120 return false
121 end
122
123 if schema.exclusiveMaximum and not (data < schema.exclusiveMaximum) then
124 return false
125 end
126
127 if schema.minimum and not (data >= schema.minimum) then
128 return false
129 end
130
131 if schema.exclusiveMinimum and not (data > schema.exclusiveMinimum) then
132 return false
133 end
134 end
135
136 if schema.allOf then
137 for _, sub in ipairs(schema.allOf) do
138 if not validate(sub, data, root) then
150 return false 139 return false
151 end 140 end
152 141 end
153 local validator = type_validators[schema.type] 142 end
154 if validator then 143
155 return validator(schema, data, root) 144 if schema.oneOf then
156 end 145 local valid = 0
157 end 146 for _, sub in ipairs(schema.oneOf) do
158 return true 147 if validate(sub, data, root) then
159 end 148 valid = valid + 1
160 end 149 end
161 150 end
162 type_validators.table = function(schema, data, root) 151 if valid ~= 1 then
152 return false
153 end
154 end
155
156 if schema.anyOf then
157 local match = false
158 for _, sub in ipairs(schema.anyOf) do
159 if validate(sub, data, root) then
160 match = true
161 break
162 end
163 end
164 if not match then
165 return false
166 end
167 end
168
169 if schema["not"] then
170 if validate(schema["not"], data, root) then
171 return false
172 end
173 end
174
175 if schema["if"] ~= nil then
176 if validate(schema["if"], data, root) then
177 if schema["then"] then
178 return validate(schema["then"], data, root)
179 end
180 else
181 if schema["else"] then
182 return validate(schema["else"], data, root)
183 end
184 end
185 end
186
187 if schema.const ~= nil and schema.const ~= data then
188 return false
189 end
190
163 if type(data) == "table" then 191 if type(data) == "table" then
164 192
165 if schema.maxItems and #data > schema.maxItems then 193 if schema.maxItems and #data > schema.maxItems then
166 return false 194 return false
167 end 195 end
176 return false 204 return false
177 end 205 end
178 end 206 end
179 end 207 end
180 208
209 if schema.propertyNames ~= nil then
210 for k in pairs(data) do
211 if not validate(schema.propertyNames, k, root) then
212 return false
213 end
214 end
215 end
216
181 if schema.properties then 217 if schema.properties then
182 local additional = schema.additionalProperties or true 218 for k, sub in pairs(schema.properties) do
219 if data[k] ~= nil and not validate(sub, data[k], root) then
220 return false
221 end
222 end
223 end
224
225 if schema.additionalProperties ~= nil then
183 for k, v in pairs(data) do 226 for k, v in pairs(data) do
184 if schema.propertyNames and not validate(schema.propertyNames, k, root) then 227 if schema.properties == nil or schema.properties[k] == nil then
185 return false 228 if not validate(schema.additionalProperties, v, root) then
186 end 229 return false
187 local s = schema.properties[k] or additional 230 end
188 if not validate(s, v, root) then
189 return false
190 end
191 end
192 elseif schema.additionalProperties then
193 for k, v in pairs(data) do
194 if schema.propertyNames and not validate(schema.propertyNames, k, root) then
195 return false
196 end
197 if not validate(schema.additionalProperties, v, root) then
198 return false
199 end 231 end
200 end 232 end
201 end 233 end
202 234
203 if schema.uniqueItems then 235 if schema.uniqueItems then
210 values[v] = true 242 values[v] = true
211 end 243 end
212 end 244 end
213 245
214 local p = 0 246 local p = 0
215 if schema.prefixItems then 247 if schema.prefixItems ~= nil then
216 for i, s in ipairs(schema.prefixItems) do 248 for i, s in ipairs(schema.prefixItems) do
217 if validate(s, data[i], root) then 249 if data[i] == nil then
250 break
251 elseif validate(s, data[i], root) then
218 p = i 252 p = i
219 else 253 else
220 return false 254 return false
221 end 255 end
222 end 256 end
223 end 257 end
224 258
225 if schema.items then 259 if schema.items ~= nil then
226 for i = p + 1, #data do 260 for i = p + 1, #data do
227 if not validate(schema.items, data[i], root) then 261 if not validate(schema.items, data[i], root) then
228 return false 262 return false
229 end 263 end
230 end 264 end
231 end 265 end
232 266
233 if schema.contains then 267 if schema.contains ~= nil then
234 local found = false 268 local found = false
235 for i = 1, #data do 269 for i = 1, #data do
236 if validate(schema.contains, data[i], root) then 270 if validate(schema.contains, data[i], root) then
237 found = true 271 found = true
238 break 272 break
240 end 274 end
241 if not found then 275 if not found then
242 return false 276 return false
243 end 277 end
244 end 278 end
245 279 end
246 return true 280
247 end 281 return true
248 return false
249 end 282 end
250 283
251 type_validators.object = function(schema, data, root)
252 if type(data) == "table" then
253 for k in pairs(data) do
254 if not (type(k) == "string") then
255 return false
256 end
257 end
258
259 return type_validators.table(schema, data, root)
260 end
261 return false
262 end
263
264 type_validators.array = function(schema, data, root)
265 if type(data) == "table" then
266
267 for i in pairs(data) do
268 if not (type(i) == "number") then
269 return false
270 end
271 end
272
273 return type_validators.table(schema, data, root)
274 end
275 return false
276 end
277
278 json_schema_object.validate = validate; 284 json_schema_object.validate = validate;
279 285
280 return json_schema_object 286 return json_schema_object