Install Rust : https://www.rust-lang.org/tools/install.
And then install codegenr
cargo install codegenr
or install the development version
cargo install --git https://github.com/eventuallyconsultant/codegenr --branch dev
codegenr documentation on docs.rs
Here is a simple folders/files Tree we're gonna use in example
|- _specs
|- openapi.yaml
|- ...
|- _templates
|- rest-tests
|- mytemplate.hbs
|- ...
|- _rest-calls
|- generated
|- file1
|- ...
flowchart LR
L[Load] --> R{Is it?}
R --> |$ref| L
R[Resolve] --> |All resolved| RE
RE[Render] --> PP
PP[Process]
To generate your files, you need to define these parameters :
[section_name]: A unique name representing each sectionsource: The file.yaml with the data you want to use for the generationtemplates: the folders containing handlebar templates (.hbs) you're using. Only one file in those folders must not be prefixed by_and then is considered as themaintemplate. The other ones are prefixed like_partial.hbsare consideredpartialtemplates.output: Theroot folderwhere the files will be generated. All files output path write will be computed from this root.custom_helpers: A place you can put.rhaifile, each file is loaded as a custom helper, usable from the.hbstemplatesintermediate: (Optional) if set,codegenrwill output intermediate files for debug purposeglobal_parameters: (Optional) Some values you want to use with theglobal_parameterhelper.
[api_section]
source = "./_specs/openapi.yaml"
templates = [ "./_templates/misc/rest-tests" ]
output = "./_rest-calls"
custom_helpers = [ "./_templates/_custom_helpers" ]
intermediate = "codegenr"
global_parameters = { apiName = "MyFirstApi", apiRoot = "/v1/api" }The load step will read the source file and turn it to json
- if it's a
jsonfile, it's quite easy - if it's a
yamlfile, it's not that hard - if it's a
graphqlsdl file, it's lead to some structure changes
If you look closely to example below, you can see that $ref: "#/components/schemas/GetMeResponse" refer to a specific path composed in 3 parts:
- The
#part is refering to thesamedocument (Also:file.yaml#...would be referring to the documentfile.yaml) /components/schemas/is the pathinthe fileGetMeResponseis the object we're looking for, here is just a simple example with a propertyusernamewhich contains a description and a type.
# `some_openapi_file.yaml` example
openapi: 3.0.3
info:
title: Example openapi
description: "Openapi specifications"
version: 1.0.0
servers:
- url: http://localhost:8000
paths:
/me:
get:
tags:
- user
summary: Get current users informations
operationId: get_current_user
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: "#/components/schemas/GetMeResponse"
# ...
# ...
components:
schemas:
GetMeResponse:
type: object
required:
- username
properties:
username:
type: string
description: a username/handle
example: just_a_usernamefor more information : https://swagger.io/docs/specification/using-ref/
this is where the resolve step comes in the game :
If the file contains from json references ($ref: "..."), the resolver will replace the reference by the value pointed at by the reference. If the reference point at another file, it'll be loaded (previous step). Then it will continue to resolve references in the value, and so on, recursively, until all $refs are replaced.
In this example, the loader finds a $ref in the source.yaml which is redirecting in the other.yaml, the loader will then load the other.yaml and resolve the reference that the $ref is pointing.
# source.yaml
openapi: 3.0.3
info:
title: Example
description: "Just an example"
version: 1.0.0
servers:
- url: http://localhost:8000
paths:
/user:
get:
tags:
- user
summary: Get current users informations
operationId: get_current_user
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: "other.yaml#/components/schemas/UserResponse"# other.yaml
components:
schemas:
UserResponse:
type: object
required:
- username
properties:
username:
type: string
description: a username/handle
example: just_a_username// all resolved & changed to json
{
"openapi": "3.0.3",
"info": {
"title": "Example",
"description": "Just an example",
"version": "1.0.0"
},
"servers": [
{
"url": "http://localhost:8000"
}
],
"paths": {
"/user": {
"get": {
"tags": ["user"],
"summary": "Get current users informations",
"operationId": "get_current_user",
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"UserResponse": {
"type": "object",
"required": ["username"],
"properties": {
"username": {
"type": "string",
"description": "a username/handle",
"example": "just_a_username"
}
},
"x-fromRef": "other.yaml#/components/schemas/UserResponse",
"x-refName": "UserResponse"
}
}
}
}
}
}
}
}
}
}flowchart LR
L[Load] --> F
F[source.yaml] --> |All Resolved/No $ref| Rend
F --> |$ref: other.yaml| L2
Res[Resolve] --> F2
F2[other.yaml] --> |No other $ref| Rend
L2[Load] --> Res
Rend[Render] --> PP
PP[Process]
Finally, when all the refs are resolved and all necessary files loaded, the render and process will do their job.
Here is our handlebar example file named mytemplate.hbs which is in the ./_templates/misc/rest-tests folder.
The goal of this template will be to ouptut a .rest file named after the apiName, that contains ready to use
one click examples from the swagger documentation. (usage with Rest Client VsCode extension)
This step will use the template folder you defined (./_templates/misc/rest-tests in the example above) to find all handlebars files (mytemplate.hbs, ...)
and render the ONE .bhs main file (the one with no _underscore) using the load & resolve result as source, and the parameters defined in the global_parameters if there are some.
for more information about the handlebar syntax : https://handlebarsjs.com/guide/
Here is what our render output will look at the end.
@host = localhost
@port = 8080
@api_root = /v1/api
# get_me
# --- --- --- --- --- --- --- --- --- --- --- ---
get http://{{host}}:{{port}}{{api_root}}/user HTTP/1.1
#### --- --- --- ---Handlebars is a pretty limited language, but it's extended with helpers :
- it has some standard helpers (
eq,ne,gt,gte,lt,lte,and,or,not...), provided by the rust handlebars implementation : https://docs.rs/handlebars/latest/handlebars/#built-in-helpers codegenrbundles thehandlebars_misc_helpersto have more helpers : https://github.com/davidB/handlebars_misc_helperscodegenralso provides some more home backed helpers : https://docs.rs/codegenr/latest/codegenr/helpers/index.html- Finally
youcan add your own helpers using the rhai embedded scripting language- any
__myhelper__.rhaifile in the custom_helpers folder will be available as amyhelperhandlebars inline helper, usable from the .hbs templates.
- any
Here is a simple example and what it is used for:
This yaml fragment is the definition of a response in your file.yaml, you can see that we defined for the userCount property the type and the format.
This property is also marked as required in the required array.
DisplayUserCount:
description: Display the number of user
type: object
required:
- userCount
properties:
userCount:
type: integer
format: uint64Now imagine you want to generate some dart client for this api, you now have to convert this property type to a type in Dart.
In this case, we can use the rhai file to perform this complex mapping : dart_type_convert.rhai
It will define a helper with 3 parameters :
type: we'll provide the type value ("integer"here)format: we'll provide the format value ("uint64"here)optional: we'll need to use theis_oapi3_property_requiredcodegenr helper and negate it withnotto passtrueis the parameter is not found in therequiredarray.
will output
intThanks to this custom rhai helper :
// dart_type_convert.rhai
let type = params[0];
let format = params[1];
let optional = if params.len() > 2 { params[2] } else { false };
// For more information about the data types, check the swagger doc
// https://swagger.io/docs/specification/data-models/data-types/
let type_name = switch [type, format] {
["string", ""] => "String",
["integer", "uint32"] => "int",
["integer", "uint64"] => "int",
["number", "float"] => "double",
["number", "double"] => "double",
_ => {
throw `UNKNOWN TYPE ${type} - ${format}`;
()
}
};
if optional {
`${type_name}?`
}else{
type_name
}
The process step is where the render output is took from memory to files or console ...
Files will written by following the instructions defined in handlebars template between ### FILE and ### /FILE.
Some very simple example here :
# source.yaml
fileName: testshell output
World !text.txt
HelloYou can also open the repository in GitPod with this button
In the command line you can :
cargo testto launch all the unit testscargo doc --opento compile and open the local documentation
cargo doc --opencompile and open the documentationcargo install --path codegenrinstalls codegenr command line from sourcescargo install --git https://github.com/eventuallyconsultant/codegenr --branch devinstalls codegenr command line from the latest githubdevbranch
This tool is based on the design of a precedent one written in C# : CodegenUP.
- a video in french, explaining a lot on this tool : https://www.youtube.com/watch?v=G--KdojP8pc
-
Load a yaml or json to serde::json
-
Resolve
$reftags -
Pass all the resulting document to an handlebar template
-
Implement some default helper (and make some doc tests about them)
-
A plugin system
-
Migrate C# custom helpers to this new plugin system
-
A this point, we could use
codegenrin place ofCodegenUP, just by calling some commands -
Rename
codegenr-clitocodegenr&codegenrtocodegenr-lib -
Publish on
crates.io -
Be able to have a
codegenr.tomlon a workspace root to describe all the templates to execute around the workspace -
Better Errors (typed ones)
-
Resolved Json Cache optimisation
-
all tests passing on windows too
-
Verbose/Tracing? mode
-
Be able to output some flowchart (mermaid?) of the codegenr section (source.yaml + templates + helpers => files in folders)
-
Be able to relate all sections in the same flowchart ... some target files can be source for other steps !!
-
Better examples
-
Smol strings optimisation ?
-
Watch mode for the file changes
-
Make a VSCode extension about all of this to make it live / super user friendly for
everyone -
Allow multiple swagger2 documents merging
-
Allow multiple swagger3 documents merging
-
Transform the json to an OpenApi Generator model, and be able to use all the
OpenApi Generatortemplates ?