├── .gitattributes ├── .gitignore ├── .ocamlformat ├── README.md ├── bin ├── dune └── main.ml ├── dune-project ├── lib ├── codegen_schema.ml ├── codegen_schema.mli ├── dune ├── gen.ml ├── gen.mli ├── mod.ml ├── mod.mli ├── param.ml ├── param.mli ├── swagger.atd ├── swagger.ml ├── swagger.mli ├── swagger_util.ml ├── type.ml ├── type.mli ├── util.ml ├── val.ml └── val.mli ├── swagger.opam └── test ├── allOf ├── composition.expected.ml ├── composition.json ├── dune ├── polymorphism.json └── test.ml ├── docker ├── dune ├── swagger.json ├── test.ml └── update.sh ├── kubernetes ├── README.md ├── dune ├── k8s.expected.ml ├── swagger.json ├── test.ml └── update.sh └── petstore ├── dune ├── petstore.expected.ml ├── swagger.json └── test.ml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | *.install 3 | _opam 4 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | version = 0.23.0 2 | ocaml-version = 4.14 3 | profile = default 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCaml-Swagger 2 | 3 | ## Introduction 4 | 5 | OCaml-Swagger is a code generator that implements 6 | [Swagger 2.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md) 7 | API clients in OCaml. 8 | 9 | The motivation for this project was the development of a 10 | [Kubernetes API](https://kubernetes.io/docs/reference/) 11 | client, which can be found in the 12 | [Kubecaml](https://github.com/andrenth/kubecaml) 13 | project. 14 | 15 | Therefore, while the Kubernetes API is quite large and uses many of Swagger's 16 | features, this library doesn't currently support all of them. Remaining features 17 | will be implemented if needed to support the Kubernetes API, though of course 18 | contributions are welcome. 19 | 20 | ## Installation 21 | 22 | OCaml-Swagger is available on opam: 23 | 24 | ```sh 25 | $ opam install swagger 26 | ``` 27 | 28 | ## Usage 29 | 30 | Use the `ocaml-swagger` executable: 31 | 32 | ``` shell 33 | ocaml-swagger [-o ] [] 34 | -path Path base 35 | -definition Definition base 36 | -reference-base Reference base 37 | -reference-root Reference root 38 | -o Set output file name 39 | -help Display this list of options 40 | --help Display this list of options 41 | ``` 42 | 43 | Most users will probably only need to use the `Swagger.codegen_from_file` 44 | function to parse an API specification and generate the OCaml code: 45 | 46 | ```ocaml 47 | let () = 48 | Swagger.codegen_from_file 49 | ~output:stdout 50 | ~path_base:"/" 51 | ~definition_base:"io.k8s." 52 | ~reference_base:"#/definitions/io.k8s." 53 | ~reference_root:"Definitions" 54 | Sys.argv.(1) 55 | ``` 56 | 57 | This call instructs OCaml-Swagger to read the API specification from the file 58 | name given in the first command-line argument and output the resulting code to 59 | the standard output. 60 | 61 | The remaining arguments are prefixes that are stripped from the API definitions 62 | when generating module names. For example, in the Kubernetes API, the 63 | [Definitions Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject)s 64 | are named using a reverse-domain name convention, as in 65 | `io.k8s.api.apps.v1.DaemonSet`. Given the `definition_base` above, the 66 | corresponding OCaml module would be `Api.Apps.V1.Daemon_set`, that is, the 67 | `io.k8s` prefix is ignored in the OCaml module structure. 68 | 69 | Similarly, references to definitions are specified in the Kubernetes API as 70 | `"$ref": "#/definitions/io.k8s.api.apps.v1.DaemonSet"`. The `reference_base` 71 | parameter allows a prefix to be ignored in 72 | [Reference Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#referenceObject)s. 73 | 74 | Finally, the `reference_root` parameter specifies the submodule in which the 75 | code for Definition Objects will be created. 76 | 77 | ## The generated code 78 | 79 | Since Swagger APIs may contain reference cycles, OCaml-Swagger uses the 80 | [recursive modules trick](https://blog.janestreet.com/a-trick-recursive-modules-from-recursive-signatures/) 81 | to work around the OCaml restriction that forces one to only reference types or 82 | values that have been previously defined. The wrapper recursive module that 83 | acts as a namespace will have its named derived from the API's 84 | [Info Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#infoObject)'s 85 | `title` field. 86 | 87 | Inside this module, a module structure mirroring the URI structure of 88 | definitions and operations defined in the Swagger specification will be created. 89 | 90 | The code for Definitions defines a type `t` for the definition, a `make` 91 | function taking as many arguments as necessary to create it, and one accessor 92 | function for each definition property. 93 | 94 | Operation modules have one function per HTTP operation defined in the API 95 | specification, named after the HTTP verb, in lowercase (i.e., `get`, `put`, 96 | `post`, `delete`, `patch` and `options`), each taking a number of parameters 97 | according to the operations' definition. Operations also take an extra 98 | parameter, an `Uri.t` (from [OCaml-URI](https://github.com/mirage/ocaml-uri)), 99 | used to connect to the API server. In principle, this URI should only contain 100 | the host and port of the API server, as path and query string parameters will 101 | be appended automatically as needed by the operation functions themselves. 102 | 103 | With regard to path templating (i.e. replaceable sections of an URL marked with 104 | a path variable name inside curly braces), OCaml-Swagger will create a submodule 105 | in the form `By_{variable_name}` for the templated path. To give a concrete 106 | example, the path template `/api/v1/namespaces/{name}`, present in the 107 | Kubernetes API, will create a module structure such as below. 108 | 109 | ```ocaml 110 | ... 111 | module Namespaces = struct 112 | ... 113 | module By_name = struct 114 | let get ~name ... = 115 | ... 116 | end 117 | ... 118 | end 119 | ... 120 | ``` 121 | 122 | Finally, OCaml-Swagger tries to define modules and functions using OCamlish 123 | name conventions. Namely, modules are defined in `Capitalized_snake_case` style 124 | and functions in `lower_snake_case` style, whenever possible. 125 | -------------------------------------------------------------------------------- /bin/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name main) 3 | (public_name ocaml-swagger) 4 | (libraries swagger)) 5 | -------------------------------------------------------------------------------- /bin/main.ml: -------------------------------------------------------------------------------- 1 | let usage_msg = "ocaml-swagger -reference-root [-o ] []" 2 | let path_base = ref "" 3 | let definition_base = ref "" 4 | let reference_base = ref "" 5 | let reference_root = ref "" 6 | let output_file = ref "" 7 | let input_file = ref "" 8 | let anon_fun filename = input_file := filename 9 | 10 | let speclist = 11 | [ 12 | ("-path", Arg.Set_string path_base, " Path base"); 13 | ("-definition", Arg.Set_string definition_base, " Definition base"); 14 | ("-reference-base", Arg.Set_string reference_base, " Reference base"); 15 | ("-reference-root", Arg.Set_string reference_root, " Reference root"); 16 | ("-o", Arg.Set_string output_file, " Set output file name"); 17 | ] 18 | 19 | let () = 20 | Arg.parse speclist anon_fun usage_msg; 21 | if !reference_root = "" then ( 22 | prerr_endline "-reference-root cannot be empty."; 23 | exit 1); 24 | let output = if !output_file = "" then stdout else open_out !output_file in 25 | if !input_file = "" then 26 | Swagger.codegen_from_channel ~path_base:!path_base 27 | ~definition_base:!definition_base ~reference_base:!reference_base 28 | ~reference_root:!reference_root ~output stdin 29 | else 30 | Swagger.codegen_from_file ~path_base:!path_base 31 | ~definition_base:!definition_base ~reference_base:!reference_base 32 | ~reference_root:!reference_root ~output !input_file 33 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.4) 2 | 3 | (name swagger) 4 | (generate_opam_files true) 5 | 6 | (source (github andrenth/ocaml-swagger)) 7 | (authors 8 | "Andre Nathan " 9 | "Antonin Décimo ") 10 | (maintainers "Antonin Décimo ") 11 | (license "MIT") 12 | (package 13 | (name swagger) 14 | (synopsis "Swagger 2.0 code generator for OCaml") 15 | (description "Swagger 2.0 code generator for OCaml") 16 | (depends 17 | (ocaml (>= 4.14)) 18 | (atdgen (>= 2.10)) 19 | (ISO8601 (>= 0.2.6)) 20 | (stdint (>= 0.7.0)) 21 | (re (>= 1.10.3)) 22 | (yojson (>= 2.0)) 23 | (ppxlib (>= 0.25.0)) 24 | (ppx_yojson_conv (and (>= v0.15) :with-test)) 25 | (cohttp-lwt-unix (and (>= 5.0.0) :with-test)) 26 | (uri (and (>= 4.2) :with-test)) 27 | (ocamlformat :with-test))) 28 | -------------------------------------------------------------------------------- /lib/codegen_schema.ml: -------------------------------------------------------------------------------- 1 | open Util 2 | 3 | let sprintf = Printf.sprintf 4 | 5 | type t = { 6 | raw : Swagger_t.schema; 7 | reference_base : string; 8 | reference_root : Mod.t; 9 | } 10 | 11 | let create ~reference_base ~reference_root raw = 12 | { raw; reference_base; reference_root } 13 | 14 | let reference t = t.raw.reference 15 | 16 | let rec to_type t = 17 | let reference_base = t.reference_base in 18 | let reference_root = t.reference_root in 19 | match (t.raw.discriminator, t.raw.all_of, t.raw.reference, t.raw.kind) with 20 | | Some discriminator, None, None, None -> 21 | polymorphism ~reference_base ~reference_root t discriminator 22 | | None, Some all_of, None, None -> 23 | composition ~reference_base ~reference_root t all_of 24 | | None, None, Some reference, None -> 25 | let t = Mod.reference_type ~reference_base ~reference_root reference in 26 | Ast_builder.(ptyp_constr (Located.lident t) []) 27 | | None, None, None, Some kind -> 28 | plain_type ~reference_base ~reference_root t kind 29 | | _ -> assert false 30 | 31 | and polymorphism ~reference_base ~reference_root t discriminator = 32 | ignore reference_base; 33 | ignore reference_root; 34 | ignore t; 35 | failwith 36 | (sprintf "allOf polymorphism (discriminator %s) isn't implemented." 37 | discriminator) 38 | 39 | and composition ~reference_base ~reference_root t all_of = 40 | assert (Mod.name t.reference_root = "Definitions"); 41 | ignore reference_base; 42 | ignore reference_root; 43 | ignore t; 44 | ignore all_of; 45 | Printf.eprintf "t: ref_base: %s ref_root: %s\n%s" t.reference_base 46 | (Mod.name t.reference_root) 47 | (Swagger_j.string_of_schema t.raw); 48 | (* failwith (sprintf "allOf composition isn't implemented.") *) 49 | [%type: unit] 50 | 51 | and plain_type ~reference_base ~reference_root t kind = 52 | match kind with 53 | | `String -> ( 54 | match t.raw.format with 55 | | Some `Byte | Some `Binary | Some `Password | None -> [%type: string] 56 | | Some `Date -> [%type: float] 57 | | Some `Date_time -> [%type: float] 58 | | Some (`Other format) -> ( 59 | match format with 60 | | "ip-address" -> [%type: string] (* Docker *) 61 | | "CIDR" -> [%type: string] (* Docker *) 62 | | "dateTime" -> 63 | [%type: float] (* Docker, alternative spelling for "date-time" *) 64 | | _ -> failwith (sprintf "Unsupported %s string format." format)) 65 | | _ -> failwith "Invalid format for string data type.") 66 | | `Number -> [%type: float] 67 | | `Integer -> ( 68 | match t.raw.format with 69 | | Some `Int32 -> [%type: int32] 70 | | Some `Int64 -> [%type: int64] 71 | | None -> [%type: int] 72 | | Some (`Other format) -> ( 73 | match format with 74 | | "uint8" -> [%type: Stdint.Uint8.t] (* Docker *) 75 | | "uint16" -> [%type: Stdint.Uint16.t] (* Docker *) 76 | | "uint32" -> [%type: Stdint.Uint32.t] (* Docker *) 77 | | "uint64" -> [%type: Stdint.Uint64.t] (* Docker *) 78 | | _ -> failwith (sprintf "Unsupported %s integer format." format)) 79 | | _ -> failwith "Invalid format for integer data type.") 80 | | `Boolean -> [%type: bool] 81 | | `Object -> ( 82 | let properties _props = failwith "Unsupported properties." in 83 | let additional_properties = function 84 | | `Boolean b -> 85 | failwith (sprintf "Unsupported boolean additionalProperties %b" b) 86 | | `Schema (props : Swagger_t.schema) -> ( 87 | match (props.reference, props.kind) with 88 | | Some r, _ -> 89 | let t = 90 | Mod.reference_module ~reference_base ~reference_root r 91 | in 92 | Ast_builder.( 93 | ptyp_constr (Located.lident (sprintf "%s.Object.t" t)) []) 94 | | None, Some `String -> ( 95 | match t.raw.format with 96 | | Some `Byte | Some `Binary | Some `Password | None -> 97 | [%type: Object.Of_strings.t] 98 | | Some `Date -> [%type: float] 99 | | Some `Date_time -> [%type: float] 100 | | Some (`Other format) -> 101 | failwith (sprintf "Unsupported %s string format." format) 102 | | _ -> failwith "Invalid format for string data type.") 103 | | None, Some `Number -> [%type: Object.Of_floats.t] 104 | | None, Some `Integer -> ( 105 | match props.format with 106 | | Some `Int32 -> [%type: Object.Of_ints32.t] 107 | | Some `Int64 -> [%type: Object.Of_ints64.t] 108 | | None -> [%type: Object.Of_ints.t] 109 | | Some (`Other format) -> 110 | failwith (sprintf "Unsupported %s integer format." format) 111 | | _ -> failwith "Invalid format for integer data type.") 112 | | None, Some `Boolean -> [%type: Object.Of_bools.t] 113 | | None, _ -> 114 | let t = 115 | to_type (create ~reference_base ~reference_root props) 116 | in 117 | [%type: (string * [%t t]) list]) 118 | in 119 | match (t.raw.properties, t.raw.additional_properties) with 120 | | Some props, None -> properties props 121 | | None, Some props -> additional_properties props 122 | | None, None -> [%type: unit] 123 | | _ -> 124 | failwith 125 | "Specifying properties and additionalProperties is currently \ 126 | unspecified.") 127 | | `Array -> ( 128 | match t.raw.items with 129 | | Some s -> 130 | let t = to_type (create ~reference_base ~reference_root s) in 131 | [%type: [%t t] list] 132 | | None -> 133 | failwith 134 | "Schema.kind_to_string: array type must have an 'items' field") 135 | -------------------------------------------------------------------------------- /lib/codegen_schema.mli: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | val create : 4 | reference_base:string -> reference_root:Mod.t -> Swagger_t.schema -> t 5 | 6 | val reference : t -> Swagger_t.reference option 7 | val to_type : t -> Ppxlib.Ast.core_type 8 | -------------------------------------------------------------------------------- /lib/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (targets swagger_j.ml swagger_j.mli) 3 | (deps swagger.atd) 4 | (action 5 | (run atdgen -j %{deps}))) 6 | 7 | (rule 8 | (targets swagger_t.ml swagger_t.mli) 9 | (deps swagger.atd) 10 | (action 11 | (run atdgen -t %{deps}))) 12 | 13 | (library 14 | (name swagger) 15 | (public_name swagger) 16 | (preprocess 17 | (pps ppxlib.metaquot)) 18 | (libraries atdgen ppxlib re.pcre yojson ISO8601 stdint)) 19 | -------------------------------------------------------------------------------- /lib/gen.ml: -------------------------------------------------------------------------------- 1 | open Util 2 | 3 | let sprintf = Printf.sprintf 4 | 5 | let merge_params (ps1 : Swagger_t.parameter list) 6 | (ps2 : Swagger_t.parameter list) = 7 | let rec merge acc = function 8 | | [] -> acc 9 | | (p : Swagger_t.parameter) :: ps -> 10 | let same_name (q : Swagger_t.parameter) = 11 | let open Swagger_t in 12 | p.name = q.name 13 | in 14 | if List.exists same_name acc then merge acc ps else merge (p :: acc) ps 15 | in 16 | merge ps2 ps1 17 | 18 | let reference_module_and_type ~reference_base ~reference_root r = 19 | let ref_module = Mod.reference_module ~reference_base ~reference_root r in 20 | let ref_type = 21 | Ast_builder.(ptyp_constr (Located.lident (sprintf "%s.t" ref_module)) []) 22 | in 23 | (Some ref_module, ref_type) 24 | 25 | let parse_or_reference f json = 26 | let open Yojson.Basic.Util in 27 | let str = Yojson.Safe.to_string json in 28 | match json |> Yojson.Safe.to_basic |> member "$ref" with 29 | | `Null -> f str 30 | | _ -> failwith "reference not supported" 31 | 32 | let parse_parameters = function 33 | | Some ps -> List.map (parse_or_reference Swagger_j.parameter_of_string) ps 34 | | None -> [] 35 | 36 | let parse_response r = parse_or_reference Swagger_j.response_of_string r 37 | let parse_responses = List.map (fun (s, r) -> (s, parse_response r)) 38 | 39 | let resp_type ~reference_base ~reference_root (resp : Swagger_t.response) = 40 | match resp.schema with 41 | | None -> (None, Ppxlib.([%type: unit])) 42 | | Some s -> ( 43 | let s = Codegen_schema.create ~reference_base ~reference_root s in 44 | match Codegen_schema.reference s with 45 | | Some r -> reference_module_and_type ~reference_base ~reference_root r 46 | | None -> (None, Codegen_schema.to_type s)) 47 | 48 | let rec return_type ~reference_root ~reference_base 49 | (resps : Swagger_t.responses) = 50 | let is_error code = 51 | if String.lowercase_ascii code = "default" then true 52 | else 53 | let code = int_of_string code in 54 | code < 200 || code >= 300 55 | in 56 | let responses_match (r1 : Swagger_t.response) (r2 : Swagger_t.response) = 57 | r1.schema = r2.schema 58 | in 59 | match resps with 60 | | [] -> (None, Ppxlib.([%type: unit])) 61 | | (code, _) :: rs when is_error code -> 62 | (* ignore errors; assume strings *) 63 | return_type ~reference_root ~reference_base rs 64 | | (_code, resp) :: rs -> 65 | (* check all 2xx responses return the same type *) 66 | let rec check first = function 67 | | [] -> () 68 | | (code, _) :: res when is_error code -> check first res 69 | | (_code', resp') :: rs when responses_match first resp' -> 70 | check first rs 71 | | (_c, (_r : Swagger_t.response)) :: _ -> 72 | failwith "multiple response types are not supported" 73 | in 74 | let resp = parse_response resp in 75 | check resp (parse_responses rs); 76 | resp_type ~reference_base ~reference_root resp 77 | 78 | let make_dups params = 79 | List.fold_left 80 | (fun dups (p : Swagger_t.parameter) -> 81 | match StringMap.find_opt p.name dups with 82 | | Some count -> StringMap.add p.name (count + 1) dups 83 | | None -> StringMap.add p.name 1 dups) 84 | StringMap.empty params 85 | 86 | let operation_val ~root:_ ~reference_base ~reference_root name 87 | (params : Swagger_t.parameter list) = function 88 | | Some (op : Swagger_t.operation) -> 89 | let op_params = parse_parameters op.parameters in 90 | let params = merge_params params op_params in 91 | let dups = make_dups params in 92 | let param_sigs, param_impls = 93 | params 94 | |> List.map (fun (p : Swagger_t.parameter) -> 95 | let duplicate = StringMap.find p.name dups > 1 in 96 | Param.create ~duplicate ~reference_base ~reference_root p) 97 | |> List.split 98 | in 99 | let return_module, return_type = 100 | return_type ~reference_root ~reference_base op.responses 101 | in 102 | let verb = Val.Impl.http_verb_of_string name in 103 | let signature = 104 | let descr = op.description in 105 | Val.Sig.http_request ?descr name param_sigs return_type 106 | in 107 | let return = 108 | match return_module with 109 | | Some module_name -> Val.Impl.module_ module_name 110 | | None -> Val.Impl.type_ return_type 111 | in 112 | let implementation = 113 | Val.Impl.http_request verb name param_impls ~return 114 | in 115 | Some (Val.create signature implementation) 116 | | None -> None 117 | 118 | let path_val path = 119 | Val.create 120 | (Val.Sig.constant "request_path_template") 121 | (Val.Impl.constant "request_path_template" path) 122 | 123 | let path_item_vals ~root ~reference_base ~reference_root ~path 124 | (item : Swagger_t.path_item) : Val.t list = 125 | let params = parse_parameters item.parameters in 126 | let operation_val name = 127 | operation_val ~root ~reference_base ~reference_root name params 128 | in 129 | let get = operation_val "get" item.get in 130 | let put = operation_val "put" item.put in 131 | let post = operation_val "post" item.post in 132 | let delete = operation_val "delete" item.delete in 133 | let options = operation_val "options" item.options in 134 | let head = operation_val "head" item.head in 135 | let patch = operation_val "patch" item.patch in 136 | path_val path :: keep_some [ get; put; post; delete; options; head; patch ] 137 | 138 | let definition_module ?(path = []) ~root ~reference_base ~name 139 | (schema : Swagger_t.schema) = 140 | let required = Option.value ~default:[] schema.required in 141 | let properties = Option.value ~default:[] schema.properties in 142 | 143 | let create_param name type_ required_params = 144 | let n = Param.name name in 145 | if List.mem name required_params then 146 | (Val.Sig.labelled n type_, Val.Impl.labelled n type_) 147 | else (Val.Sig.optional n type_, Val.Impl.optional n type_) 148 | in 149 | 150 | let create_params = 151 | List.fold_left 152 | (fun params (name, schema) -> 153 | let s = 154 | Codegen_schema.create ~reference_base ~reference_root:root schema 155 | in 156 | let param_type = Codegen_schema.to_type s in 157 | let param_sig, param_impl = create_param name param_type required in 158 | (param_sig, param_impl) :: params) 159 | [] 160 | in 161 | 162 | let alias_type () = 163 | let param_type = 164 | Codegen_schema.to_type 165 | (Codegen_schema.create ~reference_base ~reference_root:root schema) 166 | in 167 | let int_or_string = 168 | match schema.format with 169 | | Some (`Other "int-or-string") -> true (* XXX: Kubernetes only? *) 170 | | _ -> false 171 | in 172 | let typ = 173 | Type.create (Type.Sig.abstract "t") 174 | (Type.Impl.alias "t" param_type ~int_or_string) 175 | in 176 | let create = 177 | Val.create 178 | Val.Sig.(pure "make" [ nolabel param_type ] [%type: t]) 179 | Val.Impl.(identity "make" [ nolabel "t" [%type: t] ]) 180 | in 181 | ([ typ ], [ create ]) 182 | in 183 | 184 | let record_type () = 185 | let params = create_params properties in 186 | let sig_params, impl_params = params |> List.split in 187 | let create = 188 | Val.create 189 | (Val.Sig.pure "make" sig_params [%type: t]) 190 | (Val.Impl.record_constructor "make" impl_params) 191 | in 192 | let fields, values = 193 | List.fold_left 194 | (fun (fields, values) (name, schema) -> 195 | let s = 196 | Codegen_schema.create ~reference_base ~reference_root:root schema 197 | in 198 | let s = Codegen_schema.to_type s in 199 | let sig_type, impl_type = 200 | if List.mem name required then (s, s) 201 | else 202 | let intf = Ppxlib.([%type: [%t s] option]) in 203 | let name = Ast_builder.Located.mk "default" in 204 | let attr = 205 | Ast_builder.( 206 | attribute ~name ~payload:(PStr [ pstr_eval [%expr None] [] ])) 207 | in 208 | let impl = 209 | { intf with ptyp_attributes = attr :: intf.ptyp_attributes } 210 | in 211 | (intf, impl) 212 | in 213 | let pname = Param.name name in 214 | let field = 215 | Type.Impl.record_field ~name:pname ~orig_name:name ~type_:impl_type 216 | in 217 | let field_getter = 218 | let descr = schema.description in 219 | Val.create 220 | (Val.Sig.pure ?descr pname 221 | [ Val.Sig.nolabel [%type: t] ] 222 | sig_type) 223 | (Val.Impl.field_getter pname [ Val.Impl.nolabel "t" [%type: t] ]) 224 | in 225 | let field_setter = 226 | let descr = "Set the value of the " ^ pname ^ " field." in 227 | Val.create 228 | (Val.Sig.field_setter ~descr pname 229 | [ Val.Sig.nolabel sig_type; Val.Sig.nolabel [%type: t] ] 230 | [%type: t]) 231 | (Val.Impl.field_setter pname 232 | [ 233 | Val.Impl.nolabel pname sig_type; 234 | Val.Impl.nolabel "t" [%type: t]; 235 | ]) 236 | in 237 | (field :: fields, field_setter :: field_getter :: values)) 238 | ([], []) properties 239 | in 240 | let values = create :: List.rev values in 241 | let type_sig = Type.Sig.abstract "t" in 242 | let type_impl = Type.Impl.record "t" fields in 243 | let typ = Type.create type_sig type_impl in 244 | ([ typ ], values) 245 | in 246 | 247 | let unspec_type () = 248 | let typ = 249 | Type.create (Type.Sig.unspecified "t") (Type.Impl.unspecified "t") 250 | in 251 | ([ typ ], []) 252 | in 253 | 254 | let types, values = 255 | match (schema.kind, schema.properties) with 256 | | Some `Object, _ -> record_type () 257 | | Some _, _ -> alias_type () 258 | | None, Some _ -> record_type () 259 | | None, None -> unspec_type () 260 | in 261 | 262 | let descr = schema.description in 263 | Mod.create ?descr ~name ~path ~types ~values () 264 | 265 | let rec insert_module m root = function 266 | | [] -> Mod.add_mod m root 267 | | p :: ps -> ( 268 | match Mod.find_submodule p root with 269 | | Some subm -> Mod.add_mod (insert_module m subm ps) root 270 | | None -> 271 | let subm = Mod.empty p ~path:(Mod.qualified_path root) () in 272 | Mod.add_mod (insert_module m subm ps) root) 273 | 274 | let build_paths ~root ~path_base ~reference_base ~reference_root (path, item) = 275 | let parents_and_child = 276 | path |> Mod.strip_base path_base |> String.split_on_char '/' 277 | |> List.filter (( <> ) "") 278 | |> unsnoc 279 | in 280 | match parents_and_child with 281 | | Some (parents, child) -> 282 | let child_values = 283 | path_item_vals ~root ~reference_base ~reference_root ~path item 284 | in 285 | let child_module = Mod.with_values ~path:parents child child_values in 286 | insert_module child_module root parents 287 | | None -> 288 | let child_values = 289 | path_item_vals ~root ~reference_base ~reference_root ~path item 290 | in 291 | Mod.add_vals child_values root 292 | 293 | let build_paths ~root ~path_base ~reference_base ~reference_root l = 294 | List.fold_left 295 | (fun root m -> 296 | build_paths ~root ~path_base ~reference_base ~reference_root m) 297 | root l 298 | 299 | let polymorphism ~root ~definition_base ~reference_base ~discriminator 300 | (name, schema) = 301 | ignore root; 302 | ignore definition_base; 303 | ignore reference_base; 304 | ignore schema; 305 | failwith 306 | (sprintf 307 | "allOf polymorphism (found in %s, discriminator %s) isn't supported" name 308 | discriminator) 309 | 310 | let composition ~root ~reference_base ~name all_of = 311 | List.map 312 | (fun (schema : Swagger_t.schema) -> 313 | match schema.reference with 314 | | None -> definition_module ~root ~reference_base ~name:"" schema 315 | | Some reference -> ( 316 | let child = 317 | reference 318 | |> Mod.strip_base reference_base 319 | |> Mod.split_ref |> unsnoc |> Option.get |> snd 320 | in 321 | match Mod.find_submodule child root with 322 | | Some m -> m 323 | | None -> 324 | failwith 325 | (sprintf "Couldn't find submodule %s of %s." reference 326 | (Mod.name root)))) 327 | all_of 328 | |> Mod.compose ~name 329 | 330 | let build_definitions ~root ~definition_base ~reference_base def = 331 | let name, schema = def in 332 | match (schema.Swagger_t.discriminator, schema.all_of, schema.reference) with 333 | | Some discriminator, None, None -> 334 | polymorphism ~root ~definition_base ~reference_base ~discriminator def 335 | | None, Some all_of, None -> 336 | Mod.add_mod (composition ~root ~reference_base ~name all_of) root 337 | | None, None, None -> ( 338 | let parents_and_child = 339 | name |> Mod.strip_base definition_base |> Mod.split_ref |> unsnoc 340 | in 341 | match parents_and_child with 342 | | Some (parents, child) -> 343 | let def = 344 | definition_module ~root ~reference_base ~path:parents ~name:child 345 | schema 346 | in 347 | insert_module def root parents 348 | | None -> 349 | Mod.add_mod 350 | (definition_module ~root ~reference_base ~name schema) 351 | root) 352 | | None, None, Some reference -> 353 | (* XXX Ignore schemas that are simply references? Just use the 354 | referenced module? In the kubernetes API this seems to be only for 355 | deprecated stuff. *) 356 | failwith 357 | (sprintf "Unimplemented reference support %s in %s." reference name) 358 | | _ -> failwith (sprintf "Error in %s Schema Object definition" name) 359 | 360 | let build_definitions ~root ~definition_base ~reference_base l = 361 | List.fold_left 362 | (fun root m -> build_definitions ~root ~definition_base ~reference_base m) 363 | root l 364 | 365 | let of_swagger ?(path_base = "") ?(definition_base = "") ?(reference_base = "") 366 | ~reference_root s = 367 | let open Swagger_t in 368 | let definitions = Option.value ~default:[] s.definitions in 369 | let title = s.info.title in 370 | let defs = 371 | build_definitions 372 | ~root:(Mod.empty reference_root ~path:[ title ] ()) 373 | ~definition_base ~reference_base definitions 374 | in 375 | let root = 376 | build_paths 377 | ~root:(Mod.empty ~recursive:true title ()) 378 | ~path_base ~reference_base ~reference_root:defs s.paths 379 | in 380 | Mod.add_mod defs root 381 | 382 | let object_module = 383 | let open Ppxlib in 384 | [%stri 385 | module Object = struct 386 | module type Value = sig 387 | type value 388 | 389 | val value_of_yojson : Yojson.Safe.t -> value 390 | val yojson_of_value : value -> Yojson.Safe.t 391 | end 392 | 393 | module type S = sig 394 | type value 395 | type t = (string * value) list [@@deriving yojson] 396 | end 397 | 398 | module Make (V : Value) : S with type value := V.value = struct 399 | type t = (string * V.value) list [@@deriving yojson] 400 | 401 | let yojson_of_t obj = 402 | `Assoc (List.map (fun (k, v) -> (k, V.yojson_of_value v)) obj) 403 | 404 | let t_of_yojson (obj : Yojson.Safe.t) : t = 405 | let rec loop acc = function 406 | | [] -> List.rev acc 407 | | (k, v) :: obj -> 408 | let v = V.value_of_yojson v in 409 | loop ((k, v) :: acc) obj 410 | in 411 | match obj with 412 | | `Assoc obj -> loop [] obj 413 | | _ -> invalid_arg "invalid object" 414 | end 415 | 416 | module Of_strings = Make (struct 417 | type value = string [@@deriving yojson] 418 | end) 419 | 420 | module Of_floats = Make (struct 421 | type value = float [@@deriving yojson] 422 | end) 423 | 424 | module Of_ints = Make (struct 425 | type value = int [@@deriving yojson] 426 | end) 427 | 428 | module Of_ints32 = Make (struct 429 | type value = int32 [@@deriving yojson] 430 | end) 431 | 432 | module Of_ints64 = Make (struct 433 | type value = int64 [@@deriving yojson] 434 | end) 435 | 436 | module Of_bools = Make (struct 437 | type value = bool [@@deriving yojson] 438 | end) 439 | end] 440 | 441 | let to_string m = 442 | object_module :: Mod.to_mod m |> Ppxlib.Pprintast.string_of_structure 443 | -------------------------------------------------------------------------------- /lib/gen.mli: -------------------------------------------------------------------------------- 1 | val of_swagger : 2 | ?path_base:string -> 3 | ?definition_base:string -> 4 | ?reference_base:string -> 5 | reference_root:string -> 6 | Swagger_t.swagger -> 7 | Mod.t 8 | 9 | val to_string : Mod.t -> string 10 | -------------------------------------------------------------------------------- /lib/mod.ml: -------------------------------------------------------------------------------- 1 | open Ppxlib 2 | open Util 3 | 4 | let sprintf = Printf.sprintf 5 | 6 | type t = { 7 | name : string; 8 | path : string list; 9 | types : Type.t list; 10 | values : Val.t list; 11 | submodules : t StringMap.t; 12 | recursive : bool; 13 | descr : string option; 14 | } 15 | 16 | let module_name s = 17 | let s = 18 | let last = String.length s - 1 in 19 | if s.[0] = '{' && s.[last] = '}' then "By_" ^ String.sub s 1 (last - 1) 20 | else s 21 | in 22 | s |> snake_case |> String.capitalize_ascii |> String.split_on_char '.' 23 | |> List.hd 24 | 25 | let create ~name ?descr ?(recursive = false) ?(path = []) ?(types = []) 26 | ?(submodules = StringMap.empty) ?(values = []) () = 27 | { 28 | name = (if name <> "" then module_name name else ""); 29 | path = List.map module_name path; 30 | types; 31 | values; 32 | submodules; 33 | recursive; 34 | descr; 35 | } 36 | 37 | let empty name ?(recursive = false) ?(path = []) () = 38 | create ~name ~recursive ~path () 39 | 40 | let with_values name ?(recursive = false) ?(path = []) values = 41 | create ~name ~recursive ~path ~values () 42 | 43 | let name m = m.name 44 | let submodules m = StringMap.fold (fun _ -> List.cons) m.submodules [] 45 | let add_vals vs m = { m with values = m.values @ vs } 46 | 47 | let add_mod subm m = 48 | { m with submodules = StringMap.add subm.name subm m.submodules } 49 | 50 | let find_submodule name m = StringMap.find_opt (module_name name) m.submodules 51 | 52 | let compose ~name mods = 53 | let find_split modname p = function 54 | | t :: l when p t -> (t, l) 55 | | _ -> 56 | failwith 57 | (sprintf "Couldn't find ident from %s, maybe it was reordered?" 58 | modname) 59 | in 60 | let compose split compose xs = 61 | let found_idents, idents = xs |> List.map split |> List.split in 62 | let ident = 63 | match found_idents with 64 | | [] -> assert false 65 | | [ x ] -> x 66 | | x :: l -> List.fold_left (fun acc x -> compose acc x) x l 67 | in 68 | ident :: List.flatten idents 69 | in 70 | let types = 71 | compose 72 | (fun { name; types; _ } -> 73 | find_split name (fun t -> Type.name t = "t") types) 74 | Type.compose mods 75 | in 76 | let values = 77 | compose 78 | (fun { name; values; _ } -> 79 | find_split name (fun t -> Val.name t = "make") values) 80 | Val.compose mods 81 | in 82 | let submodules = 83 | List.fold_left 84 | (fun acc m -> 85 | StringMap.union 86 | (fun _ y -> 87 | failwith 88 | (sprintf "Submodule %s already exists in composition." y.name)) 89 | acc m.submodules) 90 | StringMap.empty mods 91 | in 92 | { 93 | name; 94 | path = []; 95 | types; 96 | values; 97 | submodules; 98 | recursive = false; 99 | descr = None; 100 | } 101 | 102 | let path m = m.path 103 | 104 | let qualified_name m = 105 | match m.path with 106 | | [] -> m.name 107 | | _p -> sprintf "%s.%s" (String.concat "." m.path) m.name 108 | 109 | let qualified_path m = m.path @ [ m.name ] 110 | let has_type_named n m = List.exists (fun t -> Type.name t = n) m.types 111 | let object_module_intf = [%sigi: module Object : Object.S with type value := t] 112 | 113 | let object_module_impl = 114 | [%stri 115 | module Object = Object.Make (struct 116 | type value = t [@@deriving yojson] 117 | end)] 118 | 119 | let rec to_module_type m = 120 | let open Ast_builder in 121 | let definitions, submods = 122 | StringMap.fold 123 | (fun name m (defs, submods) -> 124 | let type_ = to_module_type m in 125 | let decl = module_declaration ~name:(Located.mk (Some m.name)) ~type_ in 126 | let s = psig_module decl in 127 | (* Definitions first to simplify references *) 128 | if name = "Definitions" then (Some s, submods) else (defs, s :: submods)) 129 | m.submodules (None, []) 130 | in 131 | let types = 132 | List.concat_map (fun t -> Type.Sig.to_sig (Type.signature t)) m.types 133 | in 134 | let values = 135 | List.rev_map 136 | (fun v -> Ast_builder.psig_value (Val.Sig.to_sig (Val.signature v))) 137 | m.values 138 | in 139 | let items = 140 | (if has_type_named "t" m then [ object_module_intf ] else []) 141 | |> List.rev_append values |> List.append types |> List.rev_append submods 142 | |> Util.opt_cons definitions 143 | in 144 | let t = pmty_signature items in 145 | match m.descr with 146 | | None -> t 147 | | Some descr -> 148 | let pmty_attributes = Util.ocaml_doc descr :: t.pmty_attributes in 149 | { t with pmty_attributes } 150 | 151 | let rec to_mod_structure m = 152 | let submods = 153 | StringMap.fold 154 | (fun name m mods -> 155 | let name = Ast_builder.Located.mk (Some name) in 156 | Ast_builder.( 157 | pstr_module (module_binding ~name ~expr:(to_mod_structure m))) 158 | :: mods) 159 | m.submodules [] 160 | in 161 | let types = 162 | List.concat_map (fun t -> Type.Impl.to_impl (Type.implementation t)) m.types 163 | in 164 | let values = 165 | List.rev_map (fun v -> Val.Impl.to_impl (Val.implementation v)) m.values 166 | in 167 | let items = 168 | (if has_type_named "t" m then [ object_module_impl ] else []) 169 | |> List.rev_append values |> List.append types |> List.rev_append submods 170 | in 171 | Ast_builder.pmod_structure items 172 | 173 | let to_mod m = 174 | let open Ast_builder in 175 | let name = Located.mk (Some m.name) in 176 | let module_type = to_module_type m in 177 | let mod_structure = to_mod_structure m in 178 | let binding = 179 | module_binding ~name ~expr:(pmod_constraint mod_structure module_type) 180 | in 181 | [ (if m.recursive then pstr_recmodule [ binding ] else pstr_module binding) ] 182 | 183 | let strip_base base path = 184 | let plen = String.length path in 185 | let blen = String.length base in 186 | if plen >= blen then 187 | let pref = String.sub path 0 blen in 188 | if String.lowercase_ascii base = String.lowercase_ascii pref then 189 | String.sub path blen (plen - blen) 190 | else path 191 | else path 192 | 193 | let split_ref reference = 194 | reference |> String.split_on_char '.' 195 | |> List.filter (( <> ) "") 196 | |> List.map module_name 197 | 198 | let reference_module_path ~reference_base ~reference_root reference = 199 | let path = reference |> strip_base reference_base |> split_ref in 200 | qualified_name reference_root :: path 201 | 202 | let reference_module ~reference_base ~reference_root reference = 203 | reference_module_path ~reference_base ~reference_root reference 204 | |> String.concat "." 205 | 206 | let reference_type ~reference_base ~reference_root reference = 207 | reference_module ~reference_base ~reference_root reference ^ ".t" 208 | -------------------------------------------------------------------------------- /lib/mod.mli: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | val empty : string -> ?recursive:bool -> ?path:string list -> unit -> t 4 | 5 | val create : 6 | name:string -> 7 | ?descr:string -> 8 | ?recursive:bool -> 9 | ?path:string list -> 10 | ?types:Type.t list -> 11 | ?submodules:t Util.StringMap.t -> 12 | ?values:Val.t list -> 13 | unit -> 14 | t 15 | 16 | val with_values : 17 | string -> ?recursive:bool -> ?path:string list -> Val.t list -> t 18 | 19 | val name : t -> string 20 | val qualified_name : t -> string 21 | val submodules : t -> t list 22 | val path : t -> string list 23 | val qualified_path : t -> string list 24 | val add_mod : t -> t -> t 25 | val add_vals : Val.t list -> t -> t 26 | val find_submodule : string -> t -> t option 27 | val compose : name:string -> t list -> t 28 | 29 | val reference_module : 30 | reference_base:string -> reference_root:t -> string -> string 31 | 32 | val reference_type : 33 | reference_base:string -> reference_root:t -> string -> string 34 | 35 | val split_ref : string -> string list 36 | val strip_base : string -> string -> string 37 | val to_mod : t -> Ppxlib.Ast.structure 38 | -------------------------------------------------------------------------------- /lib/param.ml: -------------------------------------------------------------------------------- 1 | open Util 2 | 3 | let sprintf = Printf.sprintf 4 | 5 | type t = Swagger_t.parameter 6 | 7 | let rec item_kind_to_ptyp (items : Swagger_t.items option) kind = 8 | let open Ppxlib in 9 | match kind with 10 | | `String -> [%type: string] 11 | | `Number -> [%type: float] 12 | | `Integer -> [%type: int] 13 | | `Boolean -> [%type: bool] 14 | | `Array -> ( 15 | match items with 16 | | Some is -> 17 | let t = item_kind_to_ptyp is.items is.kind in 18 | [%type: [%t t] list] 19 | | None -> 20 | failwith 21 | "Param.item_kind_to_ptyp: array type must have an 'items' field") 22 | 23 | let kind_to_ptyp (p : t) = 24 | let open Ppxlib in 25 | match Option.get p.kind with 26 | | `String -> [%type: string] 27 | | `Number -> [%type: float] 28 | | `Integer -> [%type: int] 29 | | `Boolean -> [%type: bool] 30 | | `File -> [%type: file] 31 | | `Array -> ( 32 | match p.items with 33 | | Some items -> 34 | let t = item_kind_to_ptyp items.items items.kind in 35 | [%type: [%t t] array] 36 | | None -> 37 | failwith "Param.kind_to_ptyp: array type must have an 'items' field") 38 | 39 | let name n = 40 | let n = if n.[0] = '$' then String.sub n 1 (String.length n - 1) else n in 41 | let n = snake_case n |> String.lowercase_ascii in 42 | if Ppxlib.Keyword.is_keyword n then n ^ "_" else n 43 | 44 | let string_of_location = function 45 | | `Query -> "query" 46 | | `Header -> "header" 47 | | `Path -> "path" 48 | | `FormData -> "formData" 49 | | `Body -> "body" 50 | 51 | let create ?(duplicate = false) ~reference_base ~reference_root (p : t) = 52 | let t = 53 | match p.location with 54 | | `Body -> 55 | Codegen_schema.create ~reference_base ~reference_root 56 | (Option.get p.schema) 57 | |> Codegen_schema.to_type 58 | | _ -> kind_to_ptyp p 59 | in 60 | let n = 61 | let n = name p.name in 62 | let loc = string_of_location p.location in 63 | if duplicate && n <> loc then sprintf "%s_%s" loc n else n 64 | in 65 | let descr = p.description in 66 | let create_sig, create_impl = 67 | if p.required then (Val.Sig.labelled, Val.Impl.labelled) 68 | else (Val.Sig.optional, Val.Impl.optional) 69 | in 70 | (create_sig ?descr n t, create_impl n t ~origin:(Val.Impl.origin p)) 71 | -------------------------------------------------------------------------------- /lib/param.mli: -------------------------------------------------------------------------------- 1 | type t = Swagger_t.parameter 2 | 3 | val create : 4 | ?duplicate:bool -> 5 | reference_base:string -> 6 | reference_root:Mod.t -> 7 | t -> 8 | Val.Sig.param * Val.Impl.param 9 | 10 | val name : string -> string 11 | -------------------------------------------------------------------------------- /lib/swagger.atd: -------------------------------------------------------------------------------- 1 | type t = abstract 2 | 3 | type format = 4 | [ Int32 5 | | Int64 6 | | Float 7 | | Double 8 | | Byte 9 | | Binary 10 | | Date 11 | | Date_time 12 | | Password 13 | | File 14 | | Email 15 | | Hostname 16 | | IPv4 17 | | IPv6 18 | | Uri 19 | | Other of string 20 | ] 21 | 22 | (* RFC3339 full-date *) 23 | type date = string wrap 24 | 25 | (* RFC3339 date-time *) 26 | type date_time = string wrap 27 | 28 | type contact = 29 | { ?name : string option 30 | ; ?url : string option 31 | ; ?email : string option 32 | } 33 | 34 | type license = 35 | { name : string 36 | ; ?url : string option 37 | } 38 | 39 | type info = 40 | { title : string 41 | ; ?description : string option 42 | ; ?terms_of_service : string option 43 | ; ?contact : contact option 44 | ; ?license : license option 45 | ; version : string 46 | } 47 | 48 | type scheme = 49 | [ Http 50 | | Https 51 | | Ws 52 | | Wss 53 | ] 54 | 55 | type location = 56 | [ Query 57 | | Header 58 | | Path 59 | | FormData 60 | | Body 61 | ] 62 | 63 | type kind = 64 | [ String 65 | | Number 66 | | Integer 67 | | Boolean 68 | | Array 69 | | File 70 | ] 71 | 72 | type collection_format = 73 | [ Csv 74 | | Ssv 75 | | Tsv 76 | | Pipes 77 | | Multi 78 | ] 79 | 80 | type xml = 81 | { ?name : string option 82 | ; ?namespace : string option 83 | ; ?prefix : string option 84 | ; ~attribute : bool 85 | ; ~wrapped : bool 86 | } 87 | 88 | type items_kind = 89 | [ String 90 | | Number 91 | | Integer 92 | | Boolean 93 | | Array 94 | ] 95 | 96 | type items = 97 | { kind : items_kind 98 | ; ?format : format option 99 | ; ?items : items option 100 | ; ~collection_format : collection_format 101 | ; ?default : t option 102 | ; ?maximum : int option 103 | ; ?exclusive_maximum : bool option 104 | ; ?minimum : int option 105 | ; ?exclusive_minimum : bool option 106 | ; ?max_length : int option 107 | ; ?min_length : int option 108 | ; ?pattern : string option 109 | ; ?max_items : int option 110 | ; ?min_items : int option 111 | ; ?unique_items : bool option 112 | ; ?enum : t list option 113 | ; ?multiple_of : int option 114 | } 115 | 116 | type external_documentation = 117 | { ?description : string option 118 | ; url : string 119 | } 120 | 121 | type schema_kind = 122 | [ Boolean 123 | | Object 124 | | Array 125 | | Number 126 | | String 127 | | Integer 128 | ] 129 | 130 | type additional_properties = 131 | [ Boolean of bool 132 | | Schema of schema 133 | ] 134 | 135 | type schema = 136 | { ?reference : reference option 137 | ; ?format : format option 138 | ; ?title : string option 139 | ; ?description : string option 140 | ; ?default : t option 141 | ; ?multiple_of : int option 142 | ; ?maximum : int option 143 | ; ?exclusive_maximum : bool option 144 | ; ?minimum : int option 145 | ; ?exclusive_minimum : bool option 146 | ; ?max_length : int option 147 | ; ?min_length : int option 148 | ; ?pattern : string option 149 | ; ?max_items : int option 150 | ; ?min_items : int option 151 | ; ?unique_items : bool option 152 | ; ?max_properties : int option 153 | ; ?min_properties : int option 154 | ; ?required : string list option 155 | ; ?enum : t list option 156 | ; ?kind : schema_kind option 157 | ; ?items : schema option 158 | ; ?all_of : schema list option 159 | ; ?properties : (string * schema) list option 160 | ; ?additional_properties : additional_properties option 161 | ; ?discriminator : string option 162 | ; ~read_only : bool 163 | ; ?xml : xml option 164 | ; ?external_docs : external_documentation option 165 | ; ?example : t option 166 | } 167 | 168 | type parameter = 169 | { name : string 170 | ; location : location 171 | ; ?description : string option 172 | ; ~required : bool 173 | ; ?schema : schema option 174 | ; ?kind : kind option 175 | ; ?format : format option 176 | ; ~allow_empty_value : bool 177 | ; ?items : items option 178 | ; ~collection_format : collection_format 179 | ; ?default : t option 180 | ; ?maximum : int option 181 | ; ?exclusive_maximum : bool option 182 | ; ?minimum : int option 183 | ; ?exclusive_minimum : bool option 184 | ; ?max_length : int option 185 | ; ?min_length : int option 186 | ; ?pattern : string option 187 | ; ?max_items : int option 188 | ; ?min_items : int option 189 | ; ?unique_items : bool option 190 | ; ?enum : t list option 191 | ; ?multiple_of : int option 192 | } 193 | 194 | type parameters_definitions = (string * parameter) list 195 | 196 | type header = 197 | { ?description : string option 198 | ; kind : kind 199 | ; ?format : format option 200 | ; ?items : items option 201 | ; ~collection_format : collection_format 202 | ; ?default : t option 203 | ; ?maximum : int option 204 | ; ?exclusive_maximum : bool option 205 | ; ?minimum : int option 206 | ; ?exclusive_minimum : bool option 207 | ; ?max_length : int option 208 | ; ?min_length : int option 209 | ; ?pattern : string option 210 | ; ?max_items : int option 211 | ; ?min_items : int option 212 | ; ?unique_items : bool option 213 | ; ?enum : t list option 214 | ; ?multiple_of : int option 215 | } 216 | 217 | type headers = (string * header) list 218 | 219 | type example = (string * t) list 220 | 221 | type response = 222 | { description : string 223 | ; ?schema : schema option 224 | ; ?headers : headers option 225 | ; ?examples : example option 226 | } 227 | 228 | type responses_definitions = (string * response) list 229 | 230 | type reference = string 231 | 232 | type responses = (string * t) list 233 | 234 | type operation = 235 | { ?tags : string list option 236 | ; ?summary : string option 237 | ; ?description : string option 238 | ; ?external_docs : external_documentation option 239 | ; ?operation_id : string option 240 | ; ?consumes : string list option (* validate mime? *) 241 | ; ?produces : string list option (* validate mime? *) 242 | ; ?parameters : t list option 243 | ; responses : responses 244 | ; ?schemes : scheme list option 245 | ; ~deprecated : bool 246 | ; ?security : security_requirement list option 247 | } 248 | 249 | type path_item = 250 | { ?ref : string option (* validation? *) 251 | ; ?get : operation option 252 | ; ?put : operation option 253 | ; ?post : operation option 254 | ; ?delete : operation option 255 | ; ?options : operation option 256 | ; ?head : operation option 257 | ; ?patch : operation option 258 | ; ?parameters : t list option (* validate : uniqueness, only one body, required must be true if in is "path", schema only present if in is "body" *) 259 | } 260 | 261 | type paths = (string * path_item) list 262 | 263 | type security_scheme_basic = 264 | { ?description : string option } 265 | 266 | type security_scheme_location = 267 | [ Query 268 | | Header 269 | ] 270 | 271 | type security_scheme_api_key = 272 | { ?description : string option 273 | ; name : string 274 | ; location : security_scheme_location 275 | } 276 | 277 | type security_scheme_flow = 278 | [ Implicit 279 | | Password 280 | | Application 281 | | AccessCode 282 | ] 283 | 284 | type scopes = (string * string) list 285 | 286 | type security_scheme_oauth2 = 287 | { ?description : string option 288 | ; flow : security_scheme_flow 289 | ; ?authorization_url : string option 290 | ; ?token_url : string option 291 | ; scopes : scopes 292 | } 293 | 294 | type security_scheme = 295 | [ Basic of security_scheme_basic 296 | | ApiKey of security_scheme_api_key 297 | | OAuth2 of security_scheme_oauth2 298 | ] 299 | 300 | type security_definitions = (string * security_scheme) list 301 | 302 | type security_requirement = (string * string list) list 303 | 304 | type tag = 305 | { name : string 306 | ; ?description : string option 307 | ; ?external_docs : external_documentation option 308 | } 309 | 310 | type definitions = (string * schema) list 311 | 312 | type swagger = 313 | { swagger : string 314 | ; info : info 315 | ; ?host : string option 316 | ; ?base_path : string option 317 | ; ?schemes : scheme list option 318 | ; ?consumes : string list option (* validate mime? *) 319 | ; ?produces : string list option (* validate mime? *) 320 | ; paths : paths 321 | ; ?definitions : definitions option 322 | ; ?parameters : parameters_definitions option 323 | ; ?responses : responses_definitions option 324 | ; ?security_definitions : security_definitions option 325 | ; ?security : security_requirement list option 326 | ; ?tags : tag list option (* validate uniqueness *) 327 | ; ?external_docs : external_documentation option 328 | } 329 | -------------------------------------------------------------------------------- /lib/swagger.ml: -------------------------------------------------------------------------------- 1 | let codegen_from_string ~path_base ~definition_base ~reference_base 2 | ~reference_root ?(output = stdout) input = 3 | let swagger = Swagger_j.swagger_of_string input in 4 | Gen.of_swagger ~path_base ~definition_base ~reference_base ~reference_root 5 | swagger 6 | |> Gen.to_string 7 | |> Printf.fprintf output "%s%!" 8 | 9 | let read_channel input = 10 | let buffer = Buffer.create 4096 in 11 | let rec aux () = 12 | try 13 | Buffer.add_channel buffer input 4096; 14 | aux () 15 | with End_of_file -> Buffer.contents buffer 16 | in 17 | aux () 18 | 19 | let codegen_from_channel ~path_base ~definition_base ~reference_base 20 | ~reference_root ?output input = 21 | codegen_from_string ~path_base ~definition_base ~reference_base 22 | ~reference_root ?output (read_channel input) 23 | 24 | let codegen_from_file ~path_base ~definition_base ~reference_base 25 | ~reference_root ?output input = 26 | let ic = open_in_bin input in 27 | let input = really_input_string ic (in_channel_length ic) in 28 | codegen_from_string ~path_base ~definition_base ~reference_base 29 | ~reference_root ?output input 30 | -------------------------------------------------------------------------------- /lib/swagger.mli: -------------------------------------------------------------------------------- 1 | (** Generate OCaml libraries from Swagger 2.0 specs, from various sources. *) 2 | 3 | val codegen_from_string : 4 | path_base:string -> 5 | definition_base:string -> 6 | reference_base:string -> 7 | reference_root:string -> 8 | ?output:out_channel -> 9 | string -> 10 | unit 11 | (** Generate an OCaml library from a Swagger spec read from a string. *) 12 | 13 | val codegen_from_channel : 14 | path_base:string -> 15 | definition_base:string -> 16 | reference_base:string -> 17 | reference_root:string -> 18 | ?output:out_channel -> 19 | in_channel -> 20 | unit 21 | (** Generate an OCaml library from a Swagger spec read from a channel. *) 22 | 23 | val codegen_from_file : 24 | path_base:string -> 25 | definition_base:string -> 26 | reference_base:string -> 27 | reference_root:string -> 28 | ?output:out_channel -> 29 | string -> 30 | unit 31 | (** Generate an OCaml library from a Swagger spec read from a file. *) 32 | -------------------------------------------------------------------------------- /lib/swagger_util.ml: -------------------------------------------------------------------------------- 1 | module Validate = struct 2 | let version = ( = ) "2.0" 3 | let host _ = true 4 | let base_path _ = true 5 | let url _ = true 6 | let email _ = true 7 | let path p = p.[0] = '/' 8 | 9 | let length ?(min = 0) ~max s = 10 | let len = String.length s in 11 | len > min && len < max 12 | end 13 | 14 | module Date = struct 15 | type t = float 16 | 17 | let wrap str = ISO8601.Permissive.date str 18 | let unwrap = ISO8601.Permissive.string_of_date 19 | end 20 | 21 | module DateTime = struct 22 | type t = float 23 | 24 | let wrap str = ISO8601.Permissive.datetime str 25 | let unwrap date = ISO8601.Permissive.string_of_datetimezone (date, 0.) 26 | end 27 | 28 | module Additional_properties_adapter : Atdgen_runtime.Json_adapter.S = struct 29 | (** Convert from original json to ATD-compatible json *) 30 | let normalize = function 31 | | `Bool _b as b -> `Variant ("Boolean", Some b) 32 | | `Assoc _schema as schema -> `Variant ("Schema", Some schema) 33 | | _ -> assert false 34 | 35 | (** Convert from ATD-compatible json to original json *) 36 | let restore = function 37 | | `Variant ("boolean", Some b) -> b 38 | | `Variant ("schema", Some schema) -> schema 39 | | _ -> assert false 40 | end 41 | -------------------------------------------------------------------------------- /lib/type.ml: -------------------------------------------------------------------------------- 1 | open Ppxlib 2 | open Util 3 | 4 | let type_declaration' ~name ~descr ~kind ~manifest = 5 | let deriving_yojson = 6 | let name = Ast_builder.Located.mk "deriving" in 7 | Ast_builder.attribute ~name ~payload:(PStr [%str yojson]) 8 | in 9 | let name = Ast_builder.Located.mk name in 10 | let t = 11 | Ast_builder.type_declaration ~name ~params:[] ~cstrs:[] ~private_:Public 12 | ~kind ~manifest 13 | in 14 | let ptype_attributes = 15 | match descr with 16 | | Some descr -> 17 | Util.ocaml_doc descr :: deriving_yojson :: t.ptype_attributes 18 | | None -> deriving_yojson :: t.ptype_attributes 19 | in 20 | { t with ptype_attributes } 21 | 22 | let type_declaration_yojson ~name ~descr = 23 | let name = Ast_builder.Located.mk name in 24 | let t = 25 | Ast_builder.type_declaration ~name ~params:[] ~cstrs:[] ~private_:Public 26 | ~kind:Ptype_abstract 27 | ~manifest:(Some [%type: Yojson.Safe.t]) 28 | in 29 | let ptype_attributes = 30 | match descr with 31 | | Some descr -> Util.ocaml_doc descr :: t.ptype_attributes 32 | | None -> t.ptype_attributes 33 | in 34 | { t with ptype_attributes } 35 | 36 | module Sig = struct 37 | type t = 38 | | Abstract of string * string option 39 | | Unspecified of string * string option 40 | 41 | let abstract ?descr name = Abstract (name, descr) 42 | let unspecified ?descr name = Unspecified (name, descr) 43 | 44 | let to_sig t = 45 | match t with 46 | | Abstract (name, descr) -> 47 | let t = 48 | type_declaration' ~name ~descr ~kind:Ptype_abstract ~manifest:None 49 | in 50 | [ Ast_builder.psig_type Recursive [ t ] ] 51 | | Unspecified (name, descr) -> 52 | let t = type_declaration_yojson ~name ~descr in 53 | Ast_builder.psig_type Recursive [ t ] 54 | :: [%sig: 55 | val yojson_of_t : t -> Yojson.Safe.t 56 | val t_of_yojson : t -> Yojson.Safe.t] 57 | end 58 | 59 | module Impl = struct 60 | type record_field = { 61 | name : string; 62 | orig_name : string; 63 | type_ : Ast.core_type; 64 | } 65 | 66 | type t = 67 | | Alias of { name : string; target : Ast.core_type; int_or_string : bool } 68 | | Record of string * record_field list 69 | | Unspecified of string 70 | 71 | let record_field ~name ~orig_name ~type_ = { name; orig_name; type_ } 72 | 73 | let alias ?(int_or_string = false) name target = 74 | if int_or_string && target <> [%type: string] then 75 | invalid_arg "Type.alias: int_or_string only supported for string types"; 76 | Alias { name; target; int_or_string } 77 | 78 | let record name fields = Record (name, fields) 79 | let unspecified name = Unspecified name 80 | 81 | let yojson_of_int_or_string = 82 | [%stri 83 | let yojson_of_t t = 84 | (* Valid JSON integer. *) 85 | match Yojson.Safe.from_string t with 86 | | `Int _ as int -> int 87 | (* Not a valid JSON integer, use original yojson_of_t function. *) 88 | | _ -> yojson_of_t t 89 | | exception Yojson.Json_error _ -> yojson_of_t t] 90 | 91 | let int_or_string_of_yojson = 92 | [%stri 93 | let t_of_yojson j = 94 | match j with `Int _ -> Yojson.Safe.to_string j | _ -> t_of_yojson j] 95 | 96 | let to_impl t = 97 | match t with 98 | | Unspecified name -> 99 | let t = type_declaration_yojson ~name ~descr:None in 100 | Ast_builder.(pstr_type Recursive [ t ]) 101 | :: [%str 102 | let t_of_yojson = Fun.id 103 | let yojson_of_t = Fun.id] 104 | | Alias { name; target; int_or_string = false } -> 105 | let t = 106 | type_declaration' ~name ~descr:None ~kind:Ptype_abstract 107 | ~manifest:(Some target) 108 | in 109 | [ Ast_builder.(pstr_type Recursive [ t ]) ] 110 | | Alias { name; target; int_or_string = true } -> 111 | (* Aliases for string types with "int-or-string" format, in addition to 112 | the automatically generated yojson functions, will generate helper 113 | wrappers that check if the string value represents a valid integer. If 114 | that is the case the equivvalent JSON values will be encoded as ints. *) 115 | 116 | (* Only string aliases with int_or_string can be constructed. *) 117 | assert (target = [%type: string]); 118 | 119 | let t = 120 | type_declaration' ~name ~descr:None ~kind:Ptype_abstract 121 | ~manifest:(Some [%type: string]) 122 | in 123 | let t = Ast_builder.(pstr_type Recursive [ t ]) in 124 | [ t; yojson_of_int_or_string; int_or_string_of_yojson ] 125 | | Record (name, []) -> 126 | let t = 127 | let name = Ast_builder.Located.mk name in 128 | Ast_builder.type_declaration ~name ~kind:Ptype_abstract 129 | ~manifest:(Some [%type: unit]) 130 | ~params:[] ~cstrs:[] ~private_:Public 131 | in 132 | Ast_builder.(pstr_type Recursive [ t ]) 133 | :: [%str 134 | let yojson_of_t () = `Assoc [] 135 | 136 | let t_of_yojson = function 137 | | `Assoc [] | _ -> invalid_arg "Expected empty object"] 138 | | Record (name, fields) -> 139 | let labels = 140 | List.rev_map 141 | (fun { name; orig_name; type_ } -> 142 | let label_declaration = 143 | let name = Ast_builder.Located.mk name in 144 | Ast_builder.(label_declaration ~name ~mutable_:Immutable ~type_) 145 | in 146 | if name = orig_name then label_declaration 147 | else 148 | let name = Ast_builder.Located.mk "key" in 149 | let orig_name = Ast_builder.estring orig_name in 150 | let attr = 151 | Ast_builder.( 152 | attribute ~name ~payload:(PStr [%str [%e orig_name]])) 153 | in 154 | let pld_attributes = attr :: label_declaration.pld_attributes in 155 | { label_declaration with pld_attributes }) 156 | fields 157 | in 158 | let t = 159 | type_declaration' ~name ~descr:None ~kind:(Ptype_record labels) 160 | ~manifest:None 161 | in 162 | [ Ast_builder.(pstr_type Recursive [ t ]) ] 163 | end 164 | 165 | type t = { signature : Sig.t; implementation : Impl.t } 166 | 167 | let create signature implementation = { signature; implementation } 168 | 169 | let name t = 170 | match t.signature with Sig.Abstract (n, _) | Sig.Unspecified (n, _) -> n 171 | 172 | let signature t = t.signature 173 | let implementation t = t.implementation 174 | 175 | let compose t1 t2 = 176 | match (t1.signature, t2.signature, t1.implementation, t2.implementation) with 177 | | ( Sig.Abstract (sig_name1, descr1), 178 | Sig.Abstract (sig_name2, descr2), 179 | Impl.Record (impl_name1, fields1), 180 | Impl.Record (impl_name2, fields2) ) 181 | when sig_name1 = sig_name2 && impl_name1 = impl_name2 && descr1 = descr2 -> 182 | let impl = 183 | List.sort_uniq 184 | (fun { Impl.name = name1; _ } { Impl.name = name2; _ } -> 185 | String.compare name1 name2) 186 | (fields1 @ fields2) 187 | in 188 | create (Sig.Abstract (sig_name1, descr1)) (Impl.Record (impl_name1, impl)) 189 | | _ -> invalid_arg "Could not compose types. Has the order changed?" 190 | -------------------------------------------------------------------------------- /lib/type.mli: -------------------------------------------------------------------------------- 1 | (** Representation of OCaml types in signatures and implementations. *) 2 | 3 | (** {1 Signature types} *) 4 | 5 | (** Type declarations in signatures. *) 6 | module Sig : sig 7 | type t 8 | 9 | val abstract : ?descr:string -> string -> t 10 | (** Signature item for abstract types. 11 | 12 | [abstract ?descr ?is_record name] is a signature item for abstract types. 13 | The [descr] argument will be used to generate the documentation string, 14 | if provided. *) 15 | 16 | val unspecified : ?descr:string -> string -> t 17 | val to_sig : t -> Ppxlib.Ast.signature 18 | end 19 | 20 | (** {1 Implementation types} *) 21 | 22 | (** Type declarations in signatures. *) 23 | module Impl : sig 24 | type t 25 | 26 | type record_field 27 | (** The type of record fields. *) 28 | 29 | val record_field : 30 | name:string -> 31 | orig_name:string -> 32 | type_:Ppxlib.Ast.core_type -> 33 | record_field 34 | 35 | (** Constructor function for record fields. *) 36 | 37 | val alias : ?int_or_string:bool -> string -> Ppxlib.Ast.core_type -> t 38 | (** Defines an alias type to be generated in an implementation. 39 | 40 | The optional [int_or_string] flag can be passed for aliases that can 41 | repersent either integers or strings. This is defined in the Swagger 42 | specification as a ["string"] type having an ["int-or-string"] format. 43 | The generated JSON conversion functions will attempt to produce an 44 | integer value if the string represents a valid integer. *) 45 | 46 | val record : string -> record_field list -> t 47 | val unspecified : string -> t 48 | val to_impl : t -> Ppxlib.Ast.structure 49 | end 50 | 51 | (** {1 Type declarations} *) 52 | 53 | type t 54 | (** The type used to represent signature and implementation types. *) 55 | 56 | val create : Sig.t -> Impl.t -> t 57 | (** Constructs a type representation from a signature and implementation types. 58 | 59 | The signature and implementation types are assumed to represent the same 60 | type in the generated code, according to OCaml's semantics. *) 61 | 62 | val name : t -> string 63 | (** The name of the type as defined in the signature type. *) 64 | 65 | val signature : t -> Sig.t 66 | (** The signature type representation. *) 67 | 68 | val implementation : t -> Impl.t 69 | (** The implementation type representation. *) 70 | 71 | val compose : t -> t -> t 72 | (** Compose two record types (merge and flatten). *) 73 | -------------------------------------------------------------------------------- /lib/util.ml: -------------------------------------------------------------------------------- 1 | let sprintf = Printf.sprintf 2 | 3 | let snake_case = 4 | let re1 = Re.Pcre.regexp "([A-Z]+)([A-Z][a-z]{2,})" in 5 | let re2 = Re.Pcre.regexp "([a-z0-9])([A-Z])" in 6 | let re3 = Re.Pcre.regexp "-| " in 7 | let re4 = Re.Pcre.regexp "^\\." in 8 | let underscore re s = 9 | let replace groups = 10 | sprintf "%s_%s" (Re.Group.get groups 1) (Re.Group.get groups 2) 11 | in 12 | Re.replace re ~f:replace s 13 | in 14 | fun s -> 15 | let len = String.length s in 16 | if len > 1 then 17 | let s = underscore re1 s in 18 | let s = underscore re2 s in 19 | let s = Re.replace_string re3 ~by:"_" s in 20 | let s = Re.replace_string re4 ~by:"dot_" s in 21 | sprintf "%c" s.[0] 22 | ^ String.lowercase_ascii (String.sub s 1 (String.length s - 1)) 23 | else s 24 | 25 | let format_comment = 26 | let re = Re.Pcre.regexp "[{}@\\[\\]]" in 27 | let snake_case = function 28 | | "CamelCase" -> "CamelCase" 29 | | w when String.length w > 6 && String.sub w 0 7 = "http://" -> w 30 | | w when String.length w > 7 && String.sub w 0 8 = "https://" -> w 31 | | w -> snake_case w 32 | in 33 | fun text -> 34 | text 35 | |> Re.replace re ~f:(fun g -> "\\" ^ Re.Group.get g 0) 36 | |> String.split_on_char ' ' |> List.map snake_case |> String.concat " " 37 | 38 | let loc = Ppxlib.Location.none 39 | 40 | module Ast_builder = Ppxlib.Ast_builder.Make (struct 41 | let loc = loc 42 | end) 43 | 44 | let ocaml_doc descr = 45 | let open Ast_builder in 46 | let name = Located.mk "ocaml.doc" in 47 | let doc = pexp_constant (Pconst_string (descr, loc, Some "ocamlswagger")) in 48 | let doc = pstr_eval [%expr [%e doc]] [] in 49 | attribute ~name ~payload:(PStr [ doc ]) 50 | 51 | let ocaml_warning ~number = 52 | let open Ast_builder in 53 | let name = Located.mk "ocaml.warning" in 54 | let doc = 55 | pexp_constant 56 | (Pconst_string ("-" ^ string_of_int number, loc, Some "ocamlswagger")) 57 | in 58 | let doc = pstr_eval [%expr [%e doc]] [] in 59 | attribute ~name ~payload:(PStr [ doc ]) 60 | 61 | let unsnoc l = 62 | let rec go acc = function 63 | | [] -> None 64 | | [ x ] -> Some (List.rev acc, x) 65 | | x :: xs -> go (x :: acc) xs 66 | in 67 | go [] l 68 | 69 | let keep_some l = List.filter_map Fun.id l 70 | let opt_cons o l = match o with Some o -> o :: l | None -> l 71 | 72 | module StringMap = Map.Make (struct 73 | type t = string 74 | 75 | let compare = compare 76 | end) 77 | 78 | module StringSet = Set.Make (struct 79 | type t = string 80 | 81 | let compare = compare 82 | end) 83 | -------------------------------------------------------------------------------- /lib/val.ml: -------------------------------------------------------------------------------- 1 | open Ppxlib 2 | open Util 3 | 4 | let sprintf = Printf.sprintf 5 | 6 | module Sig = struct 7 | type kind = Pure | Http_request 8 | type param_data = { name : string; descr : string option } 9 | 10 | type param = 11 | | Labelled of param_data * Ast.core_type 12 | | Nolabel of Ast.core_type 13 | | Optional of param_data * Ast.core_type 14 | 15 | type return = Simple of Ast.core_type | Async of Ast.core_type 16 | 17 | type t = { 18 | name : string; 19 | params : param list; 20 | return : return; 21 | kind : kind; 22 | descr : string list; 23 | } 24 | 25 | let name t = t.name 26 | 27 | let param_descr = function 28 | | Nolabel _ 29 | | Labelled ({ descr = None; _ }, _) 30 | | Optional ({ descr = None; _ }, _) -> 31 | None 32 | | Labelled ({ name; descr = Some descr }, _) -> 33 | Some (sprintf "%s %s" name descr) 34 | | Optional ({ name; descr = Some descr }, _) -> 35 | Some (sprintf "%s %s" name descr) 36 | 37 | let create ?descr kind name params return = 38 | let descr = descr :: List.map param_descr params |> keep_some in 39 | { name; params; return; kind; descr } 40 | 41 | let labelled ?descr name type_ = Labelled ({ name; descr }, type_) 42 | let nolabel type_ = Nolabel type_ 43 | let optional ?descr name type_ = Optional ({ name; descr }, type_) 44 | let pure ?descr name params ret = create ?descr Pure name params (Simple ret) 45 | 46 | let field_setter ?descr name params ret = 47 | create ?descr Pure ("set_" ^ name) params (Simple ret) 48 | 49 | (* Here "constants" are actually [unit -> string] functions to satisfy 50 | * OCaml's recursive module safety requirements. 51 | * 52 | * See https://stackoverflow.com/q/4239045 *) 53 | let constant name = pure name [ Nolabel [%type: unit] ] [%type: string] 54 | 55 | let http_request ?descr name params ret = 56 | create ?descr Http_request name params (Async ret) 57 | 58 | let make_fun params k = 59 | let to_ptyp_arrow k = function 60 | | Nolabel t -> Ast_builder.(ptyp_arrow Nolabel t k) 61 | | Labelled ({ name; _ }, t) -> 62 | Ast_builder.(ptyp_arrow (Labelled name) t k) 63 | | Optional ({ name; _ }, t) -> 64 | Ast_builder.(ptyp_arrow (Optional name) t k) 65 | in 66 | let rec aux acc = function 67 | | [] -> acc 68 | | [ ((Optional _ | Labelled _) as p) ] -> 69 | (* Extra unit param if last one is optional or labelled *) 70 | to_ptyp_arrow [%type: unit -> [%t k]] p 71 | | p :: ps -> to_ptyp_arrow (aux acc ps) p 72 | in 73 | aux k params 74 | 75 | let to_sig { name; params; return; kind; descr } = 76 | let params = 77 | match kind with 78 | | Pure when params = [] -> [ nolabel [%type: unit] ] 79 | | Pure -> params 80 | | Http_request -> 81 | let ctx = optional "ctx" [%type: Cohttp_lwt_unix.Client.ctx] in 82 | let headers = optional "headers" [%type: Cohttp.Header.t] in 83 | let uri = nolabel [%type: Uri.t] in 84 | params @ [ ctx; headers; uri ] 85 | in 86 | let doc = 87 | match descr with 88 | | [] -> None 89 | | descr :: params -> 90 | let descr = 91 | descr 92 | :: List.map (fun d -> sprintf "@param %s" (format_comment d)) params 93 | in 94 | Some (Util.ocaml_doc (String.concat "\n" descr)) 95 | in 96 | let open Ast_builder in 97 | let name = Located.mk name in 98 | let return = 99 | match return with 100 | | Simple t -> t 101 | | Async t -> [%type: ([%t t], string) result Lwt.t] 102 | in 103 | let type_ = make_fun params return in 104 | let value_description = 105 | Ast_builder.value_description ~name ~type_ ~prim:[] 106 | in 107 | let pval_attributes = Util.opt_cons doc value_description.pval_attributes in 108 | { value_description with pval_attributes } 109 | end 110 | 111 | module Impl = struct 112 | type http_verb = Get | Put | Post | Delete | Head | Patch | Options 113 | type return = Module of string | Type of Ast.core_type 114 | 115 | type kind = 116 | | Record_constructor 117 | | Field_getter 118 | | Field_setter 119 | | Identity 120 | | Constant of string 121 | | Http_request of http_verb * return 122 | 123 | type origin = { location : Swagger_t.location; orig_name : string } 124 | type param_data = { name : string; type_ : Ast.core_type } 125 | 126 | type param = 127 | | Labelled of param_data * origin option 128 | | Nolabel of param_data 129 | | Optional of param_data * origin option 130 | 131 | let module_ name = Module name 132 | let type_ name = Type name 133 | 134 | let origin (p : Swagger_t.parameter) = 135 | { location = p.location; orig_name = p.name } 136 | 137 | let labelled ?origin name type_ = Labelled ({ name; type_ }, origin) 138 | let nolabel name type_ = Nolabel { name; type_ } 139 | let optional ?origin name type_ = Optional ({ name; type_ }, origin) 140 | 141 | type t = { name : string; params : param list; kind : kind } 142 | 143 | let name t = t.name 144 | let create kind name params = { kind; name; params } 145 | let record_constructor = create Record_constructor 146 | let field_getter = create Field_getter 147 | let field_setter = create Field_setter 148 | let identity = create Identity 149 | 150 | let constant name value = 151 | create (Constant value) name [ nolabel "()" [%type: unit] ] 152 | 153 | let http_request ~return verb = create (Http_request (verb, return)) 154 | 155 | let param_name = function 156 | | Labelled (p, _) | Nolabel p | Optional (p, _) -> p.name 157 | 158 | let param_type = function 159 | | Labelled (p, _) | Nolabel p | Optional (p, _) -> p.type_ 160 | 161 | let param_location = function 162 | | Labelled (_, Some origin) -> Some origin.location 163 | | Labelled (_, None) -> None 164 | | Optional (_, Some origin) -> Some origin.location 165 | | Optional (_, None) -> None 166 | | Nolabel _ -> None 167 | 168 | let http_verb_of_string = function 169 | | "get" -> Get 170 | | "put" -> Put 171 | | "post" -> Post 172 | | "delete" -> Delete 173 | | "head" -> Head 174 | | "patch" -> Patch 175 | | "options" -> Options 176 | | op -> failwith ("unknown HTTP verb: " ^ op) 177 | 178 | let record_constructor_body = function 179 | | [] -> Ast_builder.eunit 180 | | params -> 181 | let fields = 182 | List.fold_left 183 | (fun acc param -> 184 | let ident = Ast_builder.Located.lident (param_name param) in 185 | let exp = Ast_builder.pexp_ident ident in 186 | (ident, exp) :: acc) 187 | [] params 188 | in 189 | Ast_builder.pexp_record fields None 190 | 191 | (* FIXME: take into account collectionFormat *) 192 | 193 | let assoc_string_with f params = Ast_builder.elist (List.map f params) 194 | 195 | let assoc_opt_string params = 196 | let string_of name = 197 | let name = Ast_builder.evar name in 198 | let conv = function 199 | | [%type: string] -> name 200 | | { Ast.ptyp_desc = Ptyp_constr (lident, []); _ } -> 201 | let conv lident = 202 | Longident.parse ("string_of_" ^ Longident.name lident) 203 | in 204 | let lident = Ast_builder.Located.map conv lident in 205 | let e = Ast_builder.(pexp_ident lident) in 206 | [%expr [%e e] [%e name]] 207 | | _ -> assert false 208 | in 209 | function 210 | | [%type: [%t? t] array] -> 211 | let conv = conv t in 212 | [%expr String.concat "," (Array.to_list [%e conv])] 213 | | t -> conv t 214 | in 215 | let string_opt_to_string name = 216 | let name = Ast_builder.(pexp_ident (Located.lident name)) in 217 | [%expr match [%e name] with Some s -> s | None -> ""] 218 | in 219 | let opt_to_string name = function 220 | | { Ast.ptyp_desc = Ptyp_constr (lident, []); _ } -> 221 | let name = Ast_builder.(pexp_ident (Located.lident name)) in 222 | let conv = 223 | Ast_builder.type_constr_conv lident ~f:(sprintf "string_of_%s") 224 | [ [%expr x] ] 225 | in 226 | [%expr match [%e name] with Some x -> [%e conv] | None -> ""] 227 | | _ -> assert false 228 | in 229 | assoc_string_with 230 | (fun p -> 231 | let orig_name, value = 232 | match p with 233 | | Labelled ({ name; type_ = [%type: string] }, Some origin) -> 234 | (origin.orig_name, Ast_builder.evar name) 235 | | Labelled ({ name; type_ }, Some origin) -> 236 | (origin.orig_name, string_of name type_) 237 | | Labelled ({ name; type_ = [%type: string] }, None) -> 238 | (name, Ast_builder.evar name) 239 | | Labelled ({ name; type_ }, None) -> (name, string_of name type_) 240 | | Optional ({ name; type_ = [%type: string] }, Some origin) -> 241 | (origin.orig_name, string_opt_to_string name) 242 | | Optional ({ name; type_ }, Some origin) -> 243 | (origin.orig_name, opt_to_string name type_) 244 | | Optional ({ name; type_ }, None) -> (name, opt_to_string name type_) 245 | | Nolabel _ -> failwith "positional parameters don't go in requests" 246 | in 247 | let orig_name = Ast_builder.estring orig_name in 248 | [%expr [%e orig_name], [%e value]]) 249 | params 250 | 251 | let assoc_string = 252 | assoc_string_with (fun p -> 253 | let open Ast_builder in 254 | let name = param_name p in 255 | let value = 256 | let name = evar name in 257 | match param_type p with 258 | | [%type: string] -> name 259 | | { Ast.ptyp_desc = Ptyp_constr (lident, []); _ } -> 260 | type_constr_conv lident ~f:(sprintf "string_of_%s") [ name ] 261 | | _ -> assert false 262 | in 263 | let name = estring name in 264 | [%expr [%e name], [%e value]]) 265 | 266 | let make_query params = 267 | params 268 | |> List.filter (fun p -> param_location p = Some `Query) 269 | |> assoc_opt_string 270 | 271 | let make_path params = 272 | let path_params = 273 | params 274 | |> List.filter (fun p -> param_location p = Some `Path) 275 | |> assoc_string 276 | in 277 | [%expr 278 | let path_params = [%e path_params] in 279 | List.fold_left 280 | (fun path (name, value) -> 281 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 282 | Re.replace_string re ~by:value path) 283 | (request_path_template ()) path_params] 284 | 285 | let make_headers params = 286 | params |> List.filter (fun p -> param_location p = Some `Header) |> function 287 | | [] -> Ast_builder.evar "headers" 288 | | hs -> 289 | let headers = assoc_string hs in 290 | [%expr 291 | let headers = 292 | match headers with Some hs -> hs | None -> Header.init () 293 | in 294 | Some (Cohttp.Header.add_list headers [%e headers])] 295 | 296 | let make_body params = 297 | let body_params = 298 | params |> List.filter (fun p -> param_location p = Some `Body) 299 | in 300 | match body_params with 301 | | [] -> [%expr None] 302 | | [ p ] -> ( 303 | let lident path ident = 304 | let last = Longident.last_exn path in 305 | let name = Longident.name path in 306 | String.(sub name 0 (length name - length last) ^ ident) 307 | in 308 | let name = Ast_builder.evar (param_name p) in 309 | let conv = 310 | let convs = function 311 | | { Ast.ptyp_desc = Ptyp_constr ({ txt; _ }, []); _ } -> 312 | ( Ast_builder.( 313 | pexp_ident (Located.lident (lident txt "yojson_of_t"))), 314 | Ast_builder.( 315 | pexp_ident (Located.lident (lident txt "t_of_yojson"))) ) 316 | | _ -> assert false 317 | in 318 | match param_type p with 319 | | [%type: [%t? t] list] -> 320 | let yojson_of_t, _ = convs t in 321 | [%expr `List (List.map [%e yojson_of_t] [%e name])] 322 | | { Ast.ptyp_desc = Ptyp_constr _; _ } as t -> 323 | let yojson_of_t, _t_of_yojson = convs t in 324 | [%expr [%e yojson_of_t] [%e name]] 325 | | _ -> assert false 326 | in 327 | match p with 328 | | Optional _ -> 329 | [%expr 330 | Option.map 331 | (fun body -> 332 | Cohttp_lwt.Body.of_string (Yojson.Safe.to_string [%e conv])) 333 | body] 334 | | _ -> 335 | [%expr 336 | Some (Cohttp_lwt.Body.of_string (Yojson.Safe.to_string [%e conv]))] 337 | ) 338 | | _ -> failwith "Val.Impl.make_body: there can be only one body parameter" 339 | 340 | let string_of_http_verb = function 341 | | Get -> "get" 342 | | Put -> "put" 343 | | Post -> "post" 344 | | Delete -> "delete" 345 | | Head -> "head" 346 | | Patch -> "patch" 347 | | Options -> "options" 348 | 349 | let client_function_of_http_verb = function 350 | | Options -> [%expr Cohttp_lwt_unix.Client.call `OPTIONS] 351 | | verb -> 352 | Ast_builder.evar ("Cohttp_lwt_unix.Client." ^ string_of_http_verb verb) 353 | 354 | let continuation_of_http_verb k = function 355 | | Head -> 356 | [%expr 357 | fun resp -> 358 | let code = 359 | resp |> Cohttp_lwt_unix.Response.status 360 | |> Cohttp.Code.code_of_status 361 | in 362 | let body = "Ok" in 363 | [%e k]] 364 | | _ -> 365 | [%expr 366 | fun (resp, body) -> 367 | let code = 368 | resp |> Cohttp_lwt_unix.Response.status 369 | |> Cohttp.Code.code_of_status 370 | in 371 | Cohttp_lwt.Body.to_string body >>= fun body -> [%e k]] 372 | 373 | let make_response_code ?(body_param = false) ~return verb params = 374 | let params = 375 | let open Ast in 376 | (Optional "ctx", [%expr ctx]) 377 | :: (Optional "headers", [%expr headers]) 378 | :: (if body_param then List.cons (Optional "body", make_body params) 379 | else Fun.id) 380 | [ (Nolabel, [%expr uri]) ] 381 | in 382 | let client_fun = 383 | Ast_builder.pexp_apply (client_function_of_http_verb verb) params 384 | in 385 | let return = 386 | (* FIXME: handle the response schema. *) 387 | match return with 388 | | Module module_name -> 389 | let t_of_yojson = 390 | Ast_builder.( 391 | pexp_ident (Located.lident (module_name ^ ".t_of_yojson"))) 392 | in 393 | [%expr 394 | let json = Yojson.Safe.from_string body in 395 | Lwt.return 396 | (if code >= 200 && code < 300 then Ok ([%e t_of_yojson] json) 397 | else Error body)] 398 | | Type type_name -> 399 | let conv_result = 400 | let conv str = function 401 | | { Ast.ptyp_desc = Ptyp_constr (lident, _); _ } -> 402 | let conv lident = 403 | Longident.parse (str (Longident.name lident)) 404 | in 405 | let lident = Ast_builder.Located.map conv lident in 406 | Ast_builder.(pexp_ident lident) 407 | | _ -> assert false 408 | in 409 | match type_name with 410 | | [%type: unit] -> [%expr ()] 411 | | [%type: string] -> [%expr body] 412 | | [%type: [%t? t] list] -> 413 | let conv = conv (sprintf "%s_of_yojson") t in 414 | [%expr 415 | Yojson.Safe.(Util.convert_each [%e conv] (from_string body))] 416 | | { Ast.ptyp_desc = Ptyp_constr _; _ } as t -> 417 | let conv = conv (sprintf "%s_of_yojson") t in 418 | [%expr [%e conv] (Yojson.Safe.from_string body)] 419 | | _ -> assert false 420 | in 421 | [%expr 422 | ignore body; 423 | Lwt.return 424 | (if code >= 200 && code < 300 then Ok [%e conv_result] 425 | else Error (string_of_int code))] 426 | in 427 | let result = continuation_of_http_verb return verb in 428 | [%expr 429 | let open Lwt.Infix in 430 | [%e client_fun] >>= [%e result]] 431 | 432 | let build_http_request ?body_param ~return verb params = 433 | let query = make_query params 434 | and path = make_path params 435 | and headers = make_headers params 436 | and response_code = make_response_code ?body_param ~return verb params in 437 | [%expr 438 | let query = [%e query] in 439 | let path = [%e path] in 440 | let full_path = Uri.path uri ^ path in 441 | let uri = Uri.with_path uri full_path in 442 | let uri = 443 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 444 | in 445 | let headers = [%e headers] in 446 | [%e response_code]] 447 | 448 | let http_get ~return = build_http_request ~return Get 449 | let http_put ~return = build_http_request ~body_param:true ~return Put 450 | let http_post ~return = build_http_request ~body_param:true ~return Post 451 | let http_delete ~return = build_http_request ~body_param:true ~return Delete 452 | let http_head ~return = build_http_request ~return Head 453 | let http_patch ~return = build_http_request ~body_param:true ~return Patch 454 | let http_options ~return = build_http_request ~return Options 455 | 456 | let body t = 457 | match t.kind with 458 | | Record_constructor -> record_constructor_body t.params 459 | | Field_getter -> Ast_builder.evar ("t." ^ t.name) 460 | | Field_setter -> 461 | let ident = Ast_builder.Located.lident t.name in 462 | let exp = Ast_builder.pexp_ident ident in 463 | let t = Ast_builder.pexp_record [ (ident, exp) ] (Some [%expr t]) in 464 | (* Silences OCaml warning 23 useless-record-with: Useless record with clause. *) 465 | let pexp_attributes = ocaml_warning ~number:23 :: t.pexp_attributes in 466 | { t with pexp_attributes } 467 | | Identity -> [%expr t] 468 | | Constant v -> Ast_builder.estring v 469 | | Http_request (Get, return) -> http_get ~return t.params 470 | | Http_request (Put, return) -> http_put ~return t.params 471 | | Http_request (Post, return) -> http_post ~return t.params 472 | | Http_request (Delete, return) -> http_delete ~return t.params 473 | | Http_request (Head, return) -> http_head ~return t.params 474 | | Http_request (Patch, return) -> http_patch ~return t.params 475 | | Http_request (Options, return) -> http_options ~return t.params 476 | 477 | let make_fun params k = 478 | let to_pexp_fun k = function 479 | | Labelled (p, _) -> 480 | Ast_builder.(pexp_fun (Labelled p.name) None (pvar p.name) k) 481 | | Nolabel p -> Ast_builder.(pexp_fun Nolabel None (pvar p.name) k) 482 | | Optional (p, _) -> 483 | Ast_builder.(pexp_fun (Optional p.name) None (pvar p.name) k) 484 | in 485 | let rec aux acc = function 486 | | [] when params = [] -> [%expr fun () -> ()] 487 | | [] -> acc 488 | | [ ((Optional _ | Labelled _) as p) ] -> 489 | (* extra () param if last one is optional or labelled *) 490 | to_pexp_fun [%expr fun () -> [%e k]] p 491 | | p :: ps -> to_pexp_fun (aux acc ps) p 492 | in 493 | aux k params 494 | 495 | let to_impl ({ kind; name; params } as value) = 496 | let params = 497 | match kind with 498 | | Http_request _ -> 499 | let ctx = optional "ctx" [%type: Cohttp_lwt_unix.Client.ctx] in 500 | let headers = optional "headers" [%type: Cohttp.Header.t] in 501 | let uri = nolabel "uri" [%type: Uri.t] in 502 | params @ [ ctx; headers; uri ] 503 | | _ -> params 504 | in 505 | let name = match kind with Field_setter -> "set_" ^ name | _ -> name in 506 | let name = Ast_builder.pvar name in 507 | let e = make_fun params (body value) in 508 | [%stri let [%p name] = [%e e]] 509 | end 510 | 511 | type t = { signature : Sig.t; implementation : Impl.t } 512 | 513 | let name t = 514 | assert (Sig.name t.signature = Impl.name t.implementation); 515 | Sig.name t.signature 516 | 517 | let create signature implementation = { signature; implementation } 518 | let signature t = t.signature 519 | let implementation t = t.implementation 520 | 521 | let compose t1 t2 = 522 | match (t1.signature, t2.signature, t1.implementation, t2.implementation) with 523 | | ( Sig. 524 | { name = "make"; kind = Pure; params = sig_param1; return = return1; _ }, 525 | Sig. 526 | { 527 | name = "make"; 528 | kind = Pure; 529 | params = sig_param2; 530 | return = _return2; 531 | _; 532 | }, 533 | Impl.{ name = "make"; params = impl_params1; kind = kind1 }, 534 | Impl.{ name = "make"; params = impl_params2; kind = kind2 } ) 535 | when kind1 = kind2 && (kind1 = Record_constructor || kind1 = Identity) -> 536 | let signature = 537 | Sig. 538 | { 539 | name = "make"; 540 | kind = Pure; 541 | params = sig_param1 @ sig_param2; 542 | return = return1; 543 | descr = []; 544 | } 545 | in 546 | let implementation = 547 | Impl. 548 | { name = "make"; params = impl_params1 @ impl_params2; kind = kind1 } 549 | in 550 | create signature implementation 551 | | _ -> invalid_arg "Could not compose values. Has the order changed?" 552 | -------------------------------------------------------------------------------- /lib/val.mli: -------------------------------------------------------------------------------- 1 | module Sig : sig 2 | type t 3 | type param 4 | 5 | val name : t -> string 6 | val labelled : ?descr:string -> string -> Ppxlib.Ast.core_type -> param 7 | val optional : ?descr:string -> string -> Ppxlib.Ast.core_type -> param 8 | val nolabel : Ppxlib.Ast.core_type -> param 9 | val constant : string -> t 10 | val pure : ?descr:string -> string -> param list -> Ppxlib.Ast.core_type -> t 11 | 12 | val field_setter : 13 | ?descr:string -> string -> param list -> Ppxlib.Ast.core_type -> t 14 | 15 | val http_request : 16 | ?descr:string -> string -> param list -> Ppxlib.Ast.core_type -> t 17 | 18 | val to_sig : t -> Ppxlib.Ast.value_description 19 | end 20 | 21 | module Impl : sig 22 | type t 23 | type param 24 | type origin 25 | type http_verb 26 | type return 27 | 28 | val name : t -> string 29 | val labelled : ?origin:origin -> string -> Ppxlib.Ast.core_type -> param 30 | val optional : ?origin:origin -> string -> Ppxlib.Ast.core_type -> param 31 | val nolabel : string -> Ppxlib.Ast.core_type -> param 32 | val constant : string -> string -> t 33 | val identity : string -> param list -> t 34 | val record_constructor : string -> param list -> t 35 | val field_getter : string -> param list -> t 36 | val field_setter : string -> param list -> t 37 | val http_request : return:return -> http_verb -> string -> param list -> t 38 | val origin : Swagger_t.parameter -> origin 39 | val http_verb_of_string : string -> http_verb 40 | val module_ : string -> return 41 | val type_ : Ppxlib.Ast.core_type -> return 42 | val to_impl : t -> Ppxlib.Ast.structure_item 43 | end 44 | 45 | type t 46 | 47 | val name : t -> string 48 | val create : Sig.t -> Impl.t -> t 49 | val signature : t -> Sig.t 50 | val implementation : t -> Impl.t 51 | val compose : t -> t -> t 52 | -------------------------------------------------------------------------------- /swagger.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Swagger 2.0 code generator for OCaml" 4 | description: "Swagger 2.0 code generator for OCaml" 5 | maintainer: ["Antonin Décimo "] 6 | authors: [ 7 | "Andre Nathan " 8 | "Antonin Décimo " 9 | ] 10 | license: "MIT" 11 | homepage: "https://github.com/andrenth/ocaml-swagger" 12 | bug-reports: "https://github.com/andrenth/ocaml-swagger/issues" 13 | depends: [ 14 | "dune" {>= "3.4"} 15 | "ocaml" {>= "4.14"} 16 | "atdgen" {>= "2.10"} 17 | "ISO8601" {>= "0.2.6"} 18 | "stdint" {>= "0.7.0"} 19 | "re" {>= "1.10.3"} 20 | "yojson" {>= "2.0"} 21 | "ppxlib" {>= "0.25.0"} 22 | "ppx_yojson_conv" {>= "v0.15" & with-test} 23 | "cohttp-lwt-unix" {>= "5.0.0" & with-test} 24 | "uri" {>= "4.2" & with-test} 25 | "ocamlformat" {with-test} 26 | "odoc" {with-doc} 27 | ] 28 | build: [ 29 | ["dune" "subst"] {dev} 30 | [ 31 | "dune" 32 | "build" 33 | "-p" 34 | name 35 | "-j" 36 | jobs 37 | "@install" 38 | "@runtest" {with-test} 39 | "@doc" {with-doc} 40 | ] 41 | ] 42 | dev-repo: "git+https://github.com/andrenth/ocaml-swagger.git" 43 | -------------------------------------------------------------------------------- /test/allOf/composition.expected.ml: -------------------------------------------------------------------------------- 1 | module Object = struct 2 | module type Value = sig 3 | type value 4 | 5 | val value_of_yojson : Yojson.Safe.t -> value 6 | val yojson_of_value : value -> Yojson.Safe.t 7 | end 8 | 9 | module type S = sig 10 | type value 11 | type t = (string * value) list [@@deriving yojson] 12 | end 13 | 14 | module Make (V : Value) : S with type value := V.value = struct 15 | type t = (string * V.value) list [@@deriving yojson] 16 | 17 | let yojson_of_t obj = 18 | `Assoc (List.map (fun (k, v) -> (k, V.yojson_of_value v)) obj) 19 | 20 | let t_of_yojson (obj : Yojson.Safe.t) : t = 21 | let rec loop acc = function 22 | | [] -> List.rev acc 23 | | (k, v) :: obj -> 24 | let v = V.value_of_yojson v in 25 | loop ((k, v) :: acc) obj 26 | in 27 | match obj with 28 | | `Assoc obj -> loop [] obj 29 | | _ -> invalid_arg "invalid object" 30 | end 31 | 32 | module Of_strings = Make (struct 33 | type value = string [@@deriving yojson] 34 | end) 35 | 36 | module Of_floats = Make (struct 37 | type value = float [@@deriving yojson] 38 | end) 39 | 40 | module Of_ints = Make (struct 41 | type value = int [@@deriving yojson] 42 | end) 43 | 44 | module Of_ints32 = Make (struct 45 | type value = int32 [@@deriving yojson] 46 | end) 47 | 48 | module Of_ints64 = Make (struct 49 | type value = int64 [@@deriving yojson] 50 | end) 51 | 52 | module Of_bools = Make (struct 53 | type value = bool [@@deriving yojson] 54 | end) 55 | end 56 | 57 | module rec Composition : sig 58 | module Definitions : sig 59 | module Error_model : sig 60 | type t [@@deriving yojson] 61 | 62 | val make : code:int -> message:string -> unit -> t 63 | val message : t -> string 64 | 65 | val set_message : string -> t -> t 66 | [@@ocaml.doc 67 | {ocamlswagger|Set the value of the message field.|ocamlswagger}] 68 | 69 | val code : t -> int 70 | 71 | val set_code : int -> t -> t 72 | [@@ocaml.doc 73 | {ocamlswagger|Set the value of the code field.|ocamlswagger}] 74 | 75 | module Object : Object.S with type value := t 76 | end 77 | 78 | module ExtendedErrorModel : sig 79 | type t [@@deriving yojson] 80 | 81 | val make : code:int -> message:string -> root_cause:string -> unit -> t 82 | val message : t -> string 83 | 84 | val set_message : string -> t -> t 85 | [@@ocaml.doc 86 | {ocamlswagger|Set the value of the message field.|ocamlswagger}] 87 | 88 | val code : t -> int 89 | 90 | val set_code : int -> t -> t 91 | [@@ocaml.doc 92 | {ocamlswagger|Set the value of the code field.|ocamlswagger}] 93 | 94 | val root_cause : t -> string 95 | 96 | val set_root_cause : string -> t -> t 97 | [@@ocaml.doc 98 | {ocamlswagger|Set the value of the root_cause field.|ocamlswagger}] 99 | 100 | module Object : Object.S with type value := t 101 | end 102 | end 103 | end = struct 104 | module Definitions = struct 105 | module Error_model = struct 106 | type t = { message : string; code : int } [@@deriving yojson] 107 | 108 | let make ~code ~message () = { message; code } 109 | let message t = t.message 110 | 111 | let set_message message t = 112 | { t with message } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 113 | 114 | let code t = t.code 115 | 116 | let set_code code t = 117 | { t with code } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 118 | 119 | module Object = Object.Make (struct 120 | type value = t [@@deriving yojson] 121 | end) 122 | end 123 | 124 | module ExtendedErrorModel = struct 125 | type t = { 126 | root_cause : string; [@key "rootCause"] 127 | message : string; 128 | code : int; 129 | } 130 | [@@deriving yojson] 131 | 132 | let make ~code ~message ~root_cause () = { root_cause; message; code } 133 | let message t = t.message 134 | 135 | let set_message message t = 136 | { t with message } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 137 | 138 | let code t = t.code 139 | 140 | let set_code code t = 141 | { t with code } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 142 | 143 | let root_cause t = t.root_cause 144 | 145 | let set_root_cause root_cause t = 146 | { t with root_cause } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 147 | 148 | module Object = Object.Make (struct 149 | type value = t [@@deriving yojson] 150 | end) 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /test/allOf/composition.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "ErrorModel": { 4 | "type": "object", 5 | "required": [ 6 | "message", 7 | "code" 8 | ], 9 | "properties": { 10 | "message": { 11 | "type": "string" 12 | }, 13 | "code": { 14 | "type": "integer", 15 | "minimum": 100, 16 | "maximum": 600 17 | } 18 | } 19 | }, 20 | "ExtendedErrorModel": { 21 | "allOf": [ 22 | { 23 | "$ref": "#/definitions/ErrorModel" 24 | }, 25 | { 26 | "type": "object", 27 | "required": [ 28 | "rootCause" 29 | ], 30 | "properties": { 31 | "rootCause": { 32 | "type": "string" 33 | } 34 | } 35 | } 36 | ] 37 | } 38 | }, 39 | "swagger": "2.0", 40 | "paths": {}, 41 | "info": { 42 | "version": "", 43 | "title": "composition" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/allOf/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (deps 3 | (:gen %{bin:ocaml-swagger}) 4 | (:json composition.json)) 5 | (target composition.gen.ml) 6 | (action 7 | (run 8 | %{gen} 9 | -reference-base 10 | #/definitions/ 11 | -reference-root 12 | Definitions 13 | %{json} 14 | -o 15 | %{target}))) 16 | 17 | (rule 18 | (deps composition.gen.ml) 19 | (target composition.ml) 20 | (action 21 | (run 22 | ocamlformat 23 | --impl 24 | --profile=default 25 | --ocaml-version=%{ocaml_version} 26 | --output=%{target} 27 | %{deps}))) 28 | 29 | (rule 30 | (alias runtest) 31 | (action 32 | (diff composition.expected.ml composition.ml))) 33 | 34 | (library 35 | (name composition) 36 | (preprocess 37 | (pps ppx_yojson_conv)) 38 | (libraries cohttp-lwt-unix uri lwt ppx_yojson_conv re.pcre yojson) 39 | (modules composition)) 40 | 41 | (rule 42 | (deps 43 | (:gen %{bin:ocaml-swagger}) 44 | (:json polymorphism.json)) 45 | (target polymorphism.gen.ml) 46 | (action 47 | (run 48 | %{gen} 49 | -reference-base 50 | #/definitions/ 51 | -reference-root 52 | Definitions 53 | %{json} 54 | -o 55 | %{target}))) 56 | 57 | (rule 58 | (deps polymorphism.gen.ml) 59 | (target polymorphism.ml) 60 | (action 61 | (run 62 | ocamlformat 63 | --impl 64 | --profile=default 65 | --ocaml-version=%{ocaml_version} 66 | --output=%{target} 67 | %{deps}))) 68 | 69 | (rule 70 | (alias runtest) 71 | (action 72 | (diff polymorphism.expected.ml polymorphism.ml))) 73 | 74 | (library 75 | (name polymorphism) 76 | (preprocess 77 | (pps ppx_yojson_conv)) 78 | (libraries cohttp-lwt-unix uri lwt ppx_yojson_conv re.pcre yojson) 79 | (modules polymorphism)) 80 | 81 | (tests 82 | (names test) 83 | (libraries composition polymorphism) 84 | (modules test)) 85 | -------------------------------------------------------------------------------- /test/allOf/polymorphism.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "Pet": { 4 | "type": "object", 5 | "discriminator": "petType", 6 | "properties": { 7 | "name": { 8 | "type": "string" 9 | }, 10 | "petType": { 11 | "type": "string" 12 | } 13 | }, 14 | "required": [ 15 | "name", 16 | "petType" 17 | ] 18 | }, 19 | "Cat": { 20 | "description": "A representation of a cat", 21 | "allOf": [ 22 | { 23 | "$ref": "#/definitions/Pet" 24 | }, 25 | { 26 | "type": "object", 27 | "properties": { 28 | "huntingSkill": { 29 | "type": "string", 30 | "description": "The measured skill for hunting", 31 | "default": "lazy", 32 | "enum": [ 33 | "clueless", 34 | "lazy", 35 | "adventurous", 36 | "aggressive" 37 | ] 38 | } 39 | }, 40 | "required": [ 41 | "huntingSkill" 42 | ] 43 | } 44 | ] 45 | }, 46 | "Dog": { 47 | "description": "A representation of a dog", 48 | "allOf": [ 49 | { 50 | "$ref": "#/definitions/Pet" 51 | }, 52 | { 53 | "type": "object", 54 | "properties": { 55 | "packSize": { 56 | "type": "integer", 57 | "format": "int32", 58 | "description": "the size of the pack the dog is from", 59 | "default": 0, 60 | "minimum": 0 61 | } 62 | }, 63 | "required": [ 64 | "packSize" 65 | ] 66 | } 67 | ] 68 | } 69 | }, 70 | "swagger": "2.0", 71 | "paths": {}, 72 | "info": { 73 | "version": "", 74 | "title": "polymorphism" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/allOf/test.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | -------------------------------------------------------------------------------- /test/docker/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (deps 3 | (:gen %{bin:ocaml-swagger}) 4 | (:json swagger.json)) 5 | (target docker.gen.ml) 6 | (action 7 | (run 8 | %{gen} 9 | -reference-base 10 | #/definitions/ 11 | -reference-root 12 | Definitions 13 | %{json} 14 | -o 15 | %{target}))) 16 | 17 | (rule 18 | (deps docker.gen.ml) 19 | (target docker.ml) 20 | (action 21 | (run 22 | ocamlformat 23 | --impl 24 | --profile=default 25 | --ocaml-version=%{ocaml_version} 26 | --output=%{target} 27 | %{deps}))) 28 | 29 | (rule 30 | (alias runtest) 31 | (action 32 | (diff docker.expected.ml docker.ml))) 33 | 34 | (library 35 | (name docker) 36 | (preprocess 37 | (pps ppx_yojson_conv)) 38 | (libraries cohttp-lwt-unix uri lwt ppx_yojson_conv re.pcre yojson) 39 | (modules docker)) 40 | 41 | (tests 42 | (names test) 43 | (libraries docker) 44 | (modules test)) 45 | -------------------------------------------------------------------------------- /test/docker/test.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | -------------------------------------------------------------------------------- /test/docker/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash -ue 2 | 3 | set -x 4 | 5 | DOCKER_VERSION=v1.41 6 | 7 | tmp=$(mktemp -d) 8 | curl -L "https://docs.docker.com/engine/api/$DOCKER_VERSION.yaml" -o "$tmp"/docker.yaml 9 | docker run --pull always --rm -v "$tmp":/docs swaggerapi/swagger-codegen-cli generate -i /docs/docker.yaml -l swagger -o /docs 10 | mv "$tmp"/swagger.json . 11 | docker run --pull always --rm -v "$tmp":/docs --entrypoint /bin/rm alpine -rf -- '/docs/.swagger-codegen' 12 | rm -rf -- "$tmp" 13 | -------------------------------------------------------------------------------- /test/kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes's OpenAPI Specification 2 | 3 | This folder contains an [OpenAPI specification](https://github.com/OAI/OpenAPI-Specification) for Kubernetes API. 4 | 5 | ## Vendor Extensions 6 | 7 | Kubernetes extends OpenAPI using these extensions. Note the version that 8 | extensions has been added. 9 | 10 | ### `x-kubernetes-group-version-kind` 11 | 12 | Operations and Definitions may have `x-kubernetes-group-version-kind` if they 13 | are associated with a [kubernetes resource](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources). 14 | 15 | 16 | For example: 17 | 18 | ``` json 19 | "paths": { 20 | ... 21 | "/api/v1/namespaces/{namespace}/pods/{name}": { 22 | ... 23 | "get": { 24 | ... 25 | "x-kubernetes-group-version-kind": { 26 | "group": "", 27 | "version": "v1", 28 | "kind": "Pod" 29 | } 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | ### `x-kubernetes-action` 36 | 37 | Operations and Definitions may have `x-kubernetes-action` if they 38 | are associated with a [kubernetes resource](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources). 39 | Action can be one of `get`, `list`, `put`, `patch`, `post`, `delete`, `deletecollection`, `watch`, `watchlist`, `proxy`, or `connect`. 40 | 41 | 42 | For example: 43 | 44 | ``` json 45 | "paths": { 46 | ... 47 | "/api/v1/namespaces/{namespace}/pods/{name}": { 48 | ... 49 | "get": { 50 | ... 51 | "x-kubernetes-action": "list" 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | ### `x-kubernetes-patch-strategy` and `x-kubernetes-patch-merge-key` 58 | 59 | Some of the definitions may have these extensions. For more information about PatchStrategy and PatchMergeKey see 60 | [strategic-merge-patch](https://git.k8s.io/community/contributors/devel/sig-api-machinery/strategic-merge-patch.md). 61 | -------------------------------------------------------------------------------- /test/kubernetes/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (deps 3 | (:gen %{bin:ocaml-swagger}) 4 | (:json swagger.json)) 5 | (target k8s.gen.ml) 6 | (action 7 | (run 8 | %{gen} 9 | -reference-base 10 | #/definitions/io.k8s. 11 | -reference-root 12 | Definitions 13 | -definition 14 | io.k8s. 15 | %{json} 16 | -o 17 | %{target}))) 18 | 19 | (rule 20 | (deps k8s.gen.ml) 21 | (target k8s.ml) 22 | (action 23 | (run 24 | ocamlformat 25 | --impl 26 | --profile=default 27 | --ocaml-version=%{ocaml_version} 28 | --output=%{target} 29 | %{deps}))) 30 | 31 | (rule 32 | (alias runtest) 33 | (action 34 | (diff k8s.expected.ml k8s.ml))) 35 | 36 | (library 37 | (name k8s) 38 | (preprocess 39 | (pps ppx_yojson_conv)) 40 | (libraries cohttp-lwt-unix uri lwt ppx_yojson_conv re.pcre yojson) 41 | (modules k8s)) 42 | 43 | (tests 44 | (names test) 45 | (libraries k8s) 46 | (modules test)) 47 | -------------------------------------------------------------------------------- /test/kubernetes/test.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | -------------------------------------------------------------------------------- /test/kubernetes/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash -ue 2 | 3 | K8S_VERSION=v1.25.2 4 | 5 | curl -L "https://github.com/kubernetes/kubernetes/blob/$K8S_VERSION/api/openapi-spec/swagger.json?raw=true" -o swagger.json 6 | -------------------------------------------------------------------------------- /test/petstore/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (deps 3 | (:gen %{bin:ocaml-swagger}) 4 | (:json swagger.json)) 5 | (target petstore.gen.ml) 6 | (action 7 | (run 8 | %{gen} 9 | -reference-base 10 | #/definitions/ 11 | -reference-root 12 | Definitions 13 | %{json} 14 | -o 15 | %{target}))) 16 | 17 | (rule 18 | (deps petstore.gen.ml) 19 | (target petstore.ml) 20 | (action 21 | (run 22 | ocamlformat 23 | --impl 24 | --profile=default 25 | --ocaml-version=%{ocaml_version} 26 | --output=%{target} 27 | %{deps}))) 28 | 29 | (rule 30 | (alias runtest) 31 | (action 32 | (diff petstore.expected.ml petstore.ml))) 33 | 34 | (library 35 | (name petstore) 36 | (preprocess 37 | (pps ppx_yojson_conv)) 38 | (libraries cohttp-lwt-unix uri lwt ppx_yojson_conv re.pcre yojson) 39 | (modules petstore)) 40 | 41 | (tests 42 | (names test) 43 | (libraries petstore) 44 | (modules test)) 45 | -------------------------------------------------------------------------------- /test/petstore/petstore.expected.ml: -------------------------------------------------------------------------------- 1 | module Object = struct 2 | module type Value = sig 3 | type value 4 | 5 | val value_of_yojson : Yojson.Safe.t -> value 6 | val yojson_of_value : value -> Yojson.Safe.t 7 | end 8 | 9 | module type S = sig 10 | type value 11 | type t = (string * value) list [@@deriving yojson] 12 | end 13 | 14 | module Make (V : Value) : S with type value := V.value = struct 15 | type t = (string * V.value) list [@@deriving yojson] 16 | 17 | let yojson_of_t obj = 18 | `Assoc (List.map (fun (k, v) -> (k, V.yojson_of_value v)) obj) 19 | 20 | let t_of_yojson (obj : Yojson.Safe.t) : t = 21 | let rec loop acc = function 22 | | [] -> List.rev acc 23 | | (k, v) :: obj -> 24 | let v = V.value_of_yojson v in 25 | loop ((k, v) :: acc) obj 26 | in 27 | match obj with 28 | | `Assoc obj -> loop [] obj 29 | | _ -> invalid_arg "invalid object" 30 | end 31 | 32 | module Of_strings = Make (struct 33 | type value = string [@@deriving yojson] 34 | end) 35 | 36 | module Of_floats = Make (struct 37 | type value = float [@@deriving yojson] 38 | end) 39 | 40 | module Of_ints = Make (struct 41 | type value = int [@@deriving yojson] 42 | end) 43 | 44 | module Of_ints32 = Make (struct 45 | type value = int32 [@@deriving yojson] 46 | end) 47 | 48 | module Of_ints64 = Make (struct 49 | type value = int64 [@@deriving yojson] 50 | end) 51 | 52 | module Of_bools = Make (struct 53 | type value = bool [@@deriving yojson] 54 | end) 55 | end 56 | 57 | module rec Swagger_petstore : sig 58 | module Definitions : sig 59 | module Api_response : sig 60 | type t [@@deriving yojson] 61 | 62 | val make : ?message:string -> ?type_:string -> ?code:int32 -> unit -> t 63 | val code : t -> int32 option 64 | 65 | val set_code : int32 option -> t -> t 66 | [@@ocaml.doc 67 | {ocamlswagger|Set the value of the code field.|ocamlswagger}] 68 | 69 | val type_ : t -> string option 70 | 71 | val set_type_ : string option -> t -> t 72 | [@@ocaml.doc 73 | {ocamlswagger|Set the value of the type_ field.|ocamlswagger}] 74 | 75 | val message : t -> string option 76 | 77 | val set_message : string option -> t -> t 78 | [@@ocaml.doc 79 | {ocamlswagger|Set the value of the message field.|ocamlswagger}] 80 | 81 | module Object : Object.S with type value := t 82 | end 83 | 84 | module Category : sig 85 | type t [@@deriving yojson] 86 | 87 | val make : ?name:string -> ?id:int64 -> unit -> t 88 | val id : t -> int64 option 89 | 90 | val set_id : int64 option -> t -> t 91 | [@@ocaml.doc {ocamlswagger|Set the value of the id field.|ocamlswagger}] 92 | 93 | val name : t -> string option 94 | 95 | val set_name : string option -> t -> t 96 | [@@ocaml.doc 97 | {ocamlswagger|Set the value of the name field.|ocamlswagger}] 98 | 99 | module Object : Object.S with type value := t 100 | end 101 | 102 | module Order : sig 103 | type t [@@deriving yojson] 104 | 105 | val make : 106 | ?complete:bool -> 107 | ?status:string -> 108 | ?ship_date:float -> 109 | ?quantity:int32 -> 110 | ?pet_id:int64 -> 111 | ?id:int64 -> 112 | unit -> 113 | t 114 | 115 | val id : t -> int64 option 116 | 117 | val set_id : int64 option -> t -> t 118 | [@@ocaml.doc {ocamlswagger|Set the value of the id field.|ocamlswagger}] 119 | 120 | val pet_id : t -> int64 option 121 | 122 | val set_pet_id : int64 option -> t -> t 123 | [@@ocaml.doc 124 | {ocamlswagger|Set the value of the pet_id field.|ocamlswagger}] 125 | 126 | val quantity : t -> int32 option 127 | 128 | val set_quantity : int32 option -> t -> t 129 | [@@ocaml.doc 130 | {ocamlswagger|Set the value of the quantity field.|ocamlswagger}] 131 | 132 | val ship_date : t -> float option 133 | 134 | val set_ship_date : float option -> t -> t 135 | [@@ocaml.doc 136 | {ocamlswagger|Set the value of the ship_date field.|ocamlswagger}] 137 | 138 | val status : t -> string option 139 | [@@ocaml.doc {ocamlswagger|Order Status|ocamlswagger}] 140 | 141 | val set_status : string option -> t -> t 142 | [@@ocaml.doc 143 | {ocamlswagger|Set the value of the status field.|ocamlswagger}] 144 | 145 | val complete : t -> bool option 146 | 147 | val set_complete : bool option -> t -> t 148 | [@@ocaml.doc 149 | {ocamlswagger|Set the value of the complete field.|ocamlswagger}] 150 | 151 | module Object : Object.S with type value := t 152 | end 153 | 154 | module Pet : sig 155 | type t [@@deriving yojson] 156 | 157 | val make : 158 | ?status:string -> 159 | ?tags:Swagger_petstore.Definitions.Tag.t list -> 160 | photo_urls:string list -> 161 | name:string -> 162 | ?category:Swagger_petstore.Definitions.Category.t -> 163 | ?id:int64 -> 164 | unit -> 165 | t 166 | 167 | val id : t -> int64 option 168 | 169 | val set_id : int64 option -> t -> t 170 | [@@ocaml.doc {ocamlswagger|Set the value of the id field.|ocamlswagger}] 171 | 172 | val category : t -> Swagger_petstore.Definitions.Category.t option 173 | 174 | val set_category : 175 | Swagger_petstore.Definitions.Category.t option -> t -> t 176 | [@@ocaml.doc 177 | {ocamlswagger|Set the value of the category field.|ocamlswagger}] 178 | 179 | val name : t -> string 180 | 181 | val set_name : string -> t -> t 182 | [@@ocaml.doc 183 | {ocamlswagger|Set the value of the name field.|ocamlswagger}] 184 | 185 | val photo_urls : t -> string list 186 | 187 | val set_photo_urls : string list -> t -> t 188 | [@@ocaml.doc 189 | {ocamlswagger|Set the value of the photo_urls field.|ocamlswagger}] 190 | 191 | val tags : t -> Swagger_petstore.Definitions.Tag.t list option 192 | 193 | val set_tags : Swagger_petstore.Definitions.Tag.t list option -> t -> t 194 | [@@ocaml.doc 195 | {ocamlswagger|Set the value of the tags field.|ocamlswagger}] 196 | 197 | val status : t -> string option 198 | [@@ocaml.doc {ocamlswagger|pet status in the store|ocamlswagger}] 199 | 200 | val set_status : string option -> t -> t 201 | [@@ocaml.doc 202 | {ocamlswagger|Set the value of the status field.|ocamlswagger}] 203 | 204 | module Object : Object.S with type value := t 205 | end 206 | 207 | module Tag : sig 208 | type t [@@deriving yojson] 209 | 210 | val make : ?name:string -> ?id:int64 -> unit -> t 211 | val id : t -> int64 option 212 | 213 | val set_id : int64 option -> t -> t 214 | [@@ocaml.doc {ocamlswagger|Set the value of the id field.|ocamlswagger}] 215 | 216 | val name : t -> string option 217 | 218 | val set_name : string option -> t -> t 219 | [@@ocaml.doc 220 | {ocamlswagger|Set the value of the name field.|ocamlswagger}] 221 | 222 | module Object : Object.S with type value := t 223 | end 224 | 225 | module User : sig 226 | type t [@@deriving yojson] 227 | 228 | val make : 229 | ?user_status:int32 -> 230 | ?phone:string -> 231 | ?password:string -> 232 | ?email:string -> 233 | ?last_name:string -> 234 | ?first_name:string -> 235 | ?username:string -> 236 | ?id:int64 -> 237 | unit -> 238 | t 239 | 240 | val id : t -> int64 option 241 | 242 | val set_id : int64 option -> t -> t 243 | [@@ocaml.doc {ocamlswagger|Set the value of the id field.|ocamlswagger}] 244 | 245 | val username : t -> string option 246 | 247 | val set_username : string option -> t -> t 248 | [@@ocaml.doc 249 | {ocamlswagger|Set the value of the username field.|ocamlswagger}] 250 | 251 | val first_name : t -> string option 252 | 253 | val set_first_name : string option -> t -> t 254 | [@@ocaml.doc 255 | {ocamlswagger|Set the value of the first_name field.|ocamlswagger}] 256 | 257 | val last_name : t -> string option 258 | 259 | val set_last_name : string option -> t -> t 260 | [@@ocaml.doc 261 | {ocamlswagger|Set the value of the last_name field.|ocamlswagger}] 262 | 263 | val email : t -> string option 264 | 265 | val set_email : string option -> t -> t 266 | [@@ocaml.doc 267 | {ocamlswagger|Set the value of the email field.|ocamlswagger}] 268 | 269 | val password : t -> string option 270 | 271 | val set_password : string option -> t -> t 272 | [@@ocaml.doc 273 | {ocamlswagger|Set the value of the password field.|ocamlswagger}] 274 | 275 | val phone : t -> string option 276 | 277 | val set_phone : string option -> t -> t 278 | [@@ocaml.doc 279 | {ocamlswagger|Set the value of the phone field.|ocamlswagger}] 280 | 281 | val user_status : t -> int32 option 282 | [@@ocaml.doc {ocamlswagger|User Status|ocamlswagger}] 283 | 284 | val set_user_status : int32 option -> t -> t 285 | [@@ocaml.doc 286 | {ocamlswagger|Set the value of the user_status field.|ocamlswagger}] 287 | 288 | module Object : Object.S with type value := t 289 | end 290 | end 291 | 292 | module Pet : sig 293 | module Find_by_status : sig 294 | val request_path_template : unit -> string 295 | 296 | val get : 297 | status:string array -> 298 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 299 | ?headers:Cohttp.Header.t -> 300 | Uri.t -> 301 | (Swagger_petstore.Definitions.Pet.t list, string) result Lwt.t 302 | [@@ocaml.doc 303 | {ocamlswagger|Multiple status values can be provided with comma separated strings 304 | @param status Status values that need to be considered for filter|ocamlswagger}] 305 | end 306 | 307 | module Find_by_tags : sig 308 | val request_path_template : unit -> string 309 | 310 | val get : 311 | tags:string array -> 312 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 313 | ?headers:Cohttp.Header.t -> 314 | Uri.t -> 315 | (Swagger_petstore.Definitions.Pet.t list, string) result Lwt.t 316 | [@@ocaml.doc 317 | {ocamlswagger|Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. 318 | @param tags Tags to filter by|ocamlswagger}] 319 | end 320 | 321 | val request_path_template : unit -> string 322 | 323 | val put : 324 | body:Swagger_petstore.Definitions.Pet.t -> 325 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 326 | ?headers:Cohttp.Header.t -> 327 | Uri.t -> 328 | (unit, string) result Lwt.t 329 | [@@ocaml.doc 330 | {ocamlswagger| 331 | @param body Pet object that needs to be added to the store|ocamlswagger}] 332 | 333 | val post : 334 | body:Swagger_petstore.Definitions.Pet.t -> 335 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 336 | ?headers:Cohttp.Header.t -> 337 | Uri.t -> 338 | (unit, string) result Lwt.t 339 | [@@ocaml.doc 340 | {ocamlswagger| 341 | @param body Pet object that needs to be added to the store|ocamlswagger}] 342 | end 343 | 344 | module Store : sig 345 | module Inventory : sig 346 | val request_path_template : unit -> string 347 | 348 | val get : 349 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 350 | ?headers:Cohttp.Header.t -> 351 | Uri.t -> 352 | (Object.Of_ints32.t, string) result Lwt.t 353 | [@@ocaml.doc 354 | {ocamlswagger|Returns a map of status codes to quantities|ocamlswagger}] 355 | end 356 | 357 | module Order : sig 358 | val request_path_template : unit -> string 359 | 360 | val post : 361 | body:Swagger_petstore.Definitions.Order.t -> 362 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 363 | ?headers:Cohttp.Header.t -> 364 | Uri.t -> 365 | (Swagger_petstore.Definitions.Order.t, string) result Lwt.t 366 | [@@ocaml.doc 367 | {ocamlswagger| 368 | @param body order placed for purchasing the pet|ocamlswagger}] 369 | end 370 | end 371 | 372 | module User : sig 373 | module Create_with_array : sig 374 | val request_path_template : unit -> string 375 | 376 | val post : 377 | body:Swagger_petstore.Definitions.User.t list -> 378 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 379 | ?headers:Cohttp.Header.t -> 380 | Uri.t -> 381 | (unit, string) result Lwt.t 382 | [@@ocaml.doc 383 | {ocamlswagger| 384 | @param body List of user object|ocamlswagger}] 385 | end 386 | 387 | module Create_with_list : sig 388 | val request_path_template : unit -> string 389 | 390 | val post : 391 | body:Swagger_petstore.Definitions.User.t list -> 392 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 393 | ?headers:Cohttp.Header.t -> 394 | Uri.t -> 395 | (unit, string) result Lwt.t 396 | [@@ocaml.doc 397 | {ocamlswagger| 398 | @param body List of user object|ocamlswagger}] 399 | end 400 | 401 | module Login : sig 402 | val request_path_template : unit -> string 403 | 404 | val get : 405 | username:string -> 406 | password:string -> 407 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 408 | ?headers:Cohttp.Header.t -> 409 | Uri.t -> 410 | (string, string) result Lwt.t 411 | [@@ocaml.doc 412 | {ocamlswagger| 413 | @param username The user name for login 414 | @param password The password for login in clear text|ocamlswagger}] 415 | end 416 | 417 | module Logout : sig 418 | val request_path_template : unit -> string 419 | 420 | val get : 421 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 422 | ?headers:Cohttp.Header.t -> 423 | Uri.t -> 424 | (unit, string) result Lwt.t 425 | [@@ocaml.doc {ocamlswagger||ocamlswagger}] 426 | end 427 | 428 | val request_path_template : unit -> string 429 | 430 | val post : 431 | body:Swagger_petstore.Definitions.User.t -> 432 | ?ctx:Cohttp_lwt_unix.Client.ctx -> 433 | ?headers:Cohttp.Header.t -> 434 | Uri.t -> 435 | (unit, string) result Lwt.t 436 | [@@ocaml.doc 437 | {ocamlswagger|This can only be done by the logged in user. 438 | @param body Created user object|ocamlswagger}] 439 | end 440 | end = struct 441 | module Definitions = struct 442 | module Api_response = struct 443 | type t = { 444 | code : (int32 option[@default None]); 445 | type_ : (string option[@default None]); [@key "type"] 446 | message : (string option[@default None]); 447 | } 448 | [@@deriving yojson] 449 | 450 | let make ?message ?type_ ?code () = { code; type_; message } 451 | let code t = t.code 452 | 453 | let set_code code t = 454 | { t with code } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 455 | 456 | let type_ t = t.type_ 457 | 458 | let set_type_ type_ t = 459 | { t with type_ } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 460 | 461 | let message t = t.message 462 | 463 | let set_message message t = 464 | { t with message } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 465 | 466 | module Object = Object.Make (struct 467 | type value = t [@@deriving yojson] 468 | end) 469 | end 470 | 471 | module Category = struct 472 | type t = { 473 | id : (int64 option[@default None]); 474 | name : (string option[@default None]); 475 | } 476 | [@@deriving yojson] 477 | 478 | let make ?name ?id () = { id; name } 479 | let id t = t.id 480 | 481 | let set_id id t = 482 | { t with id } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 483 | 484 | let name t = t.name 485 | 486 | let set_name name t = 487 | { t with name } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 488 | 489 | module Object = Object.Make (struct 490 | type value = t [@@deriving yojson] 491 | end) 492 | end 493 | 494 | module Order = struct 495 | type t = { 496 | id : (int64 option[@default None]); 497 | pet_id : (int64 option[@default None]); [@key "petId"] 498 | quantity : (int32 option[@default None]); 499 | ship_date : (float option[@default None]); [@key "shipDate"] 500 | status : (string option[@default None]); 501 | complete : (bool option[@default None]); 502 | } 503 | [@@deriving yojson] 504 | 505 | let make ?complete ?status ?ship_date ?quantity ?pet_id ?id () = 506 | { id; pet_id; quantity; ship_date; status; complete } 507 | 508 | let id t = t.id 509 | 510 | let set_id id t = 511 | { t with id } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 512 | 513 | let pet_id t = t.pet_id 514 | 515 | let set_pet_id pet_id t = 516 | { t with pet_id } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 517 | 518 | let quantity t = t.quantity 519 | 520 | let set_quantity quantity t = 521 | { t with quantity } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 522 | 523 | let ship_date t = t.ship_date 524 | 525 | let set_ship_date ship_date t = 526 | { t with ship_date } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 527 | 528 | let status t = t.status 529 | 530 | let set_status status t = 531 | { t with status } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 532 | 533 | let complete t = t.complete 534 | 535 | let set_complete complete t = 536 | { t with complete } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 537 | 538 | module Object = Object.Make (struct 539 | type value = t [@@deriving yojson] 540 | end) 541 | end 542 | 543 | module Pet = struct 544 | type t = { 545 | id : (int64 option[@default None]); 546 | category : 547 | (Swagger_petstore.Definitions.Category.t option[@default None]); 548 | name : string; 549 | photo_urls : string list; [@key "photoUrls"] 550 | tags : (Swagger_petstore.Definitions.Tag.t list option[@default None]); 551 | status : (string option[@default None]); 552 | } 553 | [@@deriving yojson] 554 | 555 | let make ?status ?tags ~photo_urls ~name ?category ?id () = 556 | { id; category; name; photo_urls; tags; status } 557 | 558 | let id t = t.id 559 | 560 | let set_id id t = 561 | { t with id } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 562 | 563 | let category t = t.category 564 | 565 | let set_category category t = 566 | { t with category } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 567 | 568 | let name t = t.name 569 | 570 | let set_name name t = 571 | { t with name } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 572 | 573 | let photo_urls t = t.photo_urls 574 | 575 | let set_photo_urls photo_urls t = 576 | { t with photo_urls } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 577 | 578 | let tags t = t.tags 579 | 580 | let set_tags tags t = 581 | { t with tags } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 582 | 583 | let status t = t.status 584 | 585 | let set_status status t = 586 | { t with status } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 587 | 588 | module Object = Object.Make (struct 589 | type value = t [@@deriving yojson] 590 | end) 591 | end 592 | 593 | module Tag = struct 594 | type t = { 595 | id : (int64 option[@default None]); 596 | name : (string option[@default None]); 597 | } 598 | [@@deriving yojson] 599 | 600 | let make ?name ?id () = { id; name } 601 | let id t = t.id 602 | 603 | let set_id id t = 604 | { t with id } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 605 | 606 | let name t = t.name 607 | 608 | let set_name name t = 609 | { t with name } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 610 | 611 | module Object = Object.Make (struct 612 | type value = t [@@deriving yojson] 613 | end) 614 | end 615 | 616 | module User = struct 617 | type t = { 618 | id : (int64 option[@default None]); 619 | username : (string option[@default None]); 620 | first_name : (string option[@default None]); [@key "firstName"] 621 | last_name : (string option[@default None]); [@key "lastName"] 622 | email : (string option[@default None]); 623 | password : (string option[@default None]); 624 | phone : (string option[@default None]); 625 | user_status : (int32 option[@default None]); [@key "userStatus"] 626 | } 627 | [@@deriving yojson] 628 | 629 | let make ?user_status ?phone ?password ?email ?last_name ?first_name 630 | ?username ?id () = 631 | { 632 | id; 633 | username; 634 | first_name; 635 | last_name; 636 | email; 637 | password; 638 | phone; 639 | user_status; 640 | } 641 | 642 | let id t = t.id 643 | 644 | let set_id id t = 645 | { t with id } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 646 | 647 | let username t = t.username 648 | 649 | let set_username username t = 650 | { t with username } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 651 | 652 | let first_name t = t.first_name 653 | 654 | let set_first_name first_name t = 655 | { t with first_name } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 656 | 657 | let last_name t = t.last_name 658 | 659 | let set_last_name last_name t = 660 | { t with last_name } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 661 | 662 | let email t = t.email 663 | 664 | let set_email email t = 665 | { t with email } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 666 | 667 | let password t = t.password 668 | 669 | let set_password password t = 670 | { t with password } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 671 | 672 | let phone t = t.phone 673 | 674 | let set_phone phone t = 675 | { t with phone } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 676 | 677 | let user_status t = t.user_status 678 | 679 | let set_user_status user_status t = 680 | { t with user_status } [@ocaml.warning {ocamlswagger|-23|ocamlswagger}] 681 | 682 | module Object = Object.Make (struct 683 | type value = t [@@deriving yojson] 684 | end) 685 | end 686 | end 687 | 688 | module Pet = struct 689 | module Find_by_status = struct 690 | let request_path_template () = "/pet/findByStatus" 691 | 692 | let get ~status ?ctx ?headers uri = 693 | let query = [ ("status", String.concat "," (Array.to_list status)) ] in 694 | let path = 695 | let path_params = [] in 696 | List.fold_left 697 | (fun path (name, value) -> 698 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 699 | Re.replace_string re ~by:value path) 700 | (request_path_template ()) path_params 701 | in 702 | let full_path = Uri.path uri ^ path in 703 | let uri = Uri.with_path uri full_path in 704 | let uri = 705 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 706 | in 707 | let headers = headers in 708 | let open Lwt.Infix in 709 | Cohttp_lwt_unix.Client.get ?ctx ?headers uri >>= fun (resp, body) -> 710 | let code = 711 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 712 | in 713 | Cohttp_lwt.Body.to_string body >>= fun body -> 714 | ignore body; 715 | Lwt.return 716 | (if code >= 200 && code < 300 then 717 | Ok 718 | (let open Yojson.Safe in 719 | Util.convert_each Swagger_petstore.Definitions.Pet.t_of_yojson 720 | (from_string body)) 721 | else Error (string_of_int code)) 722 | end 723 | 724 | module Find_by_tags = struct 725 | let request_path_template () = "/pet/findByTags" 726 | 727 | let get ~tags ?ctx ?headers uri = 728 | let query = [ ("tags", String.concat "," (Array.to_list tags)) ] in 729 | let path = 730 | let path_params = [] in 731 | List.fold_left 732 | (fun path (name, value) -> 733 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 734 | Re.replace_string re ~by:value path) 735 | (request_path_template ()) path_params 736 | in 737 | let full_path = Uri.path uri ^ path in 738 | let uri = Uri.with_path uri full_path in 739 | let uri = 740 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 741 | in 742 | let headers = headers in 743 | let open Lwt.Infix in 744 | Cohttp_lwt_unix.Client.get ?ctx ?headers uri >>= fun (resp, body) -> 745 | let code = 746 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 747 | in 748 | Cohttp_lwt.Body.to_string body >>= fun body -> 749 | ignore body; 750 | Lwt.return 751 | (if code >= 200 && code < 300 then 752 | Ok 753 | (let open Yojson.Safe in 754 | Util.convert_each Swagger_petstore.Definitions.Pet.t_of_yojson 755 | (from_string body)) 756 | else Error (string_of_int code)) 757 | end 758 | 759 | let request_path_template () = "/pet" 760 | 761 | let put ~body ?ctx ?headers uri = 762 | let query = [] in 763 | let path = 764 | let path_params = [] in 765 | List.fold_left 766 | (fun path (name, value) -> 767 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 768 | Re.replace_string re ~by:value path) 769 | (request_path_template ()) path_params 770 | in 771 | let full_path = Uri.path uri ^ path in 772 | let uri = Uri.with_path uri full_path in 773 | let uri = 774 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 775 | in 776 | let headers = headers in 777 | let open Lwt.Infix in 778 | Cohttp_lwt_unix.Client.put ?ctx ?headers 779 | ?body: 780 | (Some 781 | (Cohttp_lwt.Body.of_string 782 | (Yojson.Safe.to_string 783 | (Swagger_petstore.Definitions.Pet.yojson_of_t body)))) 784 | uri 785 | >>= fun (resp, body) -> 786 | let code = 787 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 788 | in 789 | Cohttp_lwt.Body.to_string body >>= fun body -> 790 | ignore body; 791 | Lwt.return 792 | (if code >= 200 && code < 300 then Ok () 793 | else Error (string_of_int code)) 794 | 795 | let post ~body ?ctx ?headers uri = 796 | let query = [] in 797 | let path = 798 | let path_params = [] in 799 | List.fold_left 800 | (fun path (name, value) -> 801 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 802 | Re.replace_string re ~by:value path) 803 | (request_path_template ()) path_params 804 | in 805 | let full_path = Uri.path uri ^ path in 806 | let uri = Uri.with_path uri full_path in 807 | let uri = 808 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 809 | in 810 | let headers = headers in 811 | let open Lwt.Infix in 812 | Cohttp_lwt_unix.Client.post ?ctx ?headers 813 | ?body: 814 | (Some 815 | (Cohttp_lwt.Body.of_string 816 | (Yojson.Safe.to_string 817 | (Swagger_petstore.Definitions.Pet.yojson_of_t body)))) 818 | uri 819 | >>= fun (resp, body) -> 820 | let code = 821 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 822 | in 823 | Cohttp_lwt.Body.to_string body >>= fun body -> 824 | ignore body; 825 | Lwt.return 826 | (if code >= 200 && code < 300 then Ok () 827 | else Error (string_of_int code)) 828 | end 829 | 830 | module Store = struct 831 | module Inventory = struct 832 | let request_path_template () = "/store/inventory" 833 | 834 | let get ?ctx ?headers uri = 835 | let query = [] in 836 | let path = 837 | let path_params = [] in 838 | List.fold_left 839 | (fun path (name, value) -> 840 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 841 | Re.replace_string re ~by:value path) 842 | (request_path_template ()) path_params 843 | in 844 | let full_path = Uri.path uri ^ path in 845 | let uri = Uri.with_path uri full_path in 846 | let uri = 847 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 848 | in 849 | let headers = headers in 850 | let open Lwt.Infix in 851 | Cohttp_lwt_unix.Client.get ?ctx ?headers uri >>= fun (resp, body) -> 852 | let code = 853 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 854 | in 855 | Cohttp_lwt.Body.to_string body >>= fun body -> 856 | ignore body; 857 | Lwt.return 858 | (if code >= 200 && code < 300 then 859 | Ok (Object.Of_ints32.t_of_yojson (Yojson.Safe.from_string body)) 860 | else Error (string_of_int code)) 861 | end 862 | 863 | module Order = struct 864 | let request_path_template () = "/store/order" 865 | 866 | let post ~body ?ctx ?headers uri = 867 | let query = [] in 868 | let path = 869 | let path_params = [] in 870 | List.fold_left 871 | (fun path (name, value) -> 872 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 873 | Re.replace_string re ~by:value path) 874 | (request_path_template ()) path_params 875 | in 876 | let full_path = Uri.path uri ^ path in 877 | let uri = Uri.with_path uri full_path in 878 | let uri = 879 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 880 | in 881 | let headers = headers in 882 | let open Lwt.Infix in 883 | Cohttp_lwt_unix.Client.post ?ctx ?headers 884 | ?body: 885 | (Some 886 | (Cohttp_lwt.Body.of_string 887 | (Yojson.Safe.to_string 888 | (Swagger_petstore.Definitions.Order.yojson_of_t body)))) 889 | uri 890 | >>= fun (resp, body) -> 891 | let code = 892 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 893 | in 894 | Cohttp_lwt.Body.to_string body >>= fun body -> 895 | let json = Yojson.Safe.from_string body in 896 | Lwt.return 897 | (if code >= 200 && code < 300 then 898 | Ok (Swagger_petstore.Definitions.Order.t_of_yojson json) 899 | else Error body) 900 | end 901 | end 902 | 903 | module User = struct 904 | module Create_with_array = struct 905 | let request_path_template () = "/user/createWithArray" 906 | 907 | let post ~body ?ctx ?headers uri = 908 | let query = [] in 909 | let path = 910 | let path_params = [] in 911 | List.fold_left 912 | (fun path (name, value) -> 913 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 914 | Re.replace_string re ~by:value path) 915 | (request_path_template ()) path_params 916 | in 917 | let full_path = Uri.path uri ^ path in 918 | let uri = Uri.with_path uri full_path in 919 | let uri = 920 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 921 | in 922 | let headers = headers in 923 | let open Lwt.Infix in 924 | Cohttp_lwt_unix.Client.post ?ctx ?headers 925 | ?body: 926 | (Some 927 | (Cohttp_lwt.Body.of_string 928 | (Yojson.Safe.to_string 929 | (`List 930 | (List.map Swagger_petstore.Definitions.User.yojson_of_t 931 | body))))) 932 | uri 933 | >>= fun (resp, body) -> 934 | let code = 935 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 936 | in 937 | Cohttp_lwt.Body.to_string body >>= fun body -> 938 | ignore body; 939 | Lwt.return 940 | (if code >= 200 && code < 300 then Ok () 941 | else Error (string_of_int code)) 942 | end 943 | 944 | module Create_with_list = struct 945 | let request_path_template () = "/user/createWithList" 946 | 947 | let post ~body ?ctx ?headers uri = 948 | let query = [] in 949 | let path = 950 | let path_params = [] in 951 | List.fold_left 952 | (fun path (name, value) -> 953 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 954 | Re.replace_string re ~by:value path) 955 | (request_path_template ()) path_params 956 | in 957 | let full_path = Uri.path uri ^ path in 958 | let uri = Uri.with_path uri full_path in 959 | let uri = 960 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 961 | in 962 | let headers = headers in 963 | let open Lwt.Infix in 964 | Cohttp_lwt_unix.Client.post ?ctx ?headers 965 | ?body: 966 | (Some 967 | (Cohttp_lwt.Body.of_string 968 | (Yojson.Safe.to_string 969 | (`List 970 | (List.map Swagger_petstore.Definitions.User.yojson_of_t 971 | body))))) 972 | uri 973 | >>= fun (resp, body) -> 974 | let code = 975 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 976 | in 977 | Cohttp_lwt.Body.to_string body >>= fun body -> 978 | ignore body; 979 | Lwt.return 980 | (if code >= 200 && code < 300 then Ok () 981 | else Error (string_of_int code)) 982 | end 983 | 984 | module Login = struct 985 | let request_path_template () = "/user/login" 986 | 987 | let get ~username ~password ?ctx ?headers uri = 988 | let query = [ ("username", username); ("password", password) ] in 989 | let path = 990 | let path_params = [] in 991 | List.fold_left 992 | (fun path (name, value) -> 993 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 994 | Re.replace_string re ~by:value path) 995 | (request_path_template ()) path_params 996 | in 997 | let full_path = Uri.path uri ^ path in 998 | let uri = Uri.with_path uri full_path in 999 | let uri = 1000 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 1001 | in 1002 | let headers = headers in 1003 | let open Lwt.Infix in 1004 | Cohttp_lwt_unix.Client.get ?ctx ?headers uri >>= fun (resp, body) -> 1005 | let code = 1006 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 1007 | in 1008 | Cohttp_lwt.Body.to_string body >>= fun body -> 1009 | ignore body; 1010 | Lwt.return 1011 | (if code >= 200 && code < 300 then Ok body 1012 | else Error (string_of_int code)) 1013 | end 1014 | 1015 | module Logout = struct 1016 | let request_path_template () = "/user/logout" 1017 | 1018 | let get ?ctx ?headers uri = 1019 | let query = [] in 1020 | let path = 1021 | let path_params = [] in 1022 | List.fold_left 1023 | (fun path (name, value) -> 1024 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 1025 | Re.replace_string re ~by:value path) 1026 | (request_path_template ()) path_params 1027 | in 1028 | let full_path = Uri.path uri ^ path in 1029 | let uri = Uri.with_path uri full_path in 1030 | let uri = 1031 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 1032 | in 1033 | let headers = headers in 1034 | let open Lwt.Infix in 1035 | Cohttp_lwt_unix.Client.get ?ctx ?headers uri >>= fun (resp, body) -> 1036 | let code = 1037 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 1038 | in 1039 | Cohttp_lwt.Body.to_string body >>= fun body -> 1040 | ignore body; 1041 | Lwt.return 1042 | (if code >= 200 && code < 300 then Ok () 1043 | else Error (string_of_int code)) 1044 | end 1045 | 1046 | let request_path_template () = "/user" 1047 | 1048 | let post ~body ?ctx ?headers uri = 1049 | let query = [] in 1050 | let path = 1051 | let path_params = [] in 1052 | List.fold_left 1053 | (fun path (name, value) -> 1054 | let re = Re.Pcre.regexp (Printf.sprintf "\\{%s\\}" name) in 1055 | Re.replace_string re ~by:value path) 1056 | (request_path_template ()) path_params 1057 | in 1058 | let full_path = Uri.path uri ^ path in 1059 | let uri = Uri.with_path uri full_path in 1060 | let uri = 1061 | Uri.with_query' uri (List.filter (fun (_k, v) -> v <> "") query) 1062 | in 1063 | let headers = headers in 1064 | let open Lwt.Infix in 1065 | Cohttp_lwt_unix.Client.post ?ctx ?headers 1066 | ?body: 1067 | (Some 1068 | (Cohttp_lwt.Body.of_string 1069 | (Yojson.Safe.to_string 1070 | (Swagger_petstore.Definitions.User.yojson_of_t body)))) 1071 | uri 1072 | >>= fun (resp, body) -> 1073 | let code = 1074 | resp |> Cohttp_lwt_unix.Response.status |> Cohttp.Code.code_of_status 1075 | in 1076 | Cohttp_lwt.Body.to_string body >>= fun body -> 1077 | ignore body; 1078 | Lwt.return 1079 | (if code >= 200 && code < 300 then Ok () 1080 | else Error (string_of_int code)) 1081 | end 1082 | end 1083 | -------------------------------------------------------------------------------- /test/petstore/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", 5 | "version": "1.0.6", 6 | "title": "Swagger Petstore", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "email": "apiteam@swagger.io" 10 | }, 11 | "license": { 12 | "name": "Apache 2.0", 13 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 14 | } 15 | }, 16 | "host": "petstore.swagger.io", 17 | "basePath": "/v2", 18 | "tags": [ 19 | { 20 | "name": "pet", 21 | "description": "Everything about your Pets", 22 | "externalDocs": { 23 | "description": "Find out more", 24 | "url": "http://swagger.io" 25 | } 26 | }, 27 | { 28 | "name": "store", 29 | "description": "Access to Petstore orders" 30 | }, 31 | { 32 | "name": "user", 33 | "description": "Operations about user", 34 | "externalDocs": { 35 | "description": "Find out more about our store", 36 | "url": "http://swagger.io" 37 | } 38 | } 39 | ], 40 | "schemes": [ 41 | "https", 42 | "http" 43 | ], 44 | "paths": { 45 | "/pet/{petId}": { 46 | "get": { 47 | "tags": [ 48 | "pet" 49 | ], 50 | "summary": "Find pet by ID", 51 | "description": "Returns a single pet", 52 | "operationId": "getPetById", 53 | "produces": [ 54 | "application/json", 55 | "application/xml" 56 | ], 57 | "parameters": [ 58 | { 59 | "name": "petId", 60 | "in": "path", 61 | "description": "ID of pet to return", 62 | "required": true, 63 | "type": "integer", 64 | "format": "int64" 65 | } 66 | ], 67 | "responses": { 68 | "200": { 69 | "description": "successful operation", 70 | "schema": { 71 | "$ref": "#/definitions/Pet" 72 | } 73 | }, 74 | "400": { 75 | "description": "Invalid ID supplied" 76 | }, 77 | "404": { 78 | "description": "Pet not found" 79 | } 80 | }, 81 | "security": [ 82 | { 83 | "api_key": [] 84 | } 85 | ] 86 | }, 87 | "post": { 88 | "tags": [ 89 | "pet" 90 | ], 91 | "summary": "Updates a pet in the store with form data", 92 | "description": "", 93 | "operationId": "updatePetWithForm", 94 | "consumes": [ 95 | "application/x-www-form-urlencoded" 96 | ], 97 | "produces": [ 98 | "application/json", 99 | "application/xml" 100 | ], 101 | "parameters": [ 102 | { 103 | "name": "petId", 104 | "in": "path", 105 | "description": "ID of pet that needs to be updated", 106 | "required": true, 107 | "type": "integer", 108 | "format": "int64" 109 | }, 110 | { 111 | "name": "name", 112 | "in": "formData", 113 | "description": "Updated name of the pet", 114 | "required": false, 115 | "type": "string" 116 | }, 117 | { 118 | "name": "status", 119 | "in": "formData", 120 | "description": "Updated status of the pet", 121 | "required": false, 122 | "type": "string" 123 | } 124 | ], 125 | "responses": { 126 | "405": { 127 | "description": "Invalid input" 128 | } 129 | }, 130 | "security": [ 131 | { 132 | "petstore_auth": [ 133 | "write:pets", 134 | "read:pets" 135 | ] 136 | } 137 | ] 138 | }, 139 | "delete": { 140 | "tags": [ 141 | "pet" 142 | ], 143 | "summary": "Deletes a pet", 144 | "description": "", 145 | "operationId": "deletePet", 146 | "produces": [ 147 | "application/json", 148 | "application/xml" 149 | ], 150 | "parameters": [ 151 | { 152 | "name": "api_key", 153 | "in": "header", 154 | "required": false, 155 | "type": "string" 156 | }, 157 | { 158 | "name": "petId", 159 | "in": "path", 160 | "description": "Pet id to delete", 161 | "required": true, 162 | "type": "integer", 163 | "format": "int64" 164 | } 165 | ], 166 | "responses": { 167 | "400": { 168 | "description": "Invalid ID supplied" 169 | }, 170 | "404": { 171 | "description": "Pet not found" 172 | } 173 | }, 174 | "security": [ 175 | { 176 | "petstore_auth": [ 177 | "write:pets", 178 | "read:pets" 179 | ] 180 | } 181 | ] 182 | } 183 | }, 184 | "/pet/{petId}/uploadImage": { 185 | "post": { 186 | "tags": [ 187 | "pet" 188 | ], 189 | "summary": "uploads an image", 190 | "description": "", 191 | "operationId": "uploadFile", 192 | "consumes": [ 193 | "multipart/form-data" 194 | ], 195 | "produces": [ 196 | "application/json" 197 | ], 198 | "parameters": [ 199 | { 200 | "name": "petId", 201 | "in": "path", 202 | "description": "ID of pet to update", 203 | "required": true, 204 | "type": "integer", 205 | "format": "int64" 206 | }, 207 | { 208 | "name": "additionalMetadata", 209 | "in": "formData", 210 | "description": "Additional data to pass to server", 211 | "required": false, 212 | "type": "string" 213 | }, 214 | { 215 | "name": "file", 216 | "in": "formData", 217 | "description": "file to upload", 218 | "required": false, 219 | "type": "file" 220 | } 221 | ], 222 | "responses": { 223 | "200": { 224 | "description": "successful operation", 225 | "schema": { 226 | "$ref": "#/definitions/ApiResponse" 227 | } 228 | } 229 | }, 230 | "security": [ 231 | { 232 | "petstore_auth": [ 233 | "write:pets", 234 | "read:pets" 235 | ] 236 | } 237 | ] 238 | } 239 | }, 240 | "/pet": { 241 | "post": { 242 | "tags": [ 243 | "pet" 244 | ], 245 | "summary": "Add a new pet to the store", 246 | "description": "", 247 | "operationId": "addPet", 248 | "consumes": [ 249 | "application/json", 250 | "application/xml" 251 | ], 252 | "produces": [ 253 | "application/json", 254 | "application/xml" 255 | ], 256 | "parameters": [ 257 | { 258 | "in": "body", 259 | "name": "body", 260 | "description": "Pet object that needs to be added to the store", 261 | "required": true, 262 | "schema": { 263 | "$ref": "#/definitions/Pet" 264 | } 265 | } 266 | ], 267 | "responses": { 268 | "405": { 269 | "description": "Invalid input" 270 | } 271 | }, 272 | "security": [ 273 | { 274 | "petstore_auth": [ 275 | "write:pets", 276 | "read:pets" 277 | ] 278 | } 279 | ] 280 | }, 281 | "put": { 282 | "tags": [ 283 | "pet" 284 | ], 285 | "summary": "Update an existing pet", 286 | "description": "", 287 | "operationId": "updatePet", 288 | "consumes": [ 289 | "application/json", 290 | "application/xml" 291 | ], 292 | "produces": [ 293 | "application/json", 294 | "application/xml" 295 | ], 296 | "parameters": [ 297 | { 298 | "in": "body", 299 | "name": "body", 300 | "description": "Pet object that needs to be added to the store", 301 | "required": true, 302 | "schema": { 303 | "$ref": "#/definitions/Pet" 304 | } 305 | } 306 | ], 307 | "responses": { 308 | "400": { 309 | "description": "Invalid ID supplied" 310 | }, 311 | "404": { 312 | "description": "Pet not found" 313 | }, 314 | "405": { 315 | "description": "Validation exception" 316 | } 317 | }, 318 | "security": [ 319 | { 320 | "petstore_auth": [ 321 | "write:pets", 322 | "read:pets" 323 | ] 324 | } 325 | ] 326 | } 327 | }, 328 | "/pet/findByStatus": { 329 | "get": { 330 | "tags": [ 331 | "pet" 332 | ], 333 | "summary": "Finds Pets by status", 334 | "description": "Multiple status values can be provided with comma separated strings", 335 | "operationId": "findPetsByStatus", 336 | "produces": [ 337 | "application/json", 338 | "application/xml" 339 | ], 340 | "parameters": [ 341 | { 342 | "name": "status", 343 | "in": "query", 344 | "description": "Status values that need to be considered for filter", 345 | "required": true, 346 | "type": "array", 347 | "items": { 348 | "type": "string", 349 | "enum": [ 350 | "available", 351 | "pending", 352 | "sold" 353 | ], 354 | "default": "available" 355 | }, 356 | "collectionFormat": "multi" 357 | } 358 | ], 359 | "responses": { 360 | "200": { 361 | "description": "successful operation", 362 | "schema": { 363 | "type": "array", 364 | "items": { 365 | "$ref": "#/definitions/Pet" 366 | } 367 | } 368 | }, 369 | "400": { 370 | "description": "Invalid status value" 371 | } 372 | }, 373 | "security": [ 374 | { 375 | "petstore_auth": [ 376 | "write:pets", 377 | "read:pets" 378 | ] 379 | } 380 | ] 381 | } 382 | }, 383 | "/pet/findByTags": { 384 | "get": { 385 | "tags": [ 386 | "pet" 387 | ], 388 | "summary": "Finds Pets by tags", 389 | "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 390 | "operationId": "findPetsByTags", 391 | "produces": [ 392 | "application/json", 393 | "application/xml" 394 | ], 395 | "parameters": [ 396 | { 397 | "name": "tags", 398 | "in": "query", 399 | "description": "Tags to filter by", 400 | "required": true, 401 | "type": "array", 402 | "items": { 403 | "type": "string" 404 | }, 405 | "collectionFormat": "multi" 406 | } 407 | ], 408 | "responses": { 409 | "200": { 410 | "description": "successful operation", 411 | "schema": { 412 | "type": "array", 413 | "items": { 414 | "$ref": "#/definitions/Pet" 415 | } 416 | } 417 | }, 418 | "400": { 419 | "description": "Invalid tag value" 420 | } 421 | }, 422 | "security": [ 423 | { 424 | "petstore_auth": [ 425 | "write:pets", 426 | "read:pets" 427 | ] 428 | } 429 | ], 430 | "deprecated": true 431 | } 432 | }, 433 | "/store/inventory": { 434 | "get": { 435 | "tags": [ 436 | "store" 437 | ], 438 | "summary": "Returns pet inventories by status", 439 | "description": "Returns a map of status codes to quantities", 440 | "operationId": "getInventory", 441 | "produces": [ 442 | "application/json" 443 | ], 444 | "parameters": [], 445 | "responses": { 446 | "200": { 447 | "description": "successful operation", 448 | "schema": { 449 | "type": "object", 450 | "additionalProperties": { 451 | "type": "integer", 452 | "format": "int32" 453 | } 454 | } 455 | } 456 | }, 457 | "security": [ 458 | { 459 | "api_key": [] 460 | } 461 | ] 462 | } 463 | }, 464 | "/store/order/{orderId}": { 465 | "get": { 466 | "tags": [ 467 | "store" 468 | ], 469 | "summary": "Find purchase order by ID", 470 | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", 471 | "operationId": "getOrderById", 472 | "produces": [ 473 | "application/json", 474 | "application/xml" 475 | ], 476 | "parameters": [ 477 | { 478 | "name": "orderId", 479 | "in": "path", 480 | "description": "ID of pet that needs to be fetched", 481 | "required": true, 482 | "type": "integer", 483 | "maximum": 10, 484 | "minimum": 1, 485 | "format": "int64" 486 | } 487 | ], 488 | "responses": { 489 | "200": { 490 | "description": "successful operation", 491 | "schema": { 492 | "$ref": "#/definitions/Order" 493 | } 494 | }, 495 | "400": { 496 | "description": "Invalid ID supplied" 497 | }, 498 | "404": { 499 | "description": "Order not found" 500 | } 501 | } 502 | }, 503 | "delete": { 504 | "tags": [ 505 | "store" 506 | ], 507 | "summary": "Delete purchase order by ID", 508 | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", 509 | "operationId": "deleteOrder", 510 | "produces": [ 511 | "application/json", 512 | "application/xml" 513 | ], 514 | "parameters": [ 515 | { 516 | "name": "orderId", 517 | "in": "path", 518 | "description": "ID of the order that needs to be deleted", 519 | "required": true, 520 | "type": "integer", 521 | "minimum": 1, 522 | "format": "int64" 523 | } 524 | ], 525 | "responses": { 526 | "400": { 527 | "description": "Invalid ID supplied" 528 | }, 529 | "404": { 530 | "description": "Order not found" 531 | } 532 | } 533 | } 534 | }, 535 | "/store/order": { 536 | "post": { 537 | "tags": [ 538 | "store" 539 | ], 540 | "summary": "Place an order for a pet", 541 | "description": "", 542 | "operationId": "placeOrder", 543 | "consumes": [ 544 | "application/json" 545 | ], 546 | "produces": [ 547 | "application/json", 548 | "application/xml" 549 | ], 550 | "parameters": [ 551 | { 552 | "in": "body", 553 | "name": "body", 554 | "description": "order placed for purchasing the pet", 555 | "required": true, 556 | "schema": { 557 | "$ref": "#/definitions/Order" 558 | } 559 | } 560 | ], 561 | "responses": { 562 | "200": { 563 | "description": "successful operation", 564 | "schema": { 565 | "$ref": "#/definitions/Order" 566 | } 567 | }, 568 | "400": { 569 | "description": "Invalid Order" 570 | } 571 | } 572 | } 573 | }, 574 | "/user/{username}": { 575 | "get": { 576 | "tags": [ 577 | "user" 578 | ], 579 | "summary": "Get user by user name", 580 | "description": "", 581 | "operationId": "getUserByName", 582 | "produces": [ 583 | "application/json", 584 | "application/xml" 585 | ], 586 | "parameters": [ 587 | { 588 | "name": "username", 589 | "in": "path", 590 | "description": "The name that needs to be fetched. Use user1 for testing. ", 591 | "required": true, 592 | "type": "string" 593 | } 594 | ], 595 | "responses": { 596 | "200": { 597 | "description": "successful operation", 598 | "schema": { 599 | "$ref": "#/definitions/User" 600 | } 601 | }, 602 | "400": { 603 | "description": "Invalid username supplied" 604 | }, 605 | "404": { 606 | "description": "User not found" 607 | } 608 | } 609 | }, 610 | "put": { 611 | "tags": [ 612 | "user" 613 | ], 614 | "summary": "Updated user", 615 | "description": "This can only be done by the logged in user.", 616 | "operationId": "updateUser", 617 | "consumes": [ 618 | "application/json" 619 | ], 620 | "produces": [ 621 | "application/json", 622 | "application/xml" 623 | ], 624 | "parameters": [ 625 | { 626 | "name": "username", 627 | "in": "path", 628 | "description": "name that need to be updated", 629 | "required": true, 630 | "type": "string" 631 | }, 632 | { 633 | "in": "body", 634 | "name": "body", 635 | "description": "Updated user object", 636 | "required": true, 637 | "schema": { 638 | "$ref": "#/definitions/User" 639 | } 640 | } 641 | ], 642 | "responses": { 643 | "400": { 644 | "description": "Invalid user supplied" 645 | }, 646 | "404": { 647 | "description": "User not found" 648 | } 649 | } 650 | }, 651 | "delete": { 652 | "tags": [ 653 | "user" 654 | ], 655 | "summary": "Delete user", 656 | "description": "This can only be done by the logged in user.", 657 | "operationId": "deleteUser", 658 | "produces": [ 659 | "application/json", 660 | "application/xml" 661 | ], 662 | "parameters": [ 663 | { 664 | "name": "username", 665 | "in": "path", 666 | "description": "The name that needs to be deleted", 667 | "required": true, 668 | "type": "string" 669 | } 670 | ], 671 | "responses": { 672 | "400": { 673 | "description": "Invalid username supplied" 674 | }, 675 | "404": { 676 | "description": "User not found" 677 | } 678 | } 679 | } 680 | }, 681 | "/user": { 682 | "post": { 683 | "tags": [ 684 | "user" 685 | ], 686 | "summary": "Create user", 687 | "description": "This can only be done by the logged in user.", 688 | "operationId": "createUser", 689 | "consumes": [ 690 | "application/json" 691 | ], 692 | "produces": [ 693 | "application/json", 694 | "application/xml" 695 | ], 696 | "parameters": [ 697 | { 698 | "in": "body", 699 | "name": "body", 700 | "description": "Created user object", 701 | "required": true, 702 | "schema": { 703 | "$ref": "#/definitions/User" 704 | } 705 | } 706 | ], 707 | "responses": { 708 | "default": { 709 | "description": "successful operation" 710 | } 711 | } 712 | } 713 | }, 714 | "/user/createWithArray": { 715 | "post": { 716 | "tags": [ 717 | "user" 718 | ], 719 | "summary": "Creates list of users with given input array", 720 | "description": "", 721 | "operationId": "createUsersWithArrayInput", 722 | "consumes": [ 723 | "application/json" 724 | ], 725 | "produces": [ 726 | "application/json", 727 | "application/xml" 728 | ], 729 | "parameters": [ 730 | { 731 | "in": "body", 732 | "name": "body", 733 | "description": "List of user object", 734 | "required": true, 735 | "schema": { 736 | "type": "array", 737 | "items": { 738 | "$ref": "#/definitions/User" 739 | } 740 | } 741 | } 742 | ], 743 | "responses": { 744 | "default": { 745 | "description": "successful operation" 746 | } 747 | } 748 | } 749 | }, 750 | "/user/createWithList": { 751 | "post": { 752 | "tags": [ 753 | "user" 754 | ], 755 | "summary": "Creates list of users with given input array", 756 | "description": "", 757 | "operationId": "createUsersWithListInput", 758 | "consumes": [ 759 | "application/json" 760 | ], 761 | "produces": [ 762 | "application/json", 763 | "application/xml" 764 | ], 765 | "parameters": [ 766 | { 767 | "in": "body", 768 | "name": "body", 769 | "description": "List of user object", 770 | "required": true, 771 | "schema": { 772 | "type": "array", 773 | "items": { 774 | "$ref": "#/definitions/User" 775 | } 776 | } 777 | } 778 | ], 779 | "responses": { 780 | "default": { 781 | "description": "successful operation" 782 | } 783 | } 784 | } 785 | }, 786 | "/user/login": { 787 | "get": { 788 | "tags": [ 789 | "user" 790 | ], 791 | "summary": "Logs user into the system", 792 | "description": "", 793 | "operationId": "loginUser", 794 | "produces": [ 795 | "application/json", 796 | "application/xml" 797 | ], 798 | "parameters": [ 799 | { 800 | "name": "username", 801 | "in": "query", 802 | "description": "The user name for login", 803 | "required": true, 804 | "type": "string" 805 | }, 806 | { 807 | "name": "password", 808 | "in": "query", 809 | "description": "The password for login in clear text", 810 | "required": true, 811 | "type": "string" 812 | } 813 | ], 814 | "responses": { 815 | "200": { 816 | "description": "successful operation", 817 | "headers": { 818 | "X-Expires-After": { 819 | "type": "string", 820 | "format": "date-time", 821 | "description": "date in UTC when token expires" 822 | }, 823 | "X-Rate-Limit": { 824 | "type": "integer", 825 | "format": "int32", 826 | "description": "calls per hour allowed by the user" 827 | } 828 | }, 829 | "schema": { 830 | "type": "string" 831 | } 832 | }, 833 | "400": { 834 | "description": "Invalid username/password supplied" 835 | } 836 | } 837 | } 838 | }, 839 | "/user/logout": { 840 | "get": { 841 | "tags": [ 842 | "user" 843 | ], 844 | "summary": "Logs out current logged in user session", 845 | "description": "", 846 | "operationId": "logoutUser", 847 | "produces": [ 848 | "application/json", 849 | "application/xml" 850 | ], 851 | "parameters": [], 852 | "responses": { 853 | "default": { 854 | "description": "successful operation" 855 | } 856 | } 857 | } 858 | } 859 | }, 860 | "securityDefinitions": { 861 | "api_key": { 862 | "type": "apiKey", 863 | "name": "api_key", 864 | "in": "header" 865 | }, 866 | "petstore_auth": { 867 | "type": "oauth2", 868 | "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", 869 | "flow": "implicit", 870 | "scopes": { 871 | "read:pets": "read your pets", 872 | "write:pets": "modify pets in your account" 873 | } 874 | } 875 | }, 876 | "definitions": { 877 | "Category": { 878 | "type": "object", 879 | "properties": { 880 | "id": { 881 | "type": "integer", 882 | "format": "int64" 883 | }, 884 | "name": { 885 | "type": "string" 886 | } 887 | }, 888 | "xml": { 889 | "name": "Category" 890 | } 891 | }, 892 | "Pet": { 893 | "type": "object", 894 | "required": [ 895 | "name", 896 | "photoUrls" 897 | ], 898 | "properties": { 899 | "id": { 900 | "type": "integer", 901 | "format": "int64" 902 | }, 903 | "category": { 904 | "$ref": "#/definitions/Category" 905 | }, 906 | "name": { 907 | "type": "string", 908 | "example": "doggie" 909 | }, 910 | "photoUrls": { 911 | "type": "array", 912 | "xml": { 913 | "wrapped": true 914 | }, 915 | "items": { 916 | "type": "string", 917 | "xml": { 918 | "name": "photoUrl" 919 | } 920 | } 921 | }, 922 | "tags": { 923 | "type": "array", 924 | "xml": { 925 | "wrapped": true 926 | }, 927 | "items": { 928 | "xml": { 929 | "name": "tag" 930 | }, 931 | "$ref": "#/definitions/Tag" 932 | } 933 | }, 934 | "status": { 935 | "type": "string", 936 | "description": "pet status in the store", 937 | "enum": [ 938 | "available", 939 | "pending", 940 | "sold" 941 | ] 942 | } 943 | }, 944 | "xml": { 945 | "name": "Pet" 946 | } 947 | }, 948 | "Tag": { 949 | "type": "object", 950 | "properties": { 951 | "id": { 952 | "type": "integer", 953 | "format": "int64" 954 | }, 955 | "name": { 956 | "type": "string" 957 | } 958 | }, 959 | "xml": { 960 | "name": "Tag" 961 | } 962 | }, 963 | "ApiResponse": { 964 | "type": "object", 965 | "properties": { 966 | "code": { 967 | "type": "integer", 968 | "format": "int32" 969 | }, 970 | "type": { 971 | "type": "string" 972 | }, 973 | "message": { 974 | "type": "string" 975 | } 976 | } 977 | }, 978 | "Order": { 979 | "type": "object", 980 | "properties": { 981 | "id": { 982 | "type": "integer", 983 | "format": "int64" 984 | }, 985 | "petId": { 986 | "type": "integer", 987 | "format": "int64" 988 | }, 989 | "quantity": { 990 | "type": "integer", 991 | "format": "int32" 992 | }, 993 | "shipDate": { 994 | "type": "string", 995 | "format": "date-time" 996 | }, 997 | "status": { 998 | "type": "string", 999 | "description": "Order Status", 1000 | "enum": [ 1001 | "placed", 1002 | "approved", 1003 | "delivered" 1004 | ] 1005 | }, 1006 | "complete": { 1007 | "type": "boolean" 1008 | } 1009 | }, 1010 | "xml": { 1011 | "name": "Order" 1012 | } 1013 | }, 1014 | "User": { 1015 | "type": "object", 1016 | "properties": { 1017 | "id": { 1018 | "type": "integer", 1019 | "format": "int64" 1020 | }, 1021 | "username": { 1022 | "type": "string" 1023 | }, 1024 | "firstName": { 1025 | "type": "string" 1026 | }, 1027 | "lastName": { 1028 | "type": "string" 1029 | }, 1030 | "email": { 1031 | "type": "string" 1032 | }, 1033 | "password": { 1034 | "type": "string" 1035 | }, 1036 | "phone": { 1037 | "type": "string" 1038 | }, 1039 | "userStatus": { 1040 | "type": "integer", 1041 | "format": "int32", 1042 | "description": "User Status" 1043 | } 1044 | }, 1045 | "xml": { 1046 | "name": "User" 1047 | } 1048 | } 1049 | }, 1050 | "externalDocs": { 1051 | "description": "Find out more about Swagger", 1052 | "url": "http://swagger.io" 1053 | } 1054 | } 1055 | -------------------------------------------------------------------------------- /test/petstore/test.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | --------------------------------------------------------------------------------