hz custom template use
Hertz provides command-line tools (hz) that support custom template features, including:
- Customize the layout template (i.e., the directory structure of the generated code)
- Custom package templates (i.e. service-related code structures, including handler, router, etc.)
Users can provide their own templates and rendering parameters, combined with the ability of hz, to complete the custom code generation structure.
Custom layout template
Users can modify or rewrite according to the default template to meet their own needs
Hz takes advantage of the “go template” capability to support defining templates in “yaml” format and uses “json” format to define rendering data.
The so-called layout template refers to the structure of the entire project. These structures have nothing to do with the specific idl definition, and can be directly generated without idl. The default structure is as follows:
.
├── biz
│ ├── handler
│ │ └── ping.go
│ │ └── ****.go // Set of handlers divided by service, the position can be changed according to handler_dir
│ ├── model
│ │ └── model.go // idl generated struct, the position can be changed according to model_dir
│ └── router //undeveloped custom dir
│ └── register.go // Route registration, used to call the specific route registration
│ └── route.go // Specific route registration location
│ └── middleware.go // Default middleware build location
├── .hz // hz Create code flags
├── go.mod
├── main.go // Start the entrance
├── router.go // User-defined route write location
└── router_gen.go // hz generated route registration call
IDL
// hello.thrift
namespace go hello.example
struct HelloReq {
1: string Name (api.query="name");
}
struct HelloResp {
1: string RespBody;
}
service HelloService {
HelloResp HelloMethod(1: HelloReq request) (api.get="/hello");
}
Command
hz new --mod=github.com/hertz/hello --idl=./hertzDemo/hello.thrift --customize_layout=template/layout.yaml:template/data.json
The meaning of the default layout template
Note: The following bodies are all go templates
layouts:
# The directory of the generated handler will only be generated if there are files in the directory
- path: biz/handler/
delims:
- ""
- ""
body: ""
# The directory of the generated model will only be generated if there are files in the directory
- path: biz/model/
delims:
- ""
- ""
body: ""
# project main file
- path: main.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator.
package main
import (
"github.com/cloudwego/hertz/pkg/app/server"
)
func main() {
h := server.Default()
register(h)
h.Spin()
}
# go.mod file, need template rendering data {{.GoModule}} to generate
- path: go.mod
delims:
- '{{'
- '}}'
body: |-
module {{.GoModule}}
{{- if .UseApacheThrift}}
replace github.com/apache/thrift => github.com/apache/thrift v0.13.0
{{- end}}
# .gitignore file
- path: .gitignore
delims:
- ""
- ""
body: "*.o\n*.a\n*.so\n_obj\n_test\n*.[568vq]\n[568vq].out\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n_testmain.go\n*.exe\n*.exe~\n*.test\n*.prof\n*.rar\n*.zip\n*.gz\n*.psd\n*.bmd\n*.cfg\n*.pptx\n*.log\n*nohup.out\n*settings.pyc\n*.sublime-project\n*.sublime-workspace\n!.gitkeep\n.DS_Store\n/.idea\n/.vscode\n/output\n*.local.yml\ndumped_hertz_remote_config.json\n\t\t
\ "
# .hz file, containing hz version, is the logo of the project created by hz, no need to transfer rendering data
- path: .hz
delims:
- '{{'
- '}}'
body: |-
// Code generated by hz. DO NOT EDIT.
hz version: {{.hzVersion}}
# ping comes with ping handler
- path: biz/handler/ping.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator.
package handler
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/utils"
)
// Ping .
func Ping(ctx context.Context, c *app.RequestContext) {
c.JSON(200, utils.H{
"message": "pong",
})
}
# `router_gen.go` is the file that defines the route registration
- path: router_gen.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator. DO NOT EDIT.
package main
import (
"github.com/cloudwego/hertz/pkg/app/server"
router "{{.RouterPkgPath}}"
)
// register registers all routers.
func register(r *server.Hertz) {
router.GeneratedRegister(r)
customizedRegister(r)
}
# Custom route registration file
- path: router.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator.
package main
import (
"github.com/cloudwego/hertz/pkg/app/server"
handler "{{.HandlerPkgPath}}"
)
// customizeRegister registers customize routers.
func customizedRegister(r *server.Hertz){
r.GET("/ping", handler.Ping)
// your code ...
}
# Default route registration file, do not modify it
- path: biz/router/register.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator. DO NOT EDIT.
package router
import (
"github.com/cloudwego/hertz/pkg/app/server"
)
// GeneratedRegister registers routers generated by IDL.
func GeneratedRegister(r *server.Hertz){
//INSERT_POINT: DO NOT DELETE THIS LINE!
}
The meaning of template rendering parameter file
When a custom template and render data are specified, the options specified on the command line will not be used as render data, so the render data in the template needs to be defined by the user.
Hz uses “json” to specify render data, as described below
{
// global render parameters
"*": {
"GoModule": "github.com/hz/test", // must be consistent with the command line, otherwise the subsequent generation of model, handler and other code will use the mod specified by the command line, resulting in inconsistency.
"ServiceName": "p.s.m", // as specified on the command line
"UseApacheThrift": false // Set "true"/"false" depending on whether to use "thrift"
},
// router_gen.go route the registered render data,
// "biz/router"points to the module of the routing code registered by the default idl, do not modify it
"router_gen.go": {
"RouterPkgPath": "github.com/hz/test/biz/router"
}
}
Customize a layout template
At present, the project layout generated by hz is already the most basic skeleton of a hertz project, so it is not recommended to delete the files in the existing template.
However, if the user wants a different layout, of course, you can also delete the corresponding file according to your own needs (except “biz/register.go”, the rest can be modified)
We welcome users to contribute their own templates
Assuming that the user only wants “main.go” and “go.mod” files, then we modify the default template as follows:
template:
// layout.yaml
layouts:
# project main file
- path: main.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator.
package main
import (
"github.com/cloudwego/hertz/pkg/app/server"
"{{.GoModule}}/biz/router"
)
func main() {
h := server.Default()
router.GeneratedRegister(h)
// do what you wanted
// add some render data: {{.MainData}}
h.Spin()
}
# go.mod file, need template rendering data {{.GoModule}} to generate
- path: go.mod
delims:
- '{{'
- '}}'
body: |-
module {{.GoModule}}
{{- if .UseApacheThrift}}
replace github.com/apache/thrift => github.com/apache/thrift v0.13.0
{{- end}}
# Default route registration file, no need to modify
- path: biz/router/register.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator. DO NOT EDIT.
package router
import (
"github.com/cloudwego/hertz/pkg/app/server"
)
// GeneratedRegister registers routers generated by IDL.
func GeneratedRegister(r *server.Hertz){
//INSERT_POINT: DO NOT DELETE THIS LINE!
}
render data:
{
"*": {
"GoModule": "github.com/hertz/hello",
"ServiceName": "hello",
"UseApacheThrift": true
},
"main.go": {
"MainData": "this is customized render data"
}
}
Command:
hz new --mod=github.com/hertz/hello --idl=./hertzDemo/hello.thrift --customize_layout=template/layout.yaml:template/data.json
Custom package template
The template address of the hz template:
Users can modify or rewrite according to the default template to meet their own needs
- The so-called package template refers to the code related to the idl definition. This part of the code involves the service, go_package/namespace, etc. specified when defining idl. It mainly includes the following parts:
- handler.go: Handling function logic
- router.go: the route registration logic of the service defined by the specific idl
- register.go: logic for calling the content in
router.go
Model code: generated go struct; however, since the tool that uses plugins to generate model code currently does not have permission to modify the model template, this part of the function is not open for now
Command
# After that, the package template rendering data will be provided, so the form of "k-v" is retained when entering the command, and ":" needs to be added after customize_package.
hz new --mod=github.com/hertz/hello --handler_dir=handler_test --idl=hertzDemo/hello.thrift --customize_package=template/package.yaml:
Default package template
Note: Custom package templates do not provide the ability to render data.
layouts:
# Path only represents handler.go template, the specific handler path is determined by the default path and handler_dir
- path: handler.go
delims:
- '{{'
- '}}'
body: |-
// Code generated by hertz generator.
package {{.PackageName}}
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
{{- range $k, $v := .Imports}}
{{$k}} "{{$v.Package}}"
{{- end}}
)
{{range $_, $MethodInfo := .Methods}}
{{$MethodInfo.Comment}}
func {{$MethodInfo.Name}}(ctx context.Context, c *app.RequestContext) {
var err error
{{if ne $MethodInfo.RequestTypeName "" -}}
var req {{$MethodInfo.RequestTypeName}}
err = c.BindAndValidate(&req)
if err != nil {
c.String(400, err.Error())
return
}
{{end}}
resp := new({{$MethodInfo.ReturnTypeName}})
c.{{.Serializer}}(200, resp)
}
{{end}}
# path only represents router.go template, its path is fixed at: biz/router/namespace/
- path: router.go
delims:
- '{{'
- '}}'
body: |-
// Code generated by hertz generator. DO NOT EDIT.
package {{$.PackageName}}
import (
"github.com/cloudwego/hertz/pkg/app/server"
{{range $k, $v := .HandlerPackages}}{{$k}} "{{$v}}"{{end}}
)
/*
This file will register all the routes of the services in the master idl.
And it will update automatically when you use the "update" command for the idl.
So don't modify the contents of the file, or your code will be deleted when it is updated.
*/
{{define "g"}}
{{- if eq .Path "/"}}r
{{- else}}{{.GroupName}}{{end}}
{{- end}}
{{define "G"}}
{{- if ne .Handler ""}}
{{- .GroupName}}.{{.HttpMethod}}("{{.Path}}", append({{.MiddleWare}}Mw(), {{.Handler}})...)
{{- end}}
{{- if ne (len .Children) 0}}
{{.MiddleWare}} := {{template "g" .}}.Group("{{.Path}}", {{.MiddleWare}}Mw()...)
{{- end}}
{{- range $_, $router := .Children}}
{{- if ne .Handler ""}}
{{template "G" $router}}
{{- else}}
{ {{template "G" $router}}
}
{{- end}}
{{- end}}
{{- end}}
// Register register routes based on the IDL 'api.${HTTP Method}' annotation.
func Register(r *server.Hertz) {
{{template "G" .Router}}
}
# path only represents register.go template, the path of register is fixed to biz/router/register.go
- path: register.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator. DO NOT EDIT.
package router
import (
"github.com/cloudwego/hertz/pkg/app/server"
{{$.PkgAlias}} "{{$.Pkg}}"
)
// GeneratedRegister registers routers generated by IDL.
func GeneratedRegister(r *server.Hertz){
//INSERT_POINT: DO NOT DELETE THIS LINE!
{{$.PkgAlias}}.Register(r)
}
- path: model.go
delims:
- ""
- ""
body: ""
# path only represents middleware.go template, the path of middleware is the same as router.go: biz/router/namespace/
- path: middleware.go
delims:
- '{{'
- '}}'
body: |-
// Code generated by hertz generator.
package {{$.PackageName}}
import (
"github.com/cloudwego/hertz/pkg/app"
)
{{define "M"}}
func {{.MiddleWare}}Mw() []app.HandlerFunc {
// your code...
return nil
}
{{range $_, $router := $.Children}}{{template "M" $router}}{{end}}
{{- end}}
{{template "M" .Router}}
# path only represents the template of the client.go, the generation path of the client code is specified by the user "${client_dir}"
- path: client.go
delims:
- '{{'
- '}}'
body: |-
// Code generated by hertz generator.
package {{$.PackageName}}
import (
"github.com/cloudwego/hertz/pkg/app/client"
"github.com/cloudwego/hertz/pkg/common/config"
)
type {{.ServiceName}}Client struct {
client * client.Client
}
func New{{.ServiceName}}Client(opt ...config.ClientOption) (*{{.ServiceName}}Client, error) {
c, err := client.NewClient(opt...)
if err != nil {
return nil, err
}
return &{{.ServiceName}}Client{
client: c,
}, nil
}
# handler_single means a separate handler template, which is used to update each new handler when updating
- path: handler_single.go
delims:
- '{{'
- '}}'
body: |+
{{.Comment}}
func {{.Name}}(ctx context.Context, c *app.RequestContext) {
// this my demo
var err error
{{if ne .RequestTypeName "" -}}
var req {{.RequestTypeName}}
err = c.BindAndValidate(&req)
if err != nil {
c.String(400, err.Error())
return
}
{{end}}
resp := new({{.ReturnTypeName}})
c.{{.Serializer}}(200, resp)
}
# middleware_single means a separate middleware template, used to update each new middleware when updating
- path: middleware_single.go
delims:
- '{{'
- '}}'
body: |+
func {{.MiddleWare}}Mw() []app.HandlerFunc {
// your code...
return nil
}
Customize a package template
Like layout templates, users can also customize package templates.
As far as the templates provided by the package are concerned, the average user may only need to customize handler.go templates, because router.go/middleware.go/register.go are generally related to the idl definition and the user does not need to care, so hz currently also fixes the location of these templates, and generally does not need to be modified.
Therefore, users can customize the generated handler template according to their own needs to speed up development; however, since the default handler template integrates some model information and package information, the hz tool is required to provide rendering data. This part of the user can modify it according to their own situation, and it is generally recommended to leave model information.
Here is an example of a simple custom handler template, adding some comments to the handler:
template:
layouts:
# Path only represents handler.go template, the specific handler path is determined by the default path and handler_dir
- path: handler.go
delims:
- '{{'
- '}}'
body: |-
// this is my custom handler.
package {{.PackageName}}
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
{{- range $k, $v := .Imports}}
{{$k}} "{{$v.Package}}"
{{- end}}
)
{{range $_, $MethodInfo := .Methods}}
{{$MethodInfo.Comment}}
func {{$MethodInfo.Name}}(ctx context.Context, c *app.RequestContext) {
// you can code something
var err error
{{if ne $MethodInfo.RequestTypeName "" -}}
var req {{$MethodInfo.RequestTypeName}}
err = c.BindAndValidate(&req)
if err != nil {
c.String(400, err.Error())
return
}
{{end}}
resp := new({{$MethodInfo.ReturnTypeName}})
c.{{.Serializer}}(200, resp)
}
{{end}}
# path only represents router.go template, its path is fixed at: biz/router/namespace/
- path: router.go
delims:
- '{{'
- '}}'
body: |-
// Code generated by hertz generator. DO NOT EDIT.
package {{$.PackageName}}
import (
"github.com/cloudwego/hertz/pkg/app/server"
{{range $k, $v := .HandlerPackages}}{{$k}} "{{$v}}"{{end}}
)
/*
This file will register all the routes of the services in the master idl.
And it will update automatically when you use the "update" command for the idl.
So don't modify the contents of the file, or your code will be deleted when it is updated.
*/
{{define "g"}}
{{- if eq .Path "/"}}r
{{- else}}{{.GroupName}}{{end}}
{{- end}}
{{define "G"}}
{{- if ne .Handler ""}}
{{- .GroupName}}.{{.HttpMethod}}("{{.Path}}", append({{.MiddleWare}}Mw(), {{.Handler}})...)
{{- end}}
{{- if ne (len .Children) 0}}
{{.MiddleWare}} := {{template "g" .}}.Group("{{.Path}}", {{.MiddleWare}}Mw()...)
{{- end}}
{{- range $_, $router := .Children}}
{{- if ne .Handler ""}}
{{template "G" $router}}
{{- else}}
{ {{template "G" $router}}
}
{{- end}}
{{- end}}
{{- end}}
// Register register routes based on the IDL 'api.${HTTP Method}' annotation.
func Register(r *server.Hertz) {
{{template "G" .Router}}
}
# path only represents register.go template, the path of register is fixed to biz/router/register.go
- path: register.go
delims:
- ""
- ""
body: |-
// Code generated by hertz generator. DO NOT EDIT.
package router
import (
"github.com/cloudwego/hertz/pkg/app/server"
{{$.PkgAlias}} "{{$.Pkg}}"
)
// GeneratedRegister registers routers generated by IDL.
func GeneratedRegister(r *server.Hertz){
//INSERT_POINT: DO NOT DELETE THIS LINE!
{{$.PkgAlias}}.Register(r)
}
- path: model.go
delims:
- ""
- ""
body: ""
# path only represents middleware.go template, the path of middleware is the same as router.go: biz/router/namespace/
- path: middleware.go
delims:
- '{{'
- '}}'
body: |-
// Code generated by hertz generator.
package {{$.PackageName}}
import (
"github.com/cloudwego/hertz/pkg/app"
)
{{define "M"}}
func {{.MiddleWare}}Mw() []app.HandlerFunc {
// your code...
return nil
}
{{range $_, $router := $.Children}}{{template "M" $router}}{{end}}
{{- end}}
{{template "M" .Router}}
# path only represents the template of the client.go, the generation path of the client code is specified by the user "${client_dir}"
- path: client.go
delims:
- '{{'
- '}}'
body: |-
// Code generated by hertz generator.
package {{$.PackageName}}
import (
"github.com/cloudwego/hertz/pkg/app/client"
"github.com/cloudwego/hertz/pkg/common/config"
)
type {{.ServiceName}}Client struct {
client * client.Client
}
func New{{.ServiceName}}Client(opt ...config.ClientOption) (*{{.ServiceName}}Client, error) {
c, err := client.NewClient(opt...)
if err != nil {
return nil, err
}
return &{{.ServiceName}}Client{
client: c,
}, nil
}
# handler_single means a separate handler template, which is used to update each new handler when updating
- path: handler_single.go
delims:
- '{{'
- '}}'
body: |+
{{.Comment}}
func {{.Name}}(ctx context.Context, c *app.RequestContext) {
// this my demo
var err error
{{if ne .RequestTypeName "" -}}
var req {{.RequestTypeName}}
err = c.BindAndValidate(&req)
if err != nil {
c.String(400, err.Error())
return
}
{{end}}
resp := new({{.ReturnTypeName}})
c.{{.Serializer}}(200, resp)
}
# middleware_single means a separate middleware template, used to update each new middleware when updating
- path: middleware_single.go
delims:
- '{{'
- '}}'
body: |+
func {{.MiddleWare}}Mw() []app.HandlerFunc {
// your code...
return nil
}
Command:
# After that, the package template rendering data will be provided, so the form of "k-v" is retained when entering the command, and ":" needs to be added after customize_package.
hz new --mod=github.com/hertz/hello --handler_dir=handler_test --idl=hertzDemo/hello.thrift --customize_package=template/package.yaml:
The handler is generated as follows:
// this is my custom handler.
package example
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
example "test/test2/biz/model/hello/example"
)
// HelloMethod .
// @router /hello [GET]
func HelloMethod(ctx context.Context, c *app.RequestContext) {
// you can code something
var err error
var req example.HelloReq
err = c.BindAndValidate(&req)
if err != nil {
c.String(400, err.Error())
return
}
resp := new(example.HelloResp)
c.JSON(200, resp)
}
// OtherMethod .
// @router /other [POST]
func OtherMethod(ctx context.Context, c *app.RequestContext) {
// you can code something
var err error
var req example.OtherReq
err = c.BindAndValidate(&req)
if err != nil {
c.String(400, err.Error())
return
}
resp := new(example.OtherResp)
c.JSON(200, resp)
}
Precautions
Precautions for using layout templates
When the user uses the layout custom template, the generated layout and rendering data are taken over by the user, so the user needs to provide the rendering data of the defined layout.
Precautions for using package templates
Generally speaking, when users use package templates, most of them are to modify the default handler template; however, hz does not provide a single handler template at present, so when updating an existing handler file, the default handler template will be used to append a new handler function to the end of the handler file. When the corresponding handler file does not exist, a custom template will be used to generate the handler file.