OpenAPI generator for OCaml with Requests/Eio/Jsont
at main 876 lines 35 kB view raw
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