Software /
code /
prosody
Comparison
teal-src/util/jsonschema.tl @ 11434:66d4067bdfb2
util.jsonschema: Library for JSON Schema validation
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sat, 06 Mar 2021 21:07:53 +0100 |
child | 11437:87a684df4b65 |
comparison
equal
deleted
inserted
replaced
11433:bef67691a713 | 11434:66d4067bdfb2 |
---|---|
1 | |
2 local record schema_t | |
3 enum type_e | |
4 "null" | |
5 "boolean" | |
6 "object" | |
7 "array" | |
8 "number" | |
9 "string" | |
10 "integer" | |
11 end | |
12 | |
13 type : type_e | |
14 enum : { any } | |
15 const : any | |
16 | |
17 allOf : { schema_t } | |
18 anyOf : { schema_t } | |
19 oneOf : { schema_t } | |
20 | |
21 ["not"] : schema_t | |
22 ["if"] : schema_t | |
23 ["then"] : schema_t | |
24 ["else"] : schema_t | |
25 | |
26 -- numbers | |
27 multipleOf : number | |
28 maximum : number | |
29 exclusiveMaximum : number | |
30 minimum : number | |
31 exclusiveMinimum : number | |
32 | |
33 -- strings | |
34 maxLength : number | |
35 minLength : number | |
36 pattern : string | |
37 format : string | |
38 | |
39 -- arrays | |
40 items : { schema_t } | schema_t | |
41 contains : { schema_t } | |
42 maxItems : number | |
43 minItems : number | |
44 uniqueItems : boolean | |
45 maxContains : number | |
46 minContains : number | |
47 | |
48 -- objects | |
49 properties : { string : schema_t | type_e } | |
50 maxProperties : number | |
51 minProperties : number | |
52 required : { string } | |
53 dependentRequired : { string : { string } } | |
54 additionalProperties: schema_t | |
55 patternProperties: schema_t | |
56 | |
57 -- xml | |
58 record xml_t | |
59 name : string | |
60 namespace : string | |
61 prefix : string | |
62 attribute : boolean | |
63 wrapped : boolean | |
64 | |
65 -- nonstantard, maybe in the future | |
66 text : boolean | |
67 end | |
68 | |
69 xml : xml_t | |
70 | |
71 -- descriptive | |
72 title : string | |
73 description : string | |
74 deprecated : boolean | |
75 readOnly : boolean | |
76 writeOnly : boolean | |
77 end | |
78 local type_e = schema_t.type_e | |
79 | |
80 -- TODO validator function per schema property | |
81 | |
82 local type_validators : { type_e : function (schema_t, any) : boolean } = {} | |
83 | |
84 local function simple_validate(schema : type_e, data : any) : boolean | |
85 if schema == "object" and data is table then | |
86 return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "string") | |
87 elseif schema == "array" and data is table then | |
88 return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "number") | |
89 elseif schema == "integer" then | |
90 return math.type(data) == schema | |
91 else | |
92 return type(data) == schema | |
93 end | |
94 end | |
95 | |
96 type_validators.string = function (schema : schema_t, data : any) : boolean | |
97 -- XXX this is measured in byte, while JSON measures in ... bork | |
98 -- TODO use utf8.len? | |
99 if data is string then | |
100 if schema.maxLength and #data > schema.maxLength then | |
101 return false | |
102 end | |
103 if schema.minLength and #data < schema.minLength then | |
104 return false | |
105 end | |
106 return true | |
107 end | |
108 return false | |
109 end | |
110 | |
111 type_validators.number = function (schema : schema_t, data : number) : boolean | |
112 if schema.multipleOf and data % schema.multipleOf ~= 0 then | |
113 return false | |
114 end | |
115 | |
116 if schema.maximum and not ( data <= schema.maximum ) then | |
117 return false | |
118 end | |
119 | |
120 if schema.exclusiveMaximum and not ( data < schema.exclusiveMaximum ) then | |
121 return false | |
122 end | |
123 | |
124 if schema.minimum and not ( data >= schema.minimum ) then | |
125 return false | |
126 end | |
127 | |
128 if schema.exclusiveMinimum and not ( data > schema.exclusiveMinimum ) then | |
129 return false | |
130 end | |
131 | |
132 return true | |
133 end | |
134 | |
135 type_validators.integer = type_validators.number | |
136 | |
137 local function validate(schema : schema_t | type_e, data : any) : boolean | |
138 if schema is type_e then | |
139 return simple_validate(schema, data) | |
140 end | |
141 if schema is schema_t then | |
142 if schema.allOf then | |
143 for _, sub in ipairs(schema.allOf) do | |
144 if not validate(sub, data) then | |
145 return false | |
146 end | |
147 end | |
148 return true | |
149 end | |
150 | |
151 if schema.oneOf then | |
152 local valid = 0 | |
153 for _, sub in ipairs(schema.oneOf) do | |
154 if validate(sub, data) then | |
155 valid = valid + 1 | |
156 end | |
157 end | |
158 return valid == 1 | |
159 end | |
160 | |
161 if schema.anyOf then | |
162 for _, sub in ipairs(schema.anyOf) do | |
163 if validate(sub, data) then | |
164 return true | |
165 end | |
166 end | |
167 return false | |
168 end | |
169 | |
170 if schema["not"] then | |
171 if validate(schema["not"], data) then | |
172 return false | |
173 end | |
174 end | |
175 | |
176 if schema["if"] then | |
177 if validate(schema["if"], data) then | |
178 if schema["then"] then | |
179 return validate(schema["then"], data) | |
180 end | |
181 else | |
182 if schema["else"] then | |
183 return validate(schema["else"], data) | |
184 end | |
185 end | |
186 end | |
187 | |
188 if not simple_validate(schema.type, data) then | |
189 return false | |
190 end | |
191 | |
192 if schema.const ~= nil and schema.const ~= data then | |
193 return false | |
194 end | |
195 | |
196 if schema.enum ~= nil then | |
197 for _, v in ipairs(schema.enum) do | |
198 if v == data then | |
199 return true | |
200 end | |
201 end | |
202 return false | |
203 end | |
204 | |
205 local validator = type_validators[schema.type] | |
206 if not validator then | |
207 return true | |
208 end | |
209 | |
210 return validator(schema, data) | |
211 end | |
212 end | |
213 | |
214 type_validators.table = function (schema : schema_t, data : any) : boolean | |
215 if data is table then | |
216 | |
217 if schema.maxItems and #data > schema.maxItems then | |
218 return false | |
219 end | |
220 | |
221 if schema.minItems and #data < schema.minItems then | |
222 return false | |
223 end | |
224 | |
225 if schema.required then | |
226 for _, k in ipairs(schema.required) do | |
227 if data[k] == nil then | |
228 return false | |
229 end | |
230 end | |
231 end | |
232 | |
233 if schema.properties then | |
234 for k, s in pairs(schema.properties) do | |
235 if data[k] ~= nil then | |
236 if not validate(s, data[k]) then | |
237 return false | |
238 end | |
239 end | |
240 end | |
241 end | |
242 | |
243 if schema.additionalProperties then | |
244 for k, v in pairs(data) do | |
245 if k is string then | |
246 if not (schema.properties and schema.properties[k]) then | |
247 if not validate(schema.additionalProperties, v) then | |
248 return false | |
249 end | |
250 end | |
251 end | |
252 end | |
253 elseif schema.properties then | |
254 for k in pairs(data) do | |
255 if k is string then | |
256 if schema.properties[k] == nil then | |
257 return false | |
258 end | |
259 end | |
260 end | |
261 end | |
262 | |
263 if schema.uniqueItems then | |
264 -- only works for scalars, would need to deep-compare for objects/arrays/tables | |
265 local values : { any : boolean } = {} | |
266 for _, v in pairs(data) do | |
267 if values[v] then | |
268 return false | |
269 end | |
270 values[v] = true | |
271 end | |
272 return true | |
273 end | |
274 | |
275 local item_schemas = schema.items as {schema_t} | |
276 if item_schemas and item_schemas[1] == nil then | |
277 local item_schema = item_schemas as schema_t | |
278 for i, v in pairs(data) do | |
279 if i is number then | |
280 if not validate(item_schema, v) then | |
281 return false | |
282 end | |
283 end | |
284 end | |
285 elseif item_schemas and item_schemas[1] ~= nil then | |
286 for i, s in ipairs(item_schemas) do | |
287 validate(s, data[i]) | |
288 end | |
289 end | |
290 | |
291 return true | |
292 end | |
293 return false | |
294 end | |
295 | |
296 type_validators.object = function (schema : schema_t, data : any) : boolean | |
297 if data is table then | |
298 for k in pairs(data) do | |
299 if not k is string then | |
300 return false | |
301 end | |
302 end | |
303 | |
304 return type_validators.table(schema, data) | |
305 end | |
306 return false | |
307 end | |
308 | |
309 type_validators.array = function (schema : schema_t, data : any) : boolean | |
310 if data is table then | |
311 | |
312 -- just check that there the keys are all numbers | |
313 for i in pairs(data) do | |
314 if not i is number then | |
315 return false | |
316 end | |
317 end | |
318 | |
319 return type_validators.table(schema, data) | |
320 end | |
321 return false | |
322 end | |
323 | |
324 return { | |
325 validate = validate; | |
326 schema_t = schema_t; | |
327 } |