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 }