package
0.15.1
Repository: https://github.com/cisco-open/go-lanai.git
Documentation: pkg.go.dev

# README

go-lanai Code Generator

Codegen will read an openAPI contract, and generate structs & controllers for your go-lanai project.

What it will do:

  • 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 the golden directory is the generated output from that spec
  • 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

What it won't do:

  • 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 the ref file
  • 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

Running

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

Examples

Generation

To see exactly what is generated by the generator, see the test golden files when given an openAPI contract

Usage

  1. 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
  1. 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
  1. 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

Using Your Own Templates

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

  1. Make a folder for your templates
mkdir myTemplates
  1. Copy the contents of template/src into myTemplates
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)

  1. Remove the cmd and pkg/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

  1. Update codegen.yml with templateDirectory
contract: ./contract.yml
repositoryRootPath: github.com/repo_owner/testservice
templateDirectory: myTemplate
projectName: testservice
  1. Run lanai-cli codegen -o ./, and the pkg 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

Running Codegen in a Repository that has Existing Files

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

Configuration

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 on myFile.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 RuleBeforeAfter
overwrite (default)myFile.go (v1)myFile.go (v2)
ignoremyFile.go (v1)myFile.go (v1)
referencemyFile.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 (see Development/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 to pkg/controller/v1/package.go

Development

Templates

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).

Generators

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 the v8 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 line package myPackage)
  • Any other .tmpl files won't generate anything and any templates defined can be used by the other ones

Writing Your Own Templates

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.

Random Helpers

Function NameUsageComments
argsargs <...interface >Helper to provide arguments to a template block. Arguments can be accessed with index . <arg index >
incrementincrement <int>Given a number, returns that number + 1
loglog <interface>logs your message in the console
listContainslistContains <haystack []string>, <needle string>Returns true if the needle is in the haystack
derefBoolPtrderefBoolPtr <bool*>converts a boolean pointer and returns the underlying bool

Regex Related

Function NameUsageComments
regexregex <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}$")
registerRegexregisterRegex <openapi3.Schema>Registers the regex from the schema internally & returns the regex's name. If the regex has already been registered, returns nothing

String Related

Function NameUsageComments
toTitletoTitle <string>Applies title cases (myStruct -> MyStruct)
concatconcat <...string>Joins all the provided strings together
toLowertoLower <string>Sets string to all lowercase MyStruct -> mystruct
basePathbasePath <string>Given a path my/cool/path, returns the base part path
hasPrefixhasPrefix <string>Calls strings.HasPrefix on the input
replaceDashesreplaceDashes <string>Replaces any dashes in string with underscores

Path Related

Function NameUsageComments
versionListversionList <openapi3.Paths>Given all the paths, returns a list of all the versions (e.g v1, v2, etc)
mappingNamemappingName <path - string> <operation - string>'/my/api/v1/testpath/{scope}' + GET operation -> testpath-scope-get
mappingPathmappingPath <string>'/my/api/v1/testpath/{scope}' -> /api/v1/testpath/:scope

Struct/DTO Related

Constructors:

Function NameUsageComments
propertyproperty <data - interface{}, name - string, currentPkg - string, prefix ...string>
operationoperation <data - *openapi3.Operation, string>If the operation lacks an OperationID, use the second argument to assign a name
schemaschema <string, data - *openapi3.SchemaRef>
Function NameUsageComments
shouldHavePointershouldHavePointer <interface{}, isRequired bool>Check for if this struct property should be a pointer
structTagsstructTag <representation.Property>Returns the struct tags of the property, if the property is in the list of requiredParams, a required tag will be added
requiredListrequiredList <*SchemaRef or *Parameter>Returns a list of any required parameters in this object
containsSingularRefcontainsSingularRefReturns true if the object doesn't contain anything except one ref
defaultNameFromPathdefaultNameFromPath <string>/my/api/v1/testpath/{scope} -> TestpathScope
registerStructregisterStruct <schemaName string, packageName string>Registers a struct & it's package name for the purposes of importing
structLocationstructLocation <structName string>Returns the package that the struct is a part of
importsUsedByPathimportsUsedByPath <openapi3.PathItem>Returns a list of imports (i.e where structs are located) that the path is expected to use
isEmptyisEmptyReturns true if the object doesn't actually contain any fields (i.e parameters, or anything in the response)

# Packages

No description provided by the author

# Functions

No description provided by the author
No description provided by the author
No description provided by the author

# Constants

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Variables

No description provided by the author
No description provided by the author
No description provided by the author
go:embed all:template/src.
No description provided by the author

# Structs

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Type aliases

No description provided by the author
No description provided by the author
No description provided by the author