Source file jsonschema.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
open Printf
open Ast
type version =
| Draft_2019_09
| Draft_2020_12
let default_version = Draft_2020_12
type json = Yojson.Safe.t
type opt_descr = (string * json) list
type type_expr =
| Ref of string
| Null
| Boolean
| Integer
| Number
| String
| Array of type_expr
| Tuple of type_expr list
| Object of object_
| Map of type_expr
| Union of type_expr list
| Nullable of type_expr
| Const of json * opt_descr
| Any
and object_ = {
properties: property list;
required: string list;
xprop: bool;
}
and property = string * type_expr * opt_descr
type def = {
name: string;
description: string option;
type_expr: type_expr;
}
type t = {
schema: string;
root_def: def;
defs: def list;
}
let make_id type_name =
"#/definitions/" ^ type_name
let trans_description_simple loc an =
match Doc.get_doc loc an with
| None -> None
| Some blocks -> Some (Doc.print_text blocks)
let trans_description loc an =
match trans_description_simple loc an with
| None -> []
| Some doc -> ["description", `String doc]
let trans_type_expr ~xprop (x : Ast.type_expr) : type_expr =
let rec trans_type_expr (x : Ast.type_expr) : type_expr =
match x with
| Sum (loc, vl, an) ->
Union (List.map (fun x ->
match (x : variant) with
| Variant (loc, (name, an), opt_e) ->
let json_name = Json.get_json_cons name an in
let descr = trans_description loc an in
(match opt_e with
| None -> Const (`String json_name, descr)
| Some e ->
Tuple [
Const (`String json_name, descr);
trans_type_expr e;
]
)
| Inherit _ -> assert false
) vl)
| Record (loc, fl, an) ->
let fields =
List.map (fun (x : field) ->
match x with
| `Field ((loc, (name, kind, an), e) : simple_field) ->
let json_name = Json.get_json_fname name an in
let required =
match kind with
| Required -> Some json_name
| Optional
| With_default -> None
in
let unwrapped_e =
match kind, e with
| Optional, Option (loc, e, an) -> e
| _, e -> e
in
let descr = trans_description loc an in
((json_name, trans_type_expr unwrapped_e, descr), required)
| `Inherit _ -> assert false
) fl
in
let properties = List.map fst fields in
let required =
List.filter_map (fun (_, required) -> required) fields
in
Object {
properties;
required;
xprop;
}
| Tuple (loc, tl, an) ->
Tuple (List.map (fun (loc, e, an) -> trans_type_expr e) tl)
| List (loc, e, an) ->
let json_repr = Json.get_json_list an in
(match e, json_repr with
| _, Array ->
Array (trans_type_expr e)
| Tuple (loc, [(_, Name (_, (_, "string", _), _), _);
(_, value, _)], an2), Object ->
Map (trans_type_expr value)
| _, Object ->
error_at loc
"This type expression is not of the form (string * _) list. \
It can't be represented as a JSON object."
)
| Option (loc, e, an) ->
let transpiled = Sum (loc, [
Variant (loc, ("Some", []), Some e);
Variant (loc, ("None", []), None);
], an)
in
trans_type_expr transpiled
| Nullable (loc, e, an) ->
Nullable (trans_type_expr e)
| Shared (loc, e, an) -> error_at loc "unsupported: shared"
| Wrap (loc, e, an) -> trans_type_expr e
| Tvar (loc, name) -> error_at loc "unsupported: parametrized types"
| Name (loc, (loc2, name, args), a) ->
(match name with
| "unit" -> Null
| "bool" -> Boolean
| "int" -> Integer
| "float" -> Number
| "string" -> String
| "abstract" -> Any
| _ -> Ref (make_id name)
)
in
trans_type_expr x
let trans_item
~xprop
(Type (loc, (name, param, an), e) : module_item) : def =
if param <> [] then
error_at loc "unsupported: parametrized types";
let description = trans_description_simple loc an in
{
name;
description;
type_expr = trans_type_expr ~xprop e;
}
let trans_full_module
~version
~xprop
~src_name
~root_type
((head, body) : full_module) : t =
let defs = List.map (trans_item ~xprop) body in
let root_defs, defs = List.partition (fun x -> x.name = root_type) defs in
let root_def =
match root_defs with
| [x] -> x
| [] ->
failwith
(sprintf "Cannot find definition for the requested root type '%s'"
root_type)
| _ ->
failwith (sprintf "Found multiple definitions for type '%s'" root_type)
in
let description =
let loc, an = head in
let = sprintf "Translated by atdcat from %s." src_name in
[
Some auto_comment;
trans_description_simple loc an;
root_def.description
]
|> List.filter_map (fun x -> x)
|> String.concat "\n\n"
in
let root_def = { root_def with description = Some description } in
let version_url =
match version with
| Draft_2019_09 -> "https://json-schema.org/draft/2019-09/schema"
| Draft_2020_12 -> "https://json-schema.org/draft/2020-12/schema"
in
{
schema = version_url;
root_def = root_def;
defs;
}
let string s = `String s
let opt field_name f x =
match x with
| None -> []
| Some x -> [field_name, f x]
let make_type_property ~is_nullable name =
if is_nullable then
("type", `List [ `String name; `String "null" ])
else
("type", `String name)
let rec type_expr_to_assoc ?(is_nullable = false) ~version (x : type_expr)
: (string * json) list =
match x with
| Ref s ->
[ "$ref", `String s ]
| Null ->
[ make_type_property ~is_nullable:false "null" ]
| Boolean ->
[ make_type_property ~is_nullable "boolean" ]
| Integer ->
[ make_type_property ~is_nullable "integer" ]
| Number ->
[ make_type_property ~is_nullable "number" ]
| String ->
[ make_type_property ~is_nullable "string" ]
| Any ->
[]
| Array x ->
[
make_type_property ~is_nullable "array";
"items", type_expr_to_json ~version x
]
| Tuple xs ->
let shared =
[
make_type_property ~is_nullable "array";
"minItems", `Int (List.length xs);
]
in
let tail =
match version with
| Draft_2020_12 ->
[
"items", `Bool false;
"prefixItems", `List (List.map (type_expr_to_json ~version) xs);
]
| Draft_2019_09 ->
[
"additionalItems", `Bool false;
"items", `List (List.map (type_expr_to_json ~version) xs);
]
in
shared @ tail
| Object x ->
let properties =
List.map (fun (name, x, descr) ->
(name, type_expr_to_json ~descr ~version x)
) x.properties
in
let additional_properties =
if x.xprop then []
else [ "additionalProperties", `Bool false ]
in
List.flatten [
[make_type_property ~is_nullable "object"];
["required", `List (List.map string x.required)];
additional_properties;
["properties", `Assoc properties];
]
| Map x ->
[
make_type_property ~is_nullable "object";
"additionalProperties", type_expr_to_json ~version x
]
| Union xs ->
[ "oneOf", `List (List.map (type_expr_to_json ~is_nullable ~version) xs) ]
| Nullable x ->
type_expr_to_assoc ~is_nullable:true ~version x
| Const (json, descr) ->
descr @ [ "const", json ]
and type_expr_to_json
?(is_nullable = false)
?(descr = [])
~version
(x : type_expr) : json =
`Assoc (
descr @ type_expr_to_assoc ~is_nullable ~version x
)
let def_to_json ~version (x : def) =
x.name, `Assoc (
List.flatten [
opt "description" string x.description;
type_expr_to_assoc ~version x.type_expr;
]
)
let to_json ~version (x : t) : json =
let root_def =
match def_to_json ~version x.root_def with
| _, `Assoc fields ->
("title", `String x.root_def.name) :: fields
| _ -> assert false
in
`Assoc (
("$schema", `String x.schema)
:: root_def
@ ["definitions",
`Assoc (List.map (fun x -> def_to_json ~version x) x.defs)]
)
let annot_schema =
Json.annot_schema_json @ Doc.annot_schema
let print
?(version = default_version)
?(xprop = true)
~src_name ~root_type oc ast =
ast
|> trans_full_module ~version ~xprop ~src_name ~root_type
|> to_json ~version
|> Yojson.Safe.pretty_to_channel oc;
output_char oc '\n'