Merge pull request #311 from FGYFFFF/feat/handler_by_method

feat(hz): generate a separate handler file for each method
This commit is contained in:
GuangyuFan 2022-11-17 17:36:57 +08:00 committed by GitHub
commit d6b85fec12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 195 additions and 69 deletions

View File

@ -123,6 +123,7 @@ func Init() *cli.App {
snakeNameFlag := cli.BoolFlag{Name: "snake_tag", Usage: "Use snake_case style naming for tags. (Only works for 'form', 'query', 'json')", Destination: &globalArgs.SnakeName}
customLayout := cli.StringFlag{Name: "customize_layout", Usage: "Specify the layout template. ({{Template Profile}}:{{Rendering Data}})", Destination: &globalArgs.CustomizeLayout}
customPackage := cli.StringFlag{Name: "customize_package", Usage: "Specify the package template. ({{Template Profile}}:)", Destination: &globalArgs.CustomizePackage}
handlerByMethod := cli.BoolFlag{Name: "handler_by_method", Usage: "Generate a separate handler file for each method.", Destination: &globalArgs.HandlerByMethod}
// app
app := cli.NewApp()
@ -164,6 +165,7 @@ func Init() *cli.App {
&excludeFilesFlag,
&customLayout,
&customPackage,
&handlerByMethod,
&protoPluginsFlag,
&thriftPluginsFlag,
},
@ -191,6 +193,7 @@ func Init() *cli.App {
&snakeNameFlag,
&excludeFilesFlag,
&customPackage,
&handlerByMethod,
&protoPluginsFlag,
&thriftPluginsFlag,
},

View File

@ -61,6 +61,7 @@ type Argument struct {
SnakeName bool
Excludes []string
NoRecurse bool
HandlerByMethod bool
CustomizeLayout string
CustomizePackage string

View File

@ -36,6 +36,7 @@ type HttpMethod struct {
ReturnTypeName string
Path string
Serializer string
OutputDir string
// Annotations map[string]string
Models map[string]*model.Model
}
@ -54,29 +55,37 @@ type Client struct {
func (pkgGen *HttpPackageGenerator) genHandler(pkg *HttpPackage, handlerDir, handlerPackage string, root *RouterNode) error {
for _, s := range pkg.Services {
handler := Handler{
FilePath: filepath.Join(handlerDir, util.ToSnakeCase(s.Name)+".go"),
PackageName: util.SplitPackage(handlerPackage, ""),
Methods: s.Methods,
}
handler.Imports = make(map[string]*model.Model, len(s.Methods))
for _, m := range s.Methods {
for key, mm := range m.Models {
if v, ok := handler.Imports[mm.PackageName]; ok && v.Package != mm.Package {
handler.Imports[key] = mm
continue
var handler Handler
if pkgGen.HandlerByMethod { // generate handler by method
for _, m := range s.Methods {
handler = Handler{
FilePath: filepath.Join(handlerDir, m.OutputDir, util.ToSnakeCase(m.Name)+".go"),
PackageName: util.SplitPackage(handlerPackage, ""),
Methods: []*HttpMethod{m},
}
if err := pkgGen.processHandler(&handler, root, handlerDir, m.OutputDir, true); err != nil {
return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
}
if err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {
return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
}
handler.Imports[mm.PackageName] = mm
}
err := root.Update(m, handler.PackageName)
if err != nil {
return err
} else { // generate handler service
handler = Handler{
FilePath: filepath.Join(handlerDir, util.ToSnakeCase(s.Name)+".go"),
PackageName: util.SplitPackage(handlerPackage, ""),
Methods: s.Methods,
}
if err := pkgGen.processHandler(&handler, root, "", "", false); err != nil {
return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
}
if err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {
return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
}
}
handler.Format()
if err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {
return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
}
if len(pkgGen.ClientDir) != 0 {
@ -96,6 +105,31 @@ func (pkgGen *HttpPackageGenerator) genHandler(pkg *HttpPackage, handlerDir, han
return nil
}
func (pkgGen *HttpPackageGenerator) processHandler(handler *Handler, root *RouterNode, handlerDir, projectOutDir string, handlerByMethod bool) error {
singleHandlerPackage := ""
if handlerByMethod {
singleHandlerPackage = util.SubPackage(pkgGen.ProjPackage, filepath.Join(handlerDir, projectOutDir))
}
handler.Imports = make(map[string]*model.Model, len(handler.Methods))
for _, m := range handler.Methods {
// Iterate over the request and return parameters of the method to get import path.
for key, mm := range m.Models {
if v, ok := handler.Imports[mm.PackageName]; ok && v.Package != mm.Package {
handler.Imports[key] = mm
continue
}
handler.Imports[mm.PackageName] = mm
}
err := root.Update(m, handler.PackageName, singleHandlerPackage)
if err != nil {
return err
}
}
handler.Format()
return nil
}
func (pkgGen *HttpPackageGenerator) updateHandler(handler interface{}, handlerTpl, filePath string, noRepeat bool) error {
isExist, err := util.PathExist(filePath)
if err != nil {

View File

@ -44,15 +44,16 @@ type Service struct {
}
type HttpPackageGenerator struct {
ConfigPath string
Backend meta.Backend
Options []Option
ProjPackage string
HandlerDir string
RouterDir string
ModelDir string
ClientDir string
NeedModel bool
ConfigPath string
Backend meta.Backend
Options []Option
ProjPackage string
HandlerDir string
RouterDir string
ModelDir string
ClientDir string
NeedModel bool
HandlerByMethod bool
loadedBackend Backend
curModel *model.Model
@ -135,7 +136,11 @@ func (pkgGen *HttpPackageGenerator) Generate(pkg *HttpPackage) error {
}
}
// this is for handler_by_service, the handler_dir is {$HANDLER_DIR}/{$PKG}
handlerDir := util.SubDir(pkgGen.HandlerDir, pkg.Package)
if pkgGen.HandlerByMethod {
handlerDir = pkgGen.HandlerDir
}
handlerPackage := util.SubPackage(pkgGen.ProjPackage, handlerDir)
routerDir := util.SubDir(pkgGen.RouterDir, pkg.Package)
routerPackage := util.SubPackage(pkgGen.ProjPackage, routerDir)

View File

@ -97,7 +97,9 @@ package {{$.PackageName}}
import (
"github.com/cloudwego/hertz/pkg/app/server"
{{range $k, $v := .HandlerPackages}}{{$k}} "{{$v}}"{{end}}
{{- range $k, $v := .HandlerPackages}}
{{$k}} "{{$v}}"
{{- end}}
)
/*

View File

@ -42,8 +42,10 @@ type RouterNode struct {
Path string
Children childrenRouterInfo
Handler string // {{HandlerPackage}}.{{HandlerName}}
HttpMethod string
Handler string // {{HandlerPackage}}.{{HandlerName}}
HandlerPackage string
HandlerPackageAlias string
HttpMethod string
}
type RegisterDependency struct {
@ -64,7 +66,7 @@ func (routerNode *RouterNode) Sort() {
sort.Sort(routerNode.Children)
}
func (routerNode *RouterNode) Update(method *HttpMethod, handlerType string) error {
func (routerNode *RouterNode) Update(method *HttpMethod, handlerType, handlerPkg string) error {
if method.Path == "" {
return fmt.Errorf("empty path for method '%s'", method.Name)
}
@ -77,7 +79,7 @@ func (routerNode *RouterNode) Update(method *HttpMethod, handlerType string) err
return fmt.Errorf("path '%s' has been registered", method.Path)
}
name := util.ToVarName(paths[:last])
parent.Insert(name, method, handlerType, paths[last:])
parent.Insert(name, method, handlerType, paths[last:], handlerPkg)
parent.Sort()
return nil
}
@ -136,14 +138,36 @@ func (routerNode *RouterNode) DFS(i int, hook func(layer int, node *RouterNode)
return nil
}
func (routerNode *RouterNode) Insert(name string, method *HttpMethod, handlerType string, paths []string) {
var handlerPkgMap map[string]string
func (routerNode *RouterNode) Insert(name string, method *HttpMethod, handlerType string, paths []string, handlerPkg string) {
cur := routerNode
for i, p := range paths {
c := &RouterNode{
Path: "/" + p,
}
if i == len(paths)-1 {
c.Handler = handlerType + "." + method.Name
// generate handler by method
if len(handlerPkg) != 0 {
// get a unique package alias for every handler
pkgAlias := filepath.Base(handlerPkg)
pkgAlias = util.ToVarName([]string{pkgAlias})
val, exist := handlerPkgMap[handlerPkg]
if !exist {
pkgAlias, _ = util.GetHandlerPackageUniqueName(pkgAlias)
if len(handlerPkgMap) == 0 {
handlerPkgMap = make(map[string]string, 10)
}
handlerPkgMap[handlerPkg] = pkgAlias
} else {
pkgAlias = val
}
c.HandlerPackageAlias = pkgAlias
c.Handler = pkgAlias + "." + method.Name
c.HandlerPackage = handlerPkg
} else { // generate handler by service
c.Handler = handlerType + "." + method.Name
}
c.HttpMethod = getHttpMethod(method.HTTPMethod)
}
if cur.Children == nil {
@ -272,6 +296,19 @@ func (pkgGen *HttpPackageGenerator) genRouter(pkg *HttpPackage, root *RouterNode
},
Router: root,
}
if pkgGen.HandlerByMethod {
handlerMap := make(map[string]string, 1)
hook := func(layer int, node *RouterNode) error {
if len(node.HandlerPackage) != 0 {
handlerMap[node.HandlerPackageAlias] = node.HandlerPackage
}
return nil
}
root.DFS(0, hook)
router.HandlerPackages = handlerMap
}
if err := pkgGen.TemplateGenerator.Generate(router, routerTplName, router.FilePath, false); err != nil {
return fmt.Errorf("generate router %s failed, err: %v", router.FilePath, err.Error())
}

View File

@ -1,17 +1,3 @@
// Copyright 2022 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
@ -243,6 +229,14 @@ var file_api_proto_extTypes = []protoimpl.ExtensionInfo{
Tag: "bytes,50308,opt,name=baseurl",
Filename: "api.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50309,
Name: "api.handler_path",
Tag: "bytes,50309,opt,name=handler_path",
Filename: "api.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*int32)(nil),
@ -311,12 +305,14 @@ var (
E_Param = &file_api_proto_extTypes[24] // Whether client requests take public parameters
// optional string baseurl = 50308;
E_Baseurl = &file_api_proto_extTypes[25] // Baseurl used in ttnet routing
// optional string handler_path = 50309;
E_HandlerPath = &file_api_proto_extTypes[26] // handler_path specifies the path to generate the method
)
// Extension fields to descriptorpb.EnumValueOptions.
var (
// optional int32 http_code = 50401;
E_HttpCode = &file_api_proto_extTypes[26]
E_HttpCode = &file_api_proto_extTypes[27]
)
var File_api_proto protoreflect.FileDescriptor
@ -416,12 +412,16 @@ var file_api_proto_rawDesc = []byte{
0x61, 0x6d, 0x3a, 0x3a, 0x0a, 0x07, 0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x12, 0x1e, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x84, 0x89,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x3a, 0x40,
0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e,
0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe1,
0x89, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x68, 0x74, 0x74, 0x70, 0x43, 0x6f, 0x64, 0x65,
0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x3a, 0x43,
0x0a, 0x0c, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1e,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x85,
0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x50,
0x61, 0x74, 0x68, 0x3a, 0x40, 0x0a, 0x09, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0xe1, 0x89, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x68, 0x74, 0x74,
0x70, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69,
}
var file_api_proto_goTypes = []interface{}{
@ -456,11 +456,12 @@ var file_api_proto_depIdxs = []int32{
1, // 23: api.serializer:extendee -> google.protobuf.MethodOptions
1, // 24: api.param:extendee -> google.protobuf.MethodOptions
1, // 25: api.baseurl:extendee -> google.protobuf.MethodOptions
2, // 26: api.http_code:extendee -> google.protobuf.EnumValueOptions
27, // [27:27] is the sub-list for method output_type
27, // [27:27] is the sub-list for method input_type
27, // [27:27] is the sub-list for extension type_name
0, // [0:27] is the sub-list for extension extendee
1, // 26: api.handler_path:extendee -> google.protobuf.MethodOptions
2, // 27: api.http_code:extendee -> google.protobuf.EnumValueOptions
28, // [28:28] is the sub-list for method output_type
28, // [28:28] is the sub-list for method input_type
28, // [28:28] is the sub-list for extension type_name
0, // [0:28] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
@ -476,7 +477,7 @@ func file_api_proto_init() {
RawDescriptor: file_api_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 27,
NumExtensions: 28,
NumServices: 0,
},
GoTypes: file_api_proto_goTypes,

View File

@ -36,6 +36,7 @@ extend google.protobuf.MethodOptions {
optional string serializer = 50306; // Serialization method
optional string param = 50307; // Whether client requests take public parameters
optional string baseurl = 50308; // Baseurl used in ttnet routing
optional string handler_path = 50309; // handler_path specifies the path to generate the method
}
extend google.protobuf.EnumValueOptions {

View File

@ -23,6 +23,7 @@ import (
"github.com/cloudwego/hertz/cmd/hz/internal/generator"
"github.com/cloudwego/hertz/cmd/hz/internal/generator/model"
"github.com/cloudwego/hertz/cmd/hz/internal/protobuf/api"
"github.com/cloudwego/hertz/cmd/hz/internal/util"
"github.com/cloudwego/hertz/cmd/hz/internal/util/logs"
"github.com/jhump/protoreflect/desc"
@ -121,6 +122,13 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver) ([]
}
path := vpath.(string)
var handlerOutDir string
genPath := checkFirstOption(api.E_HandlerPath, m.GetOptions())
handlerOutDir, ok := genPath.(string)
if !ok || len(handlerOutDir) == 0 {
handlerOutDir = ""
}
reqName := m.GetInputType()
sb, err := resolver.ResolveIdentifier(reqName)
reqName = util.BaseName(sb.Scope.GetOptions().GetGoPackage(), "") + "." + sb.Name
@ -145,6 +153,7 @@ func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver) ([]
HTTPMethod: hmethod,
Path: path,
Serializer: serializer,
OutputDir: handlerOutDir,
}
goOptMapAlias := make(map[string]string, 1)

View File

@ -582,8 +582,9 @@ func (plugin *Plugin) genHttpPackage(ast *descriptorpb.FileDescriptorProto, deps
TemplateGenerator: generator.TemplateGenerator{
OutputDir: args.OutDir,
},
ProjPackage: pkg,
Options: options,
ProjPackage: pkg,
Options: options,
HandlerByMethod: args.HandlerByMethod,
}
if args.ModelBackend != "" {

View File

@ -70,6 +70,17 @@ func astToService(ast *parser.Thrift, resolver *Resolver) ([]*generator.Service,
if len(rs) > 1 {
return nil, fmt.Errorf("too many 'api.XXX' annotations: %s", rs)
}
var handlerOutDir string
genPaths := getAnnotation(m.Annotations, ApiGenPath)
if len(genPaths) == 0 {
handlerOutDir = ""
} else if len(genPaths) > 1 {
return nil, fmt.Errorf("too many 'api.handler_path' for %s", m.Name)
} else {
handlerOutDir = genPaths[0]
}
hmethod, path := util.GetFirstKV(rs)
if len(path) != 1 || path[0] == "" {
return nil, fmt.Errorf("invalid api.%s for %s.%s: %s", hmethod, s.Name, m.Name, path)
@ -103,6 +114,7 @@ func astToService(ast *parser.Thrift, resolver *Resolver) ([]*generator.Service,
ReturnTypeName: respName,
Path: path[0],
Serializer: sr,
OutputDir: handlerOutDir,
// Annotations: m.Annotations,
}
refs := resolver.ExportReferred(false, true)

View File

@ -90,6 +90,10 @@ func (plugin *Plugin) Run() int {
options := CheckTagOption(plugin.args)
pkgInfo, err := plugin.getPackageInfo()
if err != nil {
logs.Errorf("get http package info failed: %s", err.Error())
return meta.PluginError
}
cf, _ := util.GetColonPair(args.CustomizePackage)
pkg, err := args.GetGoPackage()
@ -126,8 +130,9 @@ func (plugin *Plugin) Run() int {
TemplateGenerator: generator.TemplateGenerator{
OutputDir: args.OutDir,
},
ProjPackage: pkg,
Options: options,
ProjPackage: pkg,
Options: options,
HandlerByMethod: args.HandlerByMethod,
}
if args.ModelBackend != "" {
sg.Backend = meta.Backend(args.ModelBackend)

View File

@ -56,6 +56,7 @@ const (
ApiAny = "api.any"
ApiPath = "api.path"
ApiSerializer = "api.serializer"
ApiGenPath = "api.handler_path"
)
var (
@ -70,6 +71,10 @@ var (
ApiAny: "ANY",
}
HttpMethodOptionAnnotations = map[string]string{
ApiGenPath: "handler_path",
}
BindingTags = map[string]string{
AnnotationPath: "path",
AnnotationQuery: "query",

View File

@ -366,8 +366,9 @@ func SubDir(root, subPkg string) string {
}
var (
uniquePackageName = map[string]bool{}
uniqueMiddlewareName = map[string]bool{}
uniquePackageName = map[string]bool{}
uniqueMiddlewareName = map[string]bool{}
uniqueHandlerPackageName = map[string]bool{}
)
// GetPackageUniqueName can get a non-repeating variable name for package alias
@ -390,6 +391,15 @@ func GetMiddlewareUniqueName(name string) (string, error) {
return name, nil
}
func GetHandlerPackageUniqueName(name string) (string, error) {
name, err := getUniqueName(name, uniqueHandlerPackageName)
if err != nil {
return "", fmt.Errorf("can not generate unique handler package name: '%s', err: %v", name, err)
}
return name, nil
}
// getUniqueName can get a non-repeating variable name
func getUniqueName(name string, uniqueNameSet map[string]bool) (string, error) {
uniqueName := name