Codegen will read an openAPI contract, and generate structs & controllers for your go-lanai project.
- Reads an openAPI spec and:
- generates structs for each component, request & response defined
- generate struct bindings from a field's required-ness, input validation and min/max fields
- generates controllers for each path, with function stubs for each operation
- generates package.go files for the created packages, as well as a go.mod for the project
- See example generated files
- The
test.yml
file is the openAPI spec, and thegolden
directory is the generated output from that spec
- The
- generates structs for each component, request & response defined
- Contains a default set of templates, and support the user to define their own
- Regeneration rules can be defined for when a file to be generated already exists:
- overwriting - new file replaces old file
- ignoring - new file is not written
- reference - new file is generated beside the old one (e.g. if
myfile.go
exists,myfile.goref
will be generated), for manual merge
- Currently, it lacks capability to "intelligently" automatically resolve user changes with contract changes when regenerating
code
- Recommended to use the
reference
regeneration rule & manually compare the original file to theref
file
- Recommended to use the
- Currently just supports creation of
cmd
,pkg/api
&pkg/controller
packages, whcih corresponds to the REST controller layer of a web application written based on go-lanai's web module. If the project needs any other go-lanai frameworks, they will need to be added manually
To generate code, you will need an openAPI spec and a codegen.yml
file (see Configuration). The following command will
generate the project source code to ./dist
:
lanai-cli codegen
You can add arguments, providing your own myConfig.yml
, and specifying your own output folder path/to/output/folder
:
lanai-cli codegen -c myConfig.yml -o path/to/output/folder
To see exactly what is generated by the generator, see the test golden files when given an openAPI contract
- Make an openAPI contract
contract.yml
openapi: 3.0.0
info:
title: 'Test Contract'
version: '1'
termsOfService: 'https://www.cisco.com'
license:
name: 'MIT'
paths:
'/idm/api/v1/hello':
get:
summary: Get Hello API
operationId: GetHello
parameters:
- name: scope
in: path
required: true
schema:
format: "^[a-zA-Z0-5-_=]{1,256}$"
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/MyObject'
'/idm/api/v2/hi':
get:
summary: Get Hi API
operationId: GetHi
parameters:
- name: scope
in: path
required: true
schema:
format: "^[a-zA-Z0-5-_=]{1,256}$"
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/MyObject'
components:
schemas:
MyObject:
type: object
properties:
id:
type: string
enabled:
type: object
properties:
inner:
type: string
- Make a
codegen.yml
Notes: If contract/templateDirectory are relative paths, they must be relative to the location of this config file.
contract: ./contract.yml
repositoryRootPath: github.com/repo_owner/testservice
projectName: testservice
- Run
lanai-cli codegen -o ./
Files will be generated to your directory:
├── cmd
│ ├── testservice
│ │ └── main.go
│ └── testservice-migrate
│ └── migrate.go
├── codegen.yml
├── contract.yml
├── go.mod
├── go.sum
└── pkg
├── api
│ ├── common.go
│ ├── v1
│ │ └── hello.go
│ └── v2
│ └── hi.go
└── controller
├── package.go
├── v1
│ ├── hello.go
│ └── package.go
└── v2
├── hi.go
└── package.go
By default, codegen uses the set of templates defined in template/src, mirroring the file structure of a typical go-lanai web service.
If you just want to generate the pkg/controller
files
- Make a folder for your templates
mkdir myTemplates
- Copy the contents of
template/src
intomyTemplates
myTemplates
├── cmd
│ ├── @ProjectName@
│ │ └── project.main.go.tmpl
│ └── @ProjectName@-migrate
│ └── project.migrate.go.tmpl
├── delete.empty.tmpl
├── pkg
│ ├── api
│ │ ├── @Version@
│ │ │ ├── api-struct.requestresponse.go.tmpl
│ │ │ ├── requeststructs.tmpl
│ │ │ └── responsestructs.tmpl
│ │ ├── project.common.go.tmpl
│ │ └── structs.tmpl
│ └── controller
│ ├── @Version@
│ │ ├── api.controllers.go.tmpl
│ │ ├── controller.tmpl
│ │ └── version.package.go.tmpl
│ └── project.package.go.tmpl
└── project.go.mod.tmpl
(For more info about the significance about the different prefixes, see Development/Generators
)
- Remove the
cmd
andpkg/api
folders
rm -rf myTemplates/cmd myTemplates/pkg/api
├── codegen.yml
├── contract.yml
├── go.mod
└── myTemplates
├── delete.empty.tmpl
├── pkg
│ └── controller
│ ├── @Version@
│ │ ├── api.controllers.go.tmpl
│ │ ├── controller.tmpl
│ │ └── version.package.go.tmpl
│ └── project.package.go.tmpl
└── project.go.mod.tmpl
- Update
codegen.yml
withtemplateDirectory
contract: ./contract.yml
repositoryRootPath: github.com/repo_owner/testservice
templateDirectory: myTemplate
projectName: testservice
- Run
lanai-cli codegen -o ./
, and thepkg
folder will be generated
├── codegen.yml
├── contract.yml
├── go.mod
├── go.sum
├── myTemplates
└── pkg
└── controller
├── package.go
├── v1
│ ├── hello.go
│ └── package.go
└── v2
├── hi.go
└── package.go
Currently, there's no automatic method of resolving changes when regenerating existing files, so there are a few regeneration rules to assist manual resolving.
Take this project, with some unique change to hello.go
└── pkg
│ ...
└── controller
├── package.go
├── v1
│ ├── hello.go* // contains user changes
│ └── package.go
In codegen.yml, if you set regeneration
to overwrite
& run:
contract: ./contract.yml
repositoryRootPath: github.com/repo_owner/testservice
projectName: testservice
regeneration:
default: overwrite
It will regenerate the files & blow away any user changes. This is the default behavior:
└── pkg
│ ...
└── controller
├── package.go
├── v1
│ ├── hello.go // user changes blown away
│ └── package.go
Change it to ignore
& run
regeneration:
default: ignore
It won't modify any existing old files:
└── pkg
│ ...
└── controller
├── package.go
├── v1
│ ├── hello.go* // file left alone
│ └── package.go
If you change it to reference
regeneration:
default: overwrite
It'll generate a new file next to the original with a ref
suffix:
└── pkg
│ ...
└── controller
├── package.go
├── v1
│ ├── hello.go* // file left alone
│ ├── hello.goref // new version created next to original for comparison
│ └── package.go
Here is an example of a configuration yml file:
contract: ./contract.yml
templateDirectory: template/src
repositoryRootPath: github.com/repo_owner/testservice
projectName: testservice
regeneration:
default: overwrite
# Applies specific rules to files matching these patterns
rules:
"pkg/controller/*/*": reference #overwrite | ignore | reference
regexes:
testRegex: "^[a-zA-Z0-5-_=]{1,256}$"
codegen.yml supports the following flags:
contract
- path to the openAPI contract. Supports OpenAPI 3.0, see an example here.projectName
- e.g.testservice
repositoryRootPath
- eg.github.com/repo_owner/testservice
templateDirectory
- if defined, the code generator will use that directory to get its templates.regeneration
- policies for generating files that already exist. This table describes the behaviors of each rule when you try to regenerate onmyFile.go
for each of the rules:- v2 would be a freshly generated file based on the contract at the time it was run - not containing any user changes in v1
Regeneration Rule | Before | After |
---|---|---|
overwrite (default) |
myFile.go (v1) | myFile.go (v2) |
ignore |
myFile.go (v1) | myFile.go (v1) |
reference |
myFile.go (v1) | myFile.go (v1), myFile.goref(v2) |
-
regexes
- a string-string array, label any regexes that appear in the contract so they can be registered by go-lanai's validator. This field is optional - any unlabelled fields will be given a generated name -
rules
- each entry of this map must a file pattern, to be applied to any generated files that match the pattern. Any rules defined here will overwrite the global rule for relevant files (seeDevelopment/Generators
). Supported rules include:regeneration
Matching is done via filepath.Match, so check what kind of patterns you can use there.
In the above example, this would apply the
reference
regeneration rule topkg/controller/v1/package.go
In most use-cases, the default set of templates defined in template/src
should be good enough for code generation,
but you can use your own template set through the templateDirectory
field in the config.
The file structure of the directory used will reflect the file structure of the generated code.
If the file path is nested in @ symbols, those will be replaced by the appropriate information from the generator (see below).
The project has different generators that behave differently based on the prefix of the templates in the filesystem.
- Templates starting with
project
will be generated once - Templates starting with
api
will be generated once per API path in the openAPI contract- Tracks the name and data of the path, as well as the appropriate API version it belongs to.
- Templates starting with
version
will be generated once per version found in the API (e.g. /idm/api/v8/roles/list will generate a file for thev8
version)- Tracks the version and all paths that are a part of that api version
- Templates starting with
delete
and containing a regex will run at the end and delete any files matching that regex. A use case would be if the generator made an excess file that doesn't contain useful information (i.e just the linepackage myPackage
) - Any other
.tmpl
files won't generate anything and any templates defined can be used by the other ones
This project uses Golang's text templating engine, and provides various models & functions to serve as helpers.
Note that some of these functions are pretty specific to the use-case of the default templates, so YMMV on their usefulness when writing your own.
Function Name | Usage | Comments |
---|---|---|
args |
args <...interface > |
Helper to provide arguments to a template block. Arguments can be accessed with index . <arg index > |
increment |
increment <int> |
Given a number, returns that number + 1 |
log |
log <interface> |
logs your message in the console |
listContains |
listContains <haystack []string>, <needle string> |
Returns true if the needle is in the haystack |
derefBoolPtr |
derefBoolPtr <bool*> |
converts a boolean pointer and returns the underlying bool |
Function Name | Usage | Comments |
---|---|---|
regex |
regex <openapi3.Schema> |
Returns a regex object, providing a regex name (i.e "myRegex", generated or defined from the regexes config field) and the value (e.g "^[a-zA-Z0-7-_=]{1,256}$") |
registerRegex |
registerRegex <openapi3.Schema> |
Registers the regex from the schema internally & returns the regex's name. If the regex has already been registered, returns nothing |
Function Name | Usage | Comments |
---|---|---|
toTitle |
toTitle <string> |
Applies title cases (myStruct -> MyStruct ) |
concat |
concat <...string> |
Joins all the provided strings together |
toLower |
toLower <string> |
Sets string to all lowercase MyStruct -> mystruct |
basePath |
basePath <string> |
Given a path my/cool/path , returns the base part path |
hasPrefix |
hasPrefix <string> |
Calls strings.HasPrefix on the input |
replaceDashes |
replaceDashes <string> |
Replaces any dashes in string with underscores |
Function Name | Usage | Comments |
---|---|---|
versionList |
versionList <openapi3.Paths> |
Given all the paths, returns a list of all the versions (e.g v1 , v2 , etc) |
mappingName |
mappingName <path - string> <operation - string> |
'/my/api/v1/testpath/{scope}' + GET operation -> testpath-scope-get |
mappingPath |
mappingPath <string> |
'/my/api/v1/testpath/{scope}' -> /api/v1/testpath/:scope |
Constructors:
Function Name | Usage | Comments |
---|---|---|
property |
property <data - interface{}, name - string, currentPkg - string, prefix ...string> |
|
operation |
operation <data - *openapi3.Operation, string> |
If the operation lacks an OperationID, use the second argument to assign a name |
schema |
schema <string, data - *openapi3.SchemaRef> |
Function Name | Usage | Comments |
---|---|---|
shouldHavePointer |
shouldHavePointer <interface{}, isRequired bool> |
Check for if this struct property should be a pointer |
structTags |
structTag <representation.Property> |
Returns the struct tags of the property, if the property is in the list of requiredParams, a required tag will be added |
requiredList |
requiredList <*SchemaRef or *Parameter> |
Returns a list of any required parameters in this object |
containsSingularRef |
containsSingularRef |
Returns true if the object doesn't contain anything except one ref |
defaultNameFromPath |
defaultNameFromPath <string> |
/my/api/v1/testpath/{scope} -> TestpathScope |
registerStruct |
registerStruct <schemaName string, packageName string> |
Registers a struct & it's package name for the purposes of importing |
structLocation |
structLocation <structName string> |
Returns the package that the struct is a part of |
importsUsedByPath |
importsUsedByPath <openapi3.PathItem> |
Returns a list of imports (i.e where structs are located) that the path is expected to use |
isEmpty |
isEmpty |
Returns true if the object doesn't actually contain any fields (i.e parameters, or anything in the response) |