OaMlPp is an OCaml source code printer of bidirectional encoders for OpenAPI.

OpenAPI 3.0 Standards Compliance

The OaDocument has its own conformance with OpenAPI 3.x.

Further deviations specific to OCaml code generation are documented in this section.

Schema Composition

The allOf ("(AND) Must be valid against all of the subschemas") OpenAPI schema composition is mapped to an OCaml record type (aka. product type). allOf is not supported as a parameter or within a request body.

The anyOf ("(OR) Must be valid against any of the subschemas") OpenAPI schema composition is mapped to an OCaml algebraic data type (aka. sum type). When reading a JSON object in a REST server, the variant that "best" matches the JSON object is chosen to repesent the incoming JSON object. The "best" is a simple heuristic which counts the number of fields in the JSON object that are present in the variant.

The oneOf ("(XOR) Must be valid against exactly one of the subschemas") OpenAPI schema composition is also mapped to an OCaml algebraic data type (aka. sum type) with the same "best" heuristic.

The not ("(NOT) Must not be valid against the given schema") is not supported and an exception will be raised.

Parameters

If a parameter has a schema, that parameter is supported when constructing URLs for a REST client and when parsing URLs in a REST server. However, if a parameter has a content but no schema that parameter is not yet supported.

Request Bodies

Only "application/json" and "application/x-www-form-urlencoded" are supported.

For "application/x-www-form-urlencoded" the Encoding objects are ignored. Instead, all fields are assumed to be form style encoded with array having explode on. However objects are deepObject style encoded with explode on.

Meta-Data Annotations

  • The default annotation is ignored by DkStdRestApis.
  • The deprecated annotation is ignored.

Additional Fields

If additional fields are enabled in an object (by default they are enabled as type any), an extra "additional" field is added to the OCaml record.

If there is already a field "additional", then the field "additional2" is added (etc.)

The type of this additional field is Ezjsonm.value option.

Responses

Responses Objects

The responses will give you a tagged pair `CH_CODE (content, headers) where CODE is named after the HTTP status code (or range of HTTP status codes), and both content and headers will be typed specifically for the CODE. However, content will have a simplistic binary blob type string if the response does not have a "application/json" content type declared in the OpenAPI spec.

The CH_ prefix was chosen as a mnemonic to remember (content, headers).

OpenAPI 3 allows three classes of responses:

  1. A response type for an explicit HTTP status code (ex. HTTP 200).
  2. A response type for a range of a hundred HTTP status codes (ex. HTTP 200-299).
  3. A default response type when an HTTP status code is not enumerated.

All three response classes are optional.

OaMlPp will generate a response object for each response class:

  1. Explicit HTTP status codes are based on the polymorphic variants in Http.Status. For example, HTTP 200 will be returned to you as a `CH_OK (content, headers) and HTTP 404 will be `CH_Not_found (content, headers).
  2. Ranged HTTP status codes have the form `CH_<n>XX. For example, the range HTTP 200 - HTTP 299 will be `CH_Http2XX (statuscode, content, headers).
  3. When the OpenAPI specification has a default response type, the form `CH_Default (statuscode, content, headers) will be returned. However, when there is no OpenAPI specified default, the form `CH_Catch_all (statuscode, body, headers) will be returned where statuscode is the integer status code and body is the response body.

Response Headers

The headers will be typed. Take for example the OpenAPI Response object:

  {
    "description": "A simple string response",
    "content": {
      "text/plain": {
        "schema": {
          "type": "string"
        }
      }
    },
    "headers": {
      "X-Rate-Limit-Limit": {
        "description": "The number of allowed requests in the current period",
        "schema": {
          "type": "integer"
        }
      },
      "X-Rate-Limit-Remaining": {
        "description": "The number of remaining requests in the current period",
        "schema": {
          "type": "integer"
        }
      },
      "X-Rate-Limit-Reset": {
        "description": "The number of seconds left in the current period",
        "schema": {
          "type": "integer"
        }
      }
    }
  }

The headers would be returned as a list containing items:

  • `X_Rate_Limit_Limit (number_allowed_requests)
  • `X_Rate_Limit_Remaining (number_remaining_requests)
  • `X_Rate_Limit_Reset (number_seconds_left)
  • `Raw (someheader,somevalue)

That list might look like:

[ [ `X_Rate_Limit_Limit 1000; `X_Rate_Limit_Remaining 99; `X_Rate_Limit_Reset 300; `Raw ("Set-Cookie", "session=12345"); `Raw ("Expires", "12345678") ] ]

OCaml Impositions and Limitations

Encoder values are accessed through functions (ex. let encoder = Stripe.account () in ...) because OpenAPI allows recursive schemas. Specifically, the let rec OCaml construct requires defining functions rather than values.

Since records are nomimally typed in OCaml, you'll see anonymous OpenAPI schema translated into OCaml types with synthetic names like t_09b61c0775 based on their JSON path in the OpenAPI schema. We could in the future based the names on the schema contents (a weak form of structural typing) but the synthetic names would still exist (just less of them).

There is no ability to support the not OpenAPI composition operator in the OCaml type system (although a runtime check could be possible but currently unimplemented).

Sourcetype t
Sourceval create : ?embed_trace:bool -> ?standard:(module OaDocument.STANDARD) -> ?vendor:(module OaDocument.VENDOR) -> ?include_odoc:bool -> doc_name:string -> file_arity:[ `OneFile | `ManyFiles of string ] -> OaDocument.t -> t
Sourceval require_separated_modules : t -> bool

require_separated_modules t is true if creating a single global module would create problems with the OCaml compiler.

For example, extensible variants behave differently (and more scalably) when their definition is in a seperate module from their accessing modules.

Confer: ocamlopt.opt 5.2.0 fails to compile very large file but ocamlc compiles fine.

Pretty Printers

All unbounded-item pretty-printers are flushed frequently to avoid huge memory requirements. An indentation ~indent must be supplied to reconfigure the state when flushing resets the state.

Sourceval pp_types_header : VBoxer.t -> unit -> unit
Sourceval pp_types : VBoxer.t -> t -> unit
Sourceval pp_encoders : VBoxer.t -> t -> unit
Sourceval pp_param_serde : VBoxer.t -> t -> unit
Sourceval pp_body_serde : VBoxer.t -> t -> unit
Sourceval pp_paths : VBoxer.t -> t -> unit
Sourceval pp_client : VBoxer.t -> t -> unit
Sourceval pp_server : VBoxer.t -> t -> unit
include DkCoder_Std.SCRIPT
Sourceval __init : DkCoder_Std.Context.t -> unit

__init context is the entry point for running a script module. The DkCoder compiler will inject this function at the top and bottom of the script module. The top __init does nothing, while the bottom __init calls the prior __init.

That means:

  1. calling the __init function guarantees that the script module is initialized; that is, all of the script module's side-effects (ex. let () = Format.printf "Hello world@.") are executed before the __init returns to the caller.
  2. you can override the __init function by simply defining the __init idempotently. That will shadow the top __init and when the bottom __init is executed your __init will be called instead of the do-nothing top __init.

Future versions of DkCoder will call __init in dependency order for all `You script modules. Your __init function may be called several times.

Sourceval __repl : DkCoder_Std.Context.t -> unit

__repl context is the entry point for debugging a script module in a REPL. The DkCoder compiler will inject this function at the top and bottom of the script module. The top __repl does nothing, while the bottom __repl calls the prior __repl.

That means:

  1. you can override the __repl function by simply defining the __repl idempotently. That will shadow the top __repl and when the bottom __repl is executed your __repl will be called instead of the do-nothing top __repl.
Sourceval __module_info : unit -> DkCoder_Std.ModuleInfo.t

The run-time module information for the script module.