(** OpenAPI 3.x specification types with nox-json codecs. This module defines types that mirror the OpenAPI 3.0/3.1 specification, with bidirectional JSON codecs using nox-json. *) (** {1 Reference handling} *) type 'a or_ref = | Ref of string (** A $ref pointer like "#/components/schemas/Pet" *) | Value of 'a (** An inline value *) (** Find a member by name in an object's member list *) let find_member name (mems : Json.member list) : Json.t option = List.find_map (fun ((n, _meta), v) -> if n = name then Some v else None ) mems (** Create an or_ref codec that handles $ref pointers. Uses JSON as intermediate to detect $ref field. *) let or_ref_jsont (value_jsont : 'a Json.codec) : 'a or_ref Json.codec = Json.Codec.map Json.Codec.Value.t ~kind:"or_ref" ~dec:(fun json -> match json with | Json.Object (mems, _meta) -> (match find_member "$ref" mems with | Some (Json.String (ref_str, _)) -> Ref ref_str | _ -> match Json.Codec.decode value_jsont json with | Ok v -> Value v | Error e -> Json.Error.fail_msgf Json.Meta.none "%a" Json.Error.pp e) | _ -> match Json.Codec.decode value_jsont json with | Ok v -> Value v | Error e -> Json.Error.fail_msgf Json.Meta.none "%a" Json.Error.pp e) ~enc:(function | Ref r -> Json.Object ([(("$ref", Json.Meta.none), Json.String (r, Json.Meta.none))], Json.Meta.none) | Value v -> try Json.Codec.encode value_jsont v with Json.Error _ -> Json.Value.null ()) (** {1 String Map} *) module StringMap = Map.Make(String) let string_map_jsont (value_jsont : 'a Json.codec) : (string * 'a) list Json.codec = let map_jsont = Json.Codec.Object.as_string_map value_jsont in Json.Codec.map ~kind:"string_map" ~dec:(fun m -> StringMap.bindings m) ~enc:(fun pairs -> List.fold_left (fun m (k, v) -> StringMap.add k v m) StringMap.empty pairs) map_jsont (** {1 Contact} *) type contact = { name : string option; url : string option; email : string option; } let contact_jsont : contact Json.codec = Json.Codec.Object.map ~kind:"Contact" (fun name url email -> { name; url; email }) |> Json.Codec.Object.opt_member "name" Json.Codec.string ~enc:(fun c -> c.name) |> Json.Codec.Object.opt_member "url" Json.Codec.string ~enc:(fun c -> c.url) |> Json.Codec.Object.opt_member "email" Json.Codec.string ~enc:(fun c -> c.email) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 License} *) type license = { name : string; url : string option; } let license_jsont : license Json.codec = Json.Codec.Object.map ~kind:"License" (fun name url -> { name; url }) |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun l -> l.name) |> Json.Codec.Object.opt_member "url" Json.Codec.string ~enc:(fun l -> l.url) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Info} *) type info = { title : string; description : string option; terms_of_service : string option; contact : contact option; license : license option; version : string; } let info_jsont : info Json.codec = Json.Codec.Object.map ~kind:"Info" (fun title description terms_of_service contact license version -> { title; description; terms_of_service; contact; license; version }) |> Json.Codec.Object.member "title" Json.Codec.string ~enc:(fun i -> i.title) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun i -> i.description) |> Json.Codec.Object.opt_member "termsOfService" Json.Codec.string ~enc:(fun i -> i.terms_of_service) |> Json.Codec.Object.opt_member "contact" contact_jsont ~enc:(fun i -> i.contact) |> Json.Codec.Object.opt_member "license" license_jsont ~enc:(fun i -> i.license) |> Json.Codec.Object.member "version" Json.Codec.string ~enc:(fun i -> i.version) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Server} *) type server_variable = { enum : string list option; default : string; description : string option; } let server_variable_jsont : server_variable Json.codec = Json.Codec.Object.map ~kind:"ServerVariable" (fun enum default description -> { enum; default; description }) |> Json.Codec.Object.opt_member "enum" Json.Codec.(list string) ~enc:(fun sv -> sv.enum) |> Json.Codec.Object.member "default" Json.Codec.string ~enc:(fun sv -> sv.default) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun sv -> sv.description) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal type server = { url : string; description : string option; variables : (string * server_variable) list; } let server_jsont : server Json.codec = Json.Codec.Object.map ~kind:"Server" (fun url description variables -> { url; description; variables }) |> Json.Codec.Object.member "url" Json.Codec.string ~enc:(fun s -> s.url) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun s -> s.description) |> Json.Codec.Object.member "variables" (string_map_jsont server_variable_jsont) ~dec_absent:[] ~enc:(fun s -> s.variables) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 External Documentation} *) type external_docs = { description : string option; url : string; } let external_docs_jsont : external_docs Json.codec = Json.Codec.Object.map ~kind:"ExternalDocs" (fun description url -> { description; url }) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun ed -> ed.description) |> Json.Codec.Object.member "url" Json.Codec.string ~enc:(fun ed -> ed.url) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Tag} *) type tag = { name : string; description : string option; external_docs : external_docs option; } let tag_jsont : tag Json.codec = Json.Codec.Object.map ~kind:"Tag" (fun name description external_docs -> { name; description; external_docs }) |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun t -> t.name) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun t -> t.description) |> Json.Codec.Object.opt_member "externalDocs" external_docs_jsont ~enc:(fun t -> t.external_docs) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Discriminator} *) type discriminator = { property_name : string; mapping : (string * string) list; } let discriminator_jsont : discriminator Json.codec = Json.Codec.Object.map ~kind:"Discriminator" (fun property_name mapping -> { property_name; mapping }) |> Json.Codec.Object.member "propertyName" Json.Codec.string ~enc:(fun d -> d.property_name) |> Json.Codec.Object.member "mapping" (string_map_jsont Json.Codec.string) ~dec_absent:[] ~enc:(fun d -> d.mapping) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Schema} JSON Schema with OpenAPI extensions. We use a simplified approach where references are stored as schema or_ref. *) type schema = { title : string option; description : string option; type_ : string option; format : string option; default : Json.t option; nullable : bool; read_only : bool; write_only : bool; deprecated : bool; (* Validation *) enum : Json.t list option; const : Json.t option; minimum : float option; maximum : float option; exclusive_minimum : float option; exclusive_maximum : float option; multiple_of : float option; min_length : int option; max_length : int option; pattern : string option; min_items : int option; max_items : int option; unique_items : bool; min_properties : int option; max_properties : int option; (* Composition - stored as JSON for simplicity *) all_of : Json.t list option; one_of : Json.t list option; any_of : Json.t list option; not_ : Json.t option; (* Object - stored as JSON for simplicity *) properties : (string * Json.t) list; required : string list; additional_properties : Json.t option; (* Array *) items : Json.t option; (* Discriminator *) discriminator : discriminator option; (* Examples *) example : Json.t option; } let empty_schema = { title = None; description = None; type_ = None; format = None; default = None; nullable = false; read_only = false; write_only = false; deprecated = false; enum = None; const = None; minimum = None; maximum = None; exclusive_minimum = None; exclusive_maximum = None; multiple_of = None; min_length = None; max_length = None; pattern = None; min_items = None; max_items = None; unique_items = false; min_properties = None; max_properties = None; all_of = None; one_of = None; any_of = None; not_ = None; properties = []; required = []; additional_properties = None; items = None; discriminator = None; example = None; } let schema_jsont : schema Json.codec = Json.Codec.Object.map ~kind:"Schema" (fun title description type_ format default nullable read_only write_only deprecated enum const minimum maximum exclusive_minimum exclusive_maximum multiple_of min_length max_length pattern min_items max_items unique_items min_properties max_properties all_of one_of any_of not_ properties required additional_properties items discriminator example -> { title; description; type_; format; default; nullable; read_only; write_only; deprecated; enum; const; minimum; maximum; exclusive_minimum; exclusive_maximum; multiple_of; min_length; max_length; pattern; min_items; max_items; unique_items; min_properties; max_properties; all_of; one_of; any_of; not_; properties; required; additional_properties; items; discriminator; example }) |> Json.Codec.Object.opt_member "title" Json.Codec.string ~enc:(fun s -> s.title) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun s -> s.description) |> Json.Codec.Object.opt_member "type" Json.Codec.string ~enc:(fun s -> s.type_) |> Json.Codec.Object.opt_member "format" Json.Codec.string ~enc:(fun s -> s.format) |> Json.Codec.Object.opt_member "default" Json.Codec.Value.t ~enc:(fun s -> s.default) |> Json.Codec.Object.member "nullable" Json.Codec.bool ~dec_absent:false ~enc:(fun s -> s.nullable) |> Json.Codec.Object.member "readOnly" Json.Codec.bool ~dec_absent:false ~enc:(fun s -> s.read_only) |> Json.Codec.Object.member "writeOnly" Json.Codec.bool ~dec_absent:false ~enc:(fun s -> s.write_only) |> Json.Codec.Object.member "deprecated" Json.Codec.bool ~dec_absent:false ~enc:(fun s -> s.deprecated) |> Json.Codec.Object.opt_member "enum" Json.Codec.(list Value.t) ~enc:(fun s -> s.enum) |> Json.Codec.Object.opt_member "const" Json.Codec.Value.t ~enc:(fun s -> s.const) |> Json.Codec.Object.opt_member "minimum" Json.Codec.number ~enc:(fun s -> s.minimum) |> Json.Codec.Object.opt_member "maximum" Json.Codec.number ~enc:(fun s -> s.maximum) |> Json.Codec.Object.opt_member "exclusiveMinimum" Json.Codec.number ~enc:(fun s -> s.exclusive_minimum) |> Json.Codec.Object.opt_member "exclusiveMaximum" Json.Codec.number ~enc:(fun s -> s.exclusive_maximum) |> Json.Codec.Object.opt_member "multipleOf" Json.Codec.number ~enc:(fun s -> s.multiple_of) |> Json.Codec.Object.opt_member "minLength" Json.Codec.int ~enc:(fun s -> s.min_length) |> Json.Codec.Object.opt_member "maxLength" Json.Codec.int ~enc:(fun s -> s.max_length) |> Json.Codec.Object.opt_member "pattern" Json.Codec.string ~enc:(fun s -> s.pattern) |> Json.Codec.Object.opt_member "minItems" Json.Codec.int ~enc:(fun s -> s.min_items) |> Json.Codec.Object.opt_member "maxItems" Json.Codec.int ~enc:(fun s -> s.max_items) |> Json.Codec.Object.member "uniqueItems" Json.Codec.bool ~dec_absent:false ~enc:(fun s -> s.unique_items) |> Json.Codec.Object.opt_member "minProperties" Json.Codec.int ~enc:(fun s -> s.min_properties) |> Json.Codec.Object.opt_member "maxProperties" Json.Codec.int ~enc:(fun s -> s.max_properties) |> Json.Codec.Object.opt_member "allOf" Json.Codec.(list Value.t) ~enc:(fun s -> s.all_of) |> Json.Codec.Object.opt_member "oneOf" Json.Codec.(list Value.t) ~enc:(fun s -> s.one_of) |> Json.Codec.Object.opt_member "anyOf" Json.Codec.(list Value.t) ~enc:(fun s -> s.any_of) |> Json.Codec.Object.opt_member "not" Json.Codec.Value.t ~enc:(fun s -> s.not_) |> Json.Codec.Object.member "properties" (string_map_jsont Json.Codec.Value.t) ~dec_absent:[] ~enc:(fun s -> s.properties) |> Json.Codec.Object.member "required" Json.Codec.(list string) ~dec_absent:[] ~enc:(fun s -> s.required) |> Json.Codec.Object.opt_member "additionalProperties" Json.Codec.Value.t ~enc:(fun s -> s.additional_properties) |> Json.Codec.Object.opt_member "items" Json.Codec.Value.t ~enc:(fun s -> s.items) |> Json.Codec.Object.opt_member "discriminator" discriminator_jsont ~enc:(fun s -> s.discriminator) |> Json.Codec.Object.opt_member "example" Json.Codec.Value.t ~enc:(fun s -> s.example) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let schema_or_ref_jsont = or_ref_jsont schema_jsont (** {1 Parameter} *) type parameter_location = Query | Header | Path | Cookie let parameter_location_jsont : parameter_location Json.codec = Json.Codec.map Json.Codec.string ~kind:"parameter_location" ~dec:(function | "query" -> Query | "header" -> Header | "path" -> Path | "cookie" -> Cookie | s -> Json.Error.fail_msgf Json.Meta.none "Unknown parameter location: %s" s) ~enc:(function | Query -> "query" | Header -> "header" | Path -> "path" | Cookie -> "cookie") type parameter_style = | Matrix | Label | Form | Simple | SpaceDelimited | PipeDelimited | DeepObject let parameter_style_jsont : parameter_style Json.codec = Json.Codec.map Json.Codec.string ~kind:"parameter_style" ~dec:(function | "matrix" -> Matrix | "label" -> Label | "form" -> Form | "simple" -> Simple | "spaceDelimited" -> SpaceDelimited | "pipeDelimited" -> PipeDelimited | "deepObject" -> DeepObject | s -> Json.Error.fail_msgf Json.Meta.none "Unknown parameter style: %s" s) ~enc:(function | Matrix -> "matrix" | Label -> "label" | Form -> "form" | Simple -> "simple" | SpaceDelimited -> "spaceDelimited" | PipeDelimited -> "pipeDelimited" | DeepObject -> "deepObject") (** {1 Example} *) type example = { summary : string option; description : string option; value : Json.t option; external_value : string option; } let example_jsont : example Json.codec = Json.Codec.Object.map ~kind:"Example" (fun summary description value external_value -> { summary; description; value; external_value }) |> Json.Codec.Object.opt_member "summary" Json.Codec.string ~enc:(fun e -> e.summary) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun e -> e.description) |> Json.Codec.Object.opt_member "value" Json.Codec.Value.t ~enc:(fun e -> e.value) |> Json.Codec.Object.opt_member "externalValue" Json.Codec.string ~enc:(fun e -> e.external_value) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let example_or_ref_jsont = or_ref_jsont example_jsont (** {1 Header} *) type header = { description : string option; required : bool; deprecated : bool; schema : schema or_ref option; } let header_jsont : header Json.codec = Json.Codec.Object.map ~kind:"Header" (fun description required deprecated schema -> { description; required; deprecated; schema }) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun h -> h.description) |> Json.Codec.Object.member "required" Json.Codec.bool ~dec_absent:false ~enc:(fun h -> h.required) |> Json.Codec.Object.member "deprecated" Json.Codec.bool ~dec_absent:false ~enc:(fun h -> h.deprecated) |> Json.Codec.Object.opt_member "schema" schema_or_ref_jsont ~enc:(fun h -> h.schema) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let header_or_ref_jsont = or_ref_jsont header_jsont (** {1 Encoding} *) type encoding = { content_type : string option; headers : (string * header or_ref) list; style : parameter_style option; explode : bool option; allow_reserved : bool; } let encoding_jsont : encoding Json.codec = Json.Codec.Object.map ~kind:"Encoding" (fun content_type headers style explode allow_reserved -> { content_type; headers; style; explode; allow_reserved }) |> Json.Codec.Object.opt_member "contentType" Json.Codec.string ~enc:(fun e -> e.content_type) |> Json.Codec.Object.member "headers" (string_map_jsont header_or_ref_jsont) ~dec_absent:[] ~enc:(fun e -> e.headers) |> Json.Codec.Object.opt_member "style" parameter_style_jsont ~enc:(fun e -> e.style) |> Json.Codec.Object.opt_member "explode" Json.Codec.bool ~enc:(fun e -> e.explode) |> Json.Codec.Object.member "allowReserved" Json.Codec.bool ~dec_absent:false ~enc:(fun e -> e.allow_reserved) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Media Type} *) type media_type = { schema : schema or_ref option; example : Json.t option; examples : (string * example or_ref) list; encoding : (string * encoding) list; } let media_type_jsont : media_type Json.codec = Json.Codec.Object.map ~kind:"MediaType" (fun schema example examples encoding -> { schema; example; examples; encoding }) |> Json.Codec.Object.opt_member "schema" schema_or_ref_jsont ~enc:(fun mt -> mt.schema) |> Json.Codec.Object.opt_member "example" Json.Codec.Value.t ~enc:(fun mt -> mt.example) |> Json.Codec.Object.member "examples" (string_map_jsont example_or_ref_jsont) ~dec_absent:[] ~enc:(fun mt -> mt.examples) |> Json.Codec.Object.member "encoding" (string_map_jsont encoding_jsont) ~dec_absent:[] ~enc:(fun mt -> mt.encoding) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Parameter} *) type parameter = { name : string; in_ : parameter_location; description : string option; required : bool; deprecated : bool; allow_empty_value : bool; style : parameter_style option; explode : bool option; allow_reserved : bool; schema : schema or_ref option; example : Json.t option; content : (string * media_type) list; } let parameter_jsont : parameter Json.codec = Json.Codec.Object.map ~kind:"Parameter" (fun name in_ description required deprecated allow_empty_value style explode allow_reserved schema example content -> { name; in_; description; required; deprecated; allow_empty_value; style; explode; allow_reserved; schema; example; content }) |> Json.Codec.Object.member "name" Json.Codec.string ~enc:(fun p -> p.name) |> Json.Codec.Object.member "in" parameter_location_jsont ~enc:(fun p -> p.in_) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun p -> p.description) |> Json.Codec.Object.member "required" Json.Codec.bool ~dec_absent:false ~enc:(fun p -> p.required) |> Json.Codec.Object.member "deprecated" Json.Codec.bool ~dec_absent:false ~enc:(fun p -> p.deprecated) |> Json.Codec.Object.member "allowEmptyValue" Json.Codec.bool ~dec_absent:false ~enc:(fun p -> p.allow_empty_value) |> Json.Codec.Object.opt_member "style" parameter_style_jsont ~enc:(fun p -> p.style) |> Json.Codec.Object.opt_member "explode" Json.Codec.bool ~enc:(fun p -> p.explode) |> Json.Codec.Object.member "allowReserved" Json.Codec.bool ~dec_absent:false ~enc:(fun p -> p.allow_reserved) |> Json.Codec.Object.opt_member "schema" schema_or_ref_jsont ~enc:(fun p -> p.schema) |> Json.Codec.Object.opt_member "example" Json.Codec.Value.t ~enc:(fun p -> p.example) |> Json.Codec.Object.member "content" (string_map_jsont media_type_jsont) ~dec_absent:[] ~enc:(fun p -> p.content) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let parameter_or_ref_jsont = or_ref_jsont parameter_jsont (** {1 Request Body} *) type request_body = { description : string option; content : (string * media_type) list; required : bool; } let request_body_jsont : request_body Json.codec = Json.Codec.Object.map ~kind:"RequestBody" (fun description content required -> { description; content; required }) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun rb -> rb.description) |> Json.Codec.Object.member "content" (string_map_jsont media_type_jsont) ~dec_absent:[] ~enc:(fun rb -> rb.content) |> Json.Codec.Object.member "required" Json.Codec.bool ~dec_absent:false ~enc:(fun rb -> rb.required) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let request_body_or_ref_jsont = or_ref_jsont request_body_jsont (** {1 Link} *) type link = { operation_ref : string option; operation_id : string option; parameters : (string * Json.t) list; request_body : Json.t option; description : string option; server : server option; } let link_jsont : link Json.codec = Json.Codec.Object.map ~kind:"Link" (fun operation_ref operation_id parameters request_body description server -> { operation_ref; operation_id; parameters; request_body; description; server }) |> Json.Codec.Object.opt_member "operationRef" Json.Codec.string ~enc:(fun l -> l.operation_ref) |> Json.Codec.Object.opt_member "operationId" Json.Codec.string ~enc:(fun l -> l.operation_id) |> Json.Codec.Object.member "parameters" (string_map_jsont Json.Codec.Value.t) ~dec_absent:[] ~enc:(fun l -> l.parameters) |> Json.Codec.Object.opt_member "requestBody" Json.Codec.Value.t ~enc:(fun l -> l.request_body) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun l -> l.description) |> Json.Codec.Object.opt_member "server" server_jsont ~enc:(fun l -> l.server) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let link_or_ref_jsont = or_ref_jsont link_jsont (** {1 Response} *) type response = { description : string; headers : (string * header or_ref) list; content : (string * media_type) list; links : (string * link or_ref) list; } let response_jsont : response Json.codec = Json.Codec.Object.map ~kind:"Response" (fun description headers content links -> { description; headers; content; links }) |> Json.Codec.Object.member "description" Json.Codec.string ~dec_absent:"" ~enc:(fun r -> r.description) |> Json.Codec.Object.member "headers" (string_map_jsont header_or_ref_jsont) ~dec_absent:[] ~enc:(fun r -> r.headers) |> Json.Codec.Object.member "content" (string_map_jsont media_type_jsont) ~dec_absent:[] ~enc:(fun r -> r.content) |> Json.Codec.Object.member "links" (string_map_jsont link_or_ref_jsont) ~dec_absent:[] ~enc:(fun r -> r.links) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let response_or_ref_jsont = or_ref_jsont response_jsont (** {1 Responses} *) type responses = { default : response or_ref option; responses : (string * response or_ref) list; (* status code -> response *) } let responses_jsont : responses Json.codec = (* Responses is an object where keys are status codes or "default" *) Json.Codec.map (Json.Codec.Object.as_string_map response_or_ref_jsont) ~kind:"Responses" ~dec:(fun m -> let default = StringMap.find_opt "default" m in let responses = StringMap.bindings m |> List.filter (fun (k, _) -> k <> "default") in { default; responses }) ~enc:(fun r -> let m = List.fold_left (fun m (k, v) -> StringMap.add k v m) StringMap.empty r.responses in match r.default with | Some d -> StringMap.add "default" d m | None -> m) (** {1 Security Requirement} *) type security_requirement = (string * string list) list let security_requirement_jsont : security_requirement Json.codec = string_map_jsont Json.Codec.(list string) (** {1 Callback - simplified to JSON} *) type callback = Json.t let callback_jsont : callback Json.codec = Json.Codec.Value.t let callback_or_ref_jsont = or_ref_jsont callback_jsont (** {1 Operation} *) type operation = { tags : string list; summary : string option; description : string option; external_docs : external_docs option; operation_id : string option; parameters : parameter or_ref list; request_body : request_body or_ref option; responses : responses; callbacks : (string * callback or_ref) list; deprecated : bool; security : security_requirement list option; servers : server list; } let operation_jsont : operation Json.codec = Json.Codec.Object.map ~kind:"Operation" (fun tags summary description external_docs operation_id parameters request_body responses callbacks deprecated security servers -> { tags; summary; description; external_docs; operation_id; parameters; request_body; responses; callbacks; deprecated; security; servers }) |> Json.Codec.Object.member "tags" Json.Codec.(list string) ~dec_absent:[] ~enc:(fun o -> o.tags) |> Json.Codec.Object.opt_member "summary" Json.Codec.string ~enc:(fun o -> o.summary) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun o -> o.description) |> Json.Codec.Object.opt_member "externalDocs" external_docs_jsont ~enc:(fun o -> o.external_docs) |> Json.Codec.Object.opt_member "operationId" Json.Codec.string ~enc:(fun o -> o.operation_id) |> Json.Codec.Object.member "parameters" Json.Codec.(list parameter_or_ref_jsont) ~dec_absent:[] ~enc:(fun o -> o.parameters) |> Json.Codec.Object.opt_member "requestBody" request_body_or_ref_jsont ~enc:(fun o -> o.request_body) |> Json.Codec.Object.member "responses" responses_jsont ~dec_absent:{ default = None; responses = [] } ~enc:(fun o -> o.responses) |> Json.Codec.Object.member "callbacks" (string_map_jsont callback_or_ref_jsont) ~dec_absent:[] ~enc:(fun o -> o.callbacks) |> Json.Codec.Object.member "deprecated" Json.Codec.bool ~dec_absent:false ~enc:(fun o -> o.deprecated) |> Json.Codec.Object.opt_member "security" Json.Codec.(list security_requirement_jsont) ~enc:(fun o -> o.security) |> Json.Codec.Object.member "servers" Json.Codec.(list server_jsont) ~dec_absent:[] ~enc:(fun o -> o.servers) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Path Item} *) type path_item = { ref_ : string option; summary : string option; description : string option; get : operation option; put : operation option; post : operation option; delete : operation option; options : operation option; head : operation option; patch : operation option; trace : operation option; servers : server list; parameters : parameter or_ref list; } let path_item_jsont : path_item Json.codec = Json.Codec.Object.map ~kind:"PathItem" (fun ref_ summary description get put post delete options head patch trace servers parameters -> { ref_; summary; description; get; put; post; delete; options; head; patch; trace; servers; parameters }) |> Json.Codec.Object.opt_member "$ref" Json.Codec.string ~enc:(fun pi -> pi.ref_) |> Json.Codec.Object.opt_member "summary" Json.Codec.string ~enc:(fun pi -> pi.summary) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun pi -> pi.description) |> Json.Codec.Object.opt_member "get" operation_jsont ~enc:(fun pi -> pi.get) |> Json.Codec.Object.opt_member "put" operation_jsont ~enc:(fun pi -> pi.put) |> Json.Codec.Object.opt_member "post" operation_jsont ~enc:(fun pi -> pi.post) |> Json.Codec.Object.opt_member "delete" operation_jsont ~enc:(fun pi -> pi.delete) |> Json.Codec.Object.opt_member "options" operation_jsont ~enc:(fun pi -> pi.options) |> Json.Codec.Object.opt_member "head" operation_jsont ~enc:(fun pi -> pi.head) |> Json.Codec.Object.opt_member "patch" operation_jsont ~enc:(fun pi -> pi.patch) |> Json.Codec.Object.opt_member "trace" operation_jsont ~enc:(fun pi -> pi.trace) |> Json.Codec.Object.member "servers" Json.Codec.(list server_jsont) ~dec_absent:[] ~enc:(fun pi -> pi.servers) |> Json.Codec.Object.member "parameters" Json.Codec.(list parameter_or_ref_jsont) ~dec_absent:[] ~enc:(fun pi -> pi.parameters) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let path_item_or_ref_jsont = or_ref_jsont path_item_jsont (** {1 Security Scheme} *) type security_scheme_type = | ApiKey | Http | OAuth2 | OpenIdConnect let security_scheme_type_jsont : security_scheme_type Json.codec = Json.Codec.map Json.Codec.string ~kind:"security_scheme_type" ~dec:(function | "apiKey" -> ApiKey | "http" -> Http | "oauth2" -> OAuth2 | "openIdConnect" -> OpenIdConnect | s -> Json.Error.fail_msgf Json.Meta.none "Unknown security scheme type: %s" s) ~enc:(function | ApiKey -> "apiKey" | Http -> "http" | OAuth2 -> "oauth2" | OpenIdConnect -> "openIdConnect") type oauth_flow = { authorization_url : string option; token_url : string option; refresh_url : string option; scopes : (string * string) list; } let oauth_flow_jsont : oauth_flow Json.codec = Json.Codec.Object.map ~kind:"OAuthFlow" (fun authorization_url token_url refresh_url scopes -> { authorization_url; token_url; refresh_url; scopes }) |> Json.Codec.Object.opt_member "authorizationUrl" Json.Codec.string ~enc:(fun f -> f.authorization_url) |> Json.Codec.Object.opt_member "tokenUrl" Json.Codec.string ~enc:(fun f -> f.token_url) |> Json.Codec.Object.opt_member "refreshUrl" Json.Codec.string ~enc:(fun f -> f.refresh_url) |> Json.Codec.Object.member "scopes" (string_map_jsont Json.Codec.string) ~dec_absent:[] ~enc:(fun f -> f.scopes) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal type oauth_flows = { implicit : oauth_flow option; password : oauth_flow option; client_credentials : oauth_flow option; authorization_code : oauth_flow option; } let oauth_flows_jsont : oauth_flows Json.codec = Json.Codec.Object.map ~kind:"OAuthFlows" (fun implicit password client_credentials authorization_code -> { implicit; password; client_credentials; authorization_code }) |> Json.Codec.Object.opt_member "implicit" oauth_flow_jsont ~enc:(fun f -> f.implicit) |> Json.Codec.Object.opt_member "password" oauth_flow_jsont ~enc:(fun f -> f.password) |> Json.Codec.Object.opt_member "clientCredentials" oauth_flow_jsont ~enc:(fun f -> f.client_credentials) |> Json.Codec.Object.opt_member "authorizationCode" oauth_flow_jsont ~enc:(fun f -> f.authorization_code) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal type security_scheme = { type_ : security_scheme_type; description : string option; name : string option; in_ : parameter_location option; scheme : string option; bearer_format : string option; flows : oauth_flows option; open_id_connect_url : string option; } let security_scheme_jsont : security_scheme Json.codec = Json.Codec.Object.map ~kind:"SecurityScheme" (fun type_ description name in_ scheme bearer_format flows open_id_connect_url -> { type_; description; name; in_; scheme; bearer_format; flows; open_id_connect_url }) |> Json.Codec.Object.member "type" security_scheme_type_jsont ~enc:(fun ss -> ss.type_) |> Json.Codec.Object.opt_member "description" Json.Codec.string ~enc:(fun ss -> ss.description) |> Json.Codec.Object.opt_member "name" Json.Codec.string ~enc:(fun ss -> ss.name) |> Json.Codec.Object.opt_member "in" parameter_location_jsont ~enc:(fun ss -> ss.in_) |> Json.Codec.Object.opt_member "scheme" Json.Codec.string ~enc:(fun ss -> ss.scheme) |> Json.Codec.Object.opt_member "bearerFormat" Json.Codec.string ~enc:(fun ss -> ss.bearer_format) |> Json.Codec.Object.opt_member "flows" oauth_flows_jsont ~enc:(fun ss -> ss.flows) |> Json.Codec.Object.opt_member "openIdConnectUrl" Json.Codec.string ~enc:(fun ss -> ss.open_id_connect_url) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal let security_scheme_or_ref_jsont = or_ref_jsont security_scheme_jsont (** {1 Components} *) type components = { schemas : (string * schema or_ref) list; responses : (string * response or_ref) list; parameters : (string * parameter or_ref) list; examples : (string * example or_ref) list; request_bodies : (string * request_body or_ref) list; headers : (string * header or_ref) list; security_schemes : (string * security_scheme or_ref) list; links : (string * link or_ref) list; callbacks : (string * callback or_ref) list; path_items : (string * path_item or_ref) list; } let components_jsont : components Json.codec = Json.Codec.Object.map ~kind:"Components" (fun schemas responses parameters examples request_bodies headers security_schemes links callbacks path_items -> { schemas; responses; parameters; examples; request_bodies; headers; security_schemes; links; callbacks; path_items }) |> Json.Codec.Object.member "schemas" (string_map_jsont schema_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.schemas) |> Json.Codec.Object.member "responses" (string_map_jsont response_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.responses) |> Json.Codec.Object.member "parameters" (string_map_jsont parameter_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.parameters) |> Json.Codec.Object.member "examples" (string_map_jsont example_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.examples) |> Json.Codec.Object.member "requestBodies" (string_map_jsont request_body_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.request_bodies) |> Json.Codec.Object.member "headers" (string_map_jsont header_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.headers) |> Json.Codec.Object.member "securitySchemes" (string_map_jsont security_scheme_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.security_schemes) |> Json.Codec.Object.member "links" (string_map_jsont link_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.links) |> Json.Codec.Object.member "callbacks" (string_map_jsont callback_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.callbacks) |> Json.Codec.Object.member "pathItems" (string_map_jsont path_item_or_ref_jsont) ~dec_absent:[] ~enc:(fun c -> c.path_items) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 OpenAPI Document} *) type t = { openapi : string; info : info; servers : server list; paths : (string * path_item) list; webhooks : (string * path_item or_ref) list; components : components option; security : security_requirement list; tags : tag list; external_docs : external_docs option; } let jsont : t Json.codec = Json.Codec.Object.map ~kind:"OpenAPI" (fun openapi info servers paths webhooks components security tags external_docs -> { openapi; info; servers; paths; webhooks; components; security; tags; external_docs }) |> Json.Codec.Object.member "openapi" Json.Codec.string ~enc:(fun t -> t.openapi) |> Json.Codec.Object.member "info" info_jsont ~enc:(fun t -> t.info) |> Json.Codec.Object.member "servers" Json.Codec.(list server_jsont) ~dec_absent:[] ~enc:(fun t -> t.servers) |> Json.Codec.Object.member "paths" (string_map_jsont path_item_jsont) ~dec_absent:[] ~enc:(fun t -> t.paths) |> Json.Codec.Object.member "webhooks" (string_map_jsont path_item_or_ref_jsont) ~dec_absent:[] ~enc:(fun t -> t.webhooks) |> Json.Codec.Object.opt_member "components" components_jsont ~enc:(fun t -> t.components) |> Json.Codec.Object.member "security" Json.Codec.(list security_requirement_jsont) ~dec_absent:[] ~enc:(fun t -> t.security) |> Json.Codec.Object.member "tags" Json.Codec.(list tag_jsont) ~dec_absent:[] ~enc:(fun t -> t.tags) |> Json.Codec.Object.opt_member "externalDocs" external_docs_jsont ~enc:(fun t -> t.external_docs) |> Json.Codec.Object.skip_unknown |> Json.Codec.Object.seal (** {1 Parsing} *) let of_string s = match Json.of_string jsont s with | Ok v -> Ok v | Error e -> Error (Format.asprintf "%a" Json.Error.pp e) let of_string' s = Json.of_string jsont s let to_string t = try Ok (Json.to_string ~indent:2 jsont t) with Json.Error e -> Error (Format.asprintf "%a" Json.Error.pp e) let to_string' t = try Ok (Json.to_string ~indent:2 jsont t) with Json.Error e -> Error e (** {1 Reference Resolution} *) let resolve_schema_ref (ref_str : string) (spec : t) : schema option = (* Parse $ref like "#/components/schemas/Pet" *) if not (String.length ref_str > 0 && ref_str.[0] = '#') then None else let parts = String.split_on_char '/' ref_str in match parts with | ["#"; "components"; "schemas"; name] -> (match spec.components with | None -> None | Some c -> match List.assoc_opt name c.schemas with | Some (Value s) -> Some s | Some (Ref _) -> None (* nested refs not supported yet *) | None -> None) | _ -> None