OpenAPI generator for OCaml with Requests/Eio/Jsont
1(** OpenAPI 3.x specification types with jsont codecs.
2
3 This module defines types that mirror the OpenAPI 3.0/3.1 specification,
4 with bidirectional JSON codecs using jsont. *)
5
6(** {1 Reference handling} *)
7
8type 'a or_ref =
9 | Ref of string (** A $ref pointer like "#/components/schemas/Pet" *)
10 | Value of 'a (** An inline value *)
11
12(** Find a member by name in an object's member list *)
13let find_member name (mems : Jsont.mem list) : Jsont.json option =
14 List.find_map (fun ((n, _meta), v) ->
15 if n = name then Some v else None
16 ) mems
17
18(** Create an or_ref codec that handles $ref pointers.
19 Uses JSON as intermediate to detect $ref field. *)
20let or_ref_jsont (value_jsont : 'a Jsont.t) : 'a or_ref Jsont.t =
21 Jsont.map Jsont.json ~kind:"or_ref"
22 ~dec:(fun json ->
23 match json with
24 | Jsont.Object (mems, _meta) ->
25 (match find_member "$ref" mems with
26 | Some (Jsont.String (ref_str, _)) -> Ref ref_str
27 | _ ->
28 (* Not a $ref, decode as value using bytesrw *)
29 match Jsont_bytesrw.decode_string value_jsont
30 (Result.get_ok (Jsont_bytesrw.encode_string Jsont.json json)) with
31 | Ok v -> Value v
32 | Error e -> Jsont.Error.msg Jsont.Meta.none e)
33 | _ ->
34 (* Non-object, decode as value *)
35 match Jsont_bytesrw.decode_string value_jsont
36 (Result.get_ok (Jsont_bytesrw.encode_string Jsont.json json)) with
37 | Ok v -> Value v
38 | Error e -> Jsont.Error.msg Jsont.Meta.none e)
39 ~enc:(function
40 | Ref r -> Jsont.Object ([(("$ref", Jsont.Meta.none), Jsont.String (r, Jsont.Meta.none))], Jsont.Meta.none)
41 | Value v ->
42 match Jsont_bytesrw.encode_string value_jsont v with
43 | Ok s ->
44 (match Jsont_bytesrw.decode_string Jsont.json s with
45 | Ok json -> json
46 | Error _ -> Jsont.Null ((), Jsont.Meta.none))
47 | Error _ -> Jsont.Null ((), Jsont.Meta.none))
48
49(** {1 String Map} *)
50
51module StringMap = Map.Make(String)
52
53let string_map_jsont (value_jsont : 'a Jsont.t) : (string * 'a) list Jsont.t =
54 let map_jsont = Jsont.Object.as_string_map value_jsont in
55 Jsont.map ~kind:"string_map"
56 ~dec:(fun m -> StringMap.bindings m)
57 ~enc:(fun pairs -> List.fold_left (fun m (k, v) -> StringMap.add k v m) StringMap.empty pairs)
58 map_jsont
59
60(** {1 Contact} *)
61
62type contact = {
63 name : string option;
64 url : string option;
65 email : string option;
66}
67
68let contact_jsont : contact Jsont.t =
69 Jsont.Object.map ~kind:"Contact"
70 (fun name url email -> { name; url; email })
71 |> Jsont.Object.opt_mem "name" Jsont.string ~enc:(fun c -> c.name)
72 |> Jsont.Object.opt_mem "url" Jsont.string ~enc:(fun c -> c.url)
73 |> Jsont.Object.opt_mem "email" Jsont.string ~enc:(fun c -> c.email)
74 |> Jsont.Object.skip_unknown
75 |> Jsont.Object.finish
76
77(** {1 License} *)
78
79type license = {
80 name : string;
81 url : string option;
82}
83
84let license_jsont : license Jsont.t =
85 Jsont.Object.map ~kind:"License"
86 (fun name url -> { name; url })
87 |> Jsont.Object.mem "name" Jsont.string ~enc:(fun l -> l.name)
88 |> Jsont.Object.opt_mem "url" Jsont.string ~enc:(fun l -> l.url)
89 |> Jsont.Object.skip_unknown
90 |> Jsont.Object.finish
91
92(** {1 Info} *)
93
94type info = {
95 title : string;
96 description : string option;
97 terms_of_service : string option;
98 contact : contact option;
99 license : license option;
100 version : string;
101}
102
103let info_jsont : info Jsont.t =
104 Jsont.Object.map ~kind:"Info"
105 (fun title description terms_of_service contact license version ->
106 { title; description; terms_of_service; contact; license; version })
107 |> Jsont.Object.mem "title" Jsont.string ~enc:(fun i -> i.title)
108 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun i -> i.description)
109 |> Jsont.Object.opt_mem "termsOfService" Jsont.string ~enc:(fun i -> i.terms_of_service)
110 |> Jsont.Object.opt_mem "contact" contact_jsont ~enc:(fun i -> i.contact)
111 |> Jsont.Object.opt_mem "license" license_jsont ~enc:(fun i -> i.license)
112 |> Jsont.Object.mem "version" Jsont.string ~enc:(fun i -> i.version)
113 |> Jsont.Object.skip_unknown
114 |> Jsont.Object.finish
115
116(** {1 Server} *)
117
118type server_variable = {
119 enum : string list option;
120 default : string;
121 description : string option;
122}
123
124let server_variable_jsont : server_variable Jsont.t =
125 Jsont.Object.map ~kind:"ServerVariable"
126 (fun enum default description -> { enum; default; description })
127 |> Jsont.Object.opt_mem "enum" Jsont.(list string) ~enc:(fun sv -> sv.enum)
128 |> Jsont.Object.mem "default" Jsont.string ~enc:(fun sv -> sv.default)
129 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun sv -> sv.description)
130 |> Jsont.Object.skip_unknown
131 |> Jsont.Object.finish
132
133type server = {
134 url : string;
135 description : string option;
136 variables : (string * server_variable) list;
137}
138
139let server_jsont : server Jsont.t =
140 Jsont.Object.map ~kind:"Server"
141 (fun url description variables -> { url; description; variables })
142 |> Jsont.Object.mem "url" Jsont.string ~enc:(fun s -> s.url)
143 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun s -> s.description)
144 |> Jsont.Object.mem "variables" (string_map_jsont server_variable_jsont)
145 ~dec_absent:[] ~enc:(fun s -> s.variables)
146 |> Jsont.Object.skip_unknown
147 |> Jsont.Object.finish
148
149(** {1 External Documentation} *)
150
151type external_docs = {
152 description : string option;
153 url : string;
154}
155
156let external_docs_jsont : external_docs Jsont.t =
157 Jsont.Object.map ~kind:"ExternalDocs"
158 (fun description url -> { description; url })
159 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun ed -> ed.description)
160 |> Jsont.Object.mem "url" Jsont.string ~enc:(fun ed -> ed.url)
161 |> Jsont.Object.skip_unknown
162 |> Jsont.Object.finish
163
164(** {1 Tag} *)
165
166type tag = {
167 name : string;
168 description : string option;
169 external_docs : external_docs option;
170}
171
172let tag_jsont : tag Jsont.t =
173 Jsont.Object.map ~kind:"Tag"
174 (fun name description external_docs -> { name; description; external_docs })
175 |> Jsont.Object.mem "name" Jsont.string ~enc:(fun t -> t.name)
176 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun t -> t.description)
177 |> Jsont.Object.opt_mem "externalDocs" external_docs_jsont ~enc:(fun t -> t.external_docs)
178 |> Jsont.Object.skip_unknown
179 |> Jsont.Object.finish
180
181(** {1 Discriminator} *)
182
183type discriminator = {
184 property_name : string;
185 mapping : (string * string) list;
186}
187
188let discriminator_jsont : discriminator Jsont.t =
189 Jsont.Object.map ~kind:"Discriminator"
190 (fun property_name mapping -> { property_name; mapping })
191 |> Jsont.Object.mem "propertyName" Jsont.string ~enc:(fun d -> d.property_name)
192 |> Jsont.Object.mem "mapping" (string_map_jsont Jsont.string)
193 ~dec_absent:[] ~enc:(fun d -> d.mapping)
194 |> Jsont.Object.skip_unknown
195 |> Jsont.Object.finish
196
197(** {1 Schema}
198
199 JSON Schema with OpenAPI extensions. We use a simplified approach
200 where references are stored as schema or_ref. *)
201
202type schema = {
203 title : string option;
204 description : string option;
205 type_ : string option;
206 format : string option;
207 default : Jsont.json option;
208 nullable : bool;
209 read_only : bool;
210 write_only : bool;
211 deprecated : bool;
212 (* Validation *)
213 enum : Jsont.json list option;
214 const : Jsont.json option;
215 minimum : float option;
216 maximum : float option;
217 exclusive_minimum : float option;
218 exclusive_maximum : float option;
219 multiple_of : float option;
220 min_length : int option;
221 max_length : int option;
222 pattern : string option;
223 min_items : int option;
224 max_items : int option;
225 unique_items : bool;
226 min_properties : int option;
227 max_properties : int option;
228 (* Composition - stored as JSON for simplicity *)
229 all_of : Jsont.json list option;
230 one_of : Jsont.json list option;
231 any_of : Jsont.json list option;
232 not_ : Jsont.json option;
233 (* Object - stored as JSON for simplicity *)
234 properties : (string * Jsont.json) list;
235 required : string list;
236 additional_properties : Jsont.json option;
237 (* Array *)
238 items : Jsont.json option;
239 (* Discriminator *)
240 discriminator : discriminator option;
241 (* Examples *)
242 example : Jsont.json option;
243}
244
245let empty_schema = {
246 title = None; description = None; type_ = None; format = None; default = None;
247 nullable = false; read_only = false; write_only = false; deprecated = false;
248 enum = None; const = None; minimum = None; maximum = None;
249 exclusive_minimum = None; exclusive_maximum = None; multiple_of = None;
250 min_length = None; max_length = None; pattern = None;
251 min_items = None; max_items = None; unique_items = false;
252 min_properties = None; max_properties = None;
253 all_of = None; one_of = None; any_of = None; not_ = None;
254 properties = []; required = []; additional_properties = None;
255 items = None; discriminator = None; example = None;
256}
257
258let schema_jsont : schema Jsont.t =
259 Jsont.Object.map ~kind:"Schema"
260 (fun title description type_ format default nullable read_only write_only
261 deprecated enum const minimum maximum exclusive_minimum exclusive_maximum
262 multiple_of min_length max_length pattern min_items max_items unique_items
263 min_properties max_properties all_of one_of any_of not_ properties required
264 additional_properties items discriminator example ->
265 { title; description; type_; format; default; nullable; read_only; write_only;
266 deprecated; enum; const; minimum; maximum; exclusive_minimum; exclusive_maximum;
267 multiple_of; min_length; max_length; pattern; min_items; max_items; unique_items;
268 min_properties; max_properties; all_of; one_of; any_of; not_; properties; required;
269 additional_properties; items; discriminator; example })
270 |> Jsont.Object.opt_mem "title" Jsont.string ~enc:(fun s -> s.title)
271 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun s -> s.description)
272 |> Jsont.Object.opt_mem "type" Jsont.string ~enc:(fun s -> s.type_)
273 |> Jsont.Object.opt_mem "format" Jsont.string ~enc:(fun s -> s.format)
274 |> Jsont.Object.opt_mem "default" Jsont.json ~enc:(fun s -> s.default)
275 |> Jsont.Object.mem "nullable" Jsont.bool ~dec_absent:false ~enc:(fun s -> s.nullable)
276 |> Jsont.Object.mem "readOnly" Jsont.bool ~dec_absent:false ~enc:(fun s -> s.read_only)
277 |> Jsont.Object.mem "writeOnly" Jsont.bool ~dec_absent:false ~enc:(fun s -> s.write_only)
278 |> Jsont.Object.mem "deprecated" Jsont.bool ~dec_absent:false ~enc:(fun s -> s.deprecated)
279 |> Jsont.Object.opt_mem "enum" Jsont.(list json) ~enc:(fun s -> s.enum)
280 |> Jsont.Object.opt_mem "const" Jsont.json ~enc:(fun s -> s.const)
281 |> Jsont.Object.opt_mem "minimum" Jsont.number ~enc:(fun s -> s.minimum)
282 |> Jsont.Object.opt_mem "maximum" Jsont.number ~enc:(fun s -> s.maximum)
283 |> Jsont.Object.opt_mem "exclusiveMinimum" Jsont.number ~enc:(fun s -> s.exclusive_minimum)
284 |> Jsont.Object.opt_mem "exclusiveMaximum" Jsont.number ~enc:(fun s -> s.exclusive_maximum)
285 |> Jsont.Object.opt_mem "multipleOf" Jsont.number ~enc:(fun s -> s.multiple_of)
286 |> Jsont.Object.opt_mem "minLength" Jsont.int ~enc:(fun s -> s.min_length)
287 |> Jsont.Object.opt_mem "maxLength" Jsont.int ~enc:(fun s -> s.max_length)
288 |> Jsont.Object.opt_mem "pattern" Jsont.string ~enc:(fun s -> s.pattern)
289 |> Jsont.Object.opt_mem "minItems" Jsont.int ~enc:(fun s -> s.min_items)
290 |> Jsont.Object.opt_mem "maxItems" Jsont.int ~enc:(fun s -> s.max_items)
291 |> Jsont.Object.mem "uniqueItems" Jsont.bool ~dec_absent:false ~enc:(fun s -> s.unique_items)
292 |> Jsont.Object.opt_mem "minProperties" Jsont.int ~enc:(fun s -> s.min_properties)
293 |> Jsont.Object.opt_mem "maxProperties" Jsont.int ~enc:(fun s -> s.max_properties)
294 |> Jsont.Object.opt_mem "allOf" Jsont.(list json) ~enc:(fun s -> s.all_of)
295 |> Jsont.Object.opt_mem "oneOf" Jsont.(list json) ~enc:(fun s -> s.one_of)
296 |> Jsont.Object.opt_mem "anyOf" Jsont.(list json) ~enc:(fun s -> s.any_of)
297 |> Jsont.Object.opt_mem "not" Jsont.json ~enc:(fun s -> s.not_)
298 |> Jsont.Object.mem "properties" (string_map_jsont Jsont.json)
299 ~dec_absent:[] ~enc:(fun s -> s.properties)
300 |> Jsont.Object.mem "required" Jsont.(list string)
301 ~dec_absent:[] ~enc:(fun s -> s.required)
302 |> Jsont.Object.opt_mem "additionalProperties" Jsont.json
303 ~enc:(fun s -> s.additional_properties)
304 |> Jsont.Object.opt_mem "items" Jsont.json ~enc:(fun s -> s.items)
305 |> Jsont.Object.opt_mem "discriminator" discriminator_jsont ~enc:(fun s -> s.discriminator)
306 |> Jsont.Object.opt_mem "example" Jsont.json ~enc:(fun s -> s.example)
307 |> Jsont.Object.skip_unknown
308 |> Jsont.Object.finish
309
310let schema_or_ref_jsont = or_ref_jsont schema_jsont
311
312(** {1 Parameter} *)
313
314type parameter_location = Query | Header | Path | Cookie
315
316let parameter_location_jsont : parameter_location Jsont.t =
317 Jsont.map Jsont.string ~kind:"parameter_location"
318 ~dec:(function
319 | "query" -> Query
320 | "header" -> Header
321 | "path" -> Path
322 | "cookie" -> Cookie
323 | s -> Jsont.Error.msgf Jsont.Meta.none "Unknown parameter location: %s" s)
324 ~enc:(function
325 | Query -> "query"
326 | Header -> "header"
327 | Path -> "path"
328 | Cookie -> "cookie")
329
330type parameter_style =
331 | Matrix | Label | Form | Simple | SpaceDelimited
332 | PipeDelimited | DeepObject
333
334let parameter_style_jsont : parameter_style Jsont.t =
335 Jsont.map Jsont.string ~kind:"parameter_style"
336 ~dec:(function
337 | "matrix" -> Matrix
338 | "label" -> Label
339 | "form" -> Form
340 | "simple" -> Simple
341 | "spaceDelimited" -> SpaceDelimited
342 | "pipeDelimited" -> PipeDelimited
343 | "deepObject" -> DeepObject
344 | s -> Jsont.Error.msgf Jsont.Meta.none "Unknown parameter style: %s" s)
345 ~enc:(function
346 | Matrix -> "matrix"
347 | Label -> "label"
348 | Form -> "form"
349 | Simple -> "simple"
350 | SpaceDelimited -> "spaceDelimited"
351 | PipeDelimited -> "pipeDelimited"
352 | DeepObject -> "deepObject")
353
354(** {1 Example} *)
355
356type example = {
357 summary : string option;
358 description : string option;
359 value : Jsont.json option;
360 external_value : string option;
361}
362
363let example_jsont : example Jsont.t =
364 Jsont.Object.map ~kind:"Example"
365 (fun summary description value external_value ->
366 { summary; description; value; external_value })
367 |> Jsont.Object.opt_mem "summary" Jsont.string ~enc:(fun e -> e.summary)
368 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun e -> e.description)
369 |> Jsont.Object.opt_mem "value" Jsont.json ~enc:(fun e -> e.value)
370 |> Jsont.Object.opt_mem "externalValue" Jsont.string ~enc:(fun e -> e.external_value)
371 |> Jsont.Object.skip_unknown
372 |> Jsont.Object.finish
373
374let example_or_ref_jsont = or_ref_jsont example_jsont
375
376(** {1 Header} *)
377
378type header = {
379 description : string option;
380 required : bool;
381 deprecated : bool;
382 schema : schema or_ref option;
383}
384
385let header_jsont : header Jsont.t =
386 Jsont.Object.map ~kind:"Header"
387 (fun description required deprecated schema ->
388 { description; required; deprecated; schema })
389 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun h -> h.description)
390 |> Jsont.Object.mem "required" Jsont.bool ~dec_absent:false ~enc:(fun h -> h.required)
391 |> Jsont.Object.mem "deprecated" Jsont.bool ~dec_absent:false ~enc:(fun h -> h.deprecated)
392 |> Jsont.Object.opt_mem "schema" schema_or_ref_jsont ~enc:(fun h -> h.schema)
393 |> Jsont.Object.skip_unknown
394 |> Jsont.Object.finish
395
396let header_or_ref_jsont = or_ref_jsont header_jsont
397
398(** {1 Encoding} *)
399
400type encoding = {
401 content_type : string option;
402 headers : (string * header or_ref) list;
403 style : parameter_style option;
404 explode : bool option;
405 allow_reserved : bool;
406}
407
408let encoding_jsont : encoding Jsont.t =
409 Jsont.Object.map ~kind:"Encoding"
410 (fun content_type headers style explode allow_reserved ->
411 { content_type; headers; style; explode; allow_reserved })
412 |> Jsont.Object.opt_mem "contentType" Jsont.string ~enc:(fun e -> e.content_type)
413 |> Jsont.Object.mem "headers" (string_map_jsont header_or_ref_jsont)
414 ~dec_absent:[] ~enc:(fun e -> e.headers)
415 |> Jsont.Object.opt_mem "style" parameter_style_jsont ~enc:(fun e -> e.style)
416 |> Jsont.Object.opt_mem "explode" Jsont.bool ~enc:(fun e -> e.explode)
417 |> Jsont.Object.mem "allowReserved" Jsont.bool ~dec_absent:false ~enc:(fun e -> e.allow_reserved)
418 |> Jsont.Object.skip_unknown
419 |> Jsont.Object.finish
420
421(** {1 Media Type} *)
422
423type media_type = {
424 schema : schema or_ref option;
425 example : Jsont.json option;
426 examples : (string * example or_ref) list;
427 encoding : (string * encoding) list;
428}
429
430let media_type_jsont : media_type Jsont.t =
431 Jsont.Object.map ~kind:"MediaType"
432 (fun schema example examples encoding ->
433 { schema; example; examples; encoding })
434 |> Jsont.Object.opt_mem "schema" schema_or_ref_jsont ~enc:(fun mt -> mt.schema)
435 |> Jsont.Object.opt_mem "example" Jsont.json ~enc:(fun mt -> mt.example)
436 |> Jsont.Object.mem "examples" (string_map_jsont example_or_ref_jsont)
437 ~dec_absent:[] ~enc:(fun mt -> mt.examples)
438 |> Jsont.Object.mem "encoding" (string_map_jsont encoding_jsont)
439 ~dec_absent:[] ~enc:(fun mt -> mt.encoding)
440 |> Jsont.Object.skip_unknown
441 |> Jsont.Object.finish
442
443(** {1 Parameter} *)
444
445type parameter = {
446 name : string;
447 in_ : parameter_location;
448 description : string option;
449 required : bool;
450 deprecated : bool;
451 allow_empty_value : bool;
452 style : parameter_style option;
453 explode : bool option;
454 allow_reserved : bool;
455 schema : schema or_ref option;
456 example : Jsont.json option;
457 content : (string * media_type) list;
458}
459
460let parameter_jsont : parameter Jsont.t =
461 Jsont.Object.map ~kind:"Parameter"
462 (fun name in_ description required deprecated allow_empty_value style
463 explode allow_reserved schema example content ->
464 { name; in_; description; required; deprecated; allow_empty_value;
465 style; explode; allow_reserved; schema; example; content })
466 |> Jsont.Object.mem "name" Jsont.string ~enc:(fun p -> p.name)
467 |> Jsont.Object.mem "in" parameter_location_jsont ~enc:(fun p -> p.in_)
468 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun p -> p.description)
469 |> Jsont.Object.mem "required" Jsont.bool ~dec_absent:false ~enc:(fun p -> p.required)
470 |> Jsont.Object.mem "deprecated" Jsont.bool ~dec_absent:false ~enc:(fun p -> p.deprecated)
471 |> Jsont.Object.mem "allowEmptyValue" Jsont.bool ~dec_absent:false ~enc:(fun p -> p.allow_empty_value)
472 |> Jsont.Object.opt_mem "style" parameter_style_jsont ~enc:(fun p -> p.style)
473 |> Jsont.Object.opt_mem "explode" Jsont.bool ~enc:(fun p -> p.explode)
474 |> Jsont.Object.mem "allowReserved" Jsont.bool ~dec_absent:false ~enc:(fun p -> p.allow_reserved)
475 |> Jsont.Object.opt_mem "schema" schema_or_ref_jsont ~enc:(fun p -> p.schema)
476 |> Jsont.Object.opt_mem "example" Jsont.json ~enc:(fun p -> p.example)
477 |> Jsont.Object.mem "content" (string_map_jsont media_type_jsont)
478 ~dec_absent:[] ~enc:(fun p -> p.content)
479 |> Jsont.Object.skip_unknown
480 |> Jsont.Object.finish
481
482let parameter_or_ref_jsont = or_ref_jsont parameter_jsont
483
484(** {1 Request Body} *)
485
486type request_body = {
487 description : string option;
488 content : (string * media_type) list;
489 required : bool;
490}
491
492let request_body_jsont : request_body Jsont.t =
493 Jsont.Object.map ~kind:"RequestBody"
494 (fun description content required -> { description; content; required })
495 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun rb -> rb.description)
496 |> Jsont.Object.mem "content" (string_map_jsont media_type_jsont)
497 ~dec_absent:[] ~enc:(fun rb -> rb.content)
498 |> Jsont.Object.mem "required" Jsont.bool ~dec_absent:false ~enc:(fun rb -> rb.required)
499 |> Jsont.Object.skip_unknown
500 |> Jsont.Object.finish
501
502let request_body_or_ref_jsont = or_ref_jsont request_body_jsont
503
504(** {1 Link} *)
505
506type link = {
507 operation_ref : string option;
508 operation_id : string option;
509 parameters : (string * Jsont.json) list;
510 request_body : Jsont.json option;
511 description : string option;
512 server : server option;
513}
514
515let link_jsont : link Jsont.t =
516 Jsont.Object.map ~kind:"Link"
517 (fun operation_ref operation_id parameters request_body description server ->
518 { operation_ref; operation_id; parameters; request_body; description; server })
519 |> Jsont.Object.opt_mem "operationRef" Jsont.string ~enc:(fun l -> l.operation_ref)
520 |> Jsont.Object.opt_mem "operationId" Jsont.string ~enc:(fun l -> l.operation_id)
521 |> Jsont.Object.mem "parameters" (string_map_jsont Jsont.json)
522 ~dec_absent:[] ~enc:(fun l -> l.parameters)
523 |> Jsont.Object.opt_mem "requestBody" Jsont.json ~enc:(fun l -> l.request_body)
524 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun l -> l.description)
525 |> Jsont.Object.opt_mem "server" server_jsont ~enc:(fun l -> l.server)
526 |> Jsont.Object.skip_unknown
527 |> Jsont.Object.finish
528
529let link_or_ref_jsont = or_ref_jsont link_jsont
530
531(** {1 Response} *)
532
533type response = {
534 description : string;
535 headers : (string * header or_ref) list;
536 content : (string * media_type) list;
537 links : (string * link or_ref) list;
538}
539
540let response_jsont : response Jsont.t =
541 Jsont.Object.map ~kind:"Response"
542 (fun description headers content links ->
543 { description; headers; content; links })
544 |> Jsont.Object.mem "description" Jsont.string ~dec_absent:"" ~enc:(fun r -> r.description)
545 |> Jsont.Object.mem "headers" (string_map_jsont header_or_ref_jsont)
546 ~dec_absent:[] ~enc:(fun r -> r.headers)
547 |> Jsont.Object.mem "content" (string_map_jsont media_type_jsont)
548 ~dec_absent:[] ~enc:(fun r -> r.content)
549 |> Jsont.Object.mem "links" (string_map_jsont link_or_ref_jsont)
550 ~dec_absent:[] ~enc:(fun r -> r.links)
551 |> Jsont.Object.skip_unknown
552 |> Jsont.Object.finish
553
554let response_or_ref_jsont = or_ref_jsont response_jsont
555
556(** {1 Responses} *)
557
558type responses = {
559 default : response or_ref option;
560 responses : (string * response or_ref) list; (* status code -> response *)
561}
562
563let responses_jsont : responses Jsont.t =
564 (* Responses is an object where keys are status codes or "default" *)
565 Jsont.map (Jsont.Object.as_string_map response_or_ref_jsont) ~kind:"Responses"
566 ~dec:(fun m ->
567 let default = StringMap.find_opt "default" m in
568 let responses =
569 StringMap.bindings m
570 |> List.filter (fun (k, _) -> k <> "default")
571 in
572 { default; responses })
573 ~enc:(fun r ->
574 let m = List.fold_left (fun m (k, v) -> StringMap.add k v m) StringMap.empty r.responses in
575 match r.default with
576 | Some d -> StringMap.add "default" d m
577 | None -> m)
578
579(** {1 Security Requirement} *)
580
581type security_requirement = (string * string list) list
582
583let security_requirement_jsont : security_requirement Jsont.t =
584 string_map_jsont Jsont.(list string)
585
586(** {1 Callback - simplified to JSON} *)
587
588type callback = Jsont.json
589
590let callback_jsont : callback Jsont.t = Jsont.json
591let callback_or_ref_jsont = or_ref_jsont callback_jsont
592
593(** {1 Operation} *)
594
595type operation = {
596 tags : string list;
597 summary : string option;
598 description : string option;
599 external_docs : external_docs option;
600 operation_id : string option;
601 parameters : parameter or_ref list;
602 request_body : request_body or_ref option;
603 responses : responses;
604 callbacks : (string * callback or_ref) list;
605 deprecated : bool;
606 security : security_requirement list option;
607 servers : server list;
608}
609
610let operation_jsont : operation Jsont.t =
611 Jsont.Object.map ~kind:"Operation"
612 (fun tags summary description external_docs operation_id parameters
613 request_body responses callbacks deprecated security servers ->
614 { tags; summary; description; external_docs; operation_id; parameters;
615 request_body; responses; callbacks; deprecated; security; servers })
616 |> Jsont.Object.mem "tags" Jsont.(list string) ~dec_absent:[] ~enc:(fun o -> o.tags)
617 |> Jsont.Object.opt_mem "summary" Jsont.string ~enc:(fun o -> o.summary)
618 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun o -> o.description)
619 |> Jsont.Object.opt_mem "externalDocs" external_docs_jsont ~enc:(fun o -> o.external_docs)
620 |> Jsont.Object.opt_mem "operationId" Jsont.string ~enc:(fun o -> o.operation_id)
621 |> Jsont.Object.mem "parameters" Jsont.(list parameter_or_ref_jsont)
622 ~dec_absent:[] ~enc:(fun o -> o.parameters)
623 |> Jsont.Object.opt_mem "requestBody" request_body_or_ref_jsont ~enc:(fun o -> o.request_body)
624 |> Jsont.Object.mem "responses" responses_jsont
625 ~dec_absent:{ default = None; responses = [] } ~enc:(fun o -> o.responses)
626 |> Jsont.Object.mem "callbacks" (string_map_jsont callback_or_ref_jsont)
627 ~dec_absent:[] ~enc:(fun o -> o.callbacks)
628 |> Jsont.Object.mem "deprecated" Jsont.bool ~dec_absent:false ~enc:(fun o -> o.deprecated)
629 |> Jsont.Object.opt_mem "security" Jsont.(list security_requirement_jsont)
630 ~enc:(fun o -> o.security)
631 |> Jsont.Object.mem "servers" Jsont.(list server_jsont)
632 ~dec_absent:[] ~enc:(fun o -> o.servers)
633 |> Jsont.Object.skip_unknown
634 |> Jsont.Object.finish
635
636(** {1 Path Item} *)
637
638type path_item = {
639 ref_ : string option;
640 summary : string option;
641 description : string option;
642 get : operation option;
643 put : operation option;
644 post : operation option;
645 delete : operation option;
646 options : operation option;
647 head : operation option;
648 patch : operation option;
649 trace : operation option;
650 servers : server list;
651 parameters : parameter or_ref list;
652}
653
654let path_item_jsont : path_item Jsont.t =
655 Jsont.Object.map ~kind:"PathItem"
656 (fun ref_ summary description get put post delete options head patch trace
657 servers parameters ->
658 { ref_; summary; description; get; put; post; delete; options; head;
659 patch; trace; servers; parameters })
660 |> Jsont.Object.opt_mem "$ref" Jsont.string ~enc:(fun pi -> pi.ref_)
661 |> Jsont.Object.opt_mem "summary" Jsont.string ~enc:(fun pi -> pi.summary)
662 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun pi -> pi.description)
663 |> Jsont.Object.opt_mem "get" operation_jsont ~enc:(fun pi -> pi.get)
664 |> Jsont.Object.opt_mem "put" operation_jsont ~enc:(fun pi -> pi.put)
665 |> Jsont.Object.opt_mem "post" operation_jsont ~enc:(fun pi -> pi.post)
666 |> Jsont.Object.opt_mem "delete" operation_jsont ~enc:(fun pi -> pi.delete)
667 |> Jsont.Object.opt_mem "options" operation_jsont ~enc:(fun pi -> pi.options)
668 |> Jsont.Object.opt_mem "head" operation_jsont ~enc:(fun pi -> pi.head)
669 |> Jsont.Object.opt_mem "patch" operation_jsont ~enc:(fun pi -> pi.patch)
670 |> Jsont.Object.opt_mem "trace" operation_jsont ~enc:(fun pi -> pi.trace)
671 |> Jsont.Object.mem "servers" Jsont.(list server_jsont)
672 ~dec_absent:[] ~enc:(fun pi -> pi.servers)
673 |> Jsont.Object.mem "parameters" Jsont.(list parameter_or_ref_jsont)
674 ~dec_absent:[] ~enc:(fun pi -> pi.parameters)
675 |> Jsont.Object.skip_unknown
676 |> Jsont.Object.finish
677
678let path_item_or_ref_jsont = or_ref_jsont path_item_jsont
679
680(** {1 Security Scheme} *)
681
682type security_scheme_type =
683 | ApiKey
684 | Http
685 | OAuth2
686 | OpenIdConnect
687
688let security_scheme_type_jsont : security_scheme_type Jsont.t =
689 Jsont.map Jsont.string ~kind:"security_scheme_type"
690 ~dec:(function
691 | "apiKey" -> ApiKey
692 | "http" -> Http
693 | "oauth2" -> OAuth2
694 | "openIdConnect" -> OpenIdConnect
695 | s -> Jsont.Error.msgf Jsont.Meta.none "Unknown security scheme type: %s" s)
696 ~enc:(function
697 | ApiKey -> "apiKey"
698 | Http -> "http"
699 | OAuth2 -> "oauth2"
700 | OpenIdConnect -> "openIdConnect")
701
702type oauth_flow = {
703 authorization_url : string option;
704 token_url : string option;
705 refresh_url : string option;
706 scopes : (string * string) list;
707}
708
709let oauth_flow_jsont : oauth_flow Jsont.t =
710 Jsont.Object.map ~kind:"OAuthFlow"
711 (fun authorization_url token_url refresh_url scopes ->
712 { authorization_url; token_url; refresh_url; scopes })
713 |> Jsont.Object.opt_mem "authorizationUrl" Jsont.string ~enc:(fun f -> f.authorization_url)
714 |> Jsont.Object.opt_mem "tokenUrl" Jsont.string ~enc:(fun f -> f.token_url)
715 |> Jsont.Object.opt_mem "refreshUrl" Jsont.string ~enc:(fun f -> f.refresh_url)
716 |> Jsont.Object.mem "scopes" (string_map_jsont Jsont.string)
717 ~dec_absent:[] ~enc:(fun f -> f.scopes)
718 |> Jsont.Object.skip_unknown
719 |> Jsont.Object.finish
720
721type oauth_flows = {
722 implicit : oauth_flow option;
723 password : oauth_flow option;
724 client_credentials : oauth_flow option;
725 authorization_code : oauth_flow option;
726}
727
728let oauth_flows_jsont : oauth_flows Jsont.t =
729 Jsont.Object.map ~kind:"OAuthFlows"
730 (fun implicit password client_credentials authorization_code ->
731 { implicit; password; client_credentials; authorization_code })
732 |> Jsont.Object.opt_mem "implicit" oauth_flow_jsont ~enc:(fun f -> f.implicit)
733 |> Jsont.Object.opt_mem "password" oauth_flow_jsont ~enc:(fun f -> f.password)
734 |> Jsont.Object.opt_mem "clientCredentials" oauth_flow_jsont ~enc:(fun f -> f.client_credentials)
735 |> Jsont.Object.opt_mem "authorizationCode" oauth_flow_jsont ~enc:(fun f -> f.authorization_code)
736 |> Jsont.Object.skip_unknown
737 |> Jsont.Object.finish
738
739type security_scheme = {
740 type_ : security_scheme_type;
741 description : string option;
742 name : string option;
743 in_ : parameter_location option;
744 scheme : string option;
745 bearer_format : string option;
746 flows : oauth_flows option;
747 open_id_connect_url : string option;
748}
749
750let security_scheme_jsont : security_scheme Jsont.t =
751 Jsont.Object.map ~kind:"SecurityScheme"
752 (fun type_ description name in_ scheme bearer_format flows open_id_connect_url ->
753 { type_; description; name; in_; scheme; bearer_format; flows; open_id_connect_url })
754 |> Jsont.Object.mem "type" security_scheme_type_jsont ~enc:(fun ss -> ss.type_)
755 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:(fun ss -> ss.description)
756 |> Jsont.Object.opt_mem "name" Jsont.string ~enc:(fun ss -> ss.name)
757 |> Jsont.Object.opt_mem "in" parameter_location_jsont ~enc:(fun ss -> ss.in_)
758 |> Jsont.Object.opt_mem "scheme" Jsont.string ~enc:(fun ss -> ss.scheme)
759 |> Jsont.Object.opt_mem "bearerFormat" Jsont.string ~enc:(fun ss -> ss.bearer_format)
760 |> Jsont.Object.opt_mem "flows" oauth_flows_jsont ~enc:(fun ss -> ss.flows)
761 |> Jsont.Object.opt_mem "openIdConnectUrl" Jsont.string ~enc:(fun ss -> ss.open_id_connect_url)
762 |> Jsont.Object.skip_unknown
763 |> Jsont.Object.finish
764
765let security_scheme_or_ref_jsont = or_ref_jsont security_scheme_jsont
766
767(** {1 Components} *)
768
769type components = {
770 schemas : (string * schema or_ref) list;
771 responses : (string * response or_ref) list;
772 parameters : (string * parameter or_ref) list;
773 examples : (string * example or_ref) list;
774 request_bodies : (string * request_body or_ref) list;
775 headers : (string * header or_ref) list;
776 security_schemes : (string * security_scheme or_ref) list;
777 links : (string * link or_ref) list;
778 callbacks : (string * callback or_ref) list;
779 path_items : (string * path_item or_ref) list;
780}
781
782let components_jsont : components Jsont.t =
783 Jsont.Object.map ~kind:"Components"
784 (fun schemas responses parameters examples request_bodies headers
785 security_schemes links callbacks path_items ->
786 { schemas; responses; parameters; examples; request_bodies;
787 headers; security_schemes; links; callbacks; path_items })
788 |> Jsont.Object.mem "schemas" (string_map_jsont schema_or_ref_jsont)
789 ~dec_absent:[] ~enc:(fun c -> c.schemas)
790 |> Jsont.Object.mem "responses" (string_map_jsont response_or_ref_jsont)
791 ~dec_absent:[] ~enc:(fun c -> c.responses)
792 |> Jsont.Object.mem "parameters" (string_map_jsont parameter_or_ref_jsont)
793 ~dec_absent:[] ~enc:(fun c -> c.parameters)
794 |> Jsont.Object.mem "examples" (string_map_jsont example_or_ref_jsont)
795 ~dec_absent:[] ~enc:(fun c -> c.examples)
796 |> Jsont.Object.mem "requestBodies" (string_map_jsont request_body_or_ref_jsont)
797 ~dec_absent:[] ~enc:(fun c -> c.request_bodies)
798 |> Jsont.Object.mem "headers" (string_map_jsont header_or_ref_jsont)
799 ~dec_absent:[] ~enc:(fun c -> c.headers)
800 |> Jsont.Object.mem "securitySchemes" (string_map_jsont security_scheme_or_ref_jsont)
801 ~dec_absent:[] ~enc:(fun c -> c.security_schemes)
802 |> Jsont.Object.mem "links" (string_map_jsont link_or_ref_jsont)
803 ~dec_absent:[] ~enc:(fun c -> c.links)
804 |> Jsont.Object.mem "callbacks" (string_map_jsont callback_or_ref_jsont)
805 ~dec_absent:[] ~enc:(fun c -> c.callbacks)
806 |> Jsont.Object.mem "pathItems" (string_map_jsont path_item_or_ref_jsont)
807 ~dec_absent:[] ~enc:(fun c -> c.path_items)
808 |> Jsont.Object.skip_unknown
809 |> Jsont.Object.finish
810
811(** {1 OpenAPI Document} *)
812
813type t = {
814 openapi : string;
815 info : info;
816 servers : server list;
817 paths : (string * path_item) list;
818 webhooks : (string * path_item or_ref) list;
819 components : components option;
820 security : security_requirement list;
821 tags : tag list;
822 external_docs : external_docs option;
823}
824
825let jsont : t Jsont.t =
826 Jsont.Object.map ~kind:"OpenAPI"
827 (fun openapi info servers paths webhooks components security tags external_docs ->
828 { openapi; info; servers; paths; webhooks; components; security; tags; external_docs })
829 |> Jsont.Object.mem "openapi" Jsont.string ~enc:(fun t -> t.openapi)
830 |> Jsont.Object.mem "info" info_jsont ~enc:(fun t -> t.info)
831 |> Jsont.Object.mem "servers" Jsont.(list server_jsont)
832 ~dec_absent:[] ~enc:(fun t -> t.servers)
833 |> Jsont.Object.mem "paths" (string_map_jsont path_item_jsont)
834 ~dec_absent:[] ~enc:(fun t -> t.paths)
835 |> Jsont.Object.mem "webhooks" (string_map_jsont path_item_or_ref_jsont)
836 ~dec_absent:[] ~enc:(fun t -> t.webhooks)
837 |> Jsont.Object.opt_mem "components" components_jsont ~enc:(fun t -> t.components)
838 |> Jsont.Object.mem "security" Jsont.(list security_requirement_jsont)
839 ~dec_absent:[] ~enc:(fun t -> t.security)
840 |> Jsont.Object.mem "tags" Jsont.(list tag_jsont)
841 ~dec_absent:[] ~enc:(fun t -> t.tags)
842 |> Jsont.Object.opt_mem "externalDocs" external_docs_jsont ~enc:(fun t -> t.external_docs)
843 |> Jsont.Object.skip_unknown
844 |> Jsont.Object.finish
845
846(** {1 Parsing} *)
847
848let of_string s =
849 Jsont_bytesrw.decode_string jsont s
850
851let of_string' s =
852 Jsont_bytesrw.decode_string' jsont s
853
854let to_string t =
855 Jsont_bytesrw.encode_string ~format:Jsont.Indent jsont t
856
857let to_string' t =
858 Jsont_bytesrw.encode_string' ~format:Jsont.Indent jsont t
859
860(** {1 Reference Resolution} *)
861
862let resolve_schema_ref (ref_str : string) (spec : t) : schema option =
863 (* Parse $ref like "#/components/schemas/Pet" *)
864 if not (String.length ref_str > 0 && ref_str.[0] = '#') then None
865 else
866 let parts = String.split_on_char '/' ref_str in
867 match parts with
868 | ["#"; "components"; "schemas"; name] ->
869 (match spec.components with
870 | None -> None
871 | Some c ->
872 match List.assoc_opt name c.schemas with
873 | Some (Value s) -> Some s
874 | Some (Ref _) -> None (* nested refs not supported yet *)
875 | None -> None)
876 | _ -> None