Templating guide #
go-asyncapi
uses the Go templates to generate the code, client application, IaC files, etc.
To customize the result, put all template you want to use to the directory and specify this directory
with the --template-dir
flag or set an appropriate option in config.
Structure #
The templates are organized in a tree structure, that allows to customize the result on any granularity level.
The whole tree is rendered starting from the root template, called main.tmpl
by default.
The preamble template is executed at the end, after all root template invocations.
Preamble template is called once per file, producing the output that is substituted at the beginning a file. Typically,
it contains the package declaration, import statements, “copyright” notice, etc. By default, its name preamble.tmpl
.
Naming #
Templates files must have the .tmpl
extension.
The file name is used as a template name if it doesn’t contain any
nested templates (define
directives).
Otherwise, every nested templates are available by their names and the file name is ignored.
Concepts #
Template context #
The context object is passed to the root template in data
argument of the Execute
method, so it initially
available by .
and $
operators. The context object does not survive between the template executions.
The context holds the rendered artifact, configuration options, current package name, current layout rule, etc.
Various “Context” structs are defined in tmpl
package.
Artifacts #
The main object to work with in the templates is artifact – the intermediate representation
object of an AsyncAPI entity in document. All artifacts are defined in render
and lang
packages and satisfy
the common.Artifact
interface.
A part of artifacts represent the complex entities (e.g. channel) and produce the complex code.
Others are simpler and represent a simple Go type (e.g. jsonschema object), they additionally satisfy the
common.GolangType
interface.
The templates goal is to render the artifacts into the desired output type: Go code, IaC configuration, etc.
For this, go-asyncapi
executes the root template separately for every artifact with .Selected == true
and
merges the results into a files and packages according the code layout.
The artifacts, compiled from several AsyncAPI documents, get to the same list. However, every artifact keeps the document URL and the location where it was defined.
Definition and usage code #
One thing that is worth to mention is main difference between goUsage
and goDef
functions. The goUsage
function render the usage code of the artifact, i.e. code snippet to “consume” the Go identifier in another place.
The goDef
function renders the definition code of the artifact, i.e. the Go code with type declaration.
For example, we have the lang.GoStruct
with a couple of fields.
{{ goDef . }}
function called in on “foo” package produces the definition code:
type MyStruct struct {
Field1 string `json:"field1"`
Field2 string `json:"field2"`
}
The {{ goUsage . }}
in “bar” package produces the usage code (import from foo
will be added automatically):
foo.MyStruct
This also works for the artifacts defined in the current package (e.g. MyStruct
), and in
third-party packages (e.g. mypackage.MyStruct
from “github.com/myuser/mypackage”).
Namespace #
Namespace stores all the names and artifacts defined in templates between root templates executions.
The main its purpose is conditional rendering to avoid the name collisions in corner cases. For example, when an entity may be referenced from several places in document, its definition will be rendered several times, which is semantic error in Go code.
We have goDef
, def
functions to define the artifact/name and defined
, ndefined
functions
to check if the artifact/name is already defined in the current namespace. If you familiar with C/C++ languages,
the namespace behavior may remind how #define
, #ifdef
, #ifndef
preprocessor directives work.
For more information, see the functions reference.
Usage #
For example, we want to add additional prefix My
to the name of the generated server interface generated near with every
channel
.
For that, create a file with any name, say my_server_interface.tmpl
, copy the code/proto/channel/serverInterface
template from the
proto_channel.tmpl
and modify it as follows:
{{define "code/proto/channel/serverInterface"}}
type My{{ .Channel | goIDUpper }}Server{{.Protocol | goIDUpper}} interface {
Open{{.Channel | goIDUpper}}{{.Protocol | goIDUpper}}(ctx {{goQual "context.Context"}}, {{if .ParametersType}}params {{ .ParametersType | goUsage }}{{end}}) (*{{ .Type | goUsage }}, error)
{{if .IsPublisher}}Producer() {{goQualR .Protocol "Producer"}}{{end}}
{{if .IsSubscriber}}Consumer() {{goQualR .Protocol "Consumer"}}{{end}}
}
{{- end}}
Now, run the go-asyncapi
tool with the --template-dir
flag pointing to the directory with the my_server_interface.tmpl
file
and your version of code/proto/channel/serverInterface
template will replace the default one:
go-asyncapi code --template-dir ./my_templates my_asyncapi.yml
Overriding the template files without ff works the same way, but you need to name your template file as the original template name. E.g.
channel.tmpl
.