Discover and annotate all data types and paths in an OpenAPI specification document.
Data Type Discovery
An OpenAPI spec document has a connected graph of Schema objects.
There are a few mutually related concepts to define before we can explain what is discovered:
- A data type is either a primitive (ex.
integer
), compound (ex.array
) and reference data type (ex."$ref"
). - Each Schema object is a primitive data type, compound data type or reference data type. A Schema object also has data type specifications and data type compositions. Each data type specification constraints a data type (ex.
maximum
constrains the allowed values of aninteger
primitive type). A data type composition (allOf
,anyOf
,oneOf
andnot
) can create polymorphic, sum and negation types from other types. - Compound data types encapsulate zero or more Schema objects.
- Reference data types reference a Schema object.
When mapping OpenAPI to a statically typed programming language, one of the mismatches is that languages like OCaml do not have a concept of data type specification. And sometimes an OpenAPI data type composition (ex. not
) does not exist in a language.
Another mismatch is that some languages are nominally typed (they need names for each level of a compound data type) rather than structurally typed. Structural typing is implicit in OpenAPI, and with nomimally typed programming languages the compound data types need to be flattened. In OCaml the record type is nominally typed, so the following compound data type is invalid code:
[ type t = { x: { a: string } } ]
However, after flattening the compound data type the equivalent representation is valid:
[ type t_1234678 = { a: string } type t = { x: t_12345678 } ]
See the 2 Flattening Data Types and Compositions
for more details.
More generally, when generating code for a programming language from an OpenAPI specification, the generator needs to map a uniquely identifiable, flattened Schema Object to one or more programming language types ("PL type").
Here are examples when generating OCaml source code, where the triples are (data type, data type specification, data type composition):
- The triple (
integer
,("minimum",0); ("maximum", 65535)
, ) maps to the OCaml PL typeint32
- The triple (
array
, ,anyOf (integer, string)
) maps to the OCaml PL typetype somename = | Integer | String
- The triple (
array
, ,not boolean
) cannot be mapped to an OCaml PL type
Use datatypes
(datatypes ~query_selector:`FlattenedAll
to enumerate all the uniquely identifiable, flattened Schema objects discovered in the OpenAPI specification document. Unlike OaDocument.fold_all_schemas
, compound data types have been flattened so that you get an enumeration over all levels of a compound data type. Compositions like anyOf
are also flattened.
You may also use datatypes ~query_selector:`All
to do the same without any flattening when your programming language supports nested compound data types.
The unique identity is the JSON pointer path to a Schema object in the OpenAPI specification document.
Flattening Data Types and Compositions
Anonymous Data Types
Anonymous data types are given names. That way anonymous data types can be lifted to top-level types. That is, the following illegal OCaml PL type is avoided:
[ type t = { x: { a: string } } ]
Instead new top-level data types are created with names:
[ type t_1234678 = { a: string } type t = { x: t_12345678 } ]
Optional Data Types
Optional data types (either not required
or a nullable
field) are represented as two data types (the non-optional type and the optional type). That way anonymous data types can be made optional. That is, the following illegal OCaml PL type is avoided:
[ type t = {x:string} option ]
The valid flat representation is:
[ type t'nonopt = {x:string} type t = t'nonopt option ]
Flattened Compositions
The compositions anyOf
, oneOf
and allOf
are flattened.
That is, the following illegal OCaml PL type induced by anyOf
is avoided:
[ type t = { fruit: | Apple | Orange } ]
Instead new top-level data types are created:
[ type t_1234678 = | Apple | Orange type t = { fruit: t_12345678 } ]
A REST verb.
type param_info = {
p_name : string;
p_required : bool;
p_explode : bool;
p_parameter : OaTypes.parameter;
}
type op = {
user_op : string;
verb : verb;
segs : OaTypes.operation_seg list;
summary : string option;
description : string option;
headers : param_info list;
queries : param_info list;
path_params : param_info list;
request_body : OaTypes.request_body option;
responses : OaTypes.responses;
}
A REST operation.
val param_info :
loc:OaTypes.param_in ->
OaTypes.parameter list ->
(string * bool * bool) list
val primitive_pp_f :
(module OaDocument.STANDARD) ->
(module OaDocument.VENDOR) ->
format:string option ->
enum:Json_repr.any list option ->
OaTypes.schema_kind ->
(Restapis_o.Open__.Format.formatter -> unit -> unit) option
val datatype_ref_of_schema :
(module OaDocument.STANDARD) ->
(module OaDocument.VENDOR) ->
path:Json_query.path ->
OaTypes.schema ->
datatype_ref
val resolve_parameter_specs :
?depth:int ->
t ->
path_template:string ->
param:string ->
OaTypes.parameter_kind ->
OaTypes.parameter_specs
resolve_parameter_specs t ~path_template kind
fully resolves the parameter specifications for the parameter kind kind
.
path_template
is the REST operation path template and param
is the parameter name; they are used only for error reporting.
val resolve_header_specs :
?depth:int ->
t ->
path_template:string ->
header:string ->
OaTypes.header_kind ->
OaTypes.header_specs
resolve_header_specs t ~path_template kind
fully resolves the header specifications for the header kind kind
.
path_template
is the REST operation path template and header
is the header name; they are used only for error reporting.
val resolve_request_body_specs :
?depth:int ->
t ->
path_template:string ->
OaTypes.request_body_kind ->
OaTypes.request_body_specs
resolve_request_body_specs t ~path_template kind
fully resolves the Request Body specifications for the request body kind kind
.
path_template
is the REST operation path template; it is used only for error reporting.
val resolve_datatype_ref :
?depth:JsonQueryPathMap.key list ->
t ->
path:JsonQueryPathMap.key ->
datatype_ref
resolve_datatype_ref t ~path
fully resolves the datatype reference through $ref
references to the original schema: ...
definition.
val resolve_schema :
?depth:Json_query.path list ->
t ->
path:Json_query.path ->
OaTypes.schema
resolve_schema t ~path
fully resolves the schema through $ref
references to the original schema: ...
definition.
include DkCoder_Std.SCRIPT
__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:
- 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. - 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.
__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:
- 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
.
The run-time module information for the script module.