Software /
code /
prosody
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 |