register改良
This commit is contained in:
commit
51c1a6b598
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
9
.idea/hz.iml
Normal file
9
.idea/hz.iml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/hz.iml" filepath="$PROJECT_DIR$/.idea/hz.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
430
app/app.go
Normal file
430
app/app.go
Normal file
@ -0,0 +1,430 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/config"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/protobuf"
|
||||
"github.com/cloudwego/hertz/cmd/hz/thrift"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// global args. MUST fork it when use
|
||||
var globalArgs = config.NewArgument()
|
||||
|
||||
func New(c *cli.Context) error {
|
||||
args, err := globalArgs.Parse(c, meta.CmdNew)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.LoadError)
|
||||
}
|
||||
setLogVerbose(args.Verbose)
|
||||
logs.Debugf("args: %#v\n", args)
|
||||
|
||||
exist, err := util.PathExist(filepath.Join(args.OutDir, meta.ManifestFile))
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.LoadError)
|
||||
}
|
||||
|
||||
if exist && !args.ForceNew {
|
||||
return cli.Exit(fmt.Errorf("the current is already a hertz project, if you want to regenerate it you can specify \"-force\""), meta.LoadError)
|
||||
}
|
||||
|
||||
err = GenerateLayout(args)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.GenerateLayoutError)
|
||||
}
|
||||
|
||||
err = TriggerPlugin(args)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.PluginError)
|
||||
}
|
||||
// ".hz" file converges to the hz tool
|
||||
manifest := new(meta.Manifest)
|
||||
args.InitManifest(manifest)
|
||||
err = manifest.Persist(args.OutDir)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Errorf("persist manifest failed: %v", err), meta.PersistError)
|
||||
}
|
||||
if !args.NeedGoMod && args.IdlType == meta.IdlThrift {
|
||||
logs.Warn(meta.AddThriftReplace)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Update(c *cli.Context) error {
|
||||
// begin to update
|
||||
args, err := globalArgs.Parse(c, meta.CmdUpdate)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.LoadError)
|
||||
}
|
||||
setLogVerbose(args.Verbose)
|
||||
logs.Debugf("Args: %#v\n", args)
|
||||
|
||||
manifest := new(meta.Manifest)
|
||||
err = manifest.InitAndValidate(args.OutDir)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.LoadError)
|
||||
}
|
||||
// update argument by ".hz", can automatically get "handler_dir"/"model_dir"/"router_dir"
|
||||
args.UpdateByManifest(manifest)
|
||||
|
||||
err = TriggerPlugin(args)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.PluginError)
|
||||
}
|
||||
// If the "handler_dir"/"model_dir" is updated, write it back to ".hz"
|
||||
args.UpdateManifest(manifest)
|
||||
err = manifest.Persist(args.OutDir)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Errorf("persist manifest failed: %v", err), meta.PersistError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Model(c *cli.Context) error {
|
||||
args, err := globalArgs.Parse(c, meta.CmdModel)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.LoadError)
|
||||
}
|
||||
setLogVerbose(args.Verbose)
|
||||
logs.Debugf("Args: %#v\n", args)
|
||||
|
||||
err = TriggerPlugin(args)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.PluginError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Client(c *cli.Context) error {
|
||||
args, err := globalArgs.Parse(c, meta.CmdClient)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.LoadError)
|
||||
}
|
||||
setLogVerbose(args.Verbose)
|
||||
logs.Debugf("Args: %#v\n", args)
|
||||
|
||||
err = TriggerPlugin(args)
|
||||
if err != nil {
|
||||
return cli.Exit(err, meta.PluginError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PluginMode() {
|
||||
mode := os.Getenv(meta.EnvPluginMode)
|
||||
if len(os.Args) <= 1 && mode != "" {
|
||||
switch mode {
|
||||
case meta.ThriftPluginName:
|
||||
plugin := new(thrift.Plugin)
|
||||
os.Exit(plugin.Run())
|
||||
case meta.ProtocPluginName:
|
||||
plugin := new(protobuf.Plugin)
|
||||
os.Exit(plugin.Run())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Init() *cli.App {
|
||||
// flags
|
||||
verboseFlag := cli.BoolFlag{Name: "verbose,vv", Usage: "turn on verbose mode", Destination: &globalArgs.Verbose}
|
||||
|
||||
idlFlag := cli.StringSliceFlag{Name: "idl", Usage: "Specify the IDL file path. (.thrift or .proto)"}
|
||||
moduleFlag := cli.StringFlag{Name: "module", Aliases: []string{"mod"}, Usage: "Specify the Go module name.", Destination: &globalArgs.Gomod}
|
||||
serviceNameFlag := cli.StringFlag{Name: "service", Usage: "Specify the service name.", Destination: &globalArgs.ServiceName}
|
||||
outDirFlag := cli.StringFlag{Name: "out_dir", Usage: "Specify the project path.", Destination: &globalArgs.OutDir}
|
||||
handlerDirFlag := cli.StringFlag{Name: "handler_dir", Usage: "Specify the handler relative path (based on \"out_dir\").", Destination: &globalArgs.HandlerDir}
|
||||
modelDirFlag := cli.StringFlag{Name: "model_dir", Usage: "Specify the model relative path (based on \"out_dir\").", Destination: &globalArgs.ModelDir}
|
||||
routerDirFlag := cli.StringFlag{Name: "router_dir", Usage: "Specify the router relative path (based on \"out_dir\").", Destination: &globalArgs.RouterDir}
|
||||
useFlag := cli.StringFlag{Name: "use", Usage: "Specify the model package to import for handler.", Destination: &globalArgs.Use}
|
||||
baseDomainFlag := cli.StringFlag{Name: "base_domain", Usage: "Specify the request domain.", Destination: &globalArgs.BaseDomain}
|
||||
clientDirFlag := cli.StringFlag{Name: "client_dir", Usage: "Specify the client path. If not specified, IDL generated path is used for 'client' command; no client code is generated for 'new' command", Destination: &globalArgs.ClientDir}
|
||||
forceClientDirFlag := cli.StringFlag{Name: "force_client_dir", Usage: "Specify the client path, and won't use namespaces as subpaths", Destination: &globalArgs.ForceClientDir}
|
||||
|
||||
optPkgFlag := cli.StringSliceFlag{Name: "option_package", Aliases: []string{"P"}, Usage: "Specify the package path. ({include_path}={import_path})"}
|
||||
includesFlag := cli.StringSliceFlag{Name: "proto_path", Aliases: []string{"I"}, Usage: "Add an IDL search path for includes. (Valid only if idl is protobuf)"}
|
||||
excludeFilesFlag := cli.StringSliceFlag{Name: "exclude_file", Aliases: []string{"E"}, Usage: "Specify the files that do not need to be updated."}
|
||||
thriftOptionsFlag := cli.StringSliceFlag{Name: "thriftgo", Aliases: []string{"t"}, Usage: "Specify arguments for the thriftgo. ({flag}={value})"}
|
||||
protoOptionsFlag := cli.StringSliceFlag{Name: "protoc", Aliases: []string{"p"}, Usage: "Specify arguments for the protoc. ({flag}={value})"}
|
||||
thriftPluginsFlag := cli.StringSliceFlag{Name: "thrift-plugins", Usage: "Specify plugins for the thriftgo. ({plugin_name}:{options})"}
|
||||
protoPluginsFlag := cli.StringSliceFlag{Name: "protoc-plugins", Usage: "Specify plugins for the protoc. ({plugin_name}:{options}:{out_dir})"}
|
||||
noRecurseFlag := cli.BoolFlag{Name: "no_recurse", Usage: "Generate master model only.", Destination: &globalArgs.NoRecurse}
|
||||
forceNewFlag := cli.BoolFlag{Name: "force", Aliases: []string{"f"}, Usage: "Force new a project, which will overwrite the generated files", Destination: &globalArgs.ForceNew}
|
||||
enableExtendsFlag := cli.BoolFlag{Name: "enable_extends", Usage: "Parse 'extends' for thrift IDL", Destination: &globalArgs.EnableExtends}
|
||||
|
||||
jsonEnumStrFlag := cli.BoolFlag{Name: "json_enumstr", Usage: "Use string instead of num for json enums when idl is thrift.", Destination: &globalArgs.JSONEnumStr}
|
||||
unsetOmitemptyFlag := cli.BoolFlag{Name: "unset_omitempty", Usage: "Remove 'omitempty' tag for generated struct.", Destination: &globalArgs.UnsetOmitempty}
|
||||
protoCamelJSONTag := cli.BoolFlag{Name: "pb_camel_json_tag", Usage: "Convert Name style for json tag to camel(Only works protobuf).", Destination: &globalArgs.ProtobufCamelJSONTag}
|
||||
snakeNameFlag := cli.BoolFlag{Name: "snake_tag", Usage: "Use snake_case style naming for tags. (Only works for 'form', 'query', 'json')", Destination: &globalArgs.SnakeName}
|
||||
rmTagFlag := cli.StringSliceFlag{Name: "rm_tag", Usage: "Remove the default tag(json/query/form). If the annotation tag is set explicitly, it will not be removed."}
|
||||
customLayout := cli.StringFlag{Name: "customize_layout", Usage: "Specify the path for layout template.", Destination: &globalArgs.CustomizeLayout}
|
||||
customLayoutData := cli.StringFlag{Name: "customize_layout_data_path", Usage: "Specify the path for layout template render data.", Destination: &globalArgs.CustomizeLayoutData}
|
||||
customPackage := cli.StringFlag{Name: "customize_package", Usage: "Specify the path for package template.", 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()
|
||||
app.Name = "hz"
|
||||
app.Usage = "A idl parser and code generator for Hertz projects"
|
||||
app.Version = meta.Version
|
||||
// The default separator for multiple parameters is modified to ";"
|
||||
app.SliceFlagSeparator = ";"
|
||||
|
||||
// global flags
|
||||
app.Flags = []cli.Flag{
|
||||
&verboseFlag,
|
||||
}
|
||||
|
||||
// Commands
|
||||
app.Commands = []*cli.Command{
|
||||
{
|
||||
Name: meta.CmdNew,
|
||||
Usage: "Generate a new Hertz project",
|
||||
Flags: []cli.Flag{
|
||||
&idlFlag,
|
||||
&serviceNameFlag,
|
||||
&moduleFlag,
|
||||
&outDirFlag,
|
||||
&handlerDirFlag,
|
||||
&modelDirFlag,
|
||||
&routerDirFlag,
|
||||
&clientDirFlag,
|
||||
&useFlag,
|
||||
|
||||
&includesFlag,
|
||||
&thriftOptionsFlag,
|
||||
&protoOptionsFlag,
|
||||
&optPkgFlag,
|
||||
&noRecurseFlag,
|
||||
&forceNewFlag,
|
||||
&enableExtendsFlag,
|
||||
|
||||
&jsonEnumStrFlag,
|
||||
&unsetOmitemptyFlag,
|
||||
&protoCamelJSONTag,
|
||||
&snakeNameFlag,
|
||||
&rmTagFlag,
|
||||
&excludeFilesFlag,
|
||||
&customLayout,
|
||||
&customLayoutData,
|
||||
&customPackage,
|
||||
&handlerByMethod,
|
||||
&protoPluginsFlag,
|
||||
&thriftPluginsFlag,
|
||||
},
|
||||
Action: New,
|
||||
},
|
||||
{
|
||||
Name: meta.CmdUpdate,
|
||||
Usage: "Update an existing Hertz project",
|
||||
Flags: []cli.Flag{
|
||||
&idlFlag,
|
||||
&moduleFlag,
|
||||
&outDirFlag,
|
||||
&handlerDirFlag,
|
||||
&modelDirFlag,
|
||||
&clientDirFlag,
|
||||
&useFlag,
|
||||
|
||||
&includesFlag,
|
||||
&thriftOptionsFlag,
|
||||
&protoOptionsFlag,
|
||||
&optPkgFlag,
|
||||
&noRecurseFlag,
|
||||
&enableExtendsFlag,
|
||||
|
||||
&jsonEnumStrFlag,
|
||||
&unsetOmitemptyFlag,
|
||||
&protoCamelJSONTag,
|
||||
&snakeNameFlag,
|
||||
&rmTagFlag,
|
||||
&excludeFilesFlag,
|
||||
&customPackage,
|
||||
&handlerByMethod,
|
||||
&protoPluginsFlag,
|
||||
&thriftPluginsFlag,
|
||||
},
|
||||
Action: Update,
|
||||
},
|
||||
{
|
||||
Name: meta.CmdModel,
|
||||
Usage: "Generate model code only",
|
||||
Flags: []cli.Flag{
|
||||
&idlFlag,
|
||||
&moduleFlag,
|
||||
&outDirFlag,
|
||||
&modelDirFlag,
|
||||
|
||||
&includesFlag,
|
||||
&thriftOptionsFlag,
|
||||
&protoOptionsFlag,
|
||||
&noRecurseFlag,
|
||||
|
||||
&jsonEnumStrFlag,
|
||||
&unsetOmitemptyFlag,
|
||||
&protoCamelJSONTag,
|
||||
&snakeNameFlag,
|
||||
&rmTagFlag,
|
||||
&excludeFilesFlag,
|
||||
},
|
||||
Action: Model,
|
||||
},
|
||||
{
|
||||
Name: meta.CmdClient,
|
||||
Usage: "Generate hertz client based on IDL",
|
||||
Flags: []cli.Flag{
|
||||
&idlFlag,
|
||||
&moduleFlag,
|
||||
&baseDomainFlag,
|
||||
&modelDirFlag,
|
||||
&clientDirFlag,
|
||||
&useFlag,
|
||||
&forceClientDirFlag,
|
||||
|
||||
&includesFlag,
|
||||
&thriftOptionsFlag,
|
||||
&protoOptionsFlag,
|
||||
&noRecurseFlag,
|
||||
&enableExtendsFlag,
|
||||
|
||||
&jsonEnumStrFlag,
|
||||
&unsetOmitemptyFlag,
|
||||
&protoCamelJSONTag,
|
||||
&snakeNameFlag,
|
||||
&rmTagFlag,
|
||||
&excludeFilesFlag,
|
||||
&customPackage,
|
||||
&protoPluginsFlag,
|
||||
&thriftPluginsFlag,
|
||||
},
|
||||
Action: Client,
|
||||
},
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
func setLogVerbose(verbose bool) {
|
||||
if verbose {
|
||||
logs.SetLevel(logs.LevelDebug)
|
||||
} else {
|
||||
logs.SetLevel(logs.LevelWarn)
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateLayout(args *config.Argument) error {
|
||||
lg := &generator.LayoutGenerator{
|
||||
TemplateGenerator: generator.TemplateGenerator{
|
||||
OutputDir: args.OutDir,
|
||||
Excludes: args.Excludes,
|
||||
},
|
||||
}
|
||||
|
||||
layout := generator.Layout{
|
||||
GoModule: args.Gomod,
|
||||
ServiceName: args.ServiceName,
|
||||
UseApacheThrift: args.IdlType == meta.IdlThrift,
|
||||
HasIdl: 0 != len(args.IdlPaths),
|
||||
ModelDir: args.ModelDir,
|
||||
HandlerDir: args.HandlerDir,
|
||||
RouterDir: args.RouterDir,
|
||||
NeedGoMod: args.NeedGoMod,
|
||||
}
|
||||
|
||||
if args.CustomizeLayout == "" {
|
||||
// generate by default
|
||||
err := lg.GenerateByService(layout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating layout failed: %v", err)
|
||||
}
|
||||
} else {
|
||||
// generate by customized layout
|
||||
configPath, dataPath := args.CustomizeLayout, args.CustomizeLayoutData
|
||||
logs.Infof("get customized layout info, layout_config_path: %s, template_data_path: %s", configPath, dataPath)
|
||||
exist, err := util.PathExist(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check customized layout config file exist failed: %v", err)
|
||||
}
|
||||
if !exist {
|
||||
return errors.New("layout_config_path doesn't exist")
|
||||
}
|
||||
lg.ConfigPath = configPath
|
||||
// generate by service info
|
||||
if dataPath == "" {
|
||||
err := lg.GenerateByService(layout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating layout failed: %v", err)
|
||||
}
|
||||
} else {
|
||||
// generate by customized data
|
||||
err := lg.GenerateByConfig(dataPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating layout failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := lg.Persist()
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating layout failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TriggerPlugin(args *config.Argument) error {
|
||||
if len(args.IdlPaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
cmd, err := config.BuildPluginCmd(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("build plugin command failed: %v", err)
|
||||
}
|
||||
|
||||
compiler, err := config.IdlTypeToCompiler(args.IdlType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get compiler failed: %v", err)
|
||||
}
|
||||
|
||||
logs.Debugf("begin to trigger plugin, compiler: %s, idl_paths: %v", compiler, args.IdlPaths)
|
||||
buf, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
out := strings.TrimSpace(string(buf))
|
||||
if !strings.HasSuffix(out, meta.TheUseOptionMessage) {
|
||||
return fmt.Errorf("plugin %s_gen_hertz returns error: %v, cause:\n%v", compiler, err, string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
// If len(buf) != 0, the plugin returned the log.
|
||||
if len(buf) != 0 {
|
||||
fmt.Println(string(buf))
|
||||
}
|
||||
logs.Debugf("end run plugin %s_gen_hertz", compiler)
|
||||
return nil
|
||||
}
|
397
config/argument.go
Normal file
397
config/argument.go
Normal file
@ -0,0 +1,397 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
type Argument struct {
|
||||
// Mode meta.Mode // operating mode(0-compiler, 1-plugin)
|
||||
CmdType string // command type
|
||||
Verbose bool // print verbose log
|
||||
Cwd string // execution path
|
||||
OutDir string // output path
|
||||
HandlerDir string // handler path
|
||||
ModelDir string // model path
|
||||
RouterDir string // router path
|
||||
ClientDir string // client path
|
||||
BaseDomain string // request domain
|
||||
ForceClientDir string // client dir (not use namespace as a subpath)
|
||||
|
||||
IdlType string // idl type
|
||||
IdlPaths []string // master idl path
|
||||
RawOptPkg []string // user-specified package import path
|
||||
OptPkgMap map[string]string
|
||||
Includes []string
|
||||
PkgPrefix string
|
||||
|
||||
Gopath string // $GOPATH
|
||||
Gosrc string // $GOPATH/src
|
||||
Gomod string
|
||||
Gopkg string // $GOPATH/src/{{gopkg}}
|
||||
ServiceName string // service name
|
||||
Use string
|
||||
NeedGoMod bool
|
||||
|
||||
JSONEnumStr bool
|
||||
UnsetOmitempty bool
|
||||
ProtobufCamelJSONTag bool
|
||||
ProtocOptions []string // options to pass through to protoc
|
||||
ThriftOptions []string // options to pass through to thriftgo for go flag
|
||||
ProtobufPlugins []string
|
||||
ThriftPlugins []string
|
||||
SnakeName bool
|
||||
RmTags []string
|
||||
Excludes []string
|
||||
NoRecurse bool
|
||||
HandlerByMethod bool
|
||||
ForceNew bool
|
||||
SnakeStyleMiddleware bool
|
||||
EnableExtends bool
|
||||
|
||||
CustomizeLayout string
|
||||
CustomizeLayoutData string
|
||||
CustomizePackage string
|
||||
ModelBackend string
|
||||
}
|
||||
|
||||
func NewArgument() *Argument {
|
||||
return &Argument{
|
||||
OptPkgMap: make(map[string]string),
|
||||
Includes: make([]string, 0, 4),
|
||||
Excludes: make([]string, 0, 4),
|
||||
ProtocOptions: make([]string, 0, 4),
|
||||
ThriftOptions: make([]string, 0, 4),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse initializes a new argument based on its own information
|
||||
func (arg *Argument) Parse(c *cli.Context, cmd string) (*Argument, error) {
|
||||
// v2 cli cannot put the StringSlice flag to struct, so we need to parse it here
|
||||
arg.parseStringSlice(c)
|
||||
args := arg.Fork()
|
||||
args.CmdType = cmd
|
||||
|
||||
err := args.checkPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = args.checkIDL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = args.checkPackage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (arg *Argument) parseStringSlice(c *cli.Context) {
|
||||
arg.IdlPaths = c.StringSlice("idl")
|
||||
arg.Includes = c.StringSlice("proto_path")
|
||||
arg.Excludes = c.StringSlice("exclude_file")
|
||||
arg.RawOptPkg = c.StringSlice("option_package")
|
||||
arg.ThriftOptions = c.StringSlice("thriftgo")
|
||||
arg.ProtocOptions = c.StringSlice("protoc")
|
||||
arg.ThriftPlugins = c.StringSlice("thrift-plugins")
|
||||
arg.ProtobufPlugins = c.StringSlice("protoc-plugins")
|
||||
arg.RmTags = c.StringSlice("rm_tag")
|
||||
}
|
||||
|
||||
func (arg *Argument) UpdateByManifest(m *meta.Manifest) {
|
||||
if arg.HandlerDir == "" && m.HandlerDir != "" {
|
||||
logs.Infof("use \"handler_dir\" in \".hz\" as the handler generated dir\n")
|
||||
arg.HandlerDir = m.HandlerDir
|
||||
}
|
||||
if arg.ModelDir == "" && m.ModelDir != "" {
|
||||
logs.Infof("use \"model_dir\" in \".hz\" as the model generated dir\n")
|
||||
arg.ModelDir = m.ModelDir
|
||||
}
|
||||
if len(m.RouterDir) != 0 {
|
||||
logs.Infof("use \"router_dir\" in \".hz\" as the router generated dir\n")
|
||||
arg.RouterDir = m.RouterDir
|
||||
}
|
||||
}
|
||||
|
||||
// checkPath sets the project path and verifies that the model、handler、router and client path is compliant
|
||||
func (arg *Argument) checkPath() error {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current path failed: %s", err)
|
||||
}
|
||||
arg.Cwd = dir
|
||||
if arg.OutDir == "" {
|
||||
arg.OutDir = dir
|
||||
}
|
||||
if !filepath.IsAbs(arg.OutDir) {
|
||||
ap := filepath.Join(arg.Cwd, arg.OutDir)
|
||||
arg.OutDir = ap
|
||||
}
|
||||
if arg.ModelDir != "" && filepath.IsAbs(arg.ModelDir) {
|
||||
return fmt.Errorf("model path %s must be relative to out_dir", arg.ModelDir)
|
||||
}
|
||||
if arg.HandlerDir != "" && filepath.IsAbs(arg.HandlerDir) {
|
||||
return fmt.Errorf("handler path %s must be relative to out_dir", arg.HandlerDir)
|
||||
}
|
||||
if arg.RouterDir != "" && filepath.IsAbs(arg.RouterDir) {
|
||||
return fmt.Errorf("router path %s must be relative to out_dir", arg.RouterDir)
|
||||
}
|
||||
if arg.ClientDir != "" && filepath.IsAbs(arg.ClientDir) {
|
||||
return fmt.Errorf("router path %s must be relative to out_dir", arg.ClientDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkIDL check if the idl path exists, set and check the idl type
|
||||
func (arg *Argument) checkIDL() error {
|
||||
for i, path := range arg.IdlPaths {
|
||||
abPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("idl path %s is not absolute", path)
|
||||
}
|
||||
ext := filepath.Ext(abPath)
|
||||
if ext == "" || ext[0] != '.' {
|
||||
return fmt.Errorf("idl path %s is not a valid file", path)
|
||||
}
|
||||
ext = ext[1:]
|
||||
switch ext {
|
||||
case meta.IdlThrift:
|
||||
arg.IdlType = meta.IdlThrift
|
||||
case meta.IdlProto:
|
||||
arg.IdlType = meta.IdlProto
|
||||
default:
|
||||
return fmt.Errorf("IDL type %s is not supported", ext)
|
||||
}
|
||||
arg.IdlPaths[i] = abPath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (arg *Argument) IsUpdate() bool {
|
||||
return arg.CmdType == meta.CmdUpdate
|
||||
}
|
||||
|
||||
func (arg *Argument) IsNew() bool {
|
||||
return arg.CmdType == meta.CmdNew
|
||||
}
|
||||
|
||||
// checkPackage check and set the gopath、 module and package name
|
||||
func (arg *Argument) checkPackage() error {
|
||||
gopath, err := util.GetGOPATH()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get gopath failed: %s", err)
|
||||
}
|
||||
if gopath == "" {
|
||||
return fmt.Errorf("GOPATH is not set")
|
||||
}
|
||||
|
||||
arg.Gopath = gopath
|
||||
arg.Gosrc = filepath.Join(gopath, "src")
|
||||
|
||||
// Generate the project under gopath, use the relative path as the package name
|
||||
if strings.HasPrefix(arg.Cwd, arg.Gosrc) {
|
||||
if gopkg, err := filepath.Rel(arg.Gosrc, arg.Cwd); err != nil {
|
||||
return fmt.Errorf("get relative path to GOPATH/src failed: %s", err)
|
||||
} else {
|
||||
arg.Gopkg = gopkg
|
||||
}
|
||||
}
|
||||
if len(arg.Gomod) == 0 { // not specified "go module"
|
||||
// search go.mod recursively
|
||||
module, path, ok := util.SearchGoMod(arg.Cwd, true)
|
||||
if ok { // find go.mod in upper level, use it as project module, don't generate go.mod
|
||||
rel, err := filepath.Rel(path, arg.Cwd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can not get relative path, err :%v", err)
|
||||
}
|
||||
arg.Gomod = filepath.Join(module, rel)
|
||||
logs.Debugf("find module '%s' from '%s/go.mod', so use it as module name", module, path)
|
||||
}
|
||||
if len(arg.Gomod) == 0 { // don't find go.mod in upper level, use relative path as module name, generate go.mod
|
||||
logs.Debugf("use gopath's relative path '%s' as the module name", arg.Gopkg)
|
||||
// gopkg will be "" under non-gopath
|
||||
arg.Gomod = arg.Gopkg
|
||||
arg.NeedGoMod = true
|
||||
}
|
||||
arg.Gomod = util.PathToImport(arg.Gomod, "")
|
||||
} else { // specified "go module"
|
||||
// search go.mod in current path
|
||||
module, path, ok := util.SearchGoMod(arg.Cwd, false)
|
||||
if ok { // go.mod exists in current path, check module name, don't generate go.mod
|
||||
if module != arg.Gomod {
|
||||
return fmt.Errorf("module name given by the '-module/mod' option ('%s') is not consist with the name defined in go.mod ('%s' from %s), try to remove '-module/mod' option in your command\n", arg.Gomod, module, path)
|
||||
}
|
||||
} else { // go.mod don't exist in current path, generate go.mod
|
||||
arg.NeedGoMod = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(arg.Gomod) == 0 {
|
||||
return fmt.Errorf("can not get go module, please specify a module name with the '-module/mod' flag")
|
||||
}
|
||||
|
||||
if len(arg.RawOptPkg) > 0 {
|
||||
arg.OptPkgMap = make(map[string]string, len(arg.RawOptPkg))
|
||||
for _, op := range arg.RawOptPkg {
|
||||
ps := strings.SplitN(op, "=", 2)
|
||||
if len(ps) != 2 {
|
||||
return fmt.Errorf("invalid option package: %s", op)
|
||||
}
|
||||
arg.OptPkgMap[ps[0]] = ps[1]
|
||||
}
|
||||
arg.RawOptPkg = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (arg *Argument) Pack() ([]string, error) {
|
||||
data, err := util.PackArgs(arg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pack argument failed: %s", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (arg *Argument) Unpack(data []string) error {
|
||||
err := util.UnpackArgs(data, arg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unpack argument failed: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fork can copy its own parameters to a new argument
|
||||
func (arg *Argument) Fork() *Argument {
|
||||
args := NewArgument()
|
||||
*args = *arg
|
||||
util.CopyString2StringMap(arg.OptPkgMap, args.OptPkgMap)
|
||||
util.CopyStringSlice(&arg.Includes, &args.Includes)
|
||||
util.CopyStringSlice(&arg.Excludes, &args.Excludes)
|
||||
util.CopyStringSlice(&arg.ProtocOptions, &args.ProtocOptions)
|
||||
util.CopyStringSlice(&arg.ThriftOptions, &args.ThriftOptions)
|
||||
return args
|
||||
}
|
||||
|
||||
func (arg *Argument) GetGoPackage() (string, error) {
|
||||
if arg.Gomod != "" {
|
||||
return arg.Gomod, nil
|
||||
} else if arg.Gopkg != "" {
|
||||
return arg.Gopkg, nil
|
||||
}
|
||||
return "", fmt.Errorf("project package name is not set")
|
||||
}
|
||||
|
||||
func IdlTypeToCompiler(idlType string) (string, error) {
|
||||
switch idlType {
|
||||
case meta.IdlProto:
|
||||
return meta.TpCompilerProto, nil
|
||||
case meta.IdlThrift:
|
||||
return meta.TpCompilerThrift, nil
|
||||
default:
|
||||
return "", fmt.Errorf("IDL type %s is not supported", idlType)
|
||||
}
|
||||
}
|
||||
|
||||
func (arg *Argument) ModelPackagePrefix() (string, error) {
|
||||
ret := arg.Gomod
|
||||
if arg.ModelDir == "" {
|
||||
path, err := util.RelativePath(meta.ModelDir)
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret += path
|
||||
} else {
|
||||
path, err := util.RelativePath(arg.ModelDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret += "/" + path
|
||||
}
|
||||
return strings.ReplaceAll(ret, string(filepath.Separator), "/"), nil
|
||||
}
|
||||
|
||||
func (arg *Argument) ModelOutDir() string {
|
||||
ret := arg.OutDir
|
||||
if arg.ModelDir == "" {
|
||||
ret = filepath.Join(ret, meta.ModelDir)
|
||||
} else {
|
||||
ret = filepath.Join(ret, arg.ModelDir)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (arg *Argument) GetHandlerDir() (string, error) {
|
||||
if arg.HandlerDir == "" {
|
||||
return util.RelativePath(meta.HandlerDir)
|
||||
}
|
||||
return util.RelativePath(arg.HandlerDir)
|
||||
}
|
||||
|
||||
func (arg *Argument) GetModelDir() (string, error) {
|
||||
if arg.ModelDir == "" {
|
||||
return util.RelativePath(meta.ModelDir)
|
||||
}
|
||||
return util.RelativePath(arg.ModelDir)
|
||||
}
|
||||
|
||||
func (arg *Argument) GetRouterDir() (string, error) {
|
||||
if arg.RouterDir == "" {
|
||||
return util.RelativePath(meta.RouterDir)
|
||||
}
|
||||
return util.RelativePath(arg.RouterDir)
|
||||
}
|
||||
|
||||
func (arg *Argument) GetClientDir() (string, error) {
|
||||
if arg.ClientDir == "" {
|
||||
return "", nil
|
||||
}
|
||||
return util.RelativePath(arg.ClientDir)
|
||||
}
|
||||
|
||||
func (arg *Argument) InitManifest(m *meta.Manifest) {
|
||||
m.Version = meta.Version
|
||||
m.HandlerDir = arg.HandlerDir
|
||||
m.ModelDir = arg.ModelDir
|
||||
m.RouterDir = arg.RouterDir
|
||||
}
|
||||
|
||||
func (arg *Argument) UpdateManifest(m *meta.Manifest) {
|
||||
m.Version = meta.Version
|
||||
if arg.HandlerDir != m.HandlerDir {
|
||||
m.HandlerDir = arg.HandlerDir
|
||||
}
|
||||
if arg.HandlerDir != m.ModelDir {
|
||||
m.ModelDir = arg.ModelDir
|
||||
}
|
||||
// "router_dir" must not be defined by "update" command
|
||||
}
|
187
config/cmd.go
Normal file
187
config/cmd.go
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
)
|
||||
|
||||
func lookupTool(idlType string) (string, error) {
|
||||
tool := meta.TpCompilerThrift
|
||||
if idlType == meta.IdlProto {
|
||||
tool = meta.TpCompilerProto
|
||||
}
|
||||
|
||||
path, err := exec.LookPath(tool)
|
||||
logs.Debugf("[DEBUG]path:%v", path)
|
||||
if err != nil {
|
||||
goPath, err := util.GetGOPATH()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get 'GOPATH' failed for find %s : %v", tool, path)
|
||||
}
|
||||
path = filepath.Join(goPath, "bin", tool)
|
||||
}
|
||||
|
||||
isExist, err := util.PathExist(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("check '%s' path error: %v", path, err)
|
||||
}
|
||||
|
||||
if !isExist {
|
||||
if tool == meta.TpCompilerThrift {
|
||||
// If thriftgo does not exist, the latest version will be installed automatically.
|
||||
err := util.InstallAndCheckThriftgo()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't install '%s' automatically, please install it manually for https://github.com/cloudwego/thriftgo, err : %v", tool, err)
|
||||
}
|
||||
} else {
|
||||
// todo: protoc automatic installation
|
||||
return "", fmt.Errorf("%s is not installed, please install it first", tool)
|
||||
}
|
||||
}
|
||||
|
||||
if tool == meta.TpCompilerThrift {
|
||||
// If thriftgo exists, the version is detected; if the version is lower than v0.2.0 then the latest version of thriftgo is automatically installed.
|
||||
err := util.CheckAndUpdateThriftgo()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("update thriftgo version failed, please install it manually for https://github.com/cloudwego/thriftgo, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// link removes the previous symbol link and rebuilds a new one.
|
||||
func link(src, dst string) error {
|
||||
err := syscall.Unlink(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("unlink %q: %s", dst, err)
|
||||
}
|
||||
err = os.Symlink(src, dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("symlink %q: %s", dst, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildPluginCmd(args *Argument) (*exec.Cmd, error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to detect current executable, err: %v", err)
|
||||
}
|
||||
|
||||
argPacks, err := args.Pack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kas := strings.Join(argPacks, ",")
|
||||
|
||||
path, err := lookupTool(args.IdlType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := &exec.Cmd{
|
||||
Path: path,
|
||||
}
|
||||
|
||||
if args.IdlType == meta.IdlThrift {
|
||||
// thriftgo
|
||||
os.Setenv(meta.EnvPluginMode, meta.ThriftPluginName)
|
||||
cmd.Args = append(cmd.Args, meta.TpCompilerThrift)
|
||||
for _, inc := range args.Includes {
|
||||
cmd.Args = append(cmd.Args, "-i", inc)
|
||||
}
|
||||
|
||||
if args.Verbose {
|
||||
cmd.Args = append(cmd.Args, "-v")
|
||||
}
|
||||
thriftOpt, err := args.GetThriftgoOptions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.Args = append(cmd.Args,
|
||||
"-o", args.ModelOutDir(),
|
||||
"-g", thriftOpt,
|
||||
"-p", "hertz="+exe+":"+kas,
|
||||
)
|
||||
for _, p := range args.ThriftPlugins {
|
||||
cmd.Args = append(cmd.Args, "-p", p)
|
||||
}
|
||||
if !args.NoRecurse {
|
||||
cmd.Args = append(cmd.Args, "-r")
|
||||
}
|
||||
} else {
|
||||
// protoc
|
||||
os.Setenv(meta.EnvPluginMode, meta.ProtocPluginName)
|
||||
cmd.Args = append(cmd.Args, meta.TpCompilerProto)
|
||||
for _, inc := range args.Includes {
|
||||
cmd.Args = append(cmd.Args, "-I", inc)
|
||||
}
|
||||
for _, inc := range args.IdlPaths {
|
||||
cmd.Args = append(cmd.Args, "-I", filepath.Dir(inc))
|
||||
}
|
||||
cmd.Args = append(cmd.Args,
|
||||
"--plugin=protoc-gen-hertz="+exe,
|
||||
"--hertz_out="+args.OutDir,
|
||||
"--hertz_opt="+kas,
|
||||
)
|
||||
for _, p := range args.ProtobufPlugins {
|
||||
pluginParams := strings.Split(p, ":")
|
||||
if len(pluginParams) != 3 {
|
||||
logs.Warnf("Failed to get the correct protoc plugin parameters for %. "+
|
||||
"Please specify the protoc plugin in the form of \"plugin_name:options:out_dir\"", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
// pluginParams[0] -> plugin name, pluginParams[1] -> plugin options, pluginParams[2] -> out_dir
|
||||
cmd.Args = append(cmd.Args,
|
||||
fmt.Sprintf("--%s_out=%s", pluginParams[0], pluginParams[2]),
|
||||
fmt.Sprintf("--%s_opt=%s", pluginParams[0], pluginParams[1]),
|
||||
)
|
||||
}
|
||||
for _, kv := range args.ProtocOptions {
|
||||
cmd.Args = append(cmd.Args, "--"+kv)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Args = append(cmd.Args, args.IdlPaths...)
|
||||
logs.Infof(strings.Join(cmd.Args, " "))
|
||||
logs.Flush()
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (arg *Argument) GetThriftgoOptions() (string, error) {
|
||||
defaultOpt := "reserve_comments,gen_json_tag=false,"
|
||||
prefix, err := arg.ModelPackagePrefix()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
arg.ThriftOptions = append(arg.ThriftOptions, "package_prefix="+prefix)
|
||||
if arg.JSONEnumStr {
|
||||
arg.ThriftOptions = append(arg.ThriftOptions, "json_enum_as_text")
|
||||
}
|
||||
gas := "go:" + defaultOpt + strings.Join(arg.ThriftOptions, ",")
|
||||
return gas, nil
|
||||
}
|
20
doc.go
Normal file
20
doc.go
Normal file
@ -0,0 +1,20 @@
|
||||
// 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.
|
||||
//
|
||||
|
||||
// Package "github.com/cloudwego/hertz/cmd/hz" contains packages for building the hz command line tool.
|
||||
// APIs exported by packages under this directory do not promise any backward
|
||||
// compatibility, so please do not rely on them.
|
||||
|
||||
package main
|
100
generator/client.go
Normal file
100
generator/client.go
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
)
|
||||
|
||||
type ClientMethod struct {
|
||||
*HttpMethod
|
||||
BodyParamsCode string
|
||||
QueryParamsCode string
|
||||
PathParamsCode string
|
||||
HeaderParamsCode string
|
||||
FormValueCode string
|
||||
FormFileCode string
|
||||
}
|
||||
|
||||
type ClientFile struct {
|
||||
FilePath string
|
||||
PackageName string
|
||||
ServiceName string
|
||||
BaseDomain string
|
||||
Imports map[string]*model.Model
|
||||
ClientMethods []*ClientMethod
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) genClient(pkg *HttpPackage, clientDir string) error {
|
||||
for _, s := range pkg.Services {
|
||||
cliDir := util.SubDir(clientDir, util.ToSnakeCase(s.Name))
|
||||
if len(pkgGen.ForceClientDir) != 0 {
|
||||
cliDir = pkgGen.ForceClientDir
|
||||
}
|
||||
hertzClientPath := filepath.Join(cliDir, hertzClientTplName)
|
||||
isExist, err := util.PathExist(hertzClientPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseDomain := s.BaseDomain
|
||||
if len(pkgGen.BaseDomain) != 0 {
|
||||
baseDomain = pkgGen.BaseDomain
|
||||
}
|
||||
client := ClientFile{
|
||||
FilePath: filepath.Join(cliDir, util.ToSnakeCase(s.Name)+".go"),
|
||||
PackageName: util.ToSnakeCase(filepath.Base(cliDir)),
|
||||
ServiceName: util.ToCamelCase(s.Name),
|
||||
ClientMethods: s.ClientMethods,
|
||||
BaseDomain: baseDomain,
|
||||
}
|
||||
if !isExist {
|
||||
err := pkgGen.TemplateGenerator.Generate(client, hertzClientTplName, hertzClientPath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
client.Imports = make(map[string]*model.Model, len(client.ClientMethods))
|
||||
for _, m := range client.ClientMethods {
|
||||
// Iterate over the request and return parameters of the method to get import path.
|
||||
for key, mm := range m.Models {
|
||||
if v, ok := client.Imports[mm.PackageName]; ok && v.Package != mm.Package {
|
||||
client.Imports[key] = mm
|
||||
continue
|
||||
}
|
||||
client.Imports[mm.PackageName] = mm
|
||||
}
|
||||
}
|
||||
if len(pkgGen.UseDir) != 0 {
|
||||
oldModelDir := filepath.Clean(filepath.Join(pkgGen.ProjPackage, pkgGen.ModelDir))
|
||||
newModelDir := filepath.Clean(pkgGen.UseDir)
|
||||
for _, m := range client.ClientMethods {
|
||||
for _, mm := range m.Models {
|
||||
mm.Package = strings.Replace(mm.Package, oldModelDir, newModelDir, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
err = pkgGen.TemplateGenerator.Generate(client, idlClientName, client.FilePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
657
generator/custom_files.go
Normal file
657
generator/custom_files.go
Normal file
@ -0,0 +1,657 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
)
|
||||
|
||||
type FilePathRenderInfo struct {
|
||||
MasterIDLName string // master IDL name
|
||||
GenPackage string // master IDL generate code package
|
||||
HandlerDir string // handler generate dir
|
||||
ModelDir string // model generate dir
|
||||
RouterDir string // router generate dir
|
||||
ProjectDir string // projectDir
|
||||
GoModule string // go module
|
||||
ServiceName string // service name, changed as services are traversed
|
||||
MethodName string // method name, changed as methods are traversed
|
||||
HandlerGenPath string // "api.gen_path" value
|
||||
}
|
||||
|
||||
type IDLPackageRenderInfo struct {
|
||||
FilePathRenderInfo
|
||||
ServiceInfos *HttpPackage
|
||||
}
|
||||
|
||||
type CustomizedFileForMethod struct {
|
||||
*HttpMethod
|
||||
FilePath string
|
||||
FilePackage string
|
||||
ServiceInfo *Service // service info for this method
|
||||
IDLPackageInfo *IDLPackageRenderInfo // IDL info for this service
|
||||
}
|
||||
|
||||
type CustomizedFileForService struct {
|
||||
*Service
|
||||
FilePath string
|
||||
FilePackage string
|
||||
IDLPackageInfo *IDLPackageRenderInfo // IDL info for this service
|
||||
}
|
||||
|
||||
type CustomizedFileForIDL struct {
|
||||
*IDLPackageRenderInfo
|
||||
FilePath string
|
||||
FilePackage string
|
||||
}
|
||||
|
||||
// todo: 1. how to import other file, if the other file name is a template
|
||||
|
||||
// genCustomizedFile generate customized file template
|
||||
func (pkgGen *HttpPackageGenerator) genCustomizedFile(pkg *HttpPackage) error {
|
||||
filePathRenderInfo := FilePathRenderInfo{
|
||||
MasterIDLName: pkg.IdlName,
|
||||
GenPackage: pkg.Package,
|
||||
HandlerDir: pkgGen.HandlerDir,
|
||||
ModelDir: pkgGen.ModelDir,
|
||||
RouterDir: pkgGen.RouterDir,
|
||||
ProjectDir: pkgGen.OutputDir,
|
||||
GoModule: pkgGen.ProjPackage,
|
||||
// methodName & serviceName will change as traverse
|
||||
}
|
||||
|
||||
idlPackageRenderInfo := IDLPackageRenderInfo{
|
||||
FilePathRenderInfo: filePathRenderInfo,
|
||||
ServiceInfos: pkg,
|
||||
}
|
||||
|
||||
for _, tplInfo := range pkgGen.tplsInfo {
|
||||
// the default template has been automatically generated by the tool, so skip
|
||||
if tplInfo.Default {
|
||||
continue
|
||||
}
|
||||
|
||||
// loop generate file
|
||||
if tplInfo.LoopService || tplInfo.LoopMethod {
|
||||
loopMethod := tplInfo.LoopMethod
|
||||
loopService := tplInfo.LoopService
|
||||
if loopService && !loopMethod { // only loop service
|
||||
for _, service := range idlPackageRenderInfo.ServiceInfos.Services {
|
||||
filePathRenderInfo.ServiceName = service.Name
|
||||
err := pkgGen.genLoopService(tplInfo, filePathRenderInfo, service, &idlPackageRenderInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else { // loop service & method, because if loop method, the service must be looped
|
||||
for _, service := range idlPackageRenderInfo.ServiceInfos.Services {
|
||||
for _, method := range service.Methods {
|
||||
filePathRenderInfo.ServiceName = service.Name
|
||||
filePathRenderInfo.MethodName = method.Name
|
||||
filePathRenderInfo.HandlerGenPath = method.OutputDir
|
||||
err := pkgGen.genLoopMethod(tplInfo, filePathRenderInfo, method, service, &idlPackageRenderInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // generate customized file single
|
||||
err := pkgGen.genSingleCustomizedFile(tplInfo, filePathRenderInfo, idlPackageRenderInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderFilePath used to render file path template to get real file path
|
||||
func renderFilePath(tplInfo *Template, filePathRenderInfo FilePathRenderInfo) (string, error) {
|
||||
tpl, err := template.New(tplInfo.Path).Funcs(funcMap).Parse(tplInfo.Path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse file path template(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
filePath := bytes.NewBuffer(nil)
|
||||
err = tpl.Execute(filePath, filePathRenderInfo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("render file path template(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
|
||||
return filePath.String(), nil
|
||||
}
|
||||
|
||||
func renderInsertKey(tplInfo *Template, data interface{}) (string, error) {
|
||||
tpl, err := template.New(tplInfo.UpdateBehavior.InsertKey).Funcs(funcMap).Parse(tplInfo.UpdateBehavior.InsertKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse insert key template(%s) failed, err: %v", tplInfo.UpdateBehavior.InsertKey, err)
|
||||
}
|
||||
insertKey := bytes.NewBuffer(nil)
|
||||
err = tpl.Execute(insertKey, data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("render insert key template(%s) failed, err: %v", tplInfo.UpdateBehavior.InsertKey, err)
|
||||
}
|
||||
|
||||
return insertKey.String(), nil
|
||||
}
|
||||
|
||||
// renderImportTpl will render import template
|
||||
// it will return the []string, like blow:
|
||||
// ["import", alias "import", import]
|
||||
// other format will be error
|
||||
func renderImportTpl(tplInfo *Template, data interface{}) ([]string, error) {
|
||||
var importList []string
|
||||
for _, impt := range tplInfo.UpdateBehavior.ImportTpl {
|
||||
tpl, err := template.New(impt).Funcs(funcMap).Parse(impt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse import template(%s) failed, err: %v", impt, err)
|
||||
}
|
||||
imptContent := bytes.NewBuffer(nil)
|
||||
err = tpl.Execute(imptContent, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("render import template(%s) failed, err: %v", impt, err)
|
||||
}
|
||||
importList = append(importList, imptContent.String())
|
||||
}
|
||||
var ret []string
|
||||
for _, impts := range importList {
|
||||
// 'import render result' may have multiple imports
|
||||
if strings.Contains(impts, "\n") {
|
||||
for _, impt := range strings.Split(impts, "\n") {
|
||||
ret = append(ret, impt)
|
||||
}
|
||||
} else {
|
||||
ret = append(ret, impts)
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// renderAppendContent used to render append content for 'update' command
|
||||
func renderAppendContent(tplInfo *Template, renderInfo interface{}) (string, error) {
|
||||
tpl, err := template.New(tplInfo.Path).Funcs(funcMap).Parse(tplInfo.UpdateBehavior.AppendTpl)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse append content template(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
appendContent := bytes.NewBuffer(nil)
|
||||
err = tpl.Execute(appendContent, renderInfo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("render append content template(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
|
||||
return appendContent.String(), nil
|
||||
}
|
||||
|
||||
// appendUpdateFile used to append content to file for 'update' command
|
||||
func appendUpdateFile(tplInfo *Template, renderInfo interface{}, fileContent []byte) ([]byte, error) {
|
||||
// render insert content
|
||||
appendContent, err := renderAppendContent(tplInfo, renderInfo)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err = buf.Write(fileContent)
|
||||
if err != nil {
|
||||
return []byte(""), fmt.Errorf("write file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
// "\r\n" && "\n" has the same suffix
|
||||
if !bytes.HasSuffix(buf.Bytes(), []byte("\n")) {
|
||||
_, err = buf.WriteString("\n")
|
||||
if err != nil {
|
||||
return []byte(""), fmt.Errorf("write file(%s) line break failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
}
|
||||
_, err = buf.WriteString(appendContent)
|
||||
if err != nil {
|
||||
return []byte(""), fmt.Errorf("append file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func getInsertImportContent(tplInfo *Template, renderInfo interface{}, fileContent []byte) ([][2]string, error) {
|
||||
importContent, err := renderImportTpl(tplInfo, renderInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var imptSlice [][2]string
|
||||
for _, impt := range importContent {
|
||||
// import has to format
|
||||
// 1. alias "import"
|
||||
// 2. "import"
|
||||
// 3. import (can not contain '"')
|
||||
impt = strings.TrimSpace(impt)
|
||||
if !strings.Contains(impt, "\"") { // 3. import (can not contain '"')
|
||||
if bytes.Contains(fileContent, []byte(impt)) {
|
||||
continue
|
||||
}
|
||||
imptSlice = append(imptSlice, [2]string{"", impt})
|
||||
} else {
|
||||
if !strings.HasSuffix(impt, "\"") {
|
||||
return nil, fmt.Errorf("import can not has suffix \"\"\", for file: %s", tplInfo.Path)
|
||||
}
|
||||
if strings.HasPrefix(impt, "\"") { // 2. "import"
|
||||
if bytes.Contains(fileContent, []byte(impt[1:len(impt)-1])) {
|
||||
continue
|
||||
}
|
||||
imptSlice = append(imptSlice, [2]string{"", impt[1 : len(impt)-1]})
|
||||
} else { // 3. alias "import"
|
||||
idx := strings.Index(impt, "\n")
|
||||
if idx == -1 {
|
||||
return nil, fmt.Errorf("error import format for file: %s", tplInfo.Path)
|
||||
}
|
||||
if bytes.Contains(fileContent, []byte(impt[idx+1:len(impt)-1])) {
|
||||
continue
|
||||
}
|
||||
imptSlice = append(imptSlice, [2]string{impt[:idx], impt[idx+1 : len(impt)-1]})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imptSlice, nil
|
||||
}
|
||||
|
||||
// genLoopService used to generate files by 'service'
|
||||
func (pkgGen *HttpPackageGenerator) genLoopService(tplInfo *Template, filePathRenderInfo FilePathRenderInfo, service *Service, idlPackageRenderInfo *IDLPackageRenderInfo) error {
|
||||
filePath, err := renderFilePath(tplInfo, filePathRenderInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// determine if a custom file exists
|
||||
exist, err := util.PathExist(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("judge file(%s) exists failed, err: %v", filePath, err)
|
||||
}
|
||||
if !exist { // create file
|
||||
data := CustomizedFileForService{
|
||||
Service: service,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
IDLPackageInfo: idlPackageRenderInfo,
|
||||
}
|
||||
err = pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
switch tplInfo.UpdateBehavior.Type {
|
||||
case Skip:
|
||||
// do nothing
|
||||
logs.Infof("do not update file '%s', because the update behavior is 'Unchanged'", filePath)
|
||||
case Cover:
|
||||
// re-generate
|
||||
logs.Infof("re-generate file '%s', because the update behavior is 'Regenerate'", filePath)
|
||||
data := CustomizedFileForService{
|
||||
Service: service,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
IDLPackageInfo: idlPackageRenderInfo,
|
||||
}
|
||||
err := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case Append: // todo: append logic need to be optimized for method
|
||||
fileContent, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var appendContent []byte
|
||||
// get insert content
|
||||
if tplInfo.UpdateBehavior.AppendKey == "method" {
|
||||
for _, method := range service.Methods {
|
||||
data := CustomizedFileForMethod{
|
||||
HttpMethod: method,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
ServiceInfo: service,
|
||||
IDLPackageInfo: idlPackageRenderInfo,
|
||||
}
|
||||
insertKey, err := renderInsertKey(tplInfo, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Contains(fileContent, []byte(insertKey)) {
|
||||
continue
|
||||
}
|
||||
imptSlice, err := getInsertImportContent(tplInfo, data, fileContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// insert new import to the fileContent
|
||||
for _, impt := range imptSlice {
|
||||
if bytes.Contains(fileContent, []byte(impt[1])) {
|
||||
continue
|
||||
}
|
||||
fileContent, err = util.AddImportForContent(fileContent, impt[0], impt[1])
|
||||
// insert import error do not influence the generated file
|
||||
if err != nil {
|
||||
logs.Warnf("can not add import(%s) for file(%s), err: %v\n", impt[1], filePath, err)
|
||||
}
|
||||
}
|
||||
appendContent, err = appendUpdateFile(tplInfo, data, appendContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(tplInfo.UpdateBehavior.AppendLocation) == 0 { // default, append to end of file
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err = buf.Write(fileContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
_, err = buf.Write(appendContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("append file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
logs.Infof("append content for file '%s', because the update behavior is 'Append' and appendKey is 'method'", filePath)
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, ""})
|
||||
} else { // 'append location', append new content after 'append location'
|
||||
part := bytes.Split(fileContent, []byte(tplInfo.UpdateBehavior.AppendLocation))
|
||||
if len(part) == 0 {
|
||||
return fmt.Errorf("can not find append location '%s' for file '%s'\n", tplInfo.UpdateBehavior.AppendLocation, filePath)
|
||||
}
|
||||
if len(part) != 2 {
|
||||
return fmt.Errorf("do not support multiple append location '%s' for file '%s'\n", tplInfo.UpdateBehavior.AppendLocation, filePath)
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err = writeBytes(buf, part[0], []byte(tplInfo.UpdateBehavior.AppendLocation), appendContent, part[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("write file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
logs.Infof("append content for file '%s', because the update behavior is 'Append' and appendKey is 'method'", filePath)
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, ""})
|
||||
}
|
||||
} else {
|
||||
logs.Warnf("Loop 'service' field for '%s' only append content by appendKey for 'method', so cannot append content", filePath)
|
||||
}
|
||||
default:
|
||||
// do nothing
|
||||
logs.Warnf("unknown update behavior, do nothing")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// genLoopMethod used to generate files by 'method'
|
||||
func (pkgGen *HttpPackageGenerator) genLoopMethod(tplInfo *Template, filePathRenderInfo FilePathRenderInfo, method *HttpMethod, service *Service, idlPackageRenderInfo *IDLPackageRenderInfo) error {
|
||||
filePath, err := renderFilePath(tplInfo, filePathRenderInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// determine if a custom file exists
|
||||
exist, err := util.PathExist(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("judge file(%s) exists failed, err: %v", filePath, err)
|
||||
}
|
||||
|
||||
if !exist { // create file
|
||||
data := CustomizedFileForMethod{
|
||||
HttpMethod: method,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
ServiceInfo: service,
|
||||
IDLPackageInfo: idlPackageRenderInfo,
|
||||
}
|
||||
err := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
switch tplInfo.UpdateBehavior.Type {
|
||||
case Skip:
|
||||
// do nothing
|
||||
logs.Infof("do not update file '%s', because the update behavior is 'Unchanged'", filePath)
|
||||
case Cover:
|
||||
// re-generate
|
||||
logs.Infof("re-generate file '%s', because the update behavior is 'Regenerate'", filePath)
|
||||
data := CustomizedFileForMethod{
|
||||
HttpMethod: method,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
ServiceInfo: service,
|
||||
IDLPackageInfo: idlPackageRenderInfo,
|
||||
}
|
||||
err := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case Append:
|
||||
// for loop method, no need to append something; so do nothing
|
||||
logs.Warnf("do not append content for file '%s', because the update behavior is 'Append' and loop 'method' have no need to append content", filePath)
|
||||
default:
|
||||
// do nothing
|
||||
logs.Warnf("unknown update behavior, do nothing")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// genSingleCustomizedFile used to generate file by 'master IDL'
|
||||
func (pkgGen *HttpPackageGenerator) genSingleCustomizedFile(tplInfo *Template, filePathRenderInfo FilePathRenderInfo, idlPackageRenderInfo IDLPackageRenderInfo) error {
|
||||
// generate file single
|
||||
filePath, err := renderFilePath(tplInfo, filePathRenderInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// determine if a custom file exists
|
||||
exist, err := util.PathExist(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("judge file(%s) exists failed, err: %v", filePath, err)
|
||||
}
|
||||
|
||||
if !exist { // create file
|
||||
data := CustomizedFileForIDL{
|
||||
IDLPackageRenderInfo: &idlPackageRenderInfo,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
}
|
||||
err := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
switch tplInfo.UpdateBehavior.Type {
|
||||
case Skip:
|
||||
// do nothing
|
||||
logs.Infof("do not update file '%s', because the update behavior is 'Unchanged'", filePath)
|
||||
case Cover:
|
||||
// re-generate
|
||||
logs.Infof("re-generate file '%s', because the update behavior is 'Regenerate'", filePath)
|
||||
data := CustomizedFileForIDL{
|
||||
IDLPackageRenderInfo: &idlPackageRenderInfo,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
}
|
||||
err := pkgGen.TemplateGenerator.Generate(data, tplInfo.Path, filePath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case Append: // todo: append logic need to be optimized for single file
|
||||
fileContent, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tplInfo.UpdateBehavior.AppendKey == "method" {
|
||||
var appendContent []byte
|
||||
for _, service := range idlPackageRenderInfo.ServiceInfos.Services {
|
||||
for _, method := range service.Methods {
|
||||
data := CustomizedFileForMethod{
|
||||
HttpMethod: method,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
ServiceInfo: service,
|
||||
IDLPackageInfo: &idlPackageRenderInfo,
|
||||
}
|
||||
insertKey, err := renderInsertKey(tplInfo, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Contains(fileContent, []byte(insertKey)) {
|
||||
continue
|
||||
}
|
||||
imptSlice, err := getInsertImportContent(tplInfo, data, fileContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, impt := range imptSlice {
|
||||
if bytes.Contains(fileContent, []byte(impt[1])) {
|
||||
continue
|
||||
}
|
||||
fileContent, err = util.AddImportForContent(fileContent, impt[0], impt[1])
|
||||
if err != nil {
|
||||
logs.Warnf("can not add import(%s) for file(%s)\n", impt[1], filePath)
|
||||
}
|
||||
}
|
||||
|
||||
appendContent, err = appendUpdateFile(tplInfo, data, appendContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(tplInfo.UpdateBehavior.AppendLocation) == 0 { // default, append to end of file
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err = buf.Write(fileContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
_, err = buf.Write(appendContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("append file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
logs.Infof("append content for file '%s', because the update behavior is 'Append' and appendKey is 'method'", filePath)
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, ""})
|
||||
} else { // 'append location', append new content after 'append location'
|
||||
part := bytes.Split(fileContent, []byte(tplInfo.UpdateBehavior.AppendLocation))
|
||||
if len(part) == 0 {
|
||||
return fmt.Errorf("can not find append location '%s' for file '%s'\n", tplInfo.UpdateBehavior.AppendLocation, filePath)
|
||||
}
|
||||
if len(part) != 2 {
|
||||
return fmt.Errorf("do not support multiple append location '%s' for file '%s'\n", tplInfo.UpdateBehavior.AppendLocation, filePath)
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err = writeBytes(buf, part[0], []byte(tplInfo.UpdateBehavior.AppendLocation), appendContent, part[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("write file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
logs.Infof("append content for file '%s', because the update behavior is 'Append' and appendKey is 'method'", filePath)
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, ""})
|
||||
}
|
||||
} else if tplInfo.UpdateBehavior.AppendKey == "service" {
|
||||
var appendContent []byte
|
||||
for _, service := range idlPackageRenderInfo.ServiceInfos.Services {
|
||||
data := CustomizedFileForService{
|
||||
Service: service,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
IDLPackageInfo: &idlPackageRenderInfo,
|
||||
}
|
||||
insertKey, err := renderInsertKey(tplInfo, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Contains(fileContent, []byte(insertKey)) {
|
||||
continue
|
||||
}
|
||||
imptSlice, err := getInsertImportContent(tplInfo, data, fileContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, impt := range imptSlice {
|
||||
if bytes.Contains(fileContent, []byte(impt[1])) {
|
||||
continue
|
||||
}
|
||||
fileContent, err = util.AddImportForContent(fileContent, impt[0], impt[1])
|
||||
if err != nil {
|
||||
logs.Warnf("can not add import(%s) for file(%s)\n", impt[1], filePath)
|
||||
}
|
||||
}
|
||||
appendContent, err = appendUpdateFile(tplInfo, data, appendContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(tplInfo.UpdateBehavior.AppendLocation) == 0 { // default, append to end of file
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err = buf.Write(fileContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
_, err = buf.Write(appendContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("append file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
logs.Infof("append content for file '%s', because the update behavior is 'Append' and appendKey is 'service'", filePath)
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, ""})
|
||||
} else { // 'append location', append new content after 'append location'
|
||||
part := bytes.Split(fileContent, []byte(tplInfo.UpdateBehavior.AppendLocation))
|
||||
if len(part) == 0 {
|
||||
return fmt.Errorf("can not find append location '%s' for file '%s'\n", tplInfo.UpdateBehavior.AppendLocation, filePath)
|
||||
}
|
||||
if len(part) != 2 {
|
||||
return fmt.Errorf("do not support multiple append location '%s' for file '%s'\n", tplInfo.UpdateBehavior.AppendLocation, filePath)
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err = writeBytes(buf, part[0], []byte(tplInfo.UpdateBehavior.AppendLocation), appendContent, part[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("write file(%s) failed, err: %v", tplInfo.Path, err)
|
||||
}
|
||||
logs.Infof("append content for file '%s', because the update behavior is 'Append' and appendKey is 'service'", filePath)
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, buf.String(), false, ""})
|
||||
}
|
||||
} else { // add append content to the file directly
|
||||
data := CustomizedFileForIDL{
|
||||
IDLPackageRenderInfo: &idlPackageRenderInfo,
|
||||
FilePath: filePath,
|
||||
FilePackage: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
}
|
||||
file, err := appendUpdateFile(tplInfo, data, fileContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, string(file), false, ""})
|
||||
}
|
||||
default:
|
||||
// do nothing
|
||||
logs.Warnf("unknown update behavior, do nothing")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeBytes(buf *bytes.Buffer, bytes ...[]byte) error {
|
||||
for _, b := range bytes {
|
||||
_, err := buf.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
46
generator/file.go
Normal file
46
generator/file.go
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/format"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Path string
|
||||
Content string
|
||||
NoRepeat bool
|
||||
FileTplName string
|
||||
}
|
||||
|
||||
// Lint is used to statically analyze and format go code
|
||||
func (file *File) Lint() error {
|
||||
name := filepath.Base(file.Path)
|
||||
if strings.HasSuffix(name, ".go") {
|
||||
out, err := format.Source(util.Str2Bytes(file.Content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("lint file '%s' failed, err: %v", name, err.Error())
|
||||
}
|
||||
file.Content = util.Bytes2Str(out)
|
||||
}
|
||||
return nil
|
||||
}
|
320
generator/handler.go
Normal file
320
generator/handler.go
Normal file
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
)
|
||||
|
||||
type HttpMethod struct {
|
||||
Name string
|
||||
HTTPMethod string
|
||||
Comment string
|
||||
RequestTypeName string
|
||||
RequestTypePackage string
|
||||
RequestTypeRawName string
|
||||
ReturnTypeName string
|
||||
ReturnTypePackage string
|
||||
ReturnTypeRawName string
|
||||
Path string
|
||||
Serializer string
|
||||
OutputDir string
|
||||
RefPackage string // handler import dir
|
||||
RefPackageAlias string // handler import alias
|
||||
ModelPackage map[string]string
|
||||
GenHandler bool // Whether to generate one handler, when an idl interface corresponds to multiple http method
|
||||
// Annotations map[string]string
|
||||
Models map[string]*model.Model
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
FilePath string
|
||||
PackageName string
|
||||
ProjPackage string
|
||||
Imports map[string]*model.Model
|
||||
Methods []*HttpMethod
|
||||
}
|
||||
|
||||
type SingleHandler struct {
|
||||
*HttpMethod
|
||||
FilePath string
|
||||
PackageName string
|
||||
ProjPackage string
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Handler
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) genHandler(pkg *HttpPackage, handlerDir, handlerPackage string, root *RouterNode) error {
|
||||
for _, s := range pkg.Services {
|
||||
var handler Handler
|
||||
if pkgGen.HandlerByMethod { // generate handler by method
|
||||
for _, m := range s.Methods {
|
||||
filePath := filepath.Join(handlerDir, m.OutputDir, util.ToSnakeCase(m.Name)+".go")
|
||||
handler = Handler{
|
||||
FilePath: filePath,
|
||||
PackageName: util.SplitPackage(filepath.Dir(filePath), ""),
|
||||
Methods: []*HttpMethod{m},
|
||||
ProjPackage: pkgGen.ProjPackage,
|
||||
}
|
||||
|
||||
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 m.GenHandler {
|
||||
if err := pkgGen.updateHandler(handler, handlerTplName, handler.FilePath, false); err != nil {
|
||||
return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // generate handler service
|
||||
tmpHandlerDir := handlerDir
|
||||
tmpHandlerPackage := handlerPackage
|
||||
if len(s.ServiceGenDir) != 0 {
|
||||
tmpHandlerDir = s.ServiceGenDir
|
||||
tmpHandlerPackage = util.SubPackage(pkgGen.ProjPackage, tmpHandlerDir)
|
||||
}
|
||||
handler = Handler{
|
||||
FilePath: filepath.Join(tmpHandlerDir, util.ToSnakeCase(s.Name)+".go"),
|
||||
PackageName: util.SplitPackage(tmpHandlerPackage, ""),
|
||||
Methods: s.Methods,
|
||||
ProjPackage: pkgGen.ProjPackage,
|
||||
}
|
||||
|
||||
for _, m := range s.Methods {
|
||||
m.RefPackage = tmpHandlerPackage
|
||||
m.RefPackageAlias = util.BaseName(tmpHandlerPackage, "")
|
||||
}
|
||||
|
||||
if err := pkgGen.processHandler(&handler, root, "", "", false); err != nil {
|
||||
return fmt.Errorf("generate handler %s failed, err: %v", handler.FilePath, err.Error())
|
||||
}
|
||||
|
||||
// Avoid generating duplicate handlers when IDL interface corresponds to multiple http methods
|
||||
methods := handler.Methods
|
||||
handler.Methods = []*HttpMethod{}
|
||||
for _, m := range methods {
|
||||
if m.GenHandler {
|
||||
handler.Methods = append(handler.Methods, m)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
clientDir := util.SubDir(pkgGen.ClientDir, pkg.Package)
|
||||
clientPackage := util.SubPackage(pkgGen.ProjPackage, clientDir)
|
||||
client := Client{}
|
||||
client.Handler = handler
|
||||
client.ServiceName = s.Name
|
||||
client.PackageName = util.SplitPackage(clientPackage, "")
|
||||
client.FilePath = filepath.Join(clientDir, util.ToSnakeCase(s.Name)+".go")
|
||||
if err := pkgGen.updateClient(client, clientTplName, client.FilePath, false); err != nil {
|
||||
return fmt.Errorf("generate client %s failed, err: %v", client.FilePath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if len(pkgGen.UseDir) != 0 {
|
||||
oldModelDir := filepath.Clean(filepath.Join(pkgGen.ProjPackage, pkgGen.ModelDir))
|
||||
newModelDir := filepath.Clean(pkgGen.UseDir)
|
||||
for _, m := range handler.Methods {
|
||||
for _, mm := range m.Models {
|
||||
mm.Package = strings.Replace(mm.Package, oldModelDir, newModelDir, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler.Format()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) updateHandler(handler interface{}, handlerTpl, filePath string, noRepeat bool) error {
|
||||
if pkgGen.tplsInfo[handlerTpl].Disable {
|
||||
return nil
|
||||
}
|
||||
isExist, err := util.PathExist(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isExist {
|
||||
return pkgGen.TemplateGenerator.Generate(handler, handlerTpl, filePath, noRepeat)
|
||||
}
|
||||
if pkgGen.HandlerByMethod { // method by handler, do not need to insert new content
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// insert new model imports
|
||||
for alias, model := range handler.(Handler).Imports {
|
||||
if bytes.Contains(file, []byte(model.Package)) {
|
||||
continue
|
||||
}
|
||||
file, err = util.AddImportForContent(file, alias, model.Package)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// insert customized imports
|
||||
if tplInfo, exist := pkgGen.TemplateGenerator.tplsInfo[handlerTpl]; exist {
|
||||
if len(tplInfo.UpdateBehavior.ImportTpl) != 0 {
|
||||
imptSlice, err := getInsertImportContent(tplInfo, handler, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, impt := range imptSlice {
|
||||
if bytes.Contains(file, []byte(impt[1])) {
|
||||
continue
|
||||
}
|
||||
file, err = util.AddImportForContent(file, impt[0], impt[1])
|
||||
if err != nil {
|
||||
logs.Warnf("can not add import(%s) for file(%s), err: %v\n", impt[1], filePath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert new handler
|
||||
for _, method := range handler.(Handler).Methods {
|
||||
if bytes.Contains(file, []byte(fmt.Sprintf("func %s(", method.Name))) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate additional handlers using templates
|
||||
handlerSingleTpl := pkgGen.tpls[handlerSingleTplName]
|
||||
if handlerSingleTpl == nil {
|
||||
return fmt.Errorf("tpl %s not found", handlerSingleTplName)
|
||||
}
|
||||
data := SingleHandler{
|
||||
HttpMethod: method,
|
||||
FilePath: handler.(Handler).FilePath,
|
||||
PackageName: handler.(Handler).PackageName,
|
||||
ProjPackage: handler.(Handler).ProjPackage,
|
||||
}
|
||||
handlerFunc := bytes.NewBuffer(nil)
|
||||
err = handlerSingleTpl.Execute(handlerFunc, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template \"%s\" failed, %v", handlerSingleTplName, err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err = buf.Write(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write handler \"%s\" failed, %v", method.Name, err)
|
||||
}
|
||||
_, err = buf.Write(handlerFunc.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("write handler \"%s\" failed, %v", method.Name, err)
|
||||
}
|
||||
file = buf.Bytes()
|
||||
}
|
||||
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, string(file), false, ""})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) updateClient(client interface{}, clientTpl, filePath string, noRepeat bool) error {
|
||||
isExist, err := util.PathExist(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isExist {
|
||||
return pkgGen.TemplateGenerator.Generate(client, clientTpl, filePath, noRepeat)
|
||||
}
|
||||
logs.Infof("Client file:%s has been generated, so don't update it", filePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *HttpMethod) InitComment() {
|
||||
text := strings.TrimLeft(strings.TrimSpace(m.Comment), "/")
|
||||
if text == "" {
|
||||
text = "// " + m.Name + " ."
|
||||
} else if strings.HasPrefix(text, m.Name) {
|
||||
text = "// " + text
|
||||
} else {
|
||||
text = "// " + m.Name + " " + text
|
||||
}
|
||||
text = strings.Replace(text, "\n", "\n// ", -1)
|
||||
if !strings.Contains(text, "@router ") {
|
||||
text += "\n// @router " + m.Path
|
||||
}
|
||||
m.Comment = text + " [" + m.HTTPMethod + "]"
|
||||
}
|
||||
|
||||
func MapSerializer(serializer string) string {
|
||||
switch serializer {
|
||||
case "json":
|
||||
return "JSON"
|
||||
case "thrift":
|
||||
return "Thrift"
|
||||
case "pb":
|
||||
return "ProtoBuf"
|
||||
default:
|
||||
return "JSON"
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Format() {
|
||||
for _, m := range h.Methods {
|
||||
m.Serializer = MapSerializer(m.Serializer)
|
||||
m.InitComment()
|
||||
}
|
||||
}
|
232
generator/layout.go
Normal file
232
generator/layout.go
Normal file
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Layout contains the basic information of idl
|
||||
type Layout struct {
|
||||
OutDir string
|
||||
GoModule string
|
||||
ServiceName string
|
||||
UseApacheThrift bool
|
||||
HasIdl bool
|
||||
NeedGoMod bool
|
||||
ModelDir string
|
||||
HandlerDir string
|
||||
RouterDir string
|
||||
}
|
||||
|
||||
// LayoutGenerator contains the information generated by generating the layout template
|
||||
type LayoutGenerator struct {
|
||||
ConfigPath string
|
||||
TemplateGenerator
|
||||
}
|
||||
|
||||
var (
|
||||
layoutConfig = defaultLayoutConfig
|
||||
packageConfig = defaultPkgConfig
|
||||
)
|
||||
|
||||
func SetDefaultTemplateConfig() {
|
||||
layoutConfig = defaultLayoutConfig
|
||||
packageConfig = defaultPkgConfig
|
||||
}
|
||||
|
||||
func (lg *LayoutGenerator) Init() error {
|
||||
config := layoutConfig
|
||||
// unmarshal from user-defined config file if it exists
|
||||
if lg.ConfigPath != "" {
|
||||
cdata, err := ioutil.ReadFile(lg.ConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read layout config from %s failed, err: %v", lg.ConfigPath, err.Error())
|
||||
}
|
||||
config = TemplateConfig{}
|
||||
if err = yaml.Unmarshal(cdata, &config); err != nil {
|
||||
return fmt.Errorf("unmarshal layout config failed, err: %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(config, TemplateConfig{}) {
|
||||
return errors.New("empty config")
|
||||
}
|
||||
lg.Config = &config
|
||||
|
||||
return lg.TemplateGenerator.Init()
|
||||
}
|
||||
|
||||
// checkInited initialize template definition
|
||||
func (lg *LayoutGenerator) checkInited() error {
|
||||
if lg.tpls == nil || lg.dirs == nil {
|
||||
if err := lg.Init(); err != nil {
|
||||
return fmt.Errorf("init layout config failed, err: %v", err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lg *LayoutGenerator) Generate(data map[string]interface{}) error {
|
||||
if err := lg.checkInited(); err != nil {
|
||||
return err
|
||||
}
|
||||
return lg.TemplateGenerator.Generate(data, "", "", false)
|
||||
}
|
||||
|
||||
func (lg *LayoutGenerator) GenerateByService(service Layout) error {
|
||||
if err := lg.checkInited(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(service.HandlerDir) != 0 {
|
||||
// override the default "biz/handler/ping.go" to "HANDLER_DIR/ping.go"
|
||||
defaultPingDir := defaultHandlerDir + sp + "ping.go"
|
||||
if tpl, exist := lg.tpls[defaultPingDir]; exist {
|
||||
delete(lg.tpls, defaultPingDir)
|
||||
newPingDir := filepath.Clean(service.HandlerDir + sp + "ping.go")
|
||||
lg.tpls[newPingDir] = tpl
|
||||
}
|
||||
}
|
||||
|
||||
if len(service.RouterDir) != 0 {
|
||||
defaultRegisterDir := defaultRouterDir + sp + registerTplName
|
||||
if tpl, exist := lg.tpls[defaultRegisterDir]; exist {
|
||||
delete(lg.tpls, defaultRegisterDir)
|
||||
newRegisterDir := filepath.Clean(service.RouterDir + sp + registerTplName)
|
||||
lg.tpls[newRegisterDir] = tpl
|
||||
}
|
||||
}
|
||||
|
||||
if !service.NeedGoMod {
|
||||
gomodFile := "go.mod"
|
||||
if _, exist := lg.tpls[gomodFile]; exist {
|
||||
delete(lg.tpls, gomodFile)
|
||||
}
|
||||
}
|
||||
|
||||
if util.IsWindows() {
|
||||
buildSh := "build.sh"
|
||||
bootstrapSh := defaultScriptDir + sp + "bootstrap.sh"
|
||||
if _, exist := lg.tpls[buildSh]; exist {
|
||||
delete(lg.tpls, buildSh)
|
||||
}
|
||||
if _, exist := lg.tpls[bootstrapSh]; exist {
|
||||
delete(lg.tpls, bootstrapSh)
|
||||
}
|
||||
}
|
||||
|
||||
sd, err := serviceToLayoutData(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rd, err := serviceToRouterData(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if service.HasIdl {
|
||||
for k := range lg.tpls {
|
||||
if strings.Contains(k, registerTplName) {
|
||||
delete(lg.tpls, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"*": sd,
|
||||
layoutConfig.Layouts[routerIndex].Path: rd, // router.go
|
||||
layoutConfig.Layouts[routerGenIndex].Path: rd, // router_gen.go
|
||||
}
|
||||
|
||||
return lg.Generate(data)
|
||||
}
|
||||
|
||||
// serviceToLayoutData stores go mod, serviceName, UseApacheThrift mapping
|
||||
func serviceToLayoutData(service Layout) (map[string]interface{}, error) {
|
||||
goMod := service.GoModule
|
||||
if goMod == "" {
|
||||
return nil, errors.New("please specify go-module")
|
||||
}
|
||||
handlerPkg := filepath.Base(defaultHandlerDir)
|
||||
if len(service.HandlerDir) != 0 {
|
||||
handlerPkg = filepath.Base(service.HandlerDir)
|
||||
}
|
||||
routerPkg := filepath.Base(defaultRouterDir)
|
||||
if len(service.RouterDir) != 0 {
|
||||
routerPkg = filepath.Base(service.RouterDir)
|
||||
}
|
||||
serviceName := service.ServiceName
|
||||
if len(serviceName) == 0 {
|
||||
serviceName = meta.DefaultServiceName
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"GoModule": goMod,
|
||||
"ServiceName": serviceName,
|
||||
"UseApacheThrift": service.UseApacheThrift,
|
||||
"HandlerPkg": handlerPkg,
|
||||
"RouterPkg": routerPkg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// serviceToRouterData stores the registers function, router import path, handler import path
|
||||
func serviceToRouterData(service Layout) (map[string]interface{}, error) {
|
||||
routerDir := sp + defaultRouterDir
|
||||
handlerDir := sp + defaultHandlerDir
|
||||
if len(service.RouterDir) != 0 {
|
||||
routerDir = sp + service.RouterDir
|
||||
}
|
||||
if len(service.HandlerDir) != 0 {
|
||||
handlerDir = sp + service.HandlerDir
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"Registers": []string{},
|
||||
"RouterPkgPath": service.GoModule + util.PathToImport(routerDir, ""),
|
||||
"HandlerPkgPath": service.GoModule + util.PathToImport(handlerDir, ""),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (lg *LayoutGenerator) GenerateByConfig(configPath string) error {
|
||||
if err := lg.checkInited(); err != nil {
|
||||
return err
|
||||
}
|
||||
buf, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read data file '%s' failed, err: %v", configPath, err.Error())
|
||||
}
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(buf, &data); err != nil {
|
||||
return fmt.Errorf("unmarshal json data failed, err: %v", err.Error())
|
||||
}
|
||||
return lg.Generate(data)
|
||||
}
|
||||
|
||||
func (lg *LayoutGenerator) Degenerate() error {
|
||||
return lg.TemplateGenerator.Degenerate()
|
||||
}
|
220
generator/layout_tpl.go
Normal file
220
generator/layout_tpl.go
Normal file
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
//-----------------------------------Default Layout-----------------------------------------
|
||||
|
||||
const (
|
||||
sp = string(filepath.Separator)
|
||||
|
||||
defaultBizDir = "biz"
|
||||
defaultModelDir = "biz" + sp + "model"
|
||||
defaultHandlerDir = "biz" + sp + "handler"
|
||||
defaultServiceDir = "biz" + sp + "service"
|
||||
defaultDalDir = "biz" + sp + "dal"
|
||||
defaultScriptDir = "script"
|
||||
defaultConfDir = "conf"
|
||||
defaultRouterDir = "biz" + sp + "router"
|
||||
defaultClientDir = "biz" + sp + "client"
|
||||
)
|
||||
|
||||
const (
|
||||
routerGenIndex = 8
|
||||
routerIndex = 9
|
||||
|
||||
RegisterFile = "router_gen.go"
|
||||
)
|
||||
|
||||
var defaultLayoutConfig = TemplateConfig{
|
||||
Layouts: []Template{
|
||||
{
|
||||
Path: defaultDalDir + sp,
|
||||
},
|
||||
{
|
||||
Path: defaultHandlerDir + sp,
|
||||
},
|
||||
{
|
||||
Path: defaultModelDir + sp,
|
||||
},
|
||||
{
|
||||
Path: defaultServiceDir + sp,
|
||||
},
|
||||
{
|
||||
Path: "main.go",
|
||||
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()
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: "go.mod",
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
Body: `module {{.GoModule}}
|
||||
{{- if .UseApacheThrift}}
|
||||
replace github.com/apache/thrift => github.com/apache/thrift v0.13.0
|
||||
{{- end}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: ".gitignore",
|
||||
Body: `*.o
|
||||
*.a
|
||||
*.so
|
||||
_obj
|
||||
_test
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
_testmain.go
|
||||
*.exe
|
||||
*.exe~
|
||||
*.test
|
||||
*.prof
|
||||
*.rar
|
||||
*.zip
|
||||
*.gz
|
||||
*.psd
|
||||
*.bmd
|
||||
*.cfg
|
||||
*.pptx
|
||||
*.log
|
||||
*nohup.out
|
||||
*settings.pyc
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
!.gitkeep
|
||||
.DS_Store
|
||||
/.idea
|
||||
/.vscode
|
||||
/output
|
||||
*.local.yml
|
||||
dumped_hertz_remote_config.json
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: defaultHandlerDir + sp + "ping.go",
|
||||
Body: `// Code generated by hertz generator.
|
||||
|
||||
package {{.HandlerPkg}}
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/cloudwego/hertz/pkg/common/utils"
|
||||
"github.com/cloudwego/hertz/pkg/protocol/consts"
|
||||
)
|
||||
|
||||
// Ping .
|
||||
func Ping(ctx context.Context, c *app.RequestContext) {
|
||||
c.JSON(consts.StatusOK, utils.H{
|
||||
"message": "pong",
|
||||
})
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: RegisterFile,
|
||||
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)
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: "router.go",
|
||||
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 ...
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: defaultRouterDir + sp + registerTplName,
|
||||
Body: `// Code generated by hertz generator. DO NOT EDIT.
|
||||
|
||||
package {{.RouterPkg}}
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/app/server"
|
||||
)
|
||||
|
||||
// GeneratedRegister registers routers generated by IDL.
|
||||
func GeneratedRegister(r *server.Hertz){
|
||||
` + insertPointNew + `
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: "build.sh",
|
||||
Body: `#!/bin/bash
|
||||
RUN_NAME={{.ServiceName}}
|
||||
mkdir -p output/bin
|
||||
cp script/* output 2>/dev/null
|
||||
chmod +x output/bootstrap.sh
|
||||
go build -o output/bin/${RUN_NAME}`,
|
||||
},
|
||||
{
|
||||
Path: defaultScriptDir + sp + "bootstrap.sh",
|
||||
Body: `#!/bin/bash
|
||||
CURDIR=$(cd $(dirname $0); pwd)
|
||||
BinaryName={{.ServiceName}}
|
||||
echo "$CURDIR/bin/${BinaryName}"
|
||||
exec $CURDIR/bin/${BinaryName}`,
|
||||
},
|
||||
},
|
||||
}
|
161
generator/model.go
Normal file
161
generator/model.go
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model/golang"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
)
|
||||
|
||||
//---------------------------------Backend----------------------------------
|
||||
|
||||
type Option string
|
||||
|
||||
const (
|
||||
OptionMarshalEnumToText Option = "MarshalEnumToText"
|
||||
OptionTypedefAsTypeAlias Option = "TypedefAsTypeAlias"
|
||||
)
|
||||
|
||||
type Backend interface {
|
||||
Template() (*template.Template, error)
|
||||
List() map[string]string
|
||||
SetOption(opts string) error
|
||||
GetOptions() []string
|
||||
Funcs(name string, fn interface{}) error
|
||||
}
|
||||
|
||||
type GolangBackend struct{}
|
||||
|
||||
func (gb *GolangBackend) Template() (*template.Template, error) {
|
||||
return golang.Template()
|
||||
}
|
||||
|
||||
func (gb *GolangBackend) List() map[string]string {
|
||||
return golang.List()
|
||||
}
|
||||
|
||||
func (gb *GolangBackend) SetOption(opts string) error {
|
||||
return golang.SetOption(opts)
|
||||
}
|
||||
|
||||
func (gb *GolangBackend) GetOptions() []string {
|
||||
return golang.GetOptions()
|
||||
}
|
||||
|
||||
func (gb *GolangBackend) Funcs(name string, fn interface{}) error {
|
||||
return golang.Funcs(name, fn)
|
||||
}
|
||||
|
||||
func switchBackend(backend meta.Backend) Backend {
|
||||
switch backend {
|
||||
case meta.BackendGolang:
|
||||
return &GolangBackend{}
|
||||
}
|
||||
return loadThirdPartyBackend(string(backend))
|
||||
}
|
||||
|
||||
func loadThirdPartyBackend(plugin string) Backend {
|
||||
panic("no implement yet!")
|
||||
}
|
||||
|
||||
/**********************Generating*************************/
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) LoadBackend(backend meta.Backend) error {
|
||||
bd := switchBackend(backend)
|
||||
if bd == nil {
|
||||
return fmt.Errorf("no found backend '%s'", backend)
|
||||
}
|
||||
for _, opt := range pkgGen.Options {
|
||||
if err := bd.SetOption(string(opt)); err != nil {
|
||||
return fmt.Errorf("set option %s error, err: %v", opt, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err := bd.Funcs("ROOT", func() *model.Model {
|
||||
return pkgGen.curModel
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("register global function in model template failed, err: %v", err.Error())
|
||||
}
|
||||
|
||||
tpl, err := bd.Template()
|
||||
if err != nil {
|
||||
return fmt.Errorf("load backend %s failed, err: %v", backend, err.Error())
|
||||
}
|
||||
|
||||
if pkgGen.tpls == nil {
|
||||
pkgGen.tpls = map[string]*template.Template{}
|
||||
}
|
||||
pkgGen.tpls[modelTplName] = tpl
|
||||
pkgGen.loadedBackend = bd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) GenModel(data *model.Model, gen bool) error {
|
||||
if pkgGen.processedModels == nil {
|
||||
pkgGen.processedModels = map[*model.Model]bool{}
|
||||
}
|
||||
|
||||
if _, ok := pkgGen.processedModels[data]; !ok {
|
||||
var path string
|
||||
var updatePackage bool
|
||||
if strings.HasPrefix(data.Package, pkgGen.ProjPackage) && data.PackageName != pkgGen.ProjPackage {
|
||||
path = data.Package[len(pkgGen.ProjPackage):]
|
||||
} else {
|
||||
path = data.Package
|
||||
updatePackage = true
|
||||
}
|
||||
modelDir := util.SubDir(pkgGen.ModelDir, path)
|
||||
if updatePackage {
|
||||
data.Package = util.SubPackage(pkgGen.ProjPackage, modelDir)
|
||||
}
|
||||
data.FilePath = filepath.Join(modelDir, util.BaseNameAndTrim(data.FilePath)+".go")
|
||||
|
||||
pkgGen.processedModels[data] = true
|
||||
}
|
||||
|
||||
for _, dep := range data.Imports {
|
||||
if err := pkgGen.GenModel(dep, false); err != nil {
|
||||
return fmt.Errorf("generate model %s failed, err: %v", dep.FilePath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if gen && !data.IsEmpty() {
|
||||
pkgGen.curModel = data
|
||||
removeDuplicateImport(data)
|
||||
err := pkgGen.TemplateGenerator.Generate(data, modelTplName, data.FilePath, false)
|
||||
pkgGen.curModel = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Idls with the same Package do not need to refer to each other
|
||||
func removeDuplicateImport(data *model.Model) {
|
||||
for k, v := range data.Imports {
|
||||
if data.Package == v.Package {
|
||||
delete(data.Imports, k)
|
||||
}
|
||||
}
|
||||
}
|
194
generator/model/define.go
Normal file
194
generator/model/define.go
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
var (
|
||||
BaseTypes = []*Type{TypeBool, TypeByte, TypeInt8, TypeInt16, TypeInt32, TypeInt64, TypeUint8, TypeUint16, TypeUint32, TypeUint64, TypeFloat64, TypeString, TypeBinary}
|
||||
ContainerTypes = []*Type{TypeBaseList, TypeBaseMap, TypeBaseSet}
|
||||
BaseModel = Model{}
|
||||
)
|
||||
|
||||
var (
|
||||
TypeBool = &Type{
|
||||
Name: "bool",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindBool,
|
||||
}
|
||||
TypeByte = &Type{
|
||||
Name: "int8",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt8,
|
||||
}
|
||||
TypePbByte = &Type{
|
||||
Name: "byte",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt8,
|
||||
}
|
||||
TypeUint8 = &Type{
|
||||
Name: "uint8",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt8,
|
||||
}
|
||||
TypeUint16 = &Type{
|
||||
Name: "uint16",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt16,
|
||||
}
|
||||
TypeUint32 = &Type{
|
||||
Name: "uint32",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt32,
|
||||
}
|
||||
TypeUint64 = &Type{
|
||||
Name: "uint64",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt64,
|
||||
}
|
||||
TypeUint = &Type{
|
||||
Name: "uint",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt,
|
||||
}
|
||||
TypeInt8 = &Type{
|
||||
Name: "int8",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt8,
|
||||
}
|
||||
TypeInt16 = &Type{
|
||||
Name: "int16",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt16,
|
||||
}
|
||||
TypeInt32 = &Type{
|
||||
Name: "int32",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt32,
|
||||
}
|
||||
TypeInt64 = &Type{
|
||||
Name: "int64",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt64,
|
||||
}
|
||||
TypeInt = &Type{
|
||||
Name: "int",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt,
|
||||
}
|
||||
TypeFloat32 = &Type{
|
||||
Name: "float32",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindFloat64,
|
||||
}
|
||||
TypeFloat64 = &Type{
|
||||
Name: "float64",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindFloat64,
|
||||
}
|
||||
TypeString = &Type{
|
||||
Name: "string",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindString,
|
||||
}
|
||||
TypeBinary = &Type{
|
||||
Name: "binary",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindSlice,
|
||||
Category: CategoryBinary,
|
||||
Extra: []*Type{TypePbByte},
|
||||
}
|
||||
|
||||
TypeBaseMap = &Type{
|
||||
Name: "map",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindMap,
|
||||
Category: CategoryMap,
|
||||
}
|
||||
TypeBaseSet = &Type{
|
||||
Name: "set",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindSlice,
|
||||
Category: CategorySet,
|
||||
}
|
||||
TypeBaseList = &Type{
|
||||
Name: "list",
|
||||
Scope: &BaseModel,
|
||||
Kind: KindSlice,
|
||||
Category: CategoryList,
|
||||
}
|
||||
)
|
||||
|
||||
func NewCategoryType(typ *Type, cg Category) *Type {
|
||||
cyp := *typ
|
||||
cyp.Category = cg
|
||||
return &cyp
|
||||
}
|
||||
|
||||
func NewStructType(name string, cg Category) *Type {
|
||||
return &Type{
|
||||
Name: name,
|
||||
Scope: nil,
|
||||
Kind: KindStruct,
|
||||
Category: cg,
|
||||
Indirect: false,
|
||||
Extra: nil,
|
||||
HasNew: true,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFuncType(name string, cg Category) *Type {
|
||||
return &Type{
|
||||
Name: name,
|
||||
Scope: nil,
|
||||
Kind: KindFunc,
|
||||
Category: cg,
|
||||
Indirect: false,
|
||||
Extra: nil,
|
||||
HasNew: false,
|
||||
}
|
||||
}
|
||||
|
||||
func IsBaseType(typ *Type) bool {
|
||||
for _, t := range BaseTypes {
|
||||
if typ == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewEnumType(name string, cg Category) *Type {
|
||||
return &Type{
|
||||
Name: name,
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInt,
|
||||
Category: cg,
|
||||
Indirect: false,
|
||||
Extra: nil,
|
||||
HasNew: true,
|
||||
}
|
||||
}
|
||||
|
||||
func NewOneofType(name string) *Type {
|
||||
return &Type{
|
||||
Name: name,
|
||||
Scope: &BaseModel,
|
||||
Kind: KindInterface,
|
||||
Indirect: false,
|
||||
Extra: nil,
|
||||
HasNew: true,
|
||||
}
|
||||
}
|
95
generator/model/expr.go
Normal file
95
generator/model/expr.go
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type BoolExpression struct {
|
||||
Src bool
|
||||
}
|
||||
|
||||
func (boolExpr BoolExpression) Expression() string {
|
||||
if boolExpr.Src {
|
||||
return "true"
|
||||
} else {
|
||||
return "false"
|
||||
}
|
||||
}
|
||||
|
||||
type StringExpression struct {
|
||||
Src string
|
||||
}
|
||||
|
||||
func (stringExpr StringExpression) Expression() string {
|
||||
return fmt.Sprintf("%q", stringExpr.Src)
|
||||
}
|
||||
|
||||
type NumberExpression struct {
|
||||
Src string
|
||||
}
|
||||
|
||||
func (numExpr NumberExpression) Expression() string {
|
||||
return numExpr.Src
|
||||
}
|
||||
|
||||
type ListExpression struct {
|
||||
ElementType *Type
|
||||
Elements []Literal
|
||||
}
|
||||
|
||||
type IntExpression struct {
|
||||
Src int
|
||||
}
|
||||
|
||||
func (intExpr IntExpression) Expression() string {
|
||||
return strconv.Itoa(intExpr.Src)
|
||||
}
|
||||
|
||||
type DoubleExpression struct {
|
||||
Src float64
|
||||
}
|
||||
|
||||
func (doubleExpr DoubleExpression) Expression() string {
|
||||
return strconv.FormatFloat(doubleExpr.Src, 'f', -1, 64)
|
||||
}
|
||||
|
||||
func (listExpr ListExpression) Expression() string {
|
||||
ret := "[]" + listExpr.ElementType.Name + "{\n"
|
||||
for _, e := range listExpr.Elements {
|
||||
ret += e.Expression() + ",\n"
|
||||
}
|
||||
ret += "\n}"
|
||||
return ret
|
||||
}
|
||||
|
||||
type MapExpression struct {
|
||||
KeyType *Type
|
||||
ValueType *Type
|
||||
Elements map[string]Literal
|
||||
}
|
||||
|
||||
func (mapExpr MapExpression) Expression() string {
|
||||
ret := "map[" + mapExpr.KeyType.Name + "]" + mapExpr.ValueType.Name + "{\n"
|
||||
for k, e := range mapExpr.Elements {
|
||||
ret += fmt.Sprintf("%q: %s,\n", k, e.Expression())
|
||||
}
|
||||
ret += "\n}"
|
||||
return ret
|
||||
}
|
23
generator/model/golang/constant.go
Normal file
23
generator/model/golang/constant.go
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
var constants = `
|
||||
{{define "Constants"}}
|
||||
const {{.Name}} {{.Type.ResolveName ROOT}} = {{.Value.Expression}}
|
||||
{{end}}
|
||||
`
|
67
generator/model/golang/enum.go
Normal file
67
generator/model/golang/enum.go
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
// Enum .
|
||||
var enum = `
|
||||
{{define "Enum"}}
|
||||
{{- $EnumType := (Identify .Name)}}
|
||||
type {{$EnumType}} {{.GoType}}
|
||||
|
||||
const (
|
||||
{{- range $i, $e := .Values}}
|
||||
{{$EnumType}}_{{$e.Name}} {{$EnumType}} = {{$e.Value.Expression}}
|
||||
{{- end}}
|
||||
)
|
||||
|
||||
func (p {{$EnumType}}) String() string {
|
||||
switch p {
|
||||
{{- range $i, $e := .Values}}
|
||||
case {{$EnumType}}_{{$e.Name}}:
|
||||
return "{{printf "%s%s" $EnumType $e.Name | SnakeCase}}"
|
||||
{{- end}}
|
||||
}
|
||||
return "<UNSET>"
|
||||
}
|
||||
|
||||
func {{$EnumType}}FromString(s string) ({{$EnumType}}, error) {
|
||||
switch s {
|
||||
{{- range $i, $e := .Values}}
|
||||
case "{{printf "%s%s" $EnumType $e.Name | SnakeCase}}":
|
||||
return {{$EnumType}}_{{$e.Name}}, nil
|
||||
{{- end}}
|
||||
}
|
||||
return {{$EnumType}}(0), fmt.Errorf("not a valid {{$EnumType}} string")
|
||||
}
|
||||
|
||||
{{- if Features.MarshalEnumToText}}
|
||||
|
||||
func (p {{$EnumType}}) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
func (p *{{$EnumType}}) UnmarshalText(text []byte) error {
|
||||
q, err := {{$EnumType}}FromString(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*p = q
|
||||
return nil
|
||||
}
|
||||
{{- end}}
|
||||
{{end}}
|
||||
`
|
62
generator/model/golang/file.go
Normal file
62
generator/model/golang/file.go
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
var file = `{{$ROOT := . -}}
|
||||
// Code generated by hz.
|
||||
|
||||
package {{.PackageName}}
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
{{- range $alias, $model := .Imports}}
|
||||
{{$model.PackageName}} "{{$model.Package}}"
|
||||
{{- end}}
|
||||
)
|
||||
|
||||
{{- range .Typedefs}}
|
||||
{{template "Typedef" .}}
|
||||
{{- end}}
|
||||
|
||||
{{- range .Constants}}
|
||||
{{template "Constants" .}}
|
||||
{{- end}}
|
||||
|
||||
{{- range .Variables}}
|
||||
{{template "Variables" .}}
|
||||
{{- end}}
|
||||
|
||||
{{- range .Functions}}
|
||||
{{template "Function" .}}
|
||||
{{- end}}
|
||||
|
||||
{{- range .Enums}}
|
||||
{{template "Enum" .}}
|
||||
{{- end}}
|
||||
|
||||
{{- range .Oneofs}}
|
||||
{{template "Oneof" .}}
|
||||
{{- end}}
|
||||
|
||||
{{- range .Structs}}
|
||||
{{template "Struct" .}}
|
||||
{{- end}}
|
||||
|
||||
{{- range .Methods}}
|
||||
{{template "Method" .}}
|
||||
{{- end}}
|
||||
`
|
46
generator/model/golang/function.go
Normal file
46
generator/model/golang/function.go
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
var function = `
|
||||
{{define "Function"}}
|
||||
func {{template "FuncBody" . -}}
|
||||
{{end}}{{/* define "Function" */}}
|
||||
|
||||
{{define "FuncBody"}}
|
||||
{{- .Name -}}(
|
||||
{{- range $i, $arg := .Args -}}
|
||||
{{- if gt $i 0}}, {{end -}}
|
||||
{{$arg.Name}} {{$arg.Type.ResolveName ROOT}}
|
||||
{{- end -}}{{/* range */}})
|
||||
{{- if gt (len .Rets) 0}} ({{end -}}
|
||||
{{- range $i, $ret := .Rets -}}
|
||||
{{- if gt $i 0}}, {{end -}}
|
||||
{{$ret.Type.ResolveName ROOT}}
|
||||
{{- end -}}{{/* range */}}
|
||||
{{- if gt (len .Rets) 0}}) {{end -}}{
|
||||
{{.Code}}
|
||||
}
|
||||
{{end}}{{/* define "FuncBody" */}}
|
||||
`
|
||||
|
||||
var method = `
|
||||
{{define "Method"}}
|
||||
func ({{.ReceiverName}} {{.ReceiverType.ResolveName ROOT}})
|
||||
{{- template "FuncBody" .Function -}}
|
||||
{{end}}
|
||||
`
|
132
generator/model/golang/init.go
Normal file
132
generator/model/golang/init.go
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var tpls *template.Template
|
||||
|
||||
var list = map[string]string{
|
||||
"file": file,
|
||||
"typedef": typedef,
|
||||
"constants": constants,
|
||||
"variables": variables,
|
||||
"function": function,
|
||||
"enum": enum,
|
||||
"struct": structLike,
|
||||
"method": method,
|
||||
"oneof": oneof,
|
||||
}
|
||||
|
||||
/***********************Export API*******************************/
|
||||
|
||||
func Template() (*template.Template, error) {
|
||||
if tpls != nil {
|
||||
return tpls, nil
|
||||
}
|
||||
tpls = new(template.Template)
|
||||
|
||||
tpls = tpls.Funcs(funcMap)
|
||||
|
||||
var err error
|
||||
for k, li := range list {
|
||||
tpls, err = tpls.Parse(li)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse template '%s' failed, err: %v", k, err.Error())
|
||||
}
|
||||
}
|
||||
return tpls, nil
|
||||
}
|
||||
|
||||
func List() map[string]string {
|
||||
return list
|
||||
}
|
||||
|
||||
/***********************Template Funcs**************************/
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"Features": getFeatures,
|
||||
"Identify": identify,
|
||||
"CamelCase": camelCase,
|
||||
"SnakeCase": snakeCase,
|
||||
"GetTypedefReturnStr": getTypedefReturnStr,
|
||||
}
|
||||
|
||||
func Funcs(name string, fn interface{}) error {
|
||||
if _, ok := funcMap[name]; ok {
|
||||
return fmt.Errorf("duplicate function: %s has been registered", name)
|
||||
}
|
||||
funcMap[name] = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
func identify(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func camelCase(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func snakeCase(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func getTypedefReturnStr(name string) string {
|
||||
if strings.Contains(name, ".") {
|
||||
idx := strings.LastIndex(name, ".")
|
||||
return name[:idx] + "." + "New" + name[idx+1:] + "()"
|
||||
|
||||
}
|
||||
return "New" + name + "()"
|
||||
}
|
||||
|
||||
/***********************Template Options**************************/
|
||||
|
||||
type feature struct {
|
||||
MarshalEnumToText bool
|
||||
TypedefAsTypeAlias bool
|
||||
}
|
||||
|
||||
var features = feature{}
|
||||
|
||||
func getFeatures() feature {
|
||||
return features
|
||||
}
|
||||
|
||||
func SetOption(opt string) error {
|
||||
switch opt {
|
||||
case "MarshalEnumToText":
|
||||
features.MarshalEnumToText = true
|
||||
case "TypedefAsTypeAlias":
|
||||
features.TypedefAsTypeAlias = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var Options = []string{
|
||||
"MarshalEnumToText",
|
||||
"TypedefAsTypeAlias",
|
||||
}
|
||||
|
||||
func GetOptions() []string {
|
||||
return Options
|
||||
}
|
45
generator/model/golang/oneof.go
Normal file
45
generator/model/golang/oneof.go
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
var oneof = `
|
||||
{{define "Oneof"}}
|
||||
type {{$.InterfaceName}} interface {
|
||||
{{$.InterfaceName}}()
|
||||
}
|
||||
|
||||
{{range $i, $f := .Choices}}
|
||||
type {{$f.MessageName}}_{{$f.ChoiceName}} struct {
|
||||
{{$f.ChoiceName}} {{$f.Type.ResolveName ROOT}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{range $i, $f := .Choices}}
|
||||
func (*{{$f.MessageName}}_{{$f.ChoiceName}}) {{$.InterfaceName}}() {}
|
||||
{{end}}
|
||||
|
||||
{{range $i, $f := .Choices}}
|
||||
func (p *{{$f.MessageName}}) Get{{$f.ChoiceName}}() {{$f.Type.ResolveName ROOT}} {
|
||||
if p, ok := p.Get{{$.OneofName}}().(*{{$f.MessageName}}_{{$f.ChoiceName}}); ok {
|
||||
return p.{{$f.ChoiceName}}
|
||||
}
|
||||
return {{$f.Type.ResolveDefaultValue}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
`
|
120
generator/model/golang/struct.go
Normal file
120
generator/model/golang/struct.go
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
// StructLike is the code template for struct, union, and exception.
|
||||
var structLike = `
|
||||
{{define "Struct"}}
|
||||
{{- $TypeName := (Identify .Name) -}}
|
||||
{{$MessageLeadingComments := .LeadingComments}}
|
||||
{{if ne (len $MessageLeadingComments) 0}}
|
||||
//{{$MessageLeadingComments}}
|
||||
{{end -}}
|
||||
type {{$TypeName}} struct {
|
||||
{{- range $i, $f := .Fields}}
|
||||
{{- $FieldLeadingComments := $f.LeadingComments}}
|
||||
{{$FieldTrailingComments := $f.TrailingComments -}}
|
||||
{{- if ne (len $FieldLeadingComments) 0 -}}
|
||||
//{{$FieldLeadingComments}}
|
||||
{{end -}}
|
||||
{{- if $f.IsPointer -}}
|
||||
{{$f.Name}} *{{$f.Type.ResolveName ROOT}} {{$f.GenGoTags}}{{if ne (len $FieldTrailingComments) 0}} //{{$FieldTrailingComments}}{{end -}}
|
||||
{{- else -}}
|
||||
{{$f.Name}} {{$f.Type.ResolveName ROOT}} {{$f.GenGoTags}}{{if ne (len $FieldTrailingComments) 0}} //{{$FieldTrailingComments}}{{end -}}
|
||||
{{- end -}}
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
func New{{$TypeName}}() *{{$TypeName}} {
|
||||
return &{{$TypeName}}{
|
||||
{{template "StructLikeDefault" .}}
|
||||
}
|
||||
}
|
||||
|
||||
{{template "FieldGetOrSet" .}}
|
||||
|
||||
{{if eq .Category 14}}
|
||||
func (p *{{$TypeName}}) CountSetFields{{$TypeName}}() int {
|
||||
count := 0
|
||||
{{- range $i, $f := .Fields}}
|
||||
{{- if $f.Type.IsSettable}}
|
||||
if p.IsSet{{$f.Name}}() {
|
||||
count++
|
||||
}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
return count
|
||||
}
|
||||
{{- end}}
|
||||
|
||||
func (p *{{$TypeName}}) String() string {
|
||||
if p == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("{{$TypeName}}(%+v)", *p)
|
||||
}
|
||||
|
||||
{{- if eq .Category 15}}
|
||||
func (p *{{$TypeName}}) Error() string {
|
||||
return p.String()
|
||||
}
|
||||
{{- end}}
|
||||
{{- end}}{{/* define "StructLike" */}}
|
||||
|
||||
{{- define "StructLikeDefault"}}
|
||||
{{- range $i, $f := .Fields}}
|
||||
{{- if $f.IsSetDefault}}
|
||||
{{$f.Name}}: {{$f.DefaultValue.Expression}},
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- end -}}{{/* define "StructLikeDefault" */}}
|
||||
|
||||
{{- define "FieldGetOrSet"}}
|
||||
{{- $TypeName := (Identify .Name)}}
|
||||
{{- range $i, $f := .Fields}}
|
||||
{{$FieldName := $f.Name}}
|
||||
{{$FieldTypeName := $f.Type.ResolveName ROOT}}
|
||||
|
||||
{{- if $f.Type.IsSettable}}
|
||||
func (p *{{$TypeName}}) IsSet{{$FieldName}}() bool {
|
||||
return p.{{$FieldName}} != nil
|
||||
}
|
||||
{{- end}}{{/* IsSettable . */}}
|
||||
|
||||
func (p *{{$TypeName}}) Get{{$FieldName}}() {{$FieldTypeName}} {
|
||||
{{- if $f.Type.IsSettable}}
|
||||
if !p.IsSet{{$FieldName}}() {
|
||||
return {{with $f.DefaultValue}}{{$f.DefaultValue.Expression}}{{else}}nil{{end}}
|
||||
}
|
||||
{{- end}}
|
||||
{{- if $f.IsPointer}}
|
||||
return *p.{{$FieldName}}
|
||||
{{else}}
|
||||
return p.{{$FieldName}}
|
||||
{{- end -}}
|
||||
}
|
||||
|
||||
func (p *{{$TypeName}}) Set{{$FieldName}}(val {{$FieldTypeName}}) {
|
||||
{{- if $f.IsPointer}}
|
||||
*p.{{$FieldName}} = val
|
||||
{{else}}
|
||||
p.{{$FieldName}} = val
|
||||
{{- end -}}
|
||||
}
|
||||
{{- end}}{{/* range .Fields */}}
|
||||
{{- end}}{{/* define "FieldGetOrSet" */}}
|
||||
`
|
32
generator/model/golang/typedef.go
Normal file
32
generator/model/golang/typedef.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
// Typedef .
|
||||
var typedef = `
|
||||
{{define "Typedef"}}
|
||||
{{- $NewTypeName := (Identify .Alias)}}
|
||||
{{- $OldTypeName := .Type.ResolveNameForTypedef ROOT}}
|
||||
type {{$NewTypeName}} = {{$OldTypeName}}
|
||||
|
||||
{{if eq .Type.Kind 25}}{{if .Type.HasNew}}
|
||||
func New{{$NewTypeName}}() *{{$NewTypeName}} {
|
||||
return {{(GetTypedefReturnStr $OldTypeName)}}
|
||||
}
|
||||
{{- end}}{{- end}}
|
||||
{{- end}}
|
||||
`
|
23
generator/model/golang/variable.go
Normal file
23
generator/model/golang/variable.go
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package golang
|
||||
|
||||
var variables = `
|
||||
{{- define "Variables"}}
|
||||
var {{.Name}} {{.Type.ResolveName ROOT}} = {{.Value.Expression}}
|
||||
{{end}}
|
||||
`
|
417
generator/model/model.go
Normal file
417
generator/model/model.go
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Kind uint
|
||||
|
||||
const (
|
||||
KindInvalid Kind = iota
|
||||
KindBool
|
||||
KindInt
|
||||
KindInt8
|
||||
KindInt16
|
||||
KindInt32
|
||||
KindInt64
|
||||
KindUint
|
||||
KindUint8
|
||||
KindUint16
|
||||
KindUint32
|
||||
KindUint64
|
||||
KindUintptr
|
||||
KindFloat32
|
||||
KindFloat64
|
||||
KindComplex64
|
||||
KindComplex128
|
||||
KindArray
|
||||
KindChan
|
||||
KindFunc
|
||||
KindInterface
|
||||
KindMap
|
||||
KindPtr
|
||||
KindSlice
|
||||
KindString
|
||||
KindStruct
|
||||
KindUnsafePointer
|
||||
)
|
||||
|
||||
type Category int64
|
||||
|
||||
const (
|
||||
CategoryConstant Category = 1
|
||||
CategoryBinary Category = 8
|
||||
CategoryMap Category = 9
|
||||
CategoryList Category = 10
|
||||
CategorySet Category = 11
|
||||
CategoryEnum Category = 12
|
||||
CategoryStruct Category = 13
|
||||
CategoryUnion Category = 14
|
||||
CategoryException Category = 15
|
||||
CategoryTypedef Category = 16
|
||||
CategoryService Category = 17
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
FilePath string
|
||||
Package string
|
||||
Imports map[string]*Model //{{import}}:Model
|
||||
|
||||
// rendering data
|
||||
PackageName string
|
||||
// Imports map[string]string //{{alias}}:{{import}}
|
||||
Typedefs []TypeDef
|
||||
Constants []Constant
|
||||
Variables []Variable
|
||||
Functions []Function
|
||||
Enums []Enum
|
||||
Structs []Struct
|
||||
Methods []Method
|
||||
Oneofs []Oneof
|
||||
}
|
||||
|
||||
func (m Model) IsEmpty() bool {
|
||||
return len(m.Typedefs) == 0 && len(m.Constants) == 0 && len(m.Variables) == 0 &&
|
||||
len(m.Functions) == 0 && len(m.Enums) == 0 && len(m.Structs) == 0 && len(m.Methods) == 0
|
||||
}
|
||||
|
||||
type Models []*Model
|
||||
|
||||
func (a *Models) MergeMap(b map[string]*Model) {
|
||||
for _, v := range b {
|
||||
insert := true
|
||||
for _, p := range *a {
|
||||
if p == v {
|
||||
insert = false
|
||||
}
|
||||
}
|
||||
if insert {
|
||||
*a = append(*a, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Models) MergeArray(b []*Model) {
|
||||
for _, v := range b {
|
||||
insert := true
|
||||
for _, p := range *a {
|
||||
if p == v {
|
||||
insert = false
|
||||
}
|
||||
}
|
||||
if insert {
|
||||
*a = append(*a, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type RequiredNess int
|
||||
|
||||
const (
|
||||
RequiredNess_Default RequiredNess = 0
|
||||
RequiredNess_Required RequiredNess = 1
|
||||
RequiredNess_Optional RequiredNess = 2
|
||||
)
|
||||
|
||||
type Type struct {
|
||||
Name string
|
||||
Scope *Model
|
||||
Kind Kind
|
||||
Indirect bool
|
||||
Category Category
|
||||
Extra []*Type // [{key_type},{value_type}] for map, [{element_type}] for list or set
|
||||
HasNew bool
|
||||
}
|
||||
|
||||
func (rt *Type) ResolveDefaultValue() string {
|
||||
if rt == nil {
|
||||
return ""
|
||||
}
|
||||
switch rt.Kind {
|
||||
case KindInt, KindInt8, KindInt16, KindInt32, KindInt64, KindUint, KindUint16, KindUint32, KindUint64,
|
||||
KindFloat32, KindFloat64, KindComplex64, KindComplex128:
|
||||
return "0"
|
||||
case KindBool:
|
||||
return "false"
|
||||
case KindString:
|
||||
return "\"\""
|
||||
default:
|
||||
return "nil"
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *Type) ResolveNameForTypedef(scope *Model) (string, error) {
|
||||
if rt == nil {
|
||||
return "", errors.New("type is nil")
|
||||
}
|
||||
name := rt.Name
|
||||
if rt.Scope == nil {
|
||||
return rt.Name, nil
|
||||
}
|
||||
|
||||
switch rt.Kind {
|
||||
case KindArray, KindSlice:
|
||||
if len(rt.Extra) != 1 {
|
||||
return "", fmt.Errorf("the type: %s should have 1 extra type, but has %d", rt.Name, len(rt.Extra))
|
||||
}
|
||||
resolveName, err := rt.Extra[0].ResolveName(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = fmt.Sprintf("[]%s", resolveName)
|
||||
case KindMap:
|
||||
if len(rt.Extra) != 2 {
|
||||
return "", fmt.Errorf("the type: %s should have 2 extra types, but has %d", rt.Name, len(rt.Extra))
|
||||
}
|
||||
resolveKey, err := rt.Extra[0].ResolveName(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolveValue, err := rt.Extra[1].ResolveName(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = fmt.Sprintf("map[%s]%s", resolveKey, resolveValue)
|
||||
case KindChan:
|
||||
if len(rt.Extra) != 1 {
|
||||
return "", fmt.Errorf("the type: %s should have 1 extra type, but has %d", rt.Name, len(rt.Extra))
|
||||
}
|
||||
resolveName, err := rt.Extra[0].ResolveName(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = fmt.Sprintf("chan %s", resolveName)
|
||||
}
|
||||
|
||||
if scope != nil && rt.Scope != &BaseModel && rt.Scope.Package != scope.Package {
|
||||
name = rt.Scope.PackageName + "." + name
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (rt *Type) ResolveName(scope *Model) (string, error) {
|
||||
if rt == nil {
|
||||
return "", fmt.Errorf("type is nil")
|
||||
}
|
||||
name := rt.Name
|
||||
if rt.Scope == nil {
|
||||
if rt.Kind == KindStruct {
|
||||
return "*" + rt.Name, nil
|
||||
}
|
||||
return rt.Name, nil
|
||||
}
|
||||
|
||||
if rt.Category == CategoryTypedef {
|
||||
if scope != nil && rt.Scope != &BaseModel && rt.Scope.Package != scope.Package {
|
||||
name = rt.Scope.PackageName + "." + name
|
||||
}
|
||||
|
||||
if rt.Kind == KindStruct {
|
||||
name = "*" + name
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
switch rt.Kind {
|
||||
case KindArray, KindSlice:
|
||||
if len(rt.Extra) != 1 {
|
||||
return "", fmt.Errorf("The type: %s should have 1 extra type, but has %d", rt.Name, len(rt.Extra))
|
||||
}
|
||||
resolveName, err := rt.Extra[0].ResolveName(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = fmt.Sprintf("[]%s", resolveName)
|
||||
case KindMap:
|
||||
if len(rt.Extra) != 2 {
|
||||
return "", fmt.Errorf("The type: %s should have 2 extra type, but has %d", rt.Name, len(rt.Extra))
|
||||
}
|
||||
resolveKey, err := rt.Extra[0].ResolveName(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolveValue, err := rt.Extra[1].ResolveName(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = fmt.Sprintf("map[%s]%s", resolveKey, resolveValue)
|
||||
case KindChan:
|
||||
if len(rt.Extra) != 1 {
|
||||
return "", fmt.Errorf("The type: %s should have 1 extra type, but has %d", rt.Name, len(rt.Extra))
|
||||
}
|
||||
resolveName, err := rt.Extra[0].ResolveName(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = fmt.Sprintf("chan %s", resolveName)
|
||||
}
|
||||
|
||||
if scope != nil && rt.Scope != &BaseModel && rt.Scope.Package != scope.Package {
|
||||
name = rt.Scope.PackageName + "." + name
|
||||
}
|
||||
|
||||
if rt.Kind == KindStruct {
|
||||
name = "*" + name
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (rt *Type) IsBinary() bool {
|
||||
return rt.Category == CategoryBinary && (rt.Kind == KindSlice || rt.Kind == KindArray)
|
||||
}
|
||||
|
||||
func (rt *Type) IsBaseType() bool {
|
||||
return rt.Kind < KindComplex64
|
||||
}
|
||||
|
||||
func (rt *Type) IsSettable() bool {
|
||||
switch rt.Kind {
|
||||
case KindArray, KindChan, KindFunc, KindInterface, KindMap, KindPtr, KindSlice, KindUnsafePointer:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type TypeDef struct {
|
||||
Scope *Model
|
||||
Alias string
|
||||
Type *Type
|
||||
}
|
||||
|
||||
type Constant struct {
|
||||
Scope *Model
|
||||
Name string
|
||||
Type *Type
|
||||
Value Literal
|
||||
}
|
||||
|
||||
type Literal interface {
|
||||
Expression() string
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
Scope *Model
|
||||
Name string
|
||||
Type *Type
|
||||
Value Literal
|
||||
}
|
||||
|
||||
type Function struct {
|
||||
Scope *Model
|
||||
Name string
|
||||
Args []Variable
|
||||
Rets []Variable
|
||||
Code string
|
||||
}
|
||||
|
||||
type Method struct {
|
||||
Scope *Model
|
||||
ReceiverName string
|
||||
ReceiverType *Type
|
||||
ByPtr bool
|
||||
Function
|
||||
}
|
||||
|
||||
type Enum struct {
|
||||
Scope *Model
|
||||
Name string
|
||||
GoType string
|
||||
Values []Constant
|
||||
}
|
||||
|
||||
type Struct struct {
|
||||
Scope *Model
|
||||
Name string
|
||||
Fields []Field
|
||||
Category Category
|
||||
LeadingComments string
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Scope *Struct
|
||||
Name string
|
||||
Type *Type
|
||||
IsSetDefault bool
|
||||
DefaultValue Literal
|
||||
Required RequiredNess
|
||||
Tags Tags
|
||||
LeadingComments string
|
||||
TrailingComments string
|
||||
IsPointer bool
|
||||
}
|
||||
|
||||
type Oneof struct {
|
||||
MessageName string
|
||||
OneofName string
|
||||
InterfaceName string
|
||||
Choices []Choice
|
||||
}
|
||||
|
||||
type Choice struct {
|
||||
MessageName string
|
||||
ChoiceName string
|
||||
Type *Type
|
||||
}
|
||||
|
||||
type Tags []Tag
|
||||
|
||||
type Tag struct {
|
||||
Key string
|
||||
Value string
|
||||
IsDefault bool // default tag
|
||||
}
|
||||
|
||||
func (ts Tags) String() string {
|
||||
ret := make([]string, 0, len(ts))
|
||||
for _, t := range ts {
|
||||
ret = append(ret, fmt.Sprintf("%v:%q", t.Key, t.Value))
|
||||
}
|
||||
return strings.Join(ret, " ")
|
||||
}
|
||||
|
||||
func (ts *Tags) Remove(name string) {
|
||||
ret := make([]Tag, 0, len(*ts))
|
||||
for _, t := range *ts {
|
||||
if t.Key != name {
|
||||
ret = append(ret, t)
|
||||
}
|
||||
}
|
||||
*ts = ret
|
||||
}
|
||||
|
||||
func (ts Tags) Len() int { return len(ts) }
|
||||
|
||||
func (ts Tags) Less(i, j int) bool {
|
||||
return ts[i].Key < ts[j].Key
|
||||
}
|
||||
|
||||
func (ts Tags) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] }
|
||||
|
||||
func (f Field) GenGoTags() string {
|
||||
if len(f.Tags) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("`%s`", f.Tags.String())
|
||||
}
|
240
generator/model_test.go
Normal file
240
generator/model_test.go
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
)
|
||||
|
||||
type StringValue struct {
|
||||
src string
|
||||
}
|
||||
|
||||
func (sv *StringValue) Expression() string {
|
||||
return sv.src
|
||||
}
|
||||
|
||||
func TestIdlGenerator_GenModel(t *testing.T) {
|
||||
typeModel := &model.Type{
|
||||
Name: "Model",
|
||||
Kind: model.KindStruct,
|
||||
Indirect: true,
|
||||
}
|
||||
typeErr := &model.Type{
|
||||
Name: "error",
|
||||
Kind: model.KindInterface,
|
||||
Indirect: false,
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
ConfigPath string
|
||||
OutputDir string
|
||||
Backend meta.Backend
|
||||
handlerDir string
|
||||
routerDir string
|
||||
modelDir string
|
||||
ProjPackage string
|
||||
Config *TemplateConfig
|
||||
tpls map[string]*template.Template
|
||||
}
|
||||
type args struct {
|
||||
data *model.Model
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "",
|
||||
fields: fields{
|
||||
OutputDir: "./testdata",
|
||||
Backend: meta.BackendGolang,
|
||||
},
|
||||
args: args{
|
||||
data: &model.Model{
|
||||
FilePath: "idl/main.thrift",
|
||||
Package: "model/psm",
|
||||
PackageName: "psm",
|
||||
Imports: map[string]*model.Model{
|
||||
"base": {
|
||||
Package: "model/base",
|
||||
PackageName: "base",
|
||||
},
|
||||
},
|
||||
Typedefs: []model.TypeDef{
|
||||
{
|
||||
Alias: "HerztModel",
|
||||
Type: typeModel,
|
||||
},
|
||||
},
|
||||
Constants: []model.Constant{
|
||||
{
|
||||
Name: "OBJ",
|
||||
Type: typeErr,
|
||||
Value: &StringValue{"fmt.Errorf(\"EOF\")"},
|
||||
},
|
||||
},
|
||||
Variables: []model.Variable{
|
||||
{
|
||||
Name: "Object",
|
||||
Type: typeModel,
|
||||
Value: &StringValue{"&Model{}"},
|
||||
},
|
||||
},
|
||||
Functions: []model.Function{
|
||||
{
|
||||
Name: "Init",
|
||||
Args: nil,
|
||||
Rets: []model.Variable{
|
||||
{
|
||||
Name: "err",
|
||||
Type: typeErr,
|
||||
},
|
||||
},
|
||||
Code: "return nil",
|
||||
},
|
||||
},
|
||||
Enums: []model.Enum{
|
||||
{
|
||||
Name: "Sex",
|
||||
Values: []model.Constant{
|
||||
{
|
||||
Name: "Male",
|
||||
Type: &model.Type{
|
||||
Name: "int",
|
||||
Kind: model.KindInt,
|
||||
Indirect: false,
|
||||
Category: 1,
|
||||
},
|
||||
Value: &StringValue{"1"},
|
||||
},
|
||||
{
|
||||
Name: "Femal",
|
||||
Type: &model.Type{
|
||||
Name: "int",
|
||||
Kind: model.KindInt,
|
||||
Indirect: false,
|
||||
Category: 1,
|
||||
},
|
||||
Value: &StringValue{"2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Structs: []model.Struct{
|
||||
{
|
||||
Name: "Model",
|
||||
Fields: []model.Field{
|
||||
{
|
||||
Name: "A",
|
||||
Type: &model.Type{
|
||||
Name: "[]byte",
|
||||
Kind: model.KindSlice,
|
||||
Indirect: false,
|
||||
Category: model.CategoryBinary,
|
||||
},
|
||||
IsSetDefault: true,
|
||||
DefaultValue: &StringValue{"[]byte(\"\")"},
|
||||
},
|
||||
{
|
||||
Name: "B",
|
||||
Type: &model.Type{
|
||||
Name: "Base",
|
||||
Kind: model.KindStruct,
|
||||
Indirect: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Category: model.CategoryUnion,
|
||||
},
|
||||
},
|
||||
Methods: []model.Method{
|
||||
{
|
||||
ReceiverName: "self",
|
||||
ReceiverType: typeModel,
|
||||
ByPtr: true,
|
||||
Function: model.Function{
|
||||
Name: "Bind",
|
||||
Args: []model.Variable{
|
||||
{
|
||||
Name: "c",
|
||||
Type: &model.Type{
|
||||
Name: "RequestContext",
|
||||
Scope: &model.Model{
|
||||
PackageName: "hertz",
|
||||
},
|
||||
Kind: model.KindStruct,
|
||||
Indirect: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Rets: []model.Variable{
|
||||
{
|
||||
Name: "error",
|
||||
Type: typeErr,
|
||||
},
|
||||
},
|
||||
Code: "return nil",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
self := &HttpPackageGenerator{
|
||||
ConfigPath: tt.fields.ConfigPath,
|
||||
Backend: tt.fields.Backend,
|
||||
HandlerDir: tt.fields.handlerDir,
|
||||
RouterDir: tt.fields.routerDir,
|
||||
ModelDir: tt.fields.modelDir,
|
||||
ProjPackage: tt.fields.ProjPackage,
|
||||
TemplateGenerator: TemplateGenerator{
|
||||
OutputDir: tt.fields.OutputDir,
|
||||
Config: tt.fields.Config,
|
||||
tpls: tt.fields.tpls,
|
||||
},
|
||||
Options: []Option{
|
||||
OptionTypedefAsTypeAlias,
|
||||
OptionMarshalEnumToText,
|
||||
},
|
||||
}
|
||||
|
||||
err := self.LoadBackend(meta.BackendGolang)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := self.GenModel(tt.args.data, true); (err != nil) != tt.wantErr {
|
||||
t.Errorf("IdlGenerator.GenModel() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if err := self.Persist(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
199
generator/package.go
Normal file
199
generator/package.go
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"text/template"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type HttpPackage struct {
|
||||
IdlName string
|
||||
Package string
|
||||
Services []*Service
|
||||
Models []*model.Model
|
||||
RouterInfo *Router
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Name string
|
||||
Methods []*HttpMethod
|
||||
ClientMethods []*ClientMethod
|
||||
Models []*model.Model // all dependency models
|
||||
BaseDomain string // base domain for client code
|
||||
ServiceGroup string // service level router group
|
||||
ServiceGenDir string // handler_dir for handler_by_service
|
||||
}
|
||||
|
||||
// HttpPackageGenerator is used to record the configuration related to generating hertz http code.
|
||||
type HttpPackageGenerator struct {
|
||||
ConfigPath string // package template path
|
||||
Backend meta.Backend // model template
|
||||
Options []Option
|
||||
CmdType string
|
||||
ProjPackage string // go module for project
|
||||
HandlerDir string
|
||||
RouterDir string
|
||||
ModelDir string
|
||||
UseDir string // model dir for third repo
|
||||
ClientDir string // client dir for "new"/"update" command
|
||||
IdlClientDir string // client dir for "client" command
|
||||
ForceClientDir string // client dir without namespace for "client" command
|
||||
BaseDomain string // request domain for "client" command
|
||||
ServiceGenDir string
|
||||
|
||||
NeedModel bool
|
||||
HandlerByMethod bool // generate handler files with method dimension
|
||||
SnakeStyleMiddleware bool // use snake name style for middleware
|
||||
|
||||
loadedBackend Backend
|
||||
curModel *model.Model
|
||||
processedModels map[*model.Model]bool
|
||||
|
||||
TemplateGenerator
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) Init() error {
|
||||
defaultConfig := packageConfig
|
||||
customConfig := TemplateConfig{}
|
||||
// unmarshal from user-defined config file if it exists
|
||||
if pkgGen.ConfigPath != "" {
|
||||
cdata, err := ioutil.ReadFile(pkgGen.ConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read layout config from %s failed, err: %v", pkgGen.ConfigPath, err.Error())
|
||||
}
|
||||
if err = yaml.Unmarshal(cdata, &customConfig); err != nil {
|
||||
return fmt.Errorf("unmarshal layout config failed, err: %v", err.Error())
|
||||
}
|
||||
if reflect.DeepEqual(customConfig, TemplateConfig{}) {
|
||||
return errors.New("empty config")
|
||||
}
|
||||
}
|
||||
|
||||
if pkgGen.tpls == nil {
|
||||
pkgGen.tpls = make(map[string]*template.Template, len(defaultConfig.Layouts))
|
||||
}
|
||||
if pkgGen.tplsInfo == nil {
|
||||
pkgGen.tplsInfo = make(map[string]*Template, len(defaultConfig.Layouts))
|
||||
}
|
||||
|
||||
// extract routerTplName/middlewareTplName/handlerTplName/registerTplName/modelTplName/clientTplName directories
|
||||
// load default template
|
||||
for _, layout := range defaultConfig.Layouts {
|
||||
// default template use "fileName" as template name
|
||||
path := filepath.Base(layout.Path)
|
||||
err := pkgGen.loadLayout(layout, path, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// override the default template, other customized file template will be loaded by "TemplateGenerator.Init"
|
||||
for _, layout := range customConfig.Layouts {
|
||||
if !IsDefaultPackageTpl(layout.Path) {
|
||||
continue
|
||||
}
|
||||
err := pkgGen.loadLayout(layout, layout.Path, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pkgGen.Config = &customConfig
|
||||
// load Model tpl if need
|
||||
if pkgGen.Backend != "" {
|
||||
if err := pkgGen.LoadBackend(pkgGen.Backend); err != nil {
|
||||
return fmt.Errorf("load model template failed, err: %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
pkgGen.processedModels = make(map[*model.Model]bool)
|
||||
pkgGen.TemplateGenerator.isPackageTpl = true
|
||||
|
||||
return pkgGen.TemplateGenerator.Init()
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) checkInited() (bool, error) {
|
||||
if pkgGen.tpls == nil {
|
||||
if err := pkgGen.Init(); err != nil {
|
||||
return false, fmt.Errorf("init layout config failed, err: %v", err.Error())
|
||||
}
|
||||
}
|
||||
return pkgGen.ConfigPath == "", nil
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) Generate(pkg *HttpPackage) error {
|
||||
if _, err := pkgGen.checkInited(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(pkg.Models) != 0 {
|
||||
for _, m := range pkg.Models {
|
||||
if err := pkgGen.GenModel(m, pkgGen.NeedModel); err != nil {
|
||||
return fmt.Errorf("generate model %s failed, err: %v", m.FilePath, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pkgGen.CmdType == meta.CmdClient {
|
||||
// default client dir
|
||||
clientDir := pkgGen.IdlClientDir
|
||||
// user specify client dir
|
||||
if len(pkgGen.ClientDir) != 0 {
|
||||
clientDir = pkgGen.ClientDir
|
||||
}
|
||||
if err := pkgGen.genClient(pkg, clientDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pkgGen.genCustomizedFile(pkg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
root := NewRouterTree()
|
||||
if err := pkgGen.genHandler(pkg, handlerDir, handlerPackage, root); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pkgGen.genRouter(pkg, root, handlerPackage, routerDir, routerPackage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pkgGen.genCustomizedFile(pkg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
978
generator/package_tpl.go
Normal file
978
generator/package_tpl.go
Normal file
@ -0,0 +1,978 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
var (
|
||||
routerTplName = "router.go"
|
||||
middlewareTplName = "middleware.go"
|
||||
middlewareSingleTplName = "middleware_single.go"
|
||||
handlerTplName = "handler.go"
|
||||
handlerSingleTplName = "handler_single.go"
|
||||
modelTplName = "model.go"
|
||||
registerTplName = "register.go"
|
||||
clientTplName = "client.go" // generate a default client for server
|
||||
hertzClientTplName = "hertz_client.go" // underlying client for client command
|
||||
idlClientName = "idl_client.go" // client of service for quick call
|
||||
|
||||
insertPointNew = "//INSERT_POINT: DO NOT DELETE THIS LINE!"
|
||||
insertPointPatternNew = `//INSERT_POINT\: DO NOT DELETE THIS LINE\!`
|
||||
)
|
||||
|
||||
var templateNameSet = map[string]string{
|
||||
routerTplName: routerTplName,
|
||||
middlewareTplName: middlewareTplName,
|
||||
middlewareSingleTplName: middlewareSingleTplName,
|
||||
handlerTplName: handlerTplName,
|
||||
handlerSingleTplName: handlerSingleTplName,
|
||||
modelTplName: modelTplName,
|
||||
registerTplName: registerTplName,
|
||||
clientTplName: clientTplName,
|
||||
hertzClientTplName: hertzClientTplName,
|
||||
idlClientName: idlClientName,
|
||||
}
|
||||
|
||||
func IsDefaultPackageTpl(name string) bool {
|
||||
if _, exist := templateNameSet[name]; exist {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var defaultPkgConfig = TemplateConfig{
|
||||
Layouts: []Template{
|
||||
{
|
||||
Path: defaultHandlerDir + sp + handlerTplName,
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
Body: `// Code generated by hertz generator.
|
||||
|
||||
package {{.PackageName}}
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/cloudwego/hertz/pkg/protocol/consts"
|
||||
|
||||
{{- 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(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
{{end}}
|
||||
resp := new({{$MethodInfo.ReturnTypeName}})
|
||||
|
||||
c.{{.Serializer}}(consts.StatusOK, resp)
|
||||
}
|
||||
{{end}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: defaultRouterDir + sp + routerTplName,
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
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({{.HandlerMiddleware}}Mw(), {{.Handler}})...)
|
||||
{{- end}}
|
||||
{{- if ne (len .Children) 0}}
|
||||
{{.MiddleWare}} := {{template "g" .}}.Group("{{.Path}}", {{.GroupMiddleware}}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{{$.IdlName}}(r *server.Hertz) {
|
||||
{{template "G" .Router}}
|
||||
}
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: defaultRouterDir + sp + registerTplName,
|
||||
Body: `// Code generated by hertz generator. DO NOT EDIT.
|
||||
|
||||
package {{.PackageName}}
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/app/server"
|
||||
{{$.DepPkgAlias}} "{{$.DepPkg}}"
|
||||
)
|
||||
|
||||
// GeneratedRegister registers routers generated by IDL.
|
||||
func GeneratedRegister(r *server.Hertz){
|
||||
` + insertPointNew + `
|
||||
{{$.DepPkgAlias}}.{{$.RegisterName}}(r)
|
||||
}
|
||||
`,
|
||||
},
|
||||
// Model tpl is imported by model generator. Here only decides model directory.
|
||||
{
|
||||
Path: defaultModelDir + sp + modelTplName,
|
||||
Body: ``,
|
||||
},
|
||||
{
|
||||
Path: defaultRouterDir + sp + middlewareTplName,
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
Body: `// Code generated by hertz generator.
|
||||
|
||||
package {{$.PackageName}}
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
)
|
||||
|
||||
{{define "M"}}
|
||||
{{- if ne .Children.Len 0}}
|
||||
func {{.GroupMiddleware}}Mw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
{{end}}
|
||||
{{- if ne .Handler ""}}
|
||||
func {{.HandlerMiddleware}}Mw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
{{end}}
|
||||
{{range $_, $router := $.Children}}{{template "M" $router}}{{end}}
|
||||
{{- end}}
|
||||
|
||||
{{template "M" .Router}}
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: defaultClientDir + sp + clientTplName,
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
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
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: defaultHandlerDir + sp + handlerSingleTplName,
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
Body: `
|
||||
{{.Comment}}
|
||||
func {{.Name}}(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
{{if ne .RequestTypeName "" -}}
|
||||
var req {{.RequestTypeName}}
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
c.String(consts.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
{{end}}
|
||||
resp := new({{.ReturnTypeName}})
|
||||
|
||||
c.{{.Serializer}}(consts.StatusOK, resp)
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: defaultRouterDir + sp + middlewareSingleTplName,
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
Body: `
|
||||
func {{.MiddleWare}}Mw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Path: defaultRouterDir + sp + hertzClientTplName,
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
Body: hertzClientTpl,
|
||||
},
|
||||
{
|
||||
Path: defaultRouterDir + sp + idlClientName,
|
||||
Delims: [2]string{"{{", "}}"},
|
||||
Body: idlClientTpl,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var hertzClientTpl = `// Code generated by hz.
|
||||
|
||||
package {{.PackageName}}
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
hertz_client "github.com/cloudwego/hertz/pkg/app/client"
|
||||
"github.com/cloudwego/hertz/pkg/common/config"
|
||||
"github.com/cloudwego/hertz/pkg/common/errors"
|
||||
"github.com/cloudwego/hertz/pkg/protocol"
|
||||
"github.com/cloudwego/hertz/pkg/protocol/client"
|
||||
)
|
||||
|
||||
type use interface {
|
||||
Use(mws ...hertz_client.Middleware)
|
||||
}
|
||||
|
||||
// Definition of global data and types.
|
||||
type ResponseResultDecider func(statusCode int, rawResponse *protocol.Response) (isError bool)
|
||||
|
||||
type (
|
||||
bindRequestBodyFunc func(c *cli, r *request) (contentType string, body io.Reader, err error)
|
||||
beforeRequestFunc func(*cli, *request) error
|
||||
afterResponseFunc func(*cli, *response) error
|
||||
)
|
||||
|
||||
var (
|
||||
hdrContentTypeKey = http.CanonicalHeaderKey("Content-Type")
|
||||
hdrContentEncodingKey = http.CanonicalHeaderKey("Content-Encoding")
|
||||
|
||||
plainTextType = "text/plain; charset=utf-8"
|
||||
jsonContentType = "application/json; charset=utf-8"
|
||||
formContentType = "multipart/form-data"
|
||||
|
||||
jsonCheck = regexp.MustCompile(` + "`(?i:(application|text)/(json|.*\\+json|json\\-.*)(; |$))`)\n" +
|
||||
`xmlCheck = regexp.MustCompile(` + "`(?i:(application|text)/(xml|.*\\+xml)(; |$))`)\n" +
|
||||
`
|
||||
)
|
||||
|
||||
// Configuration of client
|
||||
type Option struct {
|
||||
f func(*Options)
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
hostUrl string
|
||||
enumAsInt bool
|
||||
doer client.Doer
|
||||
header http.Header
|
||||
requestBodyBind bindRequestBodyFunc
|
||||
responseResultDecider ResponseResultDecider
|
||||
middlewares []hertz_client.Middleware
|
||||
clientOption []config.ClientOption
|
||||
}
|
||||
|
||||
func getOptions(ops ...Option) *Options {
|
||||
opts := &Options{}
|
||||
for _, do := range ops {
|
||||
do.f(opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// WithHertzClientOption is used to pass configuration for the hertz client
|
||||
func WithHertzClientOption(opt ...config.ClientOption) Option {
|
||||
return Option{func(op *Options) {
|
||||
op.clientOption = append(op.clientOption, opt...)
|
||||
}}
|
||||
}
|
||||
|
||||
// WithHertzClientMiddleware is used to register the middleware for the hertz client
|
||||
func WithHertzClientMiddleware(mws ...hertz_client.Middleware) Option {
|
||||
return Option{func(op *Options) {
|
||||
op.middlewares = append(op.middlewares, mws...)
|
||||
}}
|
||||
}
|
||||
|
||||
// WithHertzClient is used to register a custom hertz client
|
||||
func WithHertzClient(client client.Doer) Option {
|
||||
return Option{func(op *Options) {
|
||||
op.doer = client
|
||||
}}
|
||||
}
|
||||
|
||||
// WithHeader is used to add the default header, which is carried by every request
|
||||
func WithHeader(header http.Header) Option {
|
||||
return Option{func(op *Options) {
|
||||
op.header = header
|
||||
}}
|
||||
}
|
||||
|
||||
// WithResponseResultDecider configure custom deserialization of http response to response struct
|
||||
func WithResponseResultDecider(decider ResponseResultDecider) Option {
|
||||
return Option{func(op *Options) {
|
||||
op.responseResultDecider = decider
|
||||
}}
|
||||
}
|
||||
|
||||
// WithQueryEnumAsInt is used to set enum as int for query parameters
|
||||
func WithQueryEnumAsInt(enable bool) Option {
|
||||
return Option{func(op *Options) {
|
||||
op.enumAsInt = enable
|
||||
}}
|
||||
}
|
||||
|
||||
func withHostUrl(HostUrl string) Option {
|
||||
return Option{func(op *Options) {
|
||||
op.hostUrl = HostUrl
|
||||
}}
|
||||
}
|
||||
|
||||
// underlying client
|
||||
type cli struct {
|
||||
hostUrl string
|
||||
enumAsInt bool
|
||||
doer client.Doer
|
||||
header http.Header
|
||||
bindRequestBody bindRequestBodyFunc
|
||||
responseResultDecider ResponseResultDecider
|
||||
|
||||
beforeRequest []beforeRequestFunc
|
||||
afterResponse []afterResponseFunc
|
||||
}
|
||||
|
||||
func (c *cli) Use(mws ...hertz_client.Middleware) error {
|
||||
u, ok := c.doer.(use)
|
||||
if !ok {
|
||||
return errors.NewPublic("doer does not support middleware, choose the right doer.")
|
||||
}
|
||||
u.Use(mws...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newClient(opts *Options) (*cli, error) {
|
||||
if opts.requestBodyBind == nil {
|
||||
opts.requestBodyBind = defaultRequestBodyBind
|
||||
}
|
||||
if opts.responseResultDecider == nil {
|
||||
opts.responseResultDecider = defaultResponseResultDecider
|
||||
}
|
||||
if opts.doer == nil {
|
||||
cli, err := hertz_client.NewClient(opts.clientOption...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.doer = cli
|
||||
}
|
||||
|
||||
c := &cli{
|
||||
hostUrl: opts.hostUrl,
|
||||
enumAsInt: opts.enumAsInt,
|
||||
doer: opts.doer,
|
||||
header: opts.header,
|
||||
bindRequestBody: opts.requestBodyBind,
|
||||
responseResultDecider: opts.responseResultDecider,
|
||||
beforeRequest: []beforeRequestFunc{
|
||||
parseRequestURL,
|
||||
parseRequestHeader,
|
||||
createHTTPRequest,
|
||||
},
|
||||
afterResponse: []afterResponseFunc{
|
||||
parseResponseBody,
|
||||
},
|
||||
}
|
||||
|
||||
if len(opts.middlewares) != 0 {
|
||||
if err := c.Use(opts.middlewares...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *cli) execute(req *request) (*response, error) {
|
||||
var err error
|
||||
for _, f := range c.beforeRequest {
|
||||
if err = f(c, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if hostHeader := req.header.Get("Host"); hostHeader != "" {
|
||||
req.rawRequest.Header.SetHost(hostHeader)
|
||||
}
|
||||
|
||||
resp := protocol.Response{}
|
||||
|
||||
err = c.doer.Do(req.ctx, req.rawRequest, &resp)
|
||||
|
||||
response := &response{
|
||||
request: req,
|
||||
rawResponse: &resp,
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
body, err := resp.BodyE()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.Header.ContentLength() != 0 {
|
||||
body, err = resp.BodyGunzip()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
response.bodyByte = body
|
||||
|
||||
response.size = int64(len(response.bodyByte))
|
||||
|
||||
// Apply Response middleware
|
||||
for _, f := range c.afterResponse {
|
||||
if err = f(c, response); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return response, err
|
||||
}
|
||||
|
||||
// r get request
|
||||
func (c *cli) r() *request {
|
||||
return &request{
|
||||
queryParam: url.Values{},
|
||||
header: http.Header{},
|
||||
pathParam: map[string]string{},
|
||||
formParam: map[string]string{},
|
||||
fileParam: map[string]string{},
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type response struct {
|
||||
request *request
|
||||
rawResponse *protocol.Response
|
||||
|
||||
bodyByte []byte
|
||||
size int64
|
||||
}
|
||||
|
||||
// statusCode method returns the HTTP status code for the executed request.
|
||||
func (r *response) statusCode() int {
|
||||
if r.rawResponse == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return r.rawResponse.StatusCode()
|
||||
}
|
||||
|
||||
// body method returns HTTP response as []byte array for the executed request.
|
||||
func (r *response) body() []byte {
|
||||
if r.rawResponse == nil {
|
||||
return []byte{}
|
||||
}
|
||||
return r.bodyByte
|
||||
}
|
||||
|
||||
// Header method returns the response headers
|
||||
func (r *response) header() http.Header {
|
||||
if r.rawResponse == nil {
|
||||
return http.Header{}
|
||||
}
|
||||
h := http.Header{}
|
||||
r.rawResponse.Header.VisitAll(func(key, value []byte) {
|
||||
h.Add(string(key), string(value))
|
||||
})
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type request struct {
|
||||
client *cli
|
||||
url string
|
||||
method string
|
||||
queryParam url.Values
|
||||
header http.Header
|
||||
pathParam map[string]string
|
||||
formParam map[string]string
|
||||
fileParam map[string]string
|
||||
bodyParam interface{}
|
||||
rawRequest *protocol.Request
|
||||
ctx context.Context
|
||||
requestOptions []config.RequestOption
|
||||
result interface{}
|
||||
Error interface{}
|
||||
}
|
||||
|
||||
func (r *request) setContext(ctx context.Context) *request {
|
||||
r.ctx = ctx
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) context() context.Context {
|
||||
return r.ctx
|
||||
}
|
||||
|
||||
func (r *request) setHeader(header, value string) *request {
|
||||
r.header.Set(header, value)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) addHeader(header, value string) *request {
|
||||
r.header.Add(header, value)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) addHeaders(params map[string]string) *request {
|
||||
for k, v := range params {
|
||||
r.addHeader(k, v)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
func (r *request) setQueryParam(param string, value interface{}) *request {
|
||||
v := reflect.ValueOf(value)
|
||||
switch v.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
for index := 0; index < v.Len(); index++ {
|
||||
r.queryParam.Add(param, fmt.Sprint(v.Index(index).Interface()))
|
||||
}
|
||||
case reflect.Int32, reflect.Int64:
|
||||
if r.client.enumAsInt {
|
||||
r.queryParam.Add(param, fmt.Sprintf("%d", v.Interface()))
|
||||
} else {
|
||||
r.queryParam.Add(param, fmt.Sprint(v))
|
||||
}
|
||||
default:
|
||||
r.queryParam.Set(param, fmt.Sprint(v))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setResult(res interface{}) *request {
|
||||
r.result = res
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setError(err interface{}) *request {
|
||||
r.Error = err
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setHeaders(headers map[string]string) *request {
|
||||
for h, v := range headers {
|
||||
r.setHeader(h, v)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setQueryParams(params map[string]interface{}) *request {
|
||||
for p, v := range params {
|
||||
r.setQueryParam(p, v)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setPathParams(params map[string]string) *request {
|
||||
for p, v := range params {
|
||||
r.pathParam[p] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setFormParams(params map[string]string) *request {
|
||||
for p, v := range params {
|
||||
r.formParam[p] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setFormFileParams(params map[string]string) *request {
|
||||
for p, v := range params {
|
||||
r.fileParam[p] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setBodyParam(body interface{}) *request {
|
||||
r.bodyParam = body
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) setRequestOption(option ...config.RequestOption) *request {
|
||||
r.requestOptions = append(r.requestOptions, option...)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *request) execute(method, url string) (*response, error) {
|
||||
r.method = method
|
||||
r.url = url
|
||||
return r.client.execute(r)
|
||||
}
|
||||
|
||||
func parseRequestURL(c *cli, r *request) error {
|
||||
if len(r.pathParam) > 0 {
|
||||
for p, v := range r.pathParam {
|
||||
r.url = strings.Replace(r.url, ":"+p, url.PathEscape(v), -1)
|
||||
}
|
||||
}
|
||||
|
||||
// Parsing request URL
|
||||
reqURL, err := url.Parse(r.url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If request.URL is relative path then added c.HostURL into
|
||||
// the request URL otherwise request.URL will be used as-is
|
||||
if !reqURL.IsAbs() {
|
||||
r.url = reqURL.String()
|
||||
if len(r.url) > 0 && r.url[0] != '/' {
|
||||
r.url = "/" + r.url
|
||||
}
|
||||
|
||||
reqURL, err = url.Parse(c.hostUrl + r.url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Adding Query Param
|
||||
query := make(url.Values)
|
||||
|
||||
for k, v := range r.queryParam {
|
||||
// remove query param from client level by key
|
||||
// since overrides happens for that key in the request
|
||||
query.Del(k)
|
||||
for _, iv := range v {
|
||||
query.Add(k, iv)
|
||||
}
|
||||
}
|
||||
|
||||
if len(query) > 0 {
|
||||
if isStringEmpty(reqURL.RawQuery) {
|
||||
reqURL.RawQuery = query.Encode()
|
||||
} else {
|
||||
reqURL.RawQuery = reqURL.RawQuery + "&" + query.Encode()
|
||||
}
|
||||
}
|
||||
|
||||
r.url = reqURL.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isStringEmpty(str string) bool {
|
||||
return len(strings.TrimSpace(str)) == 0
|
||||
}
|
||||
|
||||
func parseRequestHeader(c *cli, r *request) error {
|
||||
hdr := make(http.Header)
|
||||
if c.header != nil {
|
||||
for k := range c.header {
|
||||
hdr[k] = append(hdr[k], c.header[k]...)
|
||||
}
|
||||
}
|
||||
|
||||
for k := range r.header {
|
||||
hdr.Del(k)
|
||||
hdr[k] = append(hdr[k], r.header[k]...)
|
||||
}
|
||||
|
||||
if len(r.formParam) != 0 || len(r.fileParam) != 0 {
|
||||
hdr.Add(hdrContentTypeKey, formContentType)
|
||||
}
|
||||
|
||||
r.header = hdr
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectContentType method is used to figure out "request.Body" content type for request header
|
||||
func detectContentType(body interface{}) string {
|
||||
contentType := plainTextType
|
||||
kind := reflect.Indirect(reflect.ValueOf(body)).Kind()
|
||||
switch kind {
|
||||
case reflect.Struct, reflect.Map:
|
||||
contentType = jsonContentType
|
||||
case reflect.String:
|
||||
contentType = plainTextType
|
||||
default:
|
||||
if b, ok := body.([]byte); ok {
|
||||
contentType = http.DetectContentType(b)
|
||||
} else if kind == reflect.Slice {
|
||||
contentType = jsonContentType
|
||||
}
|
||||
}
|
||||
|
||||
return contentType
|
||||
}
|
||||
|
||||
func defaultRequestBodyBind(c *cli, r *request) (contentType string, body io.Reader, err error) {
|
||||
if !isPayloadSupported(r.method) {
|
||||
return
|
||||
}
|
||||
var bodyBytes []byte
|
||||
contentType = r.header.Get(hdrContentTypeKey)
|
||||
if isStringEmpty(contentType) {
|
||||
contentType = detectContentType(r.bodyParam)
|
||||
r.header.Set(hdrContentTypeKey, contentType)
|
||||
}
|
||||
kind := reflect.Indirect(reflect.ValueOf(r.bodyParam)).Kind()
|
||||
if isJSONType(contentType) &&
|
||||
(kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
|
||||
bodyBytes, err = json.Marshal(r.bodyParam)
|
||||
} else if isXMLType(contentType) && (kind == reflect.Struct) {
|
||||
bodyBytes, err = xml.Marshal(r.bodyParam)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return contentType, strings.NewReader(string(bodyBytes)), nil
|
||||
}
|
||||
|
||||
func isPayloadSupported(m string) bool {
|
||||
return !(m == http.MethodHead || m == http.MethodOptions || m == http.MethodGet || m == http.MethodDelete)
|
||||
}
|
||||
|
||||
func createHTTPRequest(c *cli, r *request) (err error) {
|
||||
contentType, body, err := c.bindRequestBody(c, r)
|
||||
if !isStringEmpty(contentType) {
|
||||
r.header.Set(hdrContentTypeKey, contentType)
|
||||
}
|
||||
if err == nil {
|
||||
r.rawRequest = protocol.NewRequest(r.method, r.url, body)
|
||||
if contentType == formContentType && isPayloadSupported(r.method) {
|
||||
if r.rawRequest.IsBodyStream() {
|
||||
r.rawRequest.ResetBody()
|
||||
}
|
||||
r.rawRequest.SetMultipartFormData(r.formParam)
|
||||
r.rawRequest.SetFiles(r.fileParam)
|
||||
}
|
||||
for key, values := range r.header {
|
||||
for _, val := range values {
|
||||
r.rawRequest.Header.Add(key, val)
|
||||
}
|
||||
}
|
||||
r.rawRequest.SetOptions(r.requestOptions...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func silently(_ ...interface{}) {}
|
||||
|
||||
// defaultResponseResultDecider method returns true if HTTP status code >= 400 otherwise false.
|
||||
func defaultResponseResultDecider(statusCode int, rawResponse *protocol.Response) bool {
|
||||
return statusCode > 399
|
||||
}
|
||||
|
||||
// IsJSONType method is to check JSON content type or not
|
||||
func isJSONType(ct string) bool {
|
||||
return jsonCheck.MatchString(ct)
|
||||
}
|
||||
|
||||
// IsXMLType method is to check XML content type or not
|
||||
func isXMLType(ct string) bool {
|
||||
return xmlCheck.MatchString(ct)
|
||||
}
|
||||
|
||||
func parseResponseBody(c *cli, res *response) (err error) {
|
||||
if res.statusCode() == http.StatusNoContent {
|
||||
return
|
||||
}
|
||||
// Handles only JSON or XML content type
|
||||
ct := res.header().Get(hdrContentTypeKey)
|
||||
|
||||
isError := c.responseResultDecider(res.statusCode(), res.rawResponse)
|
||||
if isError {
|
||||
if res.request.Error != nil {
|
||||
if isJSONType(ct) || isXMLType(ct) {
|
||||
err = unmarshalContent(ct, res.bodyByte, res.request.Error)
|
||||
}
|
||||
} else {
|
||||
jsonByte, jsonErr := json.Marshal(map[string]interface{}{
|
||||
"status_code": res.rawResponse.StatusCode(),
|
||||
"body": string(res.bodyByte),
|
||||
})
|
||||
if jsonErr != nil {
|
||||
return jsonErr
|
||||
}
|
||||
err = fmt.Errorf(string(jsonByte))
|
||||
}
|
||||
} else if res.request.result != nil {
|
||||
if isJSONType(ct) || isXMLType(ct) {
|
||||
err = unmarshalContent(ct, res.bodyByte, res.request.result)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// unmarshalContent content into object from JSON or XML
|
||||
func unmarshalContent(ct string, b []byte, d interface{}) (err error) {
|
||||
if isJSONType(ct) {
|
||||
err = json.Unmarshal(b, d)
|
||||
} else if isXMLType(ct) {
|
||||
err = xml.Unmarshal(b, d)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
var idlClientTpl = `// Code generated by hertz generator.
|
||||
|
||||
package {{.PackageName}}
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/common/config"
|
||||
"github.com/cloudwego/hertz/pkg/protocol"
|
||||
{{- range $k, $v := .Imports}}
|
||||
{{$k}} "{{$v.Package}}"
|
||||
{{- end}}
|
||||
)
|
||||
|
||||
// unused protection
|
||||
var (
|
||||
_ = fmt.Formatter(nil)
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
{{range $_, $MethodInfo := .ClientMethods}}
|
||||
{{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error)
|
||||
{{end}}
|
||||
}
|
||||
|
||||
type {{.ServiceName}}Client struct {
|
||||
client *cli
|
||||
}
|
||||
|
||||
func New{{.ServiceName}}Client(hostUrl string, ops ...Option) (Client, error) {
|
||||
opts := getOptions(append(ops, withHostUrl(hostUrl))...)
|
||||
cli, err := newClient(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.ServiceName}}Client{
|
||||
client: cli,
|
||||
}, nil
|
||||
}
|
||||
|
||||
{{range $_, $MethodInfo := .ClientMethods}}
|
||||
func (s *{{$.ServiceName}}Client) {{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error) {
|
||||
httpResp := &{{$MethodInfo.ReturnTypeName}}{}
|
||||
ret, err := s.client.r().
|
||||
setContext(context).
|
||||
setQueryParams(map[string]interface{}{
|
||||
{{$MethodInfo.QueryParamsCode}}
|
||||
}).
|
||||
setPathParams(map[string]string{
|
||||
{{$MethodInfo.PathParamsCode}}
|
||||
}).
|
||||
addHeaders(map[string]string{
|
||||
{{$MethodInfo.HeaderParamsCode}}
|
||||
}).
|
||||
setFormParams(map[string]string{
|
||||
{{$MethodInfo.FormValueCode}}
|
||||
}).
|
||||
setFormFileParams(map[string]string{
|
||||
{{$MethodInfo.FormFileCode}}
|
||||
}).
|
||||
{{$MethodInfo.BodyParamsCode}}
|
||||
setRequestOption(reqOpt...).
|
||||
setResult(httpResp).
|
||||
execute("{{if EqualFold $MethodInfo.HTTPMethod "Any"}}POST{{else}}{{ $MethodInfo.HTTPMethod }}{{end}}", "{{$MethodInfo.Path}}")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resp = httpResp
|
||||
rawResponse = ret.rawResponse
|
||||
return resp, rawResponse, nil
|
||||
}
|
||||
{{end}}
|
||||
|
||||
var defaultClient, _ = New{{.ServiceName}}Client("{{.BaseDomain}}")
|
||||
|
||||
func ConfigDefaultClient(ops ...Option) (err error) {
|
||||
defaultClient, err = New{{.ServiceName}}Client("{{.BaseDomain}}", ops...)
|
||||
return
|
||||
}
|
||||
|
||||
{{range $_, $MethodInfo := .ClientMethods}}
|
||||
func {{$MethodInfo.Name}}(context context.Context, req *{{$MethodInfo.RequestTypeName}}, reqOpt ...config.RequestOption) (resp *{{$MethodInfo.ReturnTypeName}}, rawResponse *protocol.Response, err error) {
|
||||
return defaultClient.{{$MethodInfo.Name}}(context, req, reqOpt...)
|
||||
}
|
||||
{{end}}
|
||||
`
|
472
generator/router.go
Normal file
472
generator/router.go
Normal file
@ -0,0 +1,472 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
FilePath string
|
||||
PackageName string
|
||||
HandlerPackages map[string]string // {{basename}}:{{import_path}}
|
||||
Router *RouterNode
|
||||
IdlName string
|
||||
}
|
||||
|
||||
type RouterNode struct {
|
||||
GroupName string // current group name(the parent middleware name), used to register route. example: {{.GroupName}}.{{HttpMethod}}
|
||||
MiddleWare string // current node middleware, used to be group name for children.
|
||||
HandlerMiddleware string
|
||||
GroupMiddleware string
|
||||
PathPrefix string
|
||||
|
||||
Path string
|
||||
Parent *RouterNode
|
||||
Children childrenRouterInfo
|
||||
|
||||
Handler string // {{HandlerPackage}}.{{HandlerName}}
|
||||
HandlerPackage string
|
||||
HandlerPackageAlias string
|
||||
HttpMethod string
|
||||
}
|
||||
|
||||
type RegisterInfo struct {
|
||||
PackageName string
|
||||
DepPkgAlias string
|
||||
DepPkg string
|
||||
RegisterName string
|
||||
}
|
||||
|
||||
// NewRouterTree contains "/" as root node
|
||||
func NewRouterTree() *RouterNode {
|
||||
return &RouterNode{
|
||||
GroupName: "root",
|
||||
MiddleWare: "root",
|
||||
GroupMiddleware: "root",
|
||||
Path: "/",
|
||||
Parent: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (routerNode *RouterNode) Sort() {
|
||||
sort.Sort(routerNode.Children)
|
||||
}
|
||||
|
||||
func (routerNode *RouterNode) Update(method *HttpMethod, handlerType, handlerPkg string) error {
|
||||
if method.Path == "" {
|
||||
return fmt.Errorf("empty path for method '%s'", method.Name)
|
||||
}
|
||||
paths := strings.Split(method.Path, "/")
|
||||
if paths[0] == "" {
|
||||
paths = paths[1:]
|
||||
}
|
||||
parent, last := routerNode.FindNearest(paths)
|
||||
if last == len(paths) {
|
||||
return fmt.Errorf("path '%s' has been registered", method.Path)
|
||||
}
|
||||
name := util.ToVarName(paths[:last])
|
||||
parent.Insert(name, method, handlerType, paths[last:], handlerPkg)
|
||||
parent.Sort()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (routerNode *RouterNode) RawHandlerName() string {
|
||||
parts := strings.Split(routerNode.Handler, ".")
|
||||
handlerName := parts[len(parts)-1]
|
||||
return handlerName
|
||||
}
|
||||
|
||||
// DyeGroupName traverses the routing tree in depth and names the handler/group middleware for each node.
|
||||
// If snakeStyleMiddleware is set to true, the name style of the middleware will use snake name style.
|
||||
func (routerNode *RouterNode) DyeGroupName(snakeStyleMiddleware bool) error {
|
||||
groups := []string{"root"}
|
||||
|
||||
hook := func(layer int, node *RouterNode) error {
|
||||
node.GroupName = groups[layer]
|
||||
if node.MiddleWare == "" {
|
||||
pname := node.Path
|
||||
if len(pname) > 1 && pname[0] == '/' {
|
||||
pname = pname[1:]
|
||||
}
|
||||
|
||||
if node.Parent != nil {
|
||||
node.PathPrefix = node.Parent.PathPrefix + "_" + util.ToGoFuncName(pname)
|
||||
} else {
|
||||
node.PathPrefix = "_" + util.ToGoFuncName(pname)
|
||||
}
|
||||
|
||||
handlerMiddlewareName := ""
|
||||
isLeafNode := false
|
||||
if len(node.Handler) != 0 {
|
||||
handlerMiddlewareName = node.RawHandlerName()
|
||||
// If it is a leaf node, then "group middleware name" and "handler middleware name" are the same
|
||||
if len(node.Children) == 0 {
|
||||
pname = handlerMiddlewareName
|
||||
isLeafNode = true
|
||||
}
|
||||
}
|
||||
|
||||
pname = convertToMiddlewareName(pname)
|
||||
handlerMiddlewareName = convertToMiddlewareName(handlerMiddlewareName)
|
||||
|
||||
if isLeafNode {
|
||||
name, err := util.GetMiddlewareUniqueName(pname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get unique name for middleware '%s' failed, err: %v", name, err)
|
||||
}
|
||||
pname = name
|
||||
handlerMiddlewareName = name
|
||||
} else {
|
||||
var err error
|
||||
pname, err = util.GetMiddlewareUniqueName(pname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get unique name for middleware '%s' failed, err: %v", pname, err)
|
||||
}
|
||||
handlerMiddlewareName, err = util.GetMiddlewareUniqueName(handlerMiddlewareName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get unique name for middleware '%s' failed, err: %v", handlerMiddlewareName, err)
|
||||
}
|
||||
}
|
||||
node.MiddleWare = "_" + pname
|
||||
if len(node.Handler) != 0 {
|
||||
node.HandlerMiddleware = "_" + handlerMiddlewareName
|
||||
if snakeStyleMiddleware {
|
||||
node.HandlerMiddleware = "_" + node.RawHandlerName()
|
||||
}
|
||||
}
|
||||
node.GroupMiddleware = node.MiddleWare
|
||||
if snakeStyleMiddleware {
|
||||
node.GroupMiddleware = node.PathPrefix
|
||||
}
|
||||
}
|
||||
if layer >= len(groups)-1 {
|
||||
groups = append(groups, node.MiddleWare)
|
||||
} else {
|
||||
groups[layer+1] = node.MiddleWare
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deep traversal from the 0th level of the routing tree.
|
||||
err := routerNode.DFS(0, hook)
|
||||
return err
|
||||
}
|
||||
|
||||
func (routerNode *RouterNode) DFS(i int, hook func(layer int, node *RouterNode) error) error {
|
||||
if routerNode == nil {
|
||||
return nil
|
||||
}
|
||||
err := hook(i, routerNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range routerNode.Children {
|
||||
err = n.DFS(i+1, hook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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,
|
||||
Parent: cur,
|
||||
}
|
||||
if i == len(paths)-1 {
|
||||
// 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
|
||||
method.RefPackage = c.HandlerPackage
|
||||
method.RefPackageAlias = c.HandlerPackageAlias
|
||||
} else { // generate handler by service
|
||||
c.Handler = handlerType + "." + method.Name
|
||||
}
|
||||
c.HttpMethod = getHttpMethod(method.HTTPMethod)
|
||||
}
|
||||
if cur.Children == nil {
|
||||
cur.Children = make([]*RouterNode, 0, 1)
|
||||
}
|
||||
cur.Children = append(cur.Children, c)
|
||||
cur = c
|
||||
}
|
||||
}
|
||||
|
||||
func getHttpMethod(method string) string {
|
||||
if strings.EqualFold(method, "Any") {
|
||||
return "Any"
|
||||
}
|
||||
return strings.ToUpper(method)
|
||||
}
|
||||
|
||||
func (routerNode *RouterNode) FindNearest(paths []string) (*RouterNode, int) {
|
||||
ns := len(paths)
|
||||
cur := routerNode
|
||||
i := 0
|
||||
path := paths[i]
|
||||
for j := 0; j < len(cur.Children); j++ {
|
||||
c := cur.Children[j]
|
||||
if ("/" + path) == c.Path {
|
||||
i++
|
||||
if i == ns {
|
||||
return cur, i - 1
|
||||
}
|
||||
path = paths[i]
|
||||
cur = c
|
||||
j = -1
|
||||
}
|
||||
}
|
||||
return cur, i
|
||||
}
|
||||
|
||||
type childrenRouterInfo []*RouterNode
|
||||
|
||||
// Len is the number of elements in the collection.
|
||||
func (c childrenRouterInfo) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
// Less reports whether the element with
|
||||
// index i should sort before the element with index j.
|
||||
func (c childrenRouterInfo) Less(i, j int) bool {
|
||||
ci := c[i].Path
|
||||
if len(c[i].Children) != 0 {
|
||||
ci = ci[1:]
|
||||
}
|
||||
cj := c[j].Path
|
||||
if len(c[j].Children) != 0 {
|
||||
cj = cj[1:]
|
||||
}
|
||||
return ci < cj
|
||||
}
|
||||
|
||||
// Swap swaps the elements with indexes i and j.
|
||||
func (c childrenRouterInfo) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
var (
|
||||
regRegisterV3 = regexp.MustCompile(insertPointPatternNew)
|
||||
regImport = regexp.MustCompile(`import \(\n`)
|
||||
)
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) updateRegister(pkg, rDir, pkgName string, idlName string) error {
|
||||
if pkgGen.tplsInfo[registerTplName].Disable {
|
||||
return nil
|
||||
}
|
||||
register := RegisterInfo{
|
||||
PackageName: filepath.Base(rDir),
|
||||
DepPkgAlias: strings.ReplaceAll(pkgName, "/", "_"),
|
||||
DepPkg: pkg,
|
||||
}
|
||||
register.RegisterName = register.DepPkgAlias + ".Register" + idlName + "(r)"
|
||||
registerPath := filepath.Join(rDir, registerTplName)
|
||||
isExist, err := util.PathExist(registerPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isExist {
|
||||
return pkgGen.TemplateGenerator.Generate(register, registerTplName, registerPath, false)
|
||||
}
|
||||
|
||||
file, err := ioutil.ReadFile(registerPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read register '%s' failed, err: %v", registerPath, err.Error())
|
||||
}
|
||||
|
||||
insertReg := register.RegisterName
|
||||
if !bytes.Contains(file, []byte(insertReg)) {
|
||||
t := !bytes.Contains(file, []byte(register.DepPkg))
|
||||
|
||||
if t {
|
||||
file, err = util.AddImport(registerPath, register.DepPkgAlias, register.DepPkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//if bytes.Contains(file, []byte(insertReg)) {
|
||||
// return fmt.Errorf("the router(%s) has been registered", insertReg)
|
||||
//}
|
||||
|
||||
subIndexReg := regRegisterV3.FindSubmatchIndex(file)
|
||||
if len(subIndexReg) != 2 || subIndexReg[0] < 1 {
|
||||
return fmt.Errorf("wrong format %s: insert-point '%s' not found", string(file), insertPointPatternNew)
|
||||
}
|
||||
|
||||
bufReg := bytes.NewBuffer(nil)
|
||||
bufReg.Write(file[:subIndexReg[1]])
|
||||
bufReg.WriteString("\n")
|
||||
bufReg.WriteString(insertReg)
|
||||
if t {
|
||||
bufReg.WriteString("\n\t")
|
||||
}
|
||||
bufReg.Write(file[subIndexReg[1]:])
|
||||
|
||||
pkgGen.files = append(pkgGen.files, File{registerPath, string(bufReg.Bytes()), false, registerTplName})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) genRouter(pkg *HttpPackage, root *RouterNode, handlerPackage, routerDir, routerPackage string) error {
|
||||
err := root.DyeGroupName(pkgGen.SnakeStyleMiddleware)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idleName := util.ToCamelCase(util.BaseNameAndTrim(pkg.IdlName))
|
||||
router := Router{
|
||||
FilePath: filepath.Join(routerDir, util.BaseNameAndTrim(pkg.IdlName)+".go"),
|
||||
PackageName: filepath.Base(routerDir),
|
||||
HandlerPackages: map[string]string{
|
||||
util.BaseName(handlerPackage, ""): handlerPackage,
|
||||
},
|
||||
Router: root,
|
||||
IdlName: idleName,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// store router info
|
||||
pkg.RouterInfo = &router
|
||||
|
||||
if !pkgGen.tplsInfo[routerTplName].Disable {
|
||||
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())
|
||||
}
|
||||
}
|
||||
if err := pkgGen.updateMiddlewareReg(router, middlewareTplName, filepath.Join(routerDir, "middleware.go")); err != nil {
|
||||
return fmt.Errorf("generate middleware %s failed, err: %v", filepath.Join(routerDir, "middleware.go"), err.Error())
|
||||
}
|
||||
|
||||
if err := pkgGen.updateRegister(routerPackage, pkgGen.RouterDir, pkg.Package, idleName); err != nil {
|
||||
return fmt.Errorf("update register for %s failed, err: %v", filepath.Join(routerDir, registerTplName), err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pkgGen *HttpPackageGenerator) updateMiddlewareReg(router interface{}, middlewareTpl, filePath string) error {
|
||||
if pkgGen.tplsInfo[middlewareTpl].Disable {
|
||||
return nil
|
||||
}
|
||||
isExist, err := util.PathExist(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isExist {
|
||||
return pkgGen.TemplateGenerator.Generate(router, middlewareTpl, filePath, false)
|
||||
}
|
||||
var middlewareList []string
|
||||
|
||||
_ = router.(Router).Router.DFS(0, func(layer int, node *RouterNode) error {
|
||||
// non-leaf node will generate group middleware
|
||||
if node.Children.Len() > 0 && len(node.GroupMiddleware) > 0 {
|
||||
middlewareList = append(middlewareList, node.GroupMiddleware)
|
||||
}
|
||||
if len(node.HandlerMiddleware) > 0 {
|
||||
middlewareList = append(middlewareList, node.HandlerMiddleware)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
file, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mw := range middlewareList {
|
||||
mwNamePattern := fmt.Sprintf(" %sMw", mw)
|
||||
if pkgGen.SnakeStyleMiddleware {
|
||||
mwNamePattern = fmt.Sprintf(" %s_mw", mw)
|
||||
}
|
||||
if bytes.Contains(file, []byte(mwNamePattern)) {
|
||||
continue
|
||||
}
|
||||
middlewareSingleTpl := pkgGen.tpls[middlewareSingleTplName]
|
||||
if middlewareSingleTpl == nil {
|
||||
return fmt.Errorf("tpl %s not found", middlewareSingleTplName)
|
||||
}
|
||||
data := make(map[string]string, 1)
|
||||
data["MiddleWare"] = mw
|
||||
middlewareFunc := bytes.NewBuffer(nil)
|
||||
err = middlewareSingleTpl.Execute(middlewareFunc, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute template \"%s\" failed, %v", middlewareSingleTplName, err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_, err = buf.Write(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write middleware \"%s\" failed, %v", mw, err)
|
||||
}
|
||||
_, err = buf.Write(middlewareFunc.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("write middleware \"%s\" failed, %v", mw, err)
|
||||
}
|
||||
file = buf.Bytes()
|
||||
}
|
||||
|
||||
pkgGen.files = append(pkgGen.files, File{filePath, string(file), false, middlewareTplName})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertToMiddlewareName converts a route path to a middleware name
|
||||
func convertToMiddlewareName(path string) string {
|
||||
path = util.ToVarName([]string{path})
|
||||
path = strings.ToLower(path)
|
||||
return path
|
||||
}
|
333
generator/template.go
Normal file
333
generator/template.go
Normal file
@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
)
|
||||
|
||||
var DefaultDelimiters = [2]string{"{{", "}}"}
|
||||
|
||||
type TemplateConfig struct {
|
||||
Layouts []Template `yaml:"layouts"`
|
||||
}
|
||||
|
||||
const (
|
||||
Skip = "skip"
|
||||
Cover = "cover"
|
||||
Append = "append"
|
||||
)
|
||||
|
||||
type Template struct {
|
||||
Default bool // Is it the default template
|
||||
Path string `yaml:"path"` // The generated path and its filename, such as biz/handler/ping.go
|
||||
Delims [2]string `yaml:"delims"` // Template Action Instruction Identifier, default: "{{}}"
|
||||
Body string `yaml:"body"` // Render template, currently only supports go template syntax
|
||||
Disable bool `yaml:"disable"` // Disable generating file, used to disable default package template
|
||||
LoopMethod bool `yaml:"loop_method"` // Loop generate files based on "method"
|
||||
LoopService bool `yaml:"loop_service"` // Loop generate files based on "service"
|
||||
UpdateBehavior UpdateBehavior `yaml:"update_behavior"` // Update command behavior; 0:unchanged, 1:regenerate, 2:append
|
||||
}
|
||||
|
||||
type UpdateBehavior struct {
|
||||
Type string `yaml:"type"` // Update behavior type: skip/cover/append
|
||||
// the following variables are used for append update
|
||||
AppendKey string `yaml:"append_key"` // Append content based in key; for example: 'method'/'service'
|
||||
InsertKey string `yaml:"insert_key"` // Insert content by "insert_key"
|
||||
AppendTpl string `yaml:"append_content_tpl"` // Append content if UpdateBehavior is "append"
|
||||
ImportTpl []string `yaml:"import_tpl"` // Import insert template
|
||||
AppendLocation string `yaml:"append_location"` // AppendLocation specifies the location of append, the default is the end of the file
|
||||
}
|
||||
|
||||
// TemplateGenerator contains information about the output template
|
||||
type TemplateGenerator struct {
|
||||
OutputDir string
|
||||
Config *TemplateConfig
|
||||
Excludes []string
|
||||
tpls map[string]*template.Template // "template name" -> "Template", it is used get the "parsed template" directly
|
||||
tplsInfo map[string]*Template // "template name" -> "template info", it is used to get the original "template information"
|
||||
dirs map[string]bool
|
||||
isPackageTpl bool
|
||||
|
||||
files []File
|
||||
excludedFiles map[string]*File
|
||||
}
|
||||
|
||||
func (tg *TemplateGenerator) Init() error {
|
||||
if tg.Config == nil {
|
||||
return errors.New("config not set yet")
|
||||
}
|
||||
|
||||
if tg.tpls == nil {
|
||||
tg.tpls = make(map[string]*template.Template, len(tg.Config.Layouts))
|
||||
}
|
||||
if tg.tplsInfo == nil {
|
||||
tg.tplsInfo = make(map[string]*Template, len(tg.Config.Layouts))
|
||||
}
|
||||
if tg.dirs == nil {
|
||||
tg.dirs = make(map[string]bool)
|
||||
}
|
||||
|
||||
for _, l := range tg.Config.Layouts {
|
||||
if tg.isPackageTpl && IsDefaultPackageTpl(l.Path) {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if is a directory
|
||||
var noFile bool
|
||||
if strings.HasSuffix(l.Path, string(filepath.Separator)) {
|
||||
noFile = true
|
||||
}
|
||||
path := l.Path
|
||||
if filepath.IsAbs(path) {
|
||||
return fmt.Errorf("absolute template path '%s' is not allowed", path)
|
||||
}
|
||||
dir := filepath.Dir(path)
|
||||
isExist, err := util.PathExist(filepath.Join(tg.OutputDir, dir))
|
||||
if err != nil {
|
||||
return fmt.Errorf("check directory '%s' failed, err: %v", dir, err.Error())
|
||||
}
|
||||
if isExist {
|
||||
tg.dirs[dir] = true
|
||||
} else {
|
||||
tg.dirs[dir] = false
|
||||
}
|
||||
|
||||
if noFile {
|
||||
continue
|
||||
}
|
||||
|
||||
// parse templates
|
||||
if _, ok := tg.tpls[path]; ok {
|
||||
continue
|
||||
}
|
||||
err = tg.loadLayout(l, path, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
excludes := make(map[string]*File, len(tg.Excludes))
|
||||
for _, f := range tg.Excludes {
|
||||
excludes[f] = &File{}
|
||||
}
|
||||
|
||||
tg.excludedFiles = excludes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tg *TemplateGenerator) loadLayout(layout Template, tplName string, isDefaultTpl bool) error {
|
||||
delims := DefaultDelimiters
|
||||
if layout.Delims[0] != "" && layout.Delims[1] != "" {
|
||||
delims = layout.Delims
|
||||
}
|
||||
// insert template funcs
|
||||
tpl := template.New(tplName).Funcs(funcMap)
|
||||
tpl = tpl.Delims(delims[0], delims[1])
|
||||
var err error
|
||||
if tpl, err = tpl.Parse(layout.Body); err != nil {
|
||||
return fmt.Errorf("parse template '%s' failed, err: %v", tplName, err.Error())
|
||||
}
|
||||
layout.Default = isDefaultTpl
|
||||
tg.tpls[tplName] = tpl
|
||||
tg.tplsInfo[tplName] = &layout
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tg *TemplateGenerator) Generate(input interface{}, tplName, filepath string, noRepeat bool) error {
|
||||
// check if "*" (global scope) data exists, and stores it to all
|
||||
var all map[string]interface{}
|
||||
if data, ok := input.(map[string]interface{}); ok {
|
||||
ad, ok := data["*"]
|
||||
if ok {
|
||||
all = ad.(map[string]interface{})
|
||||
}
|
||||
if all == nil {
|
||||
all = map[string]interface{}{}
|
||||
}
|
||||
all["hzVersion"] = meta.Version
|
||||
}
|
||||
|
||||
file := bytes.NewBuffer(nil)
|
||||
if tplName != "" {
|
||||
tpl := tg.tpls[tplName]
|
||||
if tpl == nil {
|
||||
return fmt.Errorf("tpl %s not found", tplName)
|
||||
}
|
||||
if err := tpl.Execute(file, input); err != nil {
|
||||
return fmt.Errorf("render template '%s' failed, err: %v", tplName, err.Error())
|
||||
}
|
||||
|
||||
in := File{filepath, string(file.Bytes()), noRepeat, tplName}
|
||||
tg.files = append(tg.files, in)
|
||||
return nil
|
||||
}
|
||||
|
||||
for path, tpl := range tg.tpls {
|
||||
file.Reset()
|
||||
var fd interface{}
|
||||
// search and merge rendering data
|
||||
if data, ok := input.(map[string]interface{}); ok {
|
||||
td := map[string]interface{}{}
|
||||
tmp, ok := data[path]
|
||||
if ok {
|
||||
td = tmp.(map[string]interface{})
|
||||
}
|
||||
for k, v := range all {
|
||||
td[k] = v
|
||||
}
|
||||
fd = td
|
||||
} else {
|
||||
fd = input
|
||||
}
|
||||
if err := tpl.Execute(file, fd); err != nil {
|
||||
return fmt.Errorf("render template '%s' failed, err: %v", path, err.Error())
|
||||
}
|
||||
|
||||
in := File{path, string(file.Bytes()), noRepeat, tpl.Name()}
|
||||
tg.files = append(tg.files, in)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tg *TemplateGenerator) Persist() error {
|
||||
files := tg.files
|
||||
outPath := tg.OutputDir
|
||||
if !filepath.IsAbs(outPath) {
|
||||
outPath, _ = filepath.Abs(outPath)
|
||||
}
|
||||
|
||||
for _, data := range files {
|
||||
// check for -E flags
|
||||
if _, ok := tg.excludedFiles[filepath.Join(data.Path)]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// lint file
|
||||
if err := data.Lint(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create rendered file
|
||||
abPath := filepath.Join(outPath, data.Path)
|
||||
abDir := filepath.Dir(abPath)
|
||||
isExist, err := util.PathExist(abDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check directory '%s' failed, err: %v", abDir, err.Error())
|
||||
}
|
||||
if !isExist {
|
||||
if err := os.MkdirAll(abDir, os.FileMode(0o744)); err != nil {
|
||||
return fmt.Errorf("mkdir %s failed, err: %v", abDir, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
file, err := os.OpenFile(abPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.FileMode(0o755))
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("open file '%s' failed, err: %v", abPath, err.Error())
|
||||
}
|
||||
if _, err = file.WriteString(data.Content); err != nil {
|
||||
return fmt.Errorf("write file '%s' failed, err: %v", abPath, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tg.files = tg.files[:0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tg *TemplateGenerator) GetFormatAndExcludedFiles() ([]File, error) {
|
||||
var files []File
|
||||
outPath := tg.OutputDir
|
||||
if !filepath.IsAbs(outPath) {
|
||||
outPath, _ = filepath.Abs(outPath)
|
||||
}
|
||||
|
||||
for _, data := range tg.Files() {
|
||||
if _, ok := tg.excludedFiles[filepath.Join(data.Path)]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// check repeat files
|
||||
logs.Infof("Write %s", data.Path)
|
||||
isExist, err := util.PathExist(filepath.Join(data.Path))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("check file '%s' failed, err: %v", data.Path, err.Error())
|
||||
}
|
||||
if isExist && data.NoRepeat {
|
||||
if data.FileTplName == handlerTplName {
|
||||
logs.Warnf("Handler file(%s) has been generated.\n If you want to re-generate it, please copy and delete the file to prevent the already written code from being deleted.", data.Path)
|
||||
} else if data.FileTplName == routerTplName {
|
||||
logs.Warnf("Router file(%s) has been generated.\n If you want to re-generate it, please delete the file.", data.Path)
|
||||
} else {
|
||||
logs.Warnf("file '%s' already exists, so drop the generated file", data.Path)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// lint file
|
||||
if err := data.Lint(); err != nil {
|
||||
logs.Warnf("Lint file: %s failed:\n %s\n", data.Path, data.Content)
|
||||
}
|
||||
files = append(files, data)
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (tg *TemplateGenerator) Files() []File {
|
||||
return tg.files
|
||||
}
|
||||
|
||||
func (tg *TemplateGenerator) Degenerate() error {
|
||||
outPath := tg.OutputDir
|
||||
if !filepath.IsAbs(outPath) {
|
||||
outPath, _ = filepath.Abs(outPath)
|
||||
}
|
||||
for path := range tg.tpls {
|
||||
abPath := filepath.Join(outPath, path)
|
||||
if err := os.RemoveAll(abPath); err != nil {
|
||||
return fmt.Errorf("remove file '%s' failed, err: %v", path, err.Error())
|
||||
}
|
||||
}
|
||||
for dir, exist := range tg.dirs {
|
||||
if !exist {
|
||||
abDir := filepath.Join(outPath, dir)
|
||||
if err := os.RemoveAll(abDir); err != nil {
|
||||
return fmt.Errorf("remove directory '%s' failed, err: %v", dir, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
52
generator/template_funcs.go
Normal file
52
generator/template_funcs.go
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
)
|
||||
|
||||
var funcMap = func() template.FuncMap {
|
||||
m := template.FuncMap{
|
||||
"GetUniqueHandlerOutDir": getUniqueHandlerOutDir,
|
||||
"ToSnakeCase": util.ToSnakeCase,
|
||||
"Split": strings.Split,
|
||||
"Trim": strings.Trim,
|
||||
"EqualFold": strings.EqualFold,
|
||||
}
|
||||
for key, f := range sprig.TxtFuncMap() {
|
||||
m[key] = f
|
||||
}
|
||||
return m
|
||||
}()
|
||||
|
||||
// getUniqueHandlerOutDir uses to get unique "api.handler_path"
|
||||
func getUniqueHandlerOutDir(methods []*HttpMethod) (ret []string) {
|
||||
outDirMap := make(map[string]string)
|
||||
for _, method := range methods {
|
||||
if _, exist := outDirMap[method.OutputDir]; !exist {
|
||||
outDirMap[method.OutputDir] = method.OutputDir
|
||||
ret = append(ret, method.OutputDir)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
||||
module github.com/cloudwego/hertz/cmd/hz
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/cloudwego/thriftgo v0.1.7
|
||||
github.com/hashicorp/go-version v1.5.0
|
||||
github.com/jhump/protoreflect v1.12.0
|
||||
github.com/urfave/cli/v2 v2.23.0
|
||||
golang.org/x/tools v0.4.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
189
go.sum
Normal file
189
go.sum
Normal file
@ -0,0 +1,189 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/thriftgo v0.1.7 h1:mTGRv6Dtwfp0hTPZXuIHwm3vtGOuZVTrWarI0xVzUYg=
|
||||
github.com/cloudwego/thriftgo v0.1.7/go.mod h1:LzeafuLSiHA9JTiWC8TIMIq64iadeObgRUhmVG1OC/w=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E=
|
||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
|
||||
github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
|
||||
github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
|
||||
github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
|
||||
github.com/jhump/protoreflect v1.12.0 h1:1NQ4FpWMgn3by/n1X0fbeKEUxP1wBt7+Oitpv01HR10=
|
||||
github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/urfave/cli/v2 v2.23.0 h1:pkly7gKIeYv3olPAeNajNpLjeJrmTPYCoZWaV+2VfvE=
|
||||
github.com/urfave/cli/v2 v2.23.0/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
44
main.go
Normal file
44
main.go
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/app"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// run in plugin mode
|
||||
app.PluginMode()
|
||||
|
||||
// run in normal mode
|
||||
Run()
|
||||
}
|
||||
|
||||
func Run() {
|
||||
defer func() {
|
||||
logs.Flush()
|
||||
}()
|
||||
|
||||
cli := app.Init()
|
||||
err := cli.Run(os.Args)
|
||||
if err != nil {
|
||||
logs.Errorf("%v\n", err)
|
||||
}
|
||||
}
|
92
meta/const.go
Normal file
92
meta/const.go
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package meta
|
||||
|
||||
import "runtime"
|
||||
|
||||
// Version hz version
|
||||
const Version = "v0.8.1"
|
||||
|
||||
const DefaultServiceName = "hertz_service"
|
||||
|
||||
// Mode hz run modes
|
||||
type Mode int
|
||||
|
||||
// SysType is the running program's operating system type
|
||||
const SysType = runtime.GOOS
|
||||
|
||||
const WindowsOS = "windows"
|
||||
|
||||
const EnvPluginMode = "HERTZ_PLUGIN_MODE"
|
||||
|
||||
// hz Commands
|
||||
const (
|
||||
CmdUpdate = "update"
|
||||
CmdNew = "new"
|
||||
CmdModel = "model"
|
||||
CmdClient = "client"
|
||||
)
|
||||
|
||||
// hz IDLs
|
||||
const (
|
||||
IdlThrift = "thrift"
|
||||
IdlProto = "proto"
|
||||
)
|
||||
|
||||
// Third-party Compilers
|
||||
const (
|
||||
TpCompilerThrift = "thriftgo"
|
||||
TpCompilerProto = "protoc"
|
||||
)
|
||||
|
||||
// hz Plugins
|
||||
const (
|
||||
ProtocPluginName = "protoc-gen-hertz"
|
||||
ThriftPluginName = "thrift-gen-hertz"
|
||||
)
|
||||
|
||||
// hz Errors
|
||||
const (
|
||||
LoadError = 1
|
||||
GenerateLayoutError = 2
|
||||
PersistError = 3
|
||||
PluginError = 4
|
||||
)
|
||||
|
||||
// Package Dir
|
||||
const (
|
||||
ModelDir = "biz/model"
|
||||
RouterDir = "biz/router"
|
||||
HandlerDir = "biz/handler"
|
||||
)
|
||||
|
||||
// Backend Model Backends
|
||||
type Backend string
|
||||
|
||||
const (
|
||||
BackendGolang Backend = "golang"
|
||||
)
|
||||
|
||||
// template const value
|
||||
const (
|
||||
SetBodyParam = "setBodyParam(req).\n"
|
||||
)
|
||||
|
||||
// TheUseOptionMessage indicates that the generating of 'model code' is aborted due to the -use option for thrift IDL.
|
||||
const TheUseOptionMessage = "'model code' is not generated due to the '-use' option"
|
||||
|
||||
const AddThriftReplace = "do not generate 'go.mod', please add 'replace github.com/apache/thrift => github.com/apache/thrift v0.13.0' to your 'go.mod'"
|
96
meta/manifest.go
Normal file
96
meta/manifest.go
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package meta
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
gv "github.com/hashicorp/go-version"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const ManifestFile = ".hz"
|
||||
|
||||
type Manifest struct {
|
||||
Version string `yaml:"hz version"`
|
||||
HandlerDir string `yaml:"handlerDir"`
|
||||
ModelDir string `yaml:"modelDir"`
|
||||
RouterDir string `yaml:"routerDir"`
|
||||
}
|
||||
|
||||
var GoVersion *gv.Version
|
||||
|
||||
func init() {
|
||||
// valid by unit test already, so no need to check error
|
||||
GoVersion, _ = gv.NewVersion(Version)
|
||||
}
|
||||
|
||||
func (manifest *Manifest) InitAndValidate(dir string) error {
|
||||
m, err := loadConfigFile(filepath.Join(dir, ManifestFile))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can not load \".hz\", err: %v", err)
|
||||
}
|
||||
|
||||
if len(m.Version) == 0 {
|
||||
return fmt.Errorf("can not get hz version form \".hz\", current project doesn't belong to hertz framework")
|
||||
}
|
||||
|
||||
*manifest = *m
|
||||
_, err = gv.NewVersion(manifest.Version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid hz version in \".hz\", err: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const hzTitle = "// Code generated by hz. DO NOT EDIT."
|
||||
|
||||
func (manifest *Manifest) String() string {
|
||||
conf, _ := yaml.Marshal(*manifest)
|
||||
|
||||
return hzTitle + "\n\n" +
|
||||
string(conf)
|
||||
}
|
||||
|
||||
func (manifest *Manifest) Persist(dir string) error {
|
||||
file := filepath.Join(dir, ManifestFile)
|
||||
fd, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0o644))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
_, err = fd.WriteString(manifest.String())
|
||||
return err
|
||||
}
|
||||
|
||||
// loadConfigFile load config file from path
|
||||
func loadConfigFile(path string) (*Manifest, error) {
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var manifest Manifest
|
||||
file = bytes.TrimPrefix(file, []byte(hzTitle))
|
||||
if err = yaml.Unmarshal(file, &manifest); err != nil {
|
||||
return nil, fmt.Errorf("decode \".hz\" failed, err: %v", err)
|
||||
}
|
||||
return &manifest, nil
|
||||
}
|
30
meta/manifest_test.go
Normal file
30
meta/manifest_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package meta
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
gv "github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
_, err := gv.NewVersion(Version)
|
||||
if err != nil {
|
||||
t.Fatalf("not a valid version: %s", err)
|
||||
}
|
||||
}
|
679
protobuf/api/api.pb.go
Normal file
679
protobuf/api/api.pb.go
Normal file
@ -0,0 +1,679 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.30.0
|
||||
// protoc v3.21.12
|
||||
// source: api.proto
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
var file_api_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50101,
|
||||
Name: "api.raw_body",
|
||||
Tag: "bytes,50101,opt,name=raw_body",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50102,
|
||||
Name: "api.query",
|
||||
Tag: "bytes,50102,opt,name=query",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50103,
|
||||
Name: "api.header",
|
||||
Tag: "bytes,50103,opt,name=header",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50104,
|
||||
Name: "api.cookie",
|
||||
Tag: "bytes,50104,opt,name=cookie",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50105,
|
||||
Name: "api.body",
|
||||
Tag: "bytes,50105,opt,name=body",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50106,
|
||||
Name: "api.path",
|
||||
Tag: "bytes,50106,opt,name=path",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50107,
|
||||
Name: "api.vd",
|
||||
Tag: "bytes,50107,opt,name=vd",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50108,
|
||||
Name: "api.form",
|
||||
Tag: "bytes,50108,opt,name=form",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50109,
|
||||
Name: "api.js_conv",
|
||||
Tag: "bytes,50109,opt,name=js_conv",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50110,
|
||||
Name: "api.file_name",
|
||||
Tag: "bytes,50110,opt,name=file_name",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50111,
|
||||
Name: "api.none",
|
||||
Tag: "bytes,50111,opt,name=none",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50131,
|
||||
Name: "api.form_compatible",
|
||||
Tag: "bytes,50131,opt,name=form_compatible",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50132,
|
||||
Name: "api.js_conv_compatible",
|
||||
Tag: "bytes,50132,opt,name=js_conv_compatible",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50133,
|
||||
Name: "api.file_name_compatible",
|
||||
Tag: "bytes,50133,opt,name=file_name_compatible",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50134,
|
||||
Name: "api.none_compatible",
|
||||
Tag: "bytes,50134,opt,name=none_compatible",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 51001,
|
||||
Name: "api.go_tag",
|
||||
Tag: "bytes,51001,opt,name=go_tag",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50201,
|
||||
Name: "api.get",
|
||||
Tag: "bytes,50201,opt,name=get",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50202,
|
||||
Name: "api.post",
|
||||
Tag: "bytes,50202,opt,name=post",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50203,
|
||||
Name: "api.put",
|
||||
Tag: "bytes,50203,opt,name=put",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50204,
|
||||
Name: "api.delete",
|
||||
Tag: "bytes,50204,opt,name=delete",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50205,
|
||||
Name: "api.patch",
|
||||
Tag: "bytes,50205,opt,name=patch",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50206,
|
||||
Name: "api.options",
|
||||
Tag: "bytes,50206,opt,name=options",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50207,
|
||||
Name: "api.head",
|
||||
Tag: "bytes,50207,opt,name=head",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50208,
|
||||
Name: "api.any",
|
||||
Tag: "bytes,50208,opt,name=any",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50301,
|
||||
Name: "api.gen_path",
|
||||
Tag: "bytes,50301,opt,name=gen_path",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50302,
|
||||
Name: "api.api_version",
|
||||
Tag: "bytes,50302,opt,name=api_version",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50303,
|
||||
Name: "api.tag",
|
||||
Tag: "bytes,50303,opt,name=tag",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50304,
|
||||
Name: "api.name",
|
||||
Tag: "bytes,50304,opt,name=name",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50305,
|
||||
Name: "api.api_level",
|
||||
Tag: "bytes,50305,opt,name=api_level",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50306,
|
||||
Name: "api.serializer",
|
||||
Tag: "bytes,50306,opt,name=serializer",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50307,
|
||||
Name: "api.param",
|
||||
Tag: "bytes,50307,opt,name=param",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50308,
|
||||
Name: "api.baseurl",
|
||||
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.MethodOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50331,
|
||||
Name: "api.handler_path_compatible",
|
||||
Tag: "bytes,50331,opt,name=handler_path_compatible",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
|
||||
ExtensionType: (*int32)(nil),
|
||||
Field: 50401,
|
||||
Name: "api.http_code",
|
||||
Tag: "varint,50401,opt,name=http_code",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.ServiceOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50402,
|
||||
Name: "api.base_domain",
|
||||
Tag: "bytes,50402,opt,name=base_domain",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.ServiceOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50731,
|
||||
Name: "api.base_domain_compatible",
|
||||
Tag: "bytes,50731,opt,name=base_domain_compatible",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.ServiceOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50732,
|
||||
Name: "api.service_path",
|
||||
Tag: "bytes,50732,opt,name=service_path",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
{
|
||||
ExtendedType: (*descriptorpb.MessageOptions)(nil),
|
||||
ExtensionType: (*string)(nil),
|
||||
Field: 50830,
|
||||
Name: "api.reserve",
|
||||
Tag: "bytes,50830,opt,name=reserve",
|
||||
Filename: "api.proto",
|
||||
},
|
||||
}
|
||||
|
||||
// Extension fields to descriptorpb.FieldOptions.
|
||||
var (
|
||||
// optional string raw_body = 50101;
|
||||
E_RawBody = &file_api_proto_extTypes[0]
|
||||
// optional string query = 50102;
|
||||
E_Query = &file_api_proto_extTypes[1]
|
||||
// optional string header = 50103;
|
||||
E_Header = &file_api_proto_extTypes[2]
|
||||
// optional string cookie = 50104;
|
||||
E_Cookie = &file_api_proto_extTypes[3]
|
||||
// optional string body = 50105;
|
||||
E_Body = &file_api_proto_extTypes[4]
|
||||
// optional string path = 50106;
|
||||
E_Path = &file_api_proto_extTypes[5]
|
||||
// optional string vd = 50107;
|
||||
E_Vd = &file_api_proto_extTypes[6]
|
||||
// optional string form = 50108;
|
||||
E_Form = &file_api_proto_extTypes[7]
|
||||
// optional string js_conv = 50109;
|
||||
E_JsConv = &file_api_proto_extTypes[8]
|
||||
// optional string file_name = 50110;
|
||||
E_FileName = &file_api_proto_extTypes[9]
|
||||
// optional string none = 50111;
|
||||
E_None = &file_api_proto_extTypes[10]
|
||||
// 50131~50160 used to extend field option by hz
|
||||
//
|
||||
// optional string form_compatible = 50131;
|
||||
E_FormCompatible = &file_api_proto_extTypes[11]
|
||||
// optional string js_conv_compatible = 50132;
|
||||
E_JsConvCompatible = &file_api_proto_extTypes[12]
|
||||
// optional string file_name_compatible = 50133;
|
||||
E_FileNameCompatible = &file_api_proto_extTypes[13]
|
||||
// optional string none_compatible = 50134;
|
||||
E_NoneCompatible = &file_api_proto_extTypes[14]
|
||||
// optional string go_tag = 51001;
|
||||
E_GoTag = &file_api_proto_extTypes[15]
|
||||
)
|
||||
|
||||
// Extension fields to descriptorpb.MethodOptions.
|
||||
var (
|
||||
// optional string get = 50201;
|
||||
E_Get = &file_api_proto_extTypes[16]
|
||||
// optional string post = 50202;
|
||||
E_Post = &file_api_proto_extTypes[17]
|
||||
// optional string put = 50203;
|
||||
E_Put = &file_api_proto_extTypes[18]
|
||||
// optional string delete = 50204;
|
||||
E_Delete = &file_api_proto_extTypes[19]
|
||||
// optional string patch = 50205;
|
||||
E_Patch = &file_api_proto_extTypes[20]
|
||||
// optional string options = 50206;
|
||||
E_Options = &file_api_proto_extTypes[21]
|
||||
// optional string head = 50207;
|
||||
E_Head = &file_api_proto_extTypes[22]
|
||||
// optional string any = 50208;
|
||||
E_Any = &file_api_proto_extTypes[23]
|
||||
// optional string gen_path = 50301;
|
||||
E_GenPath = &file_api_proto_extTypes[24] // The path specified by the user when the client code is generated, with a higher priority than api_version
|
||||
// optional string api_version = 50302;
|
||||
E_ApiVersion = &file_api_proto_extTypes[25] // Specify the value of the :version variable in path when the client code is generated
|
||||
// optional string tag = 50303;
|
||||
E_Tag = &file_api_proto_extTypes[26] // rpc tag, can be multiple, separated by commas
|
||||
// optional string name = 50304;
|
||||
E_Name = &file_api_proto_extTypes[27] // Name of rpc
|
||||
// optional string api_level = 50305;
|
||||
E_ApiLevel = &file_api_proto_extTypes[28] // Interface Level
|
||||
// optional string serializer = 50306;
|
||||
E_Serializer = &file_api_proto_extTypes[29] // Serialization method
|
||||
// optional string param = 50307;
|
||||
E_Param = &file_api_proto_extTypes[30] // Whether client requests take public parameters
|
||||
// optional string baseurl = 50308;
|
||||
E_Baseurl = &file_api_proto_extTypes[31] // Baseurl used in ttnet routing
|
||||
// optional string handler_path = 50309;
|
||||
E_HandlerPath = &file_api_proto_extTypes[32] // handler_path specifies the path to generate the method
|
||||
// 50331~50360 used to extend method option by hz
|
||||
//
|
||||
// optional string handler_path_compatible = 50331;
|
||||
E_HandlerPathCompatible = &file_api_proto_extTypes[33] // 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[34]
|
||||
)
|
||||
|
||||
// Extension fields to descriptorpb.ServiceOptions.
|
||||
var (
|
||||
// optional string base_domain = 50402;
|
||||
E_BaseDomain = &file_api_proto_extTypes[35]
|
||||
// 50731~50760 used to extend service option by hz
|
||||
//
|
||||
// optional string base_domain_compatible = 50731;
|
||||
E_BaseDomainCompatible = &file_api_proto_extTypes[36]
|
||||
// optional string service_path = 50732;
|
||||
E_ServicePath = &file_api_proto_extTypes[37]
|
||||
)
|
||||
|
||||
// Extension fields to descriptorpb.MessageOptions.
|
||||
var (
|
||||
// optional string reserve = 50830;
|
||||
E_Reserve = &file_api_proto_extTypes[38]
|
||||
)
|
||||
|
||||
var File_api_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_api_proto_rawDesc = []byte{
|
||||
0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69,
|
||||
0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x3a, 0x3a, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1d,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb5, 0x87,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x61, 0x77, 0x42, 0x6f, 0x64, 0x79, 0x3a, 0x35,
|
||||
0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb6, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||
0x71, 0x75, 0x65, 0x72, 0x79, 0x3a, 0x37, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12,
|
||||
0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb7,
|
||||
0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x3a, 0x37,
|
||||
0x0a, 0x06, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64,
|
||||
0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb8, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x3a, 0x33, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12,
|
||||
0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb9,
|
||||
0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x3a, 0x33, 0x0a, 0x04,
|
||||
0x70, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x18, 0xba, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74,
|
||||
0x68, 0x3a, 0x2f, 0x0a, 0x02, 0x76, 0x64, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbb, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||
0x76, 0x64, 0x3a, 0x33, 0x0a, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65,
|
||||
0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbc, 0x87, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x38, 0x0a, 0x07, 0x6a, 0x73, 0x5f, 0x63, 0x6f,
|
||||
0x6e, 0x76, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x18, 0xbd, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6a, 0x73, 0x43, 0x6f, 0x6e,
|
||||
0x76, 0x3a, 0x3c, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbe, 0x87,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x3a,
|
||||
0x33, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbf, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x6e, 0x6f, 0x6e, 0x65, 0x3a, 0x48, 0x0a, 0x0f, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x63, 0x6f, 0x6d,
|
||||
0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd3, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,
|
||||
0x66, 0x6f, 0x72, 0x6d, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x4d,
|
||||
0x0a, 0x12, 0x6a, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x76, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74,
|
||||
0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x18, 0xd4, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6a, 0x73, 0x43,
|
||||
0x6f, 0x6e, 0x76, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x51, 0x0a,
|
||||
0x14, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61,
|
||||
0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x66, 0x69,
|
||||
0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65,
|
||||
0x3a, 0x48, 0x0a, 0x0f, 0x6e, 0x6f, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69,
|
||||
0x62, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x18, 0xd6, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x6e, 0x65,
|
||||
0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x36, 0x0a, 0x06, 0x67, 0x6f,
|
||||
0x5f, 0x74, 0x61, 0x67, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x18, 0xb9, 0x8e, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x6f, 0x54,
|
||||
0x61, 0x67, 0x3a, 0x32, 0x0a, 0x03, 0x67, 0x65, 0x74, 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, 0x99, 0x88, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x03, 0x67, 0x65, 0x74, 0x3a, 0x34, 0x0a, 0x04, 0x70, 0x6f, 0x73, 0x74, 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, 0x9a,
|
||||
0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x73, 0x74, 0x3a, 0x32, 0x0a, 0x03,
|
||||
0x70, 0x75, 0x74, 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, 0x9b, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x75, 0x74,
|
||||
0x3a, 0x38, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 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, 0x9c, 0x88, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61,
|
||||
0x74, 0x63, 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, 0x9d, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74,
|
||||
0x63, 0x68, 0x3a, 0x3a, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 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, 0x9e, 0x88,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x34,
|
||||
0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 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, 0x9f, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x68, 0x65, 0x61, 0x64, 0x3a, 0x32, 0x0a, 0x03, 0x61, 0x6e, 0x79, 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, 0xa0, 0x88, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x6e, 0x79, 0x3a, 0x3b, 0x0a, 0x08, 0x67, 0x65, 0x6e, 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, 0xfd, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x65,
|
||||
0x6e, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x41, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 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, 0xfe, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70,
|
||||
0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x32, 0x0a, 0x03, 0x74, 0x61, 0x67, 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,
|
||||
0xff, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x3a, 0x34, 0x0a, 0x04,
|
||||
0x6e, 0x61, 0x6d, 0x65, 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, 0x80, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x3a, 0x3d, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x5f, 0x6c, 0x65, 0x76, 0x65, 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,
|
||||
0x81, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x70, 0x69, 0x4c, 0x65, 0x76, 0x65,
|
||||
0x6c, 0x3a, 0x40, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 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,
|
||||
0x82, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69,
|
||||
0x7a, 0x65, 0x72, 0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 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, 0x83, 0x89, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x72, 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, 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, 0x58, 0x0a, 0x17,
|
||||
0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6d,
|
||||
0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 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, 0x9b, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x15, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6d, 0x70,
|
||||
0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 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, 0x3a, 0x42, 0x0a, 0x0b, 0x62, 0x61, 0x73, 0x65,
|
||||
0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe2, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0a, 0x62, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x3a, 0x57, 0x0a, 0x16,
|
||||
0x62, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x70,
|
||||
0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xab, 0x8c, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x14, 0x62, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x61,
|
||||
0x74, 0x69, 0x62, 0x6c, 0x65, 0x3a, 0x44, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4f,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x8c, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x3b, 0x0a, 0x07, 0x72,
|
||||
0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x8e, 0x8d, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x07, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69,
|
||||
}
|
||||
|
||||
var file_api_proto_goTypes = []interface{}{
|
||||
(*descriptorpb.FieldOptions)(nil), // 0: google.protobuf.FieldOptions
|
||||
(*descriptorpb.MethodOptions)(nil), // 1: google.protobuf.MethodOptions
|
||||
(*descriptorpb.EnumValueOptions)(nil), // 2: google.protobuf.EnumValueOptions
|
||||
(*descriptorpb.ServiceOptions)(nil), // 3: google.protobuf.ServiceOptions
|
||||
(*descriptorpb.MessageOptions)(nil), // 4: google.protobuf.MessageOptions
|
||||
}
|
||||
var file_api_proto_depIdxs = []int32{
|
||||
0, // 0: api.raw_body:extendee -> google.protobuf.FieldOptions
|
||||
0, // 1: api.query:extendee -> google.protobuf.FieldOptions
|
||||
0, // 2: api.header:extendee -> google.protobuf.FieldOptions
|
||||
0, // 3: api.cookie:extendee -> google.protobuf.FieldOptions
|
||||
0, // 4: api.body:extendee -> google.protobuf.FieldOptions
|
||||
0, // 5: api.path:extendee -> google.protobuf.FieldOptions
|
||||
0, // 6: api.vd:extendee -> google.protobuf.FieldOptions
|
||||
0, // 7: api.form:extendee -> google.protobuf.FieldOptions
|
||||
0, // 8: api.js_conv:extendee -> google.protobuf.FieldOptions
|
||||
0, // 9: api.file_name:extendee -> google.protobuf.FieldOptions
|
||||
0, // 10: api.none:extendee -> google.protobuf.FieldOptions
|
||||
0, // 11: api.form_compatible:extendee -> google.protobuf.FieldOptions
|
||||
0, // 12: api.js_conv_compatible:extendee -> google.protobuf.FieldOptions
|
||||
0, // 13: api.file_name_compatible:extendee -> google.protobuf.FieldOptions
|
||||
0, // 14: api.none_compatible:extendee -> google.protobuf.FieldOptions
|
||||
0, // 15: api.go_tag:extendee -> google.protobuf.FieldOptions
|
||||
1, // 16: api.get:extendee -> google.protobuf.MethodOptions
|
||||
1, // 17: api.post:extendee -> google.protobuf.MethodOptions
|
||||
1, // 18: api.put:extendee -> google.protobuf.MethodOptions
|
||||
1, // 19: api.delete:extendee -> google.protobuf.MethodOptions
|
||||
1, // 20: api.patch:extendee -> google.protobuf.MethodOptions
|
||||
1, // 21: api.options:extendee -> google.protobuf.MethodOptions
|
||||
1, // 22: api.head:extendee -> google.protobuf.MethodOptions
|
||||
1, // 23: api.any:extendee -> google.protobuf.MethodOptions
|
||||
1, // 24: api.gen_path:extendee -> google.protobuf.MethodOptions
|
||||
1, // 25: api.api_version:extendee -> google.protobuf.MethodOptions
|
||||
1, // 26: api.tag:extendee -> google.protobuf.MethodOptions
|
||||
1, // 27: api.name:extendee -> google.protobuf.MethodOptions
|
||||
1, // 28: api.api_level:extendee -> google.protobuf.MethodOptions
|
||||
1, // 29: api.serializer:extendee -> google.protobuf.MethodOptions
|
||||
1, // 30: api.param:extendee -> google.protobuf.MethodOptions
|
||||
1, // 31: api.baseurl:extendee -> google.protobuf.MethodOptions
|
||||
1, // 32: api.handler_path:extendee -> google.protobuf.MethodOptions
|
||||
1, // 33: api.handler_path_compatible:extendee -> google.protobuf.MethodOptions
|
||||
2, // 34: api.http_code:extendee -> google.protobuf.EnumValueOptions
|
||||
3, // 35: api.base_domain:extendee -> google.protobuf.ServiceOptions
|
||||
3, // 36: api.base_domain_compatible:extendee -> google.protobuf.ServiceOptions
|
||||
3, // 37: api.service_path:extendee -> google.protobuf.ServiceOptions
|
||||
4, // 38: api.reserve:extendee -> google.protobuf.MessageOptions
|
||||
39, // [39:39] is the sub-list for method output_type
|
||||
39, // [39:39] is the sub-list for method input_type
|
||||
39, // [39:39] is the sub-list for extension type_name
|
||||
0, // [0:39] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_api_proto_init() }
|
||||
func file_api_proto_init() {
|
||||
if File_api_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_api_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 0,
|
||||
NumExtensions: 39,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_api_proto_goTypes,
|
||||
DependencyIndexes: file_api_proto_depIdxs,
|
||||
ExtensionInfos: file_api_proto_extTypes,
|
||||
}.Build()
|
||||
File_api_proto = out.File
|
||||
file_api_proto_rawDesc = nil
|
||||
file_api_proto_goTypes = nil
|
||||
file_api_proto_depIdxs = nil
|
||||
}
|
76
protobuf/api/api.proto
Normal file
76
protobuf/api/api.proto
Normal file
@ -0,0 +1,76 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package api;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
option go_package = "/api";
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional string raw_body = 50101;
|
||||
optional string query = 50102;
|
||||
optional string header = 50103;
|
||||
optional string cookie = 50104;
|
||||
optional string body = 50105;
|
||||
optional string path = 50106;
|
||||
optional string vd = 50107;
|
||||
optional string form = 50108;
|
||||
optional string js_conv = 50109;
|
||||
optional string file_name = 50110;
|
||||
optional string none = 50111;
|
||||
|
||||
// 50131~50160 used to extend field option by hz
|
||||
optional string form_compatible = 50131;
|
||||
optional string js_conv_compatible = 50132;
|
||||
optional string file_name_compatible = 50133;
|
||||
optional string none_compatible = 50134;
|
||||
// 50135 is reserved to vt_compatible
|
||||
// optional FieldRules vt_compatible = 50135;
|
||||
|
||||
optional string go_tag = 51001;
|
||||
}
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
optional string get = 50201;
|
||||
optional string post = 50202;
|
||||
optional string put = 50203;
|
||||
optional string delete = 50204;
|
||||
optional string patch = 50205;
|
||||
optional string options = 50206;
|
||||
optional string head = 50207;
|
||||
optional string any = 50208;
|
||||
optional string gen_path = 50301; // The path specified by the user when the client code is generated, with a higher priority than api_version
|
||||
optional string api_version = 50302; // Specify the value of the :version variable in path when the client code is generated
|
||||
optional string tag = 50303; // rpc tag, can be multiple, separated by commas
|
||||
optional string name = 50304; // Name of rpc
|
||||
optional string api_level = 50305; // Interface Level
|
||||
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
|
||||
|
||||
// 50331~50360 used to extend method option by hz
|
||||
optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method
|
||||
}
|
||||
|
||||
extend google.protobuf.EnumValueOptions {
|
||||
optional int32 http_code = 50401;
|
||||
|
||||
// 50431~50460 used to extend enum option by hz
|
||||
}
|
||||
|
||||
extend google.protobuf.ServiceOptions {
|
||||
optional string base_domain = 50402;
|
||||
|
||||
// 50731~50760 used to extend service option by hz
|
||||
optional string base_domain_compatible = 50731;
|
||||
optional string service_path = 50732;
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
// optional FieldRules msg_vt = 50111;
|
||||
|
||||
optional string reserve = 50830;
|
||||
// 550831 is reserved to msg_vt_compatible
|
||||
// optional FieldRules msg_vt_compatible = 50831;
|
||||
}
|
760
protobuf/ast.go
Normal file
760
protobuf/ast.go
Normal file
@ -0,0 +1,760 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/protobuf/api"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"google.golang.org/protobuf/compiler/protogen"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/runtime/protoimpl"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
)
|
||||
|
||||
var BaseProto = descriptorpb.FileDescriptorProto{}
|
||||
|
||||
// getGoPackage get option go_package
|
||||
// If pkgMap is specified, the specified value is used as the go_package;
|
||||
// If go package is not specified, then the value of package is used as go_package.
|
||||
func getGoPackage(f *descriptorpb.FileDescriptorProto, pkgMap map[string]string) string {
|
||||
if f.Options == nil {
|
||||
f.Options = new(descriptorpb.FileOptions)
|
||||
}
|
||||
if f.Options.GoPackage == nil {
|
||||
f.Options.GoPackage = new(string)
|
||||
}
|
||||
goPkg := f.Options.GetGoPackage()
|
||||
|
||||
// if go_package has ";", for example go_package="/a/b/c;d", we will use "/a/b/c" as go_package
|
||||
if strings.Contains(goPkg, ";") {
|
||||
pkg := strings.Split(goPkg, ";")
|
||||
if len(pkg) == 2 {
|
||||
logs.Warnf("The go_package of the file(%s) is \"%s\", hz will use \"%s\" as the go_package.", f.GetName(), goPkg, pkg[0])
|
||||
goPkg = pkg[0]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if goPkg == "" {
|
||||
goPkg = f.GetPackage()
|
||||
}
|
||||
if opt, ok := pkgMap[f.GetName()]; ok {
|
||||
return opt
|
||||
}
|
||||
return goPkg
|
||||
}
|
||||
|
||||
func switchBaseType(typ descriptorpb.FieldDescriptorProto_Type) *model.Type {
|
||||
switch typ {
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
|
||||
return nil
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_INT64:
|
||||
return model.TypeInt64
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_INT32:
|
||||
return model.TypeInt32
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_UINT64:
|
||||
return model.TypeUint64
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_UINT32:
|
||||
return model.TypeUint32
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_FIXED64:
|
||||
return model.TypeUint64
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_FIXED32:
|
||||
return model.TypeUint32
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
|
||||
return model.TypeBool
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_STRING:
|
||||
return model.TypeString
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_BYTES:
|
||||
return model.TypeBinary
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_SFIXED32:
|
||||
return model.TypeInt32
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_SFIXED64:
|
||||
return model.TypeInt64
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_SINT32:
|
||||
return model.TypeInt32
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_SINT64:
|
||||
return model.TypeInt64
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:
|
||||
return model.TypeFloat64
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_FLOAT:
|
||||
return model.TypeFloat32
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func astToService(ast *descriptorpb.FileDescriptorProto, resolver *Resolver, cmdType string, gen *protogen.Plugin) ([]*generator.Service, error) {
|
||||
resolver.ExportReferred(true, false)
|
||||
ss := ast.GetService()
|
||||
out := make([]*generator.Service, 0, len(ss))
|
||||
var merges model.Models
|
||||
|
||||
for _, s := range ss {
|
||||
service := &generator.Service{
|
||||
Name: s.GetName(),
|
||||
}
|
||||
|
||||
service.BaseDomain = ""
|
||||
domainAnno := getCompatibleAnnotation(s.GetOptions(), api.E_BaseDomain, api.E_BaseDomainCompatible)
|
||||
if cmdType == meta.CmdClient {
|
||||
val, ok := domainAnno.(string)
|
||||
if ok && len(val) != 0 {
|
||||
service.BaseDomain = val
|
||||
}
|
||||
}
|
||||
|
||||
ms := s.GetMethod()
|
||||
methods := make([]*generator.HttpMethod, 0, len(ms))
|
||||
clientMethods := make([]*generator.ClientMethod, 0, len(ms))
|
||||
servicePathAnno := checkFirstOption(api.E_ServicePath, s.GetOptions())
|
||||
servicePath := ""
|
||||
if val, ok := servicePathAnno.(string); ok {
|
||||
servicePath = val
|
||||
}
|
||||
for _, m := range ms {
|
||||
rs := getAllOptions(HttpMethodOptions, m.GetOptions())
|
||||
if len(rs) == 0 {
|
||||
continue
|
||||
}
|
||||
httpOpts := httpOptions{}
|
||||
for k, v := range rs {
|
||||
httpOpts = append(httpOpts, httpOption{
|
||||
method: k,
|
||||
path: v.(string),
|
||||
})
|
||||
}
|
||||
// turn the map into a slice and sort it to make sure getting the results in the same order every time
|
||||
sort.Sort(httpOpts)
|
||||
|
||||
var handlerOutDir string
|
||||
genPath := getCompatibleAnnotation(m.GetOptions(), api.E_HandlerPath, api.E_HandlerPathCompatible)
|
||||
handlerOutDir, ok := genPath.(string)
|
||||
if !ok || len(handlerOutDir) == 0 {
|
||||
handlerOutDir = ""
|
||||
}
|
||||
if len(handlerOutDir) == 0 {
|
||||
handlerOutDir = servicePath
|
||||
}
|
||||
|
||||
// protoGoInfo can get generated "Go Info" for proto file.
|
||||
// the type name may be different between "***.proto" and "***.pb.go"
|
||||
protoGoInfo, exist := gen.FilesByPath[ast.GetName()]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("file(%s) can not exist", ast.GetName())
|
||||
}
|
||||
methodGoInfo, err := getMethod(protoGoInfo, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inputGoType := methodGoInfo.Input
|
||||
outputGoType := methodGoInfo.Output
|
||||
|
||||
reqName := m.GetInputType()
|
||||
sb, err := resolver.ResolveIdentifier(reqName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqName = util.BaseName(sb.Scope.GetOptions().GetGoPackage(), "") + "." + inputGoType.GoIdent.GoName
|
||||
reqRawName := inputGoType.GoIdent.GoName
|
||||
reqPackage := util.BaseName(sb.Scope.GetOptions().GetGoPackage(), "")
|
||||
respName := m.GetOutputType()
|
||||
st, err := resolver.ResolveIdentifier(respName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
respName = util.BaseName(st.Scope.GetOptions().GetGoPackage(), "") + "." + outputGoType.GoIdent.GoName
|
||||
respRawName := outputGoType.GoIdent.GoName
|
||||
respPackage := util.BaseName(sb.Scope.GetOptions().GetGoPackage(), "")
|
||||
|
||||
var serializer string
|
||||
sl, sv := checkFirstOptions(SerializerOptions, m.GetOptions())
|
||||
if sl != "" {
|
||||
serializer = sv.(string)
|
||||
}
|
||||
|
||||
method := &generator.HttpMethod{
|
||||
Name: util.CamelString(m.GetName()),
|
||||
HTTPMethod: httpOpts[0].method,
|
||||
Path: httpOpts[0].path,
|
||||
Serializer: serializer,
|
||||
OutputDir: handlerOutDir,
|
||||
GenHandler: true,
|
||||
}
|
||||
|
||||
goOptMapAlias := make(map[string]string, 1)
|
||||
refs := resolver.ExportReferred(false, true)
|
||||
method.Models = make(map[string]*model.Model, len(refs))
|
||||
for _, ref := range refs {
|
||||
if val, exist := method.Models[ref.Model.PackageName]; exist {
|
||||
if val.Package == ref.Model.Package {
|
||||
method.Models[ref.Model.PackageName] = ref.Model
|
||||
goOptMapAlias[ref.Model.Package] = ref.Model.PackageName
|
||||
} else {
|
||||
file := filepath.Base(ref.Model.FilePath)
|
||||
fileName := strings.Split(file, ".")
|
||||
newPkg := fileName[len(fileName)-2] + "_" + val.PackageName
|
||||
method.Models[newPkg] = ref.Model
|
||||
goOptMapAlias[ref.Model.Package] = newPkg
|
||||
}
|
||||
continue
|
||||
}
|
||||
method.Models[ref.Model.PackageName] = ref.Model
|
||||
goOptMapAlias[ref.Model.Package] = ref.Model.PackageName
|
||||
}
|
||||
merges = service.Models
|
||||
merges.MergeMap(method.Models)
|
||||
if goOptMapAlias[sb.Scope.GetOptions().GetGoPackage()] != "" {
|
||||
reqName = goOptMapAlias[sb.Scope.GetOptions().GetGoPackage()] + "." + inputGoType.GoIdent.GoName
|
||||
}
|
||||
if goOptMapAlias[sb.Scope.GetOptions().GetGoPackage()] != "" {
|
||||
respName = goOptMapAlias[st.Scope.GetOptions().GetGoPackage()] + "." + outputGoType.GoIdent.GoName
|
||||
}
|
||||
method.RequestTypeName = reqName
|
||||
method.RequestTypeRawName = reqRawName
|
||||
method.RequestTypePackage = reqPackage
|
||||
method.ReturnTypeName = respName
|
||||
method.ReturnTypeRawName = respRawName
|
||||
method.ReturnTypePackage = respPackage
|
||||
|
||||
methods = append(methods, method)
|
||||
for idx, anno := range httpOpts {
|
||||
if idx == 0 {
|
||||
continue
|
||||
}
|
||||
tmp := *method
|
||||
tmp.HTTPMethod = anno.method
|
||||
tmp.Path = anno.path
|
||||
tmp.GenHandler = false
|
||||
methods = append(methods, &tmp)
|
||||
}
|
||||
|
||||
if cmdType == meta.CmdClient {
|
||||
clientMethod := &generator.ClientMethod{}
|
||||
clientMethod.HttpMethod = method
|
||||
err := parseAnnotationToClient(clientMethod, gen, ast, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientMethods = append(clientMethods, clientMethod)
|
||||
}
|
||||
}
|
||||
|
||||
service.ClientMethods = clientMethods
|
||||
service.Methods = methods
|
||||
service.Models = merges
|
||||
out = append(out, service)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func getCompatibleAnnotation(options proto.Message, anno, compatibleAnno *protoimpl.ExtensionInfo) interface{} {
|
||||
if proto.HasExtension(options, anno) {
|
||||
return checkFirstOption(anno, options)
|
||||
} else if proto.HasExtension(options, compatibleAnno) {
|
||||
return checkFirstOption(compatibleAnno, options)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAnnotationToClient(clientMethod *generator.ClientMethod, gen *protogen.Plugin, ast *descriptorpb.FileDescriptorProto, m *descriptorpb.MethodDescriptorProto) error {
|
||||
file, exist := gen.FilesByPath[ast.GetName()]
|
||||
if !exist {
|
||||
return fmt.Errorf("file(%s) can not exist", ast.GetName())
|
||||
}
|
||||
method, err := getMethod(file, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// pb input type must be message
|
||||
inputType := method.Input
|
||||
var (
|
||||
hasBodyAnnotation bool
|
||||
hasFormAnnotation bool
|
||||
)
|
||||
for _, f := range inputType.Fields {
|
||||
hasAnnotation := false
|
||||
isStringFieldType := false
|
||||
if f.Desc.Kind() == protoreflect.StringKind {
|
||||
isStringFieldType = true
|
||||
}
|
||||
if proto.HasExtension(f.Desc.Options(), api.E_Query) {
|
||||
hasAnnotation = true
|
||||
queryAnnos := proto.GetExtension(f.Desc.Options(), api.E_Query)
|
||||
val := checkSnakeName(queryAnnos.(string))
|
||||
clientMethod.QueryParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", val, f.GoName)
|
||||
}
|
||||
|
||||
if proto.HasExtension(f.Desc.Options(), api.E_Path) {
|
||||
hasAnnotation = true
|
||||
pathAnnos := proto.GetExtension(f.Desc.Options(), api.E_Path)
|
||||
val := checkSnakeName(pathAnnos.(string))
|
||||
if isStringFieldType {
|
||||
clientMethod.PathParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", val, f.GoName)
|
||||
} else {
|
||||
clientMethod.PathParamsCode += fmt.Sprintf("%q: fmt.Sprint(req.Get%s()),\n", val, f.GoName)
|
||||
}
|
||||
}
|
||||
|
||||
if proto.HasExtension(f.Desc.Options(), api.E_Header) {
|
||||
hasAnnotation = true
|
||||
headerAnnos := proto.GetExtension(f.Desc.Options(), api.E_Header)
|
||||
val := checkSnakeName(headerAnnos.(string))
|
||||
if isStringFieldType {
|
||||
clientMethod.HeaderParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", val, f.GoName)
|
||||
} else {
|
||||
clientMethod.HeaderParamsCode += fmt.Sprintf("%q: fmt.Sprint(req.Get%s()),\n", val, f.GoName)
|
||||
}
|
||||
}
|
||||
|
||||
if formAnnos := getCompatibleAnnotation(f.Desc.Options(), api.E_Form, api.E_FormCompatible); formAnnos != nil {
|
||||
hasAnnotation = true
|
||||
hasFormAnnotation = true
|
||||
val := checkSnakeName(formAnnos.(string))
|
||||
if isStringFieldType {
|
||||
clientMethod.FormValueCode += fmt.Sprintf("%q: req.Get%s(),\n", val, f.GoName)
|
||||
} else {
|
||||
clientMethod.FormValueCode += fmt.Sprintf("%q: fmt.Sprint(req.Get%s()),\n", val, f.GoName)
|
||||
}
|
||||
}
|
||||
|
||||
if proto.HasExtension(f.Desc.Options(), api.E_Body) {
|
||||
hasAnnotation = true
|
||||
hasBodyAnnotation = true
|
||||
}
|
||||
|
||||
if fileAnnos := getCompatibleAnnotation(f.Desc.Options(), api.E_FileName, api.E_FileNameCompatible); fileAnnos != nil {
|
||||
hasAnnotation = true
|
||||
hasFormAnnotation = true
|
||||
val := checkSnakeName(fileAnnos.(string))
|
||||
clientMethod.FormFileCode += fmt.Sprintf("%q: req.Get%s(),\n", val, f.GoName)
|
||||
}
|
||||
if !hasAnnotation && strings.EqualFold(clientMethod.HTTPMethod, "get") {
|
||||
clientMethod.QueryParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", checkSnakeName(string(f.Desc.Name())), f.GoName)
|
||||
}
|
||||
}
|
||||
clientMethod.BodyParamsCode = meta.SetBodyParam
|
||||
if hasBodyAnnotation && hasFormAnnotation {
|
||||
clientMethod.FormValueCode = ""
|
||||
clientMethod.FormFileCode = ""
|
||||
}
|
||||
if !hasBodyAnnotation && hasFormAnnotation {
|
||||
clientMethod.BodyParamsCode = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMethod(file *protogen.File, m *descriptorpb.MethodDescriptorProto) (*protogen.Method, error) {
|
||||
for _, f := range file.Services {
|
||||
for _, method := range f.Methods {
|
||||
if string(method.Desc.Name()) == m.GetName() {
|
||||
return method, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can not find method: %s", m.GetName())
|
||||
}
|
||||
|
||||
//---------------------------------Model--------------------------------
|
||||
|
||||
func astToModel(ast *descriptorpb.FileDescriptorProto, rs *Resolver) (*model.Model, error) {
|
||||
main := rs.mainPkg.Model
|
||||
if main == nil {
|
||||
main = new(model.Model)
|
||||
}
|
||||
|
||||
mainFileDes := rs.files.PbReflect[ast.GetName()]
|
||||
isProto3 := mainFileDes.IsProto3()
|
||||
// Enums
|
||||
ems := ast.GetEnumType()
|
||||
enums := make([]model.Enum, 0, len(ems))
|
||||
for _, e := range ems {
|
||||
em := model.Enum{
|
||||
Scope: main,
|
||||
Name: e.GetName(),
|
||||
GoType: "int32",
|
||||
}
|
||||
es := e.GetValue()
|
||||
vs := make([]model.Constant, 0, len(es))
|
||||
for _, ee := range es {
|
||||
vs = append(vs, model.Constant{
|
||||
Scope: main,
|
||||
Name: ee.GetName(),
|
||||
Type: model.TypeInt32,
|
||||
Value: model.IntExpression{Src: int(ee.GetNumber())},
|
||||
})
|
||||
}
|
||||
em.Values = vs
|
||||
enums = append(enums, em)
|
||||
}
|
||||
main.Enums = enums
|
||||
|
||||
// Structs
|
||||
sts := ast.GetMessageType()
|
||||
structs := make([]model.Struct, 0, len(sts)*2)
|
||||
oneofs := make([]model.Oneof, 0, 1)
|
||||
for _, st := range sts {
|
||||
stMessage := mainFileDes.FindMessage(ast.GetPackage() + "." + st.GetName())
|
||||
stLeadingComments := getMessageLeadingComments(stMessage)
|
||||
s := model.Struct{
|
||||
Scope: main,
|
||||
Name: st.GetName(),
|
||||
Category: model.CategoryStruct,
|
||||
LeadingComments: stLeadingComments,
|
||||
}
|
||||
|
||||
ns := st.GetNestedType()
|
||||
nestedMessageInfoMap := getNestedMessageInfoMap(stMessage)
|
||||
for _, nt := range ns {
|
||||
if IsMapEntry(nt) {
|
||||
continue
|
||||
}
|
||||
|
||||
nestedMessageInfo := nestedMessageInfoMap[nt.GetName()]
|
||||
nestedMessageLeadingComment := getMessageLeadingComments(nestedMessageInfo)
|
||||
s := model.Struct{
|
||||
Scope: main,
|
||||
Name: st.GetName() + "_" + nt.GetName(),
|
||||
Category: model.CategoryStruct,
|
||||
LeadingComments: nestedMessageLeadingComment,
|
||||
}
|
||||
fs := nt.GetField()
|
||||
ns := nt.GetNestedType()
|
||||
vs := make([]model.Field, 0, len(fs))
|
||||
|
||||
oneofMap := make(map[string]model.Field)
|
||||
oneofType, err := resolveOneof(nestedMessageInfo, oneofMap, rs, isProto3, s, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oneofs = append(oneofs, oneofType...)
|
||||
|
||||
choiceSet := make(map[string]bool)
|
||||
|
||||
for _, f := range fs {
|
||||
if field, exist := oneofMap[f.GetName()]; exist {
|
||||
if _, ex := choiceSet[field.Name]; !ex {
|
||||
choiceSet[field.Name] = true
|
||||
vs = append(vs, field)
|
||||
}
|
||||
continue
|
||||
}
|
||||
dv := f.GetDefaultValue()
|
||||
fieldLeadingComments, fieldTrailingComments := getFiledComments(f, nestedMessageInfo)
|
||||
t, err := rs.ResolveType(f, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
field := model.Field{
|
||||
Scope: &s,
|
||||
Name: util.CamelString(f.GetName()),
|
||||
Type: t,
|
||||
LeadingComments: fieldLeadingComments,
|
||||
TrailingComments: fieldTrailingComments,
|
||||
IsPointer: isPointer(f, isProto3),
|
||||
}
|
||||
if dv != "" {
|
||||
field.IsSetDefault = true
|
||||
field.DefaultValue, err = parseDefaultValue(f.GetType(), f.GetDefaultValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = injectTagsToModel(f, &field, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs = append(vs, field)
|
||||
}
|
||||
checkDuplicatedFileName(vs)
|
||||
s.Fields = vs
|
||||
structs = append(structs, s)
|
||||
}
|
||||
|
||||
fs := st.GetField()
|
||||
vs := make([]model.Field, 0, len(fs))
|
||||
|
||||
oneofMap := make(map[string]model.Field)
|
||||
oneofType, err := resolveOneof(stMessage, oneofMap, rs, isProto3, s, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oneofs = append(oneofs, oneofType...)
|
||||
|
||||
choiceSet := make(map[string]bool)
|
||||
|
||||
for _, f := range fs {
|
||||
if field, exist := oneofMap[f.GetName()]; exist {
|
||||
if _, ex := choiceSet[field.Name]; !ex {
|
||||
choiceSet[field.Name] = true
|
||||
vs = append(vs, field)
|
||||
}
|
||||
continue
|
||||
}
|
||||
dv := f.GetDefaultValue()
|
||||
fieldLeadingComments, fieldTrailingComments := getFiledComments(f, stMessage)
|
||||
t, err := rs.ResolveType(f, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
field := model.Field{
|
||||
Scope: &s,
|
||||
Name: util.CamelString(f.GetName()),
|
||||
Type: t,
|
||||
LeadingComments: fieldLeadingComments,
|
||||
TrailingComments: fieldTrailingComments,
|
||||
IsPointer: isPointer(f, isProto3),
|
||||
}
|
||||
if dv != "" {
|
||||
field.IsSetDefault = true
|
||||
field.DefaultValue, err = parseDefaultValue(f.GetType(), f.GetDefaultValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = injectTagsToModel(f, &field, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs = append(vs, field)
|
||||
}
|
||||
checkDuplicatedFileName(vs)
|
||||
s.Fields = vs
|
||||
structs = append(structs, s)
|
||||
|
||||
}
|
||||
main.Oneofs = oneofs
|
||||
main.Structs = structs
|
||||
|
||||
// In case of only the service refers another model, therefore scanning service is necessary
|
||||
ss := ast.GetService()
|
||||
for _, s := range ss {
|
||||
ms := s.GetMethod()
|
||||
for _, m := range ms {
|
||||
_, err := rs.ResolveIdentifier(m.GetInputType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = rs.ResolveIdentifier(m.GetOutputType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return main, nil
|
||||
}
|
||||
|
||||
// getMessageLeadingComments can get struct LeadingComment
|
||||
func getMessageLeadingComments(stMessage *desc.MessageDescriptor) string {
|
||||
if stMessage == nil {
|
||||
return ""
|
||||
}
|
||||
stComments := stMessage.GetSourceInfo().GetLeadingComments()
|
||||
stComments = formatComments(stComments)
|
||||
|
||||
return stComments
|
||||
}
|
||||
|
||||
// getFiledComments can get field LeadingComments and field TailingComments for field
|
||||
func getFiledComments(f *descriptorpb.FieldDescriptorProto, stMessage *desc.MessageDescriptor) (string, string) {
|
||||
if stMessage == nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
fieldNum := f.GetNumber()
|
||||
field := stMessage.FindFieldByNumber(fieldNum)
|
||||
fieldInfo := field.GetSourceInfo()
|
||||
|
||||
fieldLeadingComments := fieldInfo.GetLeadingComments()
|
||||
fieldTailingComments := fieldInfo.GetTrailingComments()
|
||||
|
||||
fieldLeadingComments = formatComments(fieldLeadingComments)
|
||||
fieldTailingComments = formatComments(fieldTailingComments)
|
||||
|
||||
return fieldLeadingComments, fieldTailingComments
|
||||
}
|
||||
|
||||
// formatComments can format the comments for beauty
|
||||
func formatComments(comments string) string {
|
||||
if len(comments) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
comments = util.TrimLastChar(comments)
|
||||
comments = util.AddSlashForComments(comments)
|
||||
|
||||
return comments
|
||||
}
|
||||
|
||||
// getNestedMessageInfoMap can get all nested struct
|
||||
func getNestedMessageInfoMap(stMessage *desc.MessageDescriptor) map[string]*desc.MessageDescriptor {
|
||||
nestedMessage := stMessage.GetNestedMessageTypes()
|
||||
nestedMessageInfoMap := make(map[string]*desc.MessageDescriptor, len(nestedMessage))
|
||||
|
||||
for _, nestedMsg := range nestedMessage {
|
||||
nestedMsgName := nestedMsg.GetName()
|
||||
nestedMessageInfoMap[nestedMsgName] = nestedMsg
|
||||
}
|
||||
|
||||
return nestedMessageInfoMap
|
||||
}
|
||||
|
||||
func parseDefaultValue(typ descriptorpb.FieldDescriptorProto_Type, val string) (model.Literal, error) {
|
||||
switch typ {
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_BYTES, descriptorpb.FieldDescriptorProto_TYPE_STRING:
|
||||
return model.StringExpression{Src: val}, nil
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
|
||||
return model.BoolExpression{Src: val == "true"}, nil
|
||||
case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_FLOAT,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_INT64,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_UINT64,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_INT32,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_FIXED64,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_FIXED32,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_UINT32,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_ENUM,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_SFIXED32,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_SFIXED64,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_SINT32,
|
||||
descriptorpb.FieldDescriptorProto_TYPE_SINT64:
|
||||
return model.NumberExpression{Src: val}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type %s", typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
func isPointer(f *descriptorpb.FieldDescriptorProto, isProto3 bool) bool {
|
||||
if f.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE || f.GetType() == descriptorpb.FieldDescriptorProto_TYPE_BYTES {
|
||||
return false
|
||||
}
|
||||
|
||||
if !isProto3 {
|
||||
if f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
switch f.GetLabel() {
|
||||
case descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL:
|
||||
if !f.GetProto3Optional() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func resolveOneof(stMessage *desc.MessageDescriptor, oneofMap map[string]model.Field, rs *Resolver, isProto3 bool, s model.Struct, ns []*descriptorpb.DescriptorProto) ([]model.Oneof, error) {
|
||||
oneofs := make([]model.Oneof, 0, 1)
|
||||
if len(stMessage.GetOneOfs()) != 0 {
|
||||
for _, oneof := range stMessage.GetOneOfs() {
|
||||
if isProto3 {
|
||||
if oneof.IsSynthetic() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
oneofName := oneof.GetName()
|
||||
messageName := s.Name
|
||||
typeName := "is" + messageName + "_" + oneofName
|
||||
field := model.Field{
|
||||
Scope: &s,
|
||||
Name: util.CamelString(oneofName),
|
||||
Type: model.NewOneofType(typeName),
|
||||
IsPointer: false,
|
||||
}
|
||||
|
||||
oneofComment := oneof.GetSourceInfo().GetLeadingComments()
|
||||
oneofComment = formatComments(oneofComment)
|
||||
var oneofLeadingComments string
|
||||
if oneofComment == "" {
|
||||
oneofLeadingComments = fmt.Sprintf(" Types that are assignable to %s:\n", oneofName)
|
||||
} else {
|
||||
oneofLeadingComments = fmt.Sprintf("%s\n//\n// Types that are assignable to %s:\n", oneofComment, oneofName)
|
||||
}
|
||||
for idx, ch := range oneof.GetChoices() {
|
||||
if idx == len(oneof.GetChoices())-1 {
|
||||
oneofLeadingComments = oneofLeadingComments + fmt.Sprintf("// *%s_%s", messageName, ch.GetName())
|
||||
} else {
|
||||
oneofLeadingComments = oneofLeadingComments + fmt.Sprintf("// *%s_%s\n", messageName, ch.GetName())
|
||||
}
|
||||
}
|
||||
field.LeadingComments = oneofLeadingComments
|
||||
|
||||
choices := make([]model.Choice, 0, len(oneof.GetChoices()))
|
||||
for _, ch := range oneof.GetChoices() {
|
||||
t, err := rs.ResolveType(ch.AsFieldDescriptorProto(), ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
choice := model.Choice{
|
||||
MessageName: messageName,
|
||||
ChoiceName: ch.GetName(),
|
||||
Type: t,
|
||||
}
|
||||
choices = append(choices, choice)
|
||||
oneofMap[ch.GetName()] = field
|
||||
}
|
||||
|
||||
oneofType := model.Oneof{
|
||||
MessageName: messageName,
|
||||
OneofName: oneofName,
|
||||
InterfaceName: typeName,
|
||||
Choices: choices,
|
||||
}
|
||||
|
||||
oneofs = append(oneofs, oneofType)
|
||||
}
|
||||
}
|
||||
return oneofs, nil
|
||||
}
|
||||
|
||||
func getNewFieldName(fieldName string, fieldNameSet map[string]bool) string {
|
||||
if _, ex := fieldNameSet[fieldName]; ex {
|
||||
fieldName = fieldName + "_"
|
||||
return getNewFieldName(fieldName, fieldNameSet)
|
||||
}
|
||||
return fieldName
|
||||
}
|
||||
|
||||
func checkDuplicatedFileName(vs []model.Field) {
|
||||
fieldNameSet := make(map[string]bool)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
if _, ex := fieldNameSet[vs[i].Name]; ex {
|
||||
newName := getNewFieldName(vs[i].Name, fieldNameSet)
|
||||
fieldNameSet[newName] = true
|
||||
vs[i].Name = newName
|
||||
} else {
|
||||
fieldNameSet[vs[i].Name] = true
|
||||
}
|
||||
}
|
||||
}
|
639
protobuf/plugin.go
Normal file
639
protobuf/plugin.go
Normal file
@ -0,0 +1,639 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* Copyright (c) 2018 The Go Authors. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This file may have been modified by CloudWeGo authors. All CloudWeGo
|
||||
* Modifications are Copyright 2022 CloudWeGo Authors.
|
||||
*/
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/config"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
gengo "google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo"
|
||||
"google.golang.org/protobuf/compiler/protogen"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/runtime/protoimpl"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
"google.golang.org/protobuf/types/pluginpb"
|
||||
)
|
||||
|
||||
type Plugin struct {
|
||||
*protogen.Plugin
|
||||
Package string
|
||||
Recursive bool
|
||||
OutDir string
|
||||
ModelDir string
|
||||
UseDir string
|
||||
IdlClientDir string
|
||||
RmTags RemoveTags
|
||||
PkgMap map[string]string
|
||||
logger *logs.StdLogger
|
||||
}
|
||||
|
||||
type RemoveTags []string
|
||||
|
||||
func (rm *RemoveTags) Exist(tag string) bool {
|
||||
for _, rmTag := range *rm {
|
||||
if rmTag == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *Plugin) Run() int {
|
||||
plugin.setLogger()
|
||||
args := &config.Argument{}
|
||||
defer func() {
|
||||
if args == nil {
|
||||
return
|
||||
}
|
||||
if args.Verbose {
|
||||
verboseLog := plugin.recvVerboseLogger()
|
||||
if len(verboseLog) != 0 {
|
||||
fmt.Fprintf(os.Stderr, verboseLog)
|
||||
}
|
||||
} else {
|
||||
warning := plugin.recvWarningLogger()
|
||||
if len(warning) != 0 {
|
||||
fmt.Fprintf(os.Stderr, warning)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// read protoc request
|
||||
in, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
logs.Errorf("read request failed: %s\n", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
|
||||
req := &pluginpb.CodeGeneratorRequest{}
|
||||
err = proto.Unmarshal(in, req)
|
||||
if err != nil {
|
||||
logs.Errorf("unmarshal request failed: %s\n", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
|
||||
args, err = plugin.parseArgs(*req.Parameter)
|
||||
if err != nil {
|
||||
logs.Errorf("parse args failed: %s\n", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
CheckTagOption(args)
|
||||
// generate
|
||||
err = plugin.Handle(req, args)
|
||||
if err != nil {
|
||||
logs.Errorf("generate failed: %s\n", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (plugin *Plugin) setLogger() {
|
||||
plugin.logger = logs.NewStdLogger(logs.LevelInfo)
|
||||
plugin.logger.Defer = true
|
||||
plugin.logger.ErrOnly = true
|
||||
logs.SetLogger(plugin.logger)
|
||||
}
|
||||
|
||||
func (plugin *Plugin) recvWarningLogger() string {
|
||||
warns := plugin.logger.Warn()
|
||||
plugin.logger.Flush()
|
||||
logs.SetLogger(logs.NewStdLogger(logs.LevelInfo))
|
||||
return warns
|
||||
}
|
||||
|
||||
func (plugin *Plugin) recvVerboseLogger() string {
|
||||
info := plugin.logger.Out()
|
||||
warns := plugin.logger.Warn()
|
||||
verboseLog := string(info) + warns
|
||||
plugin.logger.Flush()
|
||||
logs.SetLogger(logs.NewStdLogger(logs.LevelInfo))
|
||||
return verboseLog
|
||||
}
|
||||
|
||||
func (plugin *Plugin) parseArgs(param string) (*config.Argument, error) {
|
||||
args := new(config.Argument)
|
||||
params := strings.Split(param, ",")
|
||||
err := args.Unpack(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plugin.Package, err = args.GetGoPackage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plugin.Recursive = !args.NoRecurse
|
||||
plugin.ModelDir, err = args.GetModelDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plugin.OutDir = args.OutDir
|
||||
plugin.PkgMap = args.OptPkgMap
|
||||
plugin.UseDir = args.Use
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) Response(resp *pluginpb.CodeGeneratorResponse) error {
|
||||
out, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal response failed: %s", err.Error())
|
||||
}
|
||||
_, err = os.Stdout.Write(out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write response failed: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) Handle(req *pluginpb.CodeGeneratorRequest, args *config.Argument) error {
|
||||
plugin.fixGoPackage(req, plugin.PkgMap)
|
||||
|
||||
// new plugin
|
||||
opts := protogen.Options{}
|
||||
gen, err := opts.New(req)
|
||||
plugin.Plugin = gen
|
||||
plugin.RmTags = args.RmTags
|
||||
if err != nil {
|
||||
return fmt.Errorf("new protoc plugin failed: %s", err.Error())
|
||||
}
|
||||
// plugin start working
|
||||
err = plugin.GenerateFiles(gen)
|
||||
if err != nil {
|
||||
// Error within the plugin will be responded by the plugin.
|
||||
// But if the plugin does not response correctly, the error is returned to the upper level.
|
||||
err := fmt.Errorf("generate model file failed: %s", err.Error())
|
||||
gen.Error(err)
|
||||
resp := gen.Response()
|
||||
err2 := plugin.Response(resp)
|
||||
if err2 != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if args.CmdType == meta.CmdModel {
|
||||
resp := gen.Response()
|
||||
// plugin stop working
|
||||
err = plugin.Response(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write response failed: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
files := gen.Request.ProtoFile
|
||||
maps := make(map[string]*descriptorpb.FileDescriptorProto, len(files))
|
||||
for _, file := range files {
|
||||
maps[file.GetName()] = file
|
||||
}
|
||||
main := maps[gen.Request.FileToGenerate[len(gen.Request.FileToGenerate)-1]]
|
||||
deps := make(map[string]*descriptorpb.FileDescriptorProto, len(main.GetDependency()))
|
||||
for _, dep := range main.GetDependency() {
|
||||
if f, ok := maps[dep]; !ok {
|
||||
err := fmt.Errorf("dependency file not found: %s", dep)
|
||||
gen.Error(err)
|
||||
resp := gen.Response()
|
||||
err2 := plugin.Response(resp)
|
||||
if err2 != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
deps[dep] = f
|
||||
}
|
||||
}
|
||||
|
||||
pkgFiles, err := plugin.genHttpPackage(main, deps, args)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("generate package files failed: %s", err.Error())
|
||||
gen.Error(err)
|
||||
resp := gen.Response()
|
||||
err2 := plugin.Response(resp)
|
||||
if err2 != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// construct plugin response
|
||||
resp := gen.Response()
|
||||
// all files that need to be generated are returned to protoc
|
||||
for _, pkgFile := range pkgFiles {
|
||||
filePath := pkgFile.Path
|
||||
content := pkgFile.Content
|
||||
renderFile := &pluginpb.CodeGeneratorResponse_File{
|
||||
Name: &filePath,
|
||||
Content: &content,
|
||||
}
|
||||
resp.File = append(resp.File, renderFile)
|
||||
}
|
||||
|
||||
// plugin stop working
|
||||
err = plugin.Response(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write response failed: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fixGoPackage will update go_package to store all the model files in ${model_dir}
|
||||
func (plugin *Plugin) fixGoPackage(req *pluginpb.CodeGeneratorRequest, pkgMap map[string]string) {
|
||||
gopkg := plugin.Package
|
||||
for _, f := range req.ProtoFile {
|
||||
if strings.HasPrefix(f.GetPackage(), "google.protobuf") {
|
||||
continue
|
||||
}
|
||||
opt := getGoPackage(f, pkgMap)
|
||||
if !strings.Contains(opt, gopkg) {
|
||||
if strings.HasPrefix(opt, "/") {
|
||||
opt = gopkg + opt
|
||||
} else {
|
||||
opt = gopkg + "/" + opt
|
||||
}
|
||||
}
|
||||
impt, _ := plugin.fixModelPathAndPackage(opt)
|
||||
*f.Options.GoPackage = impt
|
||||
}
|
||||
}
|
||||
|
||||
// fixModelPathAndPackage will modify the go_package to adapt the go_package of the hz,
|
||||
// for example adding the go module and model dir.
|
||||
func (plugin *Plugin) fixModelPathAndPackage(pkg string) (impt, path string) {
|
||||
if strings.HasPrefix(pkg, plugin.Package) {
|
||||
impt = util.ImportToPathAndConcat(pkg[len(plugin.Package):], "")
|
||||
}
|
||||
if plugin.ModelDir != "" && plugin.ModelDir != "." {
|
||||
modelImpt := util.PathToImport(string(filepath.Separator)+plugin.ModelDir, "")
|
||||
// trim model dir for go package
|
||||
if strings.HasPrefix(impt, modelImpt) {
|
||||
impt = impt[len(modelImpt):]
|
||||
}
|
||||
impt = util.PathToImport(plugin.ModelDir, "") + impt
|
||||
}
|
||||
path = util.ImportToPath(impt, "")
|
||||
impt = plugin.Package + "/" + impt
|
||||
if util.IsWindows() {
|
||||
impt = util.PathToImport(impt, "")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (plugin *Plugin) GenerateFiles(pluginPb *protogen.Plugin) error {
|
||||
idl := pluginPb.Request.FileToGenerate[len(pluginPb.Request.FileToGenerate)-1]
|
||||
pluginPb.SupportedFeatures = gengo.SupportedFeatures
|
||||
for _, f := range pluginPb.Files {
|
||||
if f.Proto.GetName() == idl {
|
||||
err := plugin.GenerateFile(pluginPb, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
impt := string(f.GoImportPath)
|
||||
if strings.HasPrefix(impt, plugin.Package) {
|
||||
impt = impt[len(plugin.Package):]
|
||||
}
|
||||
plugin.IdlClientDir = impt
|
||||
} else if plugin.Recursive {
|
||||
if strings.HasPrefix(f.Proto.GetPackage(), "google.protobuf") {
|
||||
continue
|
||||
}
|
||||
err := plugin.GenerateFile(pluginPb, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) GenerateFile(gen *protogen.Plugin, f *protogen.File) error {
|
||||
impt := string(f.GoImportPath)
|
||||
if strings.HasPrefix(impt, plugin.Package) {
|
||||
impt = impt[len(plugin.Package):]
|
||||
}
|
||||
f.GeneratedFilenamePrefix = filepath.Join(util.ImportToPath(impt, ""), util.BaseName(f.Proto.GetName(), ".proto"))
|
||||
f.Generate = true
|
||||
// if use third-party model, no model code is generated within the project
|
||||
if len(plugin.UseDir) != 0 {
|
||||
return nil
|
||||
}
|
||||
file, err := generateFile(gen, f, plugin.RmTags)
|
||||
if err != nil || file == nil {
|
||||
return fmt.Errorf("generate file %s failed: %s", f.Proto.GetName(), err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateFile generates the contents of a .pb.go file.
|
||||
func generateFile(gen *protogen.Plugin, file *protogen.File, rmTags RemoveTags) (*protogen.GeneratedFile, error) {
|
||||
filename := file.GeneratedFilenamePrefix + ".pb.go"
|
||||
g := gen.NewGeneratedFile(filename, file.GoImportPath)
|
||||
f := newFileInfo(file)
|
||||
|
||||
genStandaloneComments(g, f, int32(FileDescriptorProto_Syntax_field_number))
|
||||
genGeneratedHeader(gen, g, f)
|
||||
genStandaloneComments(g, f, int32(FileDescriptorProto_Package_field_number))
|
||||
|
||||
packageDoc := genPackageKnownComment(f)
|
||||
g.P(packageDoc, "package ", f.GoPackageName)
|
||||
g.P()
|
||||
|
||||
// Emit a static check that enforces a minimum version of the proto package.
|
||||
if gengo.GenerateVersionMarkers {
|
||||
g.P("const (")
|
||||
g.P("// Verify that this generated code is sufficiently up-to-date.")
|
||||
g.P("_ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimpl.GenVersion, " - ", protoimplPackage.Ident("MinVersion"), ")")
|
||||
g.P("// Verify that runtime/protoimpl is sufficiently up-to-date.")
|
||||
g.P("_ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimplPackage.Ident("MaxVersion"), " - ", protoimpl.GenVersion, ")")
|
||||
g.P(")")
|
||||
g.P()
|
||||
}
|
||||
|
||||
for i, imps := 0, f.Desc.Imports(); i < imps.Len(); i++ {
|
||||
genImport(gen, g, f, imps.Get(i))
|
||||
}
|
||||
for _, enum := range f.allEnums {
|
||||
genEnum(g, f, enum)
|
||||
}
|
||||
var err error
|
||||
for _, message := range f.allMessages {
|
||||
err = genMessage(g, f, message, rmTags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
genExtensions(g, f)
|
||||
|
||||
genReflectFileDescriptor(gen, g, f)
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func genMessage(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, rmTags RemoveTags) error {
|
||||
if m.Desc.IsMapEntry() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Message type declaration.
|
||||
g.Annotate(m.GoIdent.GoName, m.Location)
|
||||
leadingComments := appendDeprecationSuffix(m.Comments.Leading,
|
||||
m.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated())
|
||||
g.P(leadingComments,
|
||||
"type ", m.GoIdent, " struct {")
|
||||
err := genMessageFields(g, f, m, rmTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.P("}")
|
||||
g.P()
|
||||
|
||||
genMessageKnownFunctions(g, f, m)
|
||||
genMessageDefaultDecls(g, f, m)
|
||||
genMessageMethods(g, f, m)
|
||||
genMessageOneofWrapperTypes(g, f, m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func genMessageFields(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, rmTags RemoveTags) error {
|
||||
sf := f.allMessageFieldsByPtr[m]
|
||||
genMessageInternalFields(g, f, m, sf)
|
||||
var err error
|
||||
for _, field := range m.Fields {
|
||||
err = genMessageField(g, f, m, field, sf, rmTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func genMessageField(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, field *protogen.Field, sf *structFields, rmTags RemoveTags) error {
|
||||
if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() {
|
||||
// It would be a bit simpler to iterate over the oneofs below,
|
||||
// but generating the field here keeps the contents of the Go
|
||||
// struct in the same order as the contents of the source
|
||||
// .proto file.
|
||||
if oneof.Fields[0] != field {
|
||||
return nil // only generate for first appearance
|
||||
}
|
||||
|
||||
tags := structTags{
|
||||
{"protobuf_oneof", string(oneof.Desc.Name())},
|
||||
}
|
||||
if m.isTracked {
|
||||
tags = append(tags, gotrackTags...)
|
||||
}
|
||||
|
||||
g.Annotate(m.GoIdent.GoName+"."+oneof.GoName, oneof.Location)
|
||||
leadingComments := oneof.Comments.Leading
|
||||
if leadingComments != "" {
|
||||
leadingComments += "\n"
|
||||
}
|
||||
ss := []string{fmt.Sprintf(" Types that are assignable to %s:\n", oneof.GoName)}
|
||||
for _, field := range oneof.Fields {
|
||||
ss = append(ss, "\t*"+field.GoIdent.GoName+"\n")
|
||||
}
|
||||
leadingComments += protogen.Comments(strings.Join(ss, ""))
|
||||
g.P(leadingComments,
|
||||
oneof.GoName, " ", oneofInterfaceName(oneof), tags)
|
||||
sf.append(oneof.GoName)
|
||||
return nil
|
||||
}
|
||||
goType, pointer := fieldGoType(g, f, field)
|
||||
if pointer {
|
||||
goType = "*" + goType
|
||||
}
|
||||
tags := structTags{
|
||||
{"protobuf", fieldProtobufTagValue(field)},
|
||||
//{"json", fieldJSONTagValue(field)},
|
||||
}
|
||||
if field.Desc.IsMap() {
|
||||
key := field.Message.Fields[0]
|
||||
val := field.Message.Fields[1]
|
||||
tags = append(tags, structTags{
|
||||
{"protobuf_key", fieldProtobufTagValue(key)},
|
||||
{"protobuf_val", fieldProtobufTagValue(val)},
|
||||
}...)
|
||||
}
|
||||
|
||||
err := injectTagsToStructTags(field.Desc, &tags, true, rmTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.isTracked {
|
||||
tags = append(tags, gotrackTags...)
|
||||
}
|
||||
|
||||
name := field.GoName
|
||||
if field.Desc.IsWeak() {
|
||||
name = WeakFieldPrefix_goname + name
|
||||
}
|
||||
g.Annotate(m.GoIdent.GoName+"."+name, field.Location)
|
||||
leadingComments := appendDeprecationSuffix(field.Comments.Leading,
|
||||
field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
|
||||
g.P(leadingComments,
|
||||
name, " ", goType, tags,
|
||||
trailingComment(field.Comments.Trailing))
|
||||
sf.append(field.GoName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) getIdlInfo(ast *descriptorpb.FileDescriptorProto, deps map[string]*descriptorpb.FileDescriptorProto, args *config.Argument) (*generator.HttpPackage, error) {
|
||||
if ast == nil {
|
||||
return nil, fmt.Errorf("ast is nil")
|
||||
}
|
||||
|
||||
pkg := getGoPackage(ast, map[string]string{})
|
||||
main := &model.Model{
|
||||
FilePath: ast.GetName(),
|
||||
Package: pkg,
|
||||
PackageName: util.BaseName(pkg, ""),
|
||||
}
|
||||
fileInfo := FileInfos{
|
||||
Official: deps,
|
||||
PbReflect: nil,
|
||||
}
|
||||
rs, err := NewResolver(ast, fileInfo, main, map[string]string{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new protobuf resolver failed, err:%v", err)
|
||||
}
|
||||
err = rs.LoadAll(ast)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
services, err := astToService(ast, rs, args.CmdType, plugin.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var models model.Models
|
||||
for _, s := range services {
|
||||
models.MergeArray(s.Models)
|
||||
}
|
||||
|
||||
return &generator.HttpPackage{
|
||||
Services: services,
|
||||
IdlName: ast.GetName(),
|
||||
Package: util.BaseName(pkg, ""),
|
||||
Models: models,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) genHttpPackage(ast *descriptorpb.FileDescriptorProto, deps map[string]*descriptorpb.FileDescriptorProto, args *config.Argument) ([]generator.File, error) {
|
||||
options := CheckTagOption(args)
|
||||
idl, err := plugin.getIdlInfo(ast, deps, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customPackageTemplate := args.CustomizePackage
|
||||
pkg, err := args.GetGoPackage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handlerDir, err := args.GetHandlerDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
routerDir, err := args.GetRouterDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modelDir, err := args.GetModelDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientDir, err := args.GetClientDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sg := generator.HttpPackageGenerator{
|
||||
ConfigPath: customPackageTemplate,
|
||||
HandlerDir: handlerDir,
|
||||
RouterDir: routerDir,
|
||||
ModelDir: modelDir,
|
||||
UseDir: args.Use,
|
||||
ClientDir: clientDir,
|
||||
TemplateGenerator: generator.TemplateGenerator{
|
||||
OutputDir: args.OutDir,
|
||||
Excludes: args.Excludes,
|
||||
},
|
||||
ProjPackage: pkg,
|
||||
Options: options,
|
||||
HandlerByMethod: args.HandlerByMethod,
|
||||
CmdType: args.CmdType,
|
||||
IdlClientDir: plugin.IdlClientDir,
|
||||
ForceClientDir: args.ForceClientDir,
|
||||
BaseDomain: args.BaseDomain,
|
||||
SnakeStyleMiddleware: args.SnakeStyleMiddleware,
|
||||
}
|
||||
|
||||
if args.ModelBackend != "" {
|
||||
sg.Backend = meta.Backend(args.ModelBackend)
|
||||
}
|
||||
generator.SetDefaultTemplateConfig()
|
||||
|
||||
err = sg.Generate(idl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generate http package error: %v", err)
|
||||
}
|
||||
files, err := sg.GetFormatAndExcludedFiles()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persist http package error: %v", err)
|
||||
}
|
||||
return files, nil
|
||||
}
|
232
protobuf/plugin_stubs.go
Normal file
232
protobuf/plugin_stubs.go
Normal file
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* Copyright (c) 2018 The Go Authors. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This file may have been modified by CloudWeGo authors. All CloudWeGo
|
||||
* Modifications are Copyright 2022 CloudWeGo Authors.
|
||||
*/
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
_ "unsafe"
|
||||
|
||||
"google.golang.org/protobuf/compiler/protogen"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
// Field numbers for google.protobuf.FileDescriptorProto.
|
||||
const (
|
||||
FileDescriptorProto_Name_field_number protoreflect.FieldNumber = 1
|
||||
FileDescriptorProto_Package_field_number protoreflect.FieldNumber = 2
|
||||
FileDescriptorProto_Dependency_field_number protoreflect.FieldNumber = 3
|
||||
FileDescriptorProto_PublicDependency_field_number protoreflect.FieldNumber = 10
|
||||
FileDescriptorProto_WeakDependency_field_number protoreflect.FieldNumber = 11
|
||||
FileDescriptorProto_MessageType_field_number protoreflect.FieldNumber = 4
|
||||
FileDescriptorProto_EnumType_field_number protoreflect.FieldNumber = 5
|
||||
FileDescriptorProto_Service_field_number protoreflect.FieldNumber = 6
|
||||
FileDescriptorProto_Extension_field_number protoreflect.FieldNumber = 7
|
||||
FileDescriptorProto_Options_field_number protoreflect.FieldNumber = 8
|
||||
FileDescriptorProto_SourceCodeInfo_field_number protoreflect.FieldNumber = 9
|
||||
FileDescriptorProto_Syntax_field_number protoreflect.FieldNumber = 12
|
||||
)
|
||||
|
||||
const WeakFieldPrefix_goname = "XXX_weak_"
|
||||
|
||||
type fileInfo struct {
|
||||
*protogen.File
|
||||
|
||||
allEnums []*enumInfo
|
||||
allMessages []*messageInfo
|
||||
allExtensions []*extensionInfo
|
||||
|
||||
allEnumsByPtr map[*enumInfo]int // value is index into allEnums
|
||||
allMessagesByPtr map[*messageInfo]int // value is index into allMessages
|
||||
allMessageFieldsByPtr map[*messageInfo]*structFields
|
||||
|
||||
// needRawDesc specifies whether the generator should emit logic to provide
|
||||
// the legacy raw descriptor in GZIP'd form.
|
||||
// This is updated by enum and message generation logic as necessary,
|
||||
// and checked at the end of file generation.
|
||||
needRawDesc bool
|
||||
}
|
||||
|
||||
type enumInfo struct {
|
||||
*protogen.Enum
|
||||
|
||||
genJSONMethod bool
|
||||
genRawDescMethod bool
|
||||
}
|
||||
|
||||
type messageInfo struct {
|
||||
*protogen.Message
|
||||
|
||||
genRawDescMethod bool
|
||||
genExtRangeMethod bool
|
||||
|
||||
isTracked bool
|
||||
hasWeak bool
|
||||
}
|
||||
|
||||
type extensionInfo struct {
|
||||
*protogen.Extension
|
||||
}
|
||||
|
||||
type structFields struct {
|
||||
count int
|
||||
unexported map[int]string
|
||||
}
|
||||
|
||||
func (sf *structFields) append(name string) {
|
||||
if r, _ := utf8.DecodeRuneInString(name); !unicode.IsUpper(r) {
|
||||
if sf.unexported == nil {
|
||||
sf.unexported = make(map[int]string)
|
||||
}
|
||||
sf.unexported[sf.count] = name
|
||||
}
|
||||
sf.count++
|
||||
}
|
||||
|
||||
type structTags [][2]string
|
||||
|
||||
func (tags structTags) String() string {
|
||||
if len(tags) == 0 {
|
||||
return ""
|
||||
}
|
||||
var ss []string
|
||||
for _, tag := range tags {
|
||||
// NOTE: When quoting the value, we need to make sure the backtick
|
||||
// character does not appear. Convert all cases to the escaped hex form.
|
||||
key := tag[0]
|
||||
val := strings.Replace(strconv.Quote(tag[1]), "`", `\x60`, -1)
|
||||
ss = append(ss, fmt.Sprintf("%s:%s", key, val))
|
||||
}
|
||||
return "`" + strings.Join(ss, " ") + "`"
|
||||
}
|
||||
|
||||
type goImportPath interface {
|
||||
String() string
|
||||
Ident(string) protogen.GoIdent
|
||||
}
|
||||
|
||||
type trailingComment protogen.Comments
|
||||
|
||||
func (c trailingComment) String() string {
|
||||
s := strings.TrimSuffix(protogen.Comments(c).String(), "\n")
|
||||
if strings.Contains(s, "\n") {
|
||||
// We don't support multi-lined trailing comments as it is unclear
|
||||
// how to best render them in the generated code.
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
//go:linkname gotrackTags google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.gotrackTags
|
||||
var gotrackTags structTags
|
||||
|
||||
var (
|
||||
protoPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/proto")
|
||||
protoifacePackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/runtime/protoiface")
|
||||
protoimplPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/runtime/protoimpl")
|
||||
protojsonPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/encoding/protojson")
|
||||
protoreflectPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/reflect/protoreflect")
|
||||
protoregistryPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/reflect/protoregistry")
|
||||
)
|
||||
|
||||
//go:linkname newFileInfo google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.newFileInfo
|
||||
func newFileInfo(file *protogen.File) *fileInfo
|
||||
|
||||
//go:linkname genPackageKnownComment google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genPackageKnownComment
|
||||
func genPackageKnownComment(f *fileInfo) protogen.Comments
|
||||
|
||||
//go:linkname genStandaloneComments google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genStandaloneComments
|
||||
func genStandaloneComments(g *protogen.GeneratedFile, f *fileInfo, n int32)
|
||||
|
||||
//go:linkname genGeneratedHeader google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genGeneratedHeader
|
||||
func genGeneratedHeader(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo)
|
||||
|
||||
//go:linkname genImport google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genImport
|
||||
func genImport(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, imp protoreflect.FileImport)
|
||||
|
||||
//go:linkname genEnum google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genEnum
|
||||
func genEnum(g *protogen.GeneratedFile, f *fileInfo, e *enumInfo)
|
||||
|
||||
//go:linkname genMessageInternalFields google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageInternalFields
|
||||
func genMessageInternalFields(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, sf *structFields)
|
||||
|
||||
//go:linkname genExtensions google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genExtensions
|
||||
func genExtensions(g *protogen.GeneratedFile, f *fileInfo)
|
||||
|
||||
//go:linkname genReflectFileDescriptor google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genReflectFileDescriptor
|
||||
func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo)
|
||||
|
||||
//go:linkname appendDeprecationSuffix google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.appendDeprecationSuffix
|
||||
func appendDeprecationSuffix(prefix protogen.Comments, deprecated bool) protogen.Comments
|
||||
|
||||
//go:linkname genMessageDefaultDecls google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageDefaultDecls
|
||||
func genMessageDefaultDecls(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo)
|
||||
|
||||
//go:linkname genMessageKnownFunctions google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageKnownFunctions
|
||||
func genMessageKnownFunctions(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo)
|
||||
|
||||
//go:linkname genMessageMethods google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageMethods
|
||||
func genMessageMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo)
|
||||
|
||||
//go:linkname genMessageOneofWrapperTypes google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.genMessageOneofWrapperTypes
|
||||
func genMessageOneofWrapperTypes(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo)
|
||||
|
||||
//go:linkname oneofInterfaceName google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.oneofInterfaceName
|
||||
func oneofInterfaceName(oneof *protogen.Oneof) string
|
||||
|
||||
//go:linkname fieldGoType google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.fieldGoType
|
||||
func fieldGoType(g *protogen.GeneratedFile, f *fileInfo, field *protogen.Field) (goType string, pointer bool)
|
||||
|
||||
//go:linkname fieldProtobufTagValue google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.fieldProtobufTagValue
|
||||
func fieldProtobufTagValue(field *protogen.Field) string
|
||||
|
||||
//go:linkname fieldJSONTagValue google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo.fieldJSONTagValue
|
||||
func fieldJSONTagValue(field *protogen.Field) string
|
98
protobuf/plugin_test.go
Normal file
98
protobuf/plugin_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/pluginpb"
|
||||
)
|
||||
|
||||
func TestPlugin_Handle(t *testing.T) {
|
||||
in, err := ioutil.ReadFile("../testdata/request_protoc.out")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := &pluginpb.CodeGeneratorRequest{}
|
||||
err = proto.Unmarshal(in, req)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal stdin request error: %v", err)
|
||||
}
|
||||
|
||||
// prepare args
|
||||
plu := &Plugin{}
|
||||
plu.setLogger()
|
||||
args, _ := plu.parseArgs(*req.Parameter)
|
||||
|
||||
plu.Handle(req, args)
|
||||
plu.recvWarningLogger()
|
||||
}
|
||||
|
||||
func TestFixModelPathAndPackage(t *testing.T) {
|
||||
plu := &Plugin{}
|
||||
plu.Package = "cloudwego/hertz"
|
||||
plu.ModelDir = meta.ModelDir
|
||||
// default model dir
|
||||
ret1 := [][]string{
|
||||
{"a/b/c", "cloudwego/hertz/biz/model/a/b/c"},
|
||||
{"biz/model/a/b/c", "cloudwego/hertz/biz/model/a/b/c"},
|
||||
{"cloudwego/hertz/a/b/c", "cloudwego/hertz/biz/model/a/b/c"},
|
||||
{"cloudwego/hertz/biz/model/a/b/c", "cloudwego/hertz/biz/model/a/b/c"},
|
||||
}
|
||||
for _, r := range ret1 {
|
||||
tmp := r[0]
|
||||
if !strings.Contains(tmp, plu.Package) {
|
||||
if strings.HasPrefix(tmp, "/") {
|
||||
tmp = plu.Package + tmp
|
||||
} else {
|
||||
tmp = plu.Package + "/" + tmp
|
||||
}
|
||||
}
|
||||
result, _ := plu.fixModelPathAndPackage(tmp)
|
||||
if result != r[1] {
|
||||
t.Fatalf("want go package: %s, but get: %s", r[1], result)
|
||||
}
|
||||
}
|
||||
|
||||
plu.ModelDir = "model_test"
|
||||
// customized model dir
|
||||
ret2 := [][]string{
|
||||
{"a/b/c", "cloudwego/hertz/model_test/a/b/c"},
|
||||
{"model_test/a/b/c", "cloudwego/hertz/model_test/a/b/c"},
|
||||
{"cloudwego/hertz/a/b/c", "cloudwego/hertz/model_test/a/b/c"},
|
||||
{"cloudwego/hertz/model_test/a/b/c", "cloudwego/hertz/model_test/a/b/c"},
|
||||
}
|
||||
for _, r := range ret2 {
|
||||
tmp := r[0]
|
||||
if !strings.Contains(tmp, plu.Package) {
|
||||
if strings.HasPrefix(tmp, "/") {
|
||||
tmp = plu.Package + tmp
|
||||
} else {
|
||||
tmp = plu.Package + "/" + tmp
|
||||
}
|
||||
}
|
||||
result, _ := plu.fixModelPathAndPackage(tmp)
|
||||
if result != r[1] {
|
||||
t.Fatalf("want go package: %s, but get: %s", r[1], result)
|
||||
}
|
||||
}
|
||||
}
|
530
protobuf/resolver.go
Normal file
530
protobuf/resolver.go
Normal file
@ -0,0 +1,530 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
)
|
||||
|
||||
type Symbol struct {
|
||||
Space string
|
||||
Name string
|
||||
IsValue bool
|
||||
Type *model.Type
|
||||
Value interface{}
|
||||
Scope *descriptorpb.FileDescriptorProto
|
||||
}
|
||||
|
||||
type NameSpace map[string]*Symbol
|
||||
|
||||
var (
|
||||
ConstTrue = Symbol{
|
||||
IsValue: true,
|
||||
Type: model.TypeBool,
|
||||
Value: true,
|
||||
Scope: &BaseProto,
|
||||
}
|
||||
ConstFalse = Symbol{
|
||||
IsValue: true,
|
||||
Type: model.TypeBool,
|
||||
Value: false,
|
||||
Scope: &BaseProto,
|
||||
}
|
||||
ConstEmptyString = Symbol{
|
||||
IsValue: true,
|
||||
Type: model.TypeString,
|
||||
Value: "",
|
||||
Scope: &BaseProto,
|
||||
}
|
||||
)
|
||||
|
||||
type PackageReference struct {
|
||||
IncludeBase string
|
||||
IncludePath string
|
||||
Model *model.Model
|
||||
Ast *descriptorpb.FileDescriptorProto
|
||||
Referred bool
|
||||
}
|
||||
|
||||
func getReferPkgMap(pkgMap map[string]string, incs []*descriptorpb.FileDescriptorProto, mainModel *model.Model) (map[string]*PackageReference, error) {
|
||||
var err error
|
||||
out := make(map[string]*PackageReference, len(pkgMap))
|
||||
pkgAliasMap := make(map[string]string, len(incs))
|
||||
// bugfix: add main package to avoid namespace conflict
|
||||
mainPkg := mainModel.Package
|
||||
mainPkgName := mainModel.PackageName
|
||||
mainPkgName, err = util.GetPackageUniqueName(mainPkgName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgAliasMap[mainPkg] = mainPkgName
|
||||
for _, inc := range incs {
|
||||
pkg := getGoPackage(inc, pkgMap)
|
||||
path := inc.GetName()
|
||||
base := util.BaseName(path, ".proto")
|
||||
fileName := inc.GetName()
|
||||
pkgName := util.BaseName(pkg, "")
|
||||
if pn, exist := pkgAliasMap[pkg]; exist {
|
||||
pkgName = pn
|
||||
} else {
|
||||
pkgName, err = util.GetPackageUniqueName(pkgName)
|
||||
pkgAliasMap[pkg] = pkgName
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get package unique name failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
out[fileName] = &PackageReference{base, path, &model.Model{
|
||||
FilePath: path,
|
||||
Package: pkg,
|
||||
PackageName: pkgName,
|
||||
}, inc, false}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type FileInfos struct {
|
||||
Official map[string]*descriptorpb.FileDescriptorProto
|
||||
PbReflect map[string]*desc.FileDescriptor
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
// idl symbols
|
||||
rootName string
|
||||
root NameSpace
|
||||
deps map[string]NameSpace
|
||||
|
||||
// exported models
|
||||
mainPkg PackageReference
|
||||
refPkgs map[string]*PackageReference
|
||||
|
||||
files FileInfos
|
||||
}
|
||||
|
||||
func updateFiles(fileName string, files FileInfos) (FileInfos, error) {
|
||||
file, _ := files.PbReflect[fileName]
|
||||
if file == nil {
|
||||
return FileInfos{}, fmt.Errorf("%s not found", fileName)
|
||||
}
|
||||
fileDep := file.GetDependencies()
|
||||
|
||||
maps := make(map[string]*descriptorpb.FileDescriptorProto, len(fileDep)+1)
|
||||
sourceInfoMap := make(map[string]*desc.FileDescriptor, len(fileDep)+1)
|
||||
for _, dep := range fileDep {
|
||||
ast := dep.AsFileDescriptorProto()
|
||||
maps[dep.GetName()] = ast
|
||||
sourceInfoMap[dep.GetName()] = dep
|
||||
}
|
||||
ast := file.AsFileDescriptorProto()
|
||||
maps[file.GetName()] = ast
|
||||
sourceInfoMap[file.GetName()] = file
|
||||
|
||||
newFileInfo := FileInfos{
|
||||
Official: maps,
|
||||
PbReflect: sourceInfoMap,
|
||||
}
|
||||
|
||||
return newFileInfo, nil
|
||||
}
|
||||
|
||||
func NewResolver(ast *descriptorpb.FileDescriptorProto, files FileInfos, model *model.Model, pkgMap map[string]string) (*Resolver, error) {
|
||||
file := ast.GetName()
|
||||
deps := ast.GetDependency()
|
||||
var err error
|
||||
if files.PbReflect != nil {
|
||||
files, err = updateFiles(file, files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
incs := make([]*descriptorpb.FileDescriptorProto, 0, len(deps))
|
||||
for _, dep := range deps {
|
||||
if v, ok := files.Official[dep]; ok {
|
||||
incs = append(incs, v)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s not found", dep)
|
||||
}
|
||||
}
|
||||
pm, err := getReferPkgMap(pkgMap, incs, model)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get package map failed, err: %v", err)
|
||||
}
|
||||
return &Resolver{
|
||||
root: make(NameSpace),
|
||||
deps: make(map[string]NameSpace),
|
||||
refPkgs: pm,
|
||||
files: files,
|
||||
mainPkg: PackageReference{
|
||||
IncludeBase: util.BaseName(file, ".proto"),
|
||||
IncludePath: file,
|
||||
Model: model,
|
||||
Ast: ast,
|
||||
Referred: false,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) GetRefModel(includeBase string) (*model.Model, error) {
|
||||
if includeBase == "" {
|
||||
return resolver.mainPkg.Model, nil
|
||||
}
|
||||
ref, ok := resolver.refPkgs[includeBase]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s not found", includeBase)
|
||||
}
|
||||
return ref.Model, nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) getBaseType(f *descriptorpb.FieldDescriptorProto, nested []*descriptorpb.DescriptorProto) (*model.Type, error) {
|
||||
bt := switchBaseType(f.GetType())
|
||||
if bt != nil {
|
||||
return checkListType(bt, f.GetLabel()), nil
|
||||
}
|
||||
|
||||
nt := getNestedType(f, nested)
|
||||
if nt != nil {
|
||||
fields := nt.GetField()
|
||||
if IsMapEntry(nt) {
|
||||
t := *model.TypeBaseMap
|
||||
tk, err := resolver.ResolveType(fields[0], nt.GetNestedType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tv, err := resolver.ResolveType(fields[1], nt.GetNestedType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Extra = []*model.Type{tk, tv}
|
||||
return &t, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func IsMapEntry(nt *descriptorpb.DescriptorProto) bool {
|
||||
fields := nt.GetField()
|
||||
return len(fields) == 2 && fields[0].GetName() == "key" && fields[1].GetName() == "value"
|
||||
}
|
||||
|
||||
func checkListType(typ *model.Type, label descriptorpb.FieldDescriptorProto_Label) *model.Type {
|
||||
if label == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
|
||||
t := *model.TypeBaseList
|
||||
t.Extra = []*model.Type{typ}
|
||||
return &t
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func getNestedType(f *descriptorpb.FieldDescriptorProto, nested []*descriptorpb.DescriptorProto) *descriptorpb.DescriptorProto {
|
||||
tName := f.GetTypeName()
|
||||
entry := util.SplitPackageName(tName, "")
|
||||
for _, nt := range nested {
|
||||
if nt.GetName() == entry {
|
||||
return nt
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) ResolveType(f *descriptorpb.FieldDescriptorProto, nested []*descriptorpb.DescriptorProto) (*model.Type, error) {
|
||||
bt, err := resolver.getBaseType(f, nested)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bt != nil {
|
||||
return bt, nil
|
||||
}
|
||||
|
||||
tName := f.GetTypeName()
|
||||
symbol, err := resolver.ResolveIdentifier(tName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deepType := checkListType(symbol.Type, f.GetLabel())
|
||||
return deepType, nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) ResolveIdentifier(id string) (ret *Symbol, err error) {
|
||||
ret = resolver.Get(id)
|
||||
if ret == nil {
|
||||
return nil, fmt.Errorf("not found identifier %s", id)
|
||||
}
|
||||
|
||||
var ref *PackageReference
|
||||
if _, ok := resolver.deps[ret.Space]; ok {
|
||||
ref = resolver.refPkgs[ret.Scope.GetName()]
|
||||
if ref != nil {
|
||||
ref.Referred = true
|
||||
ret.Type.Scope = ref.Model
|
||||
}
|
||||
}
|
||||
// bugfix: root & dep file has the same package(namespace), the 'ret' will miss the namespace match for root.
|
||||
// This results in a lack of dependencies in the generated handlers.
|
||||
if ref == nil && ret.Scope == resolver.mainPkg.Ast {
|
||||
resolver.mainPkg.Referred = true
|
||||
ret.Type.Scope = resolver.mainPkg.Model
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (resolver *Resolver) getFieldType(f *descriptorpb.FieldDescriptorProto, nested []*descriptorpb.DescriptorProto) (*model.Type, error) {
|
||||
dt, err := resolver.getBaseType(f, nested)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dt != nil {
|
||||
return dt, nil
|
||||
}
|
||||
sb := resolver.Get(f.GetTypeName())
|
||||
if sb != nil {
|
||||
return sb.Type, nil
|
||||
}
|
||||
return nil, fmt.Errorf("not found type %s", f.GetTypeName())
|
||||
}
|
||||
|
||||
func (resolver *Resolver) Get(name string) *Symbol {
|
||||
if strings.HasPrefix(name, "."+resolver.rootName) {
|
||||
id := strings.TrimPrefix(name, "."+resolver.rootName+".")
|
||||
if v, ok := resolver.root[id]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// directly map first
|
||||
var space string
|
||||
if idx := strings.LastIndex(name, "."); idx >= 0 && idx < len(name)-1 {
|
||||
space = strings.TrimLeft(name[:idx], ".")
|
||||
}
|
||||
if ns, ok := resolver.deps[space]; ok {
|
||||
id := strings.TrimPrefix(name, "."+space+".")
|
||||
if s, ok := ns[id]; ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// iterate check nested type in dependencies
|
||||
for s, m := range resolver.deps {
|
||||
if strings.HasPrefix(name, "."+s) {
|
||||
id := strings.TrimPrefix(name, "."+s+".")
|
||||
if s, ok := m[id]; ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) ExportReferred(all, needMain bool) (ret []*PackageReference) {
|
||||
for _, v := range resolver.refPkgs {
|
||||
if all {
|
||||
ret = append(ret, v)
|
||||
} else if v.Referred {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
v.Referred = false
|
||||
}
|
||||
|
||||
if needMain && (all || resolver.mainPkg.Referred) {
|
||||
ret = append(ret, &resolver.mainPkg)
|
||||
}
|
||||
resolver.mainPkg.Referred = false
|
||||
return
|
||||
}
|
||||
|
||||
func (resolver *Resolver) LoadAll(ast *descriptorpb.FileDescriptorProto) error {
|
||||
var err error
|
||||
resolver.root, err = resolver.LoadOne(ast)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load main idl failed: %s", err)
|
||||
}
|
||||
resolver.rootName = ast.GetPackage()
|
||||
|
||||
includes := ast.GetDependency()
|
||||
astMap := make(map[string]NameSpace, len(includes))
|
||||
for _, dep := range includes {
|
||||
file, ok := resolver.files.Official[dep]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found included idl %s", dep)
|
||||
}
|
||||
depNamespace, err := resolver.LoadOne(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load idl '%s' failed: %s", dep, err)
|
||||
}
|
||||
ns, existed := astMap[file.GetPackage()]
|
||||
if existed {
|
||||
depNamespace = mergeNamespace(ns, depNamespace)
|
||||
}
|
||||
astMap[file.GetPackage()] = depNamespace
|
||||
}
|
||||
resolver.deps = astMap
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeNamespace(first, second NameSpace) NameSpace {
|
||||
for k, v := range second {
|
||||
if _, existed := first[k]; !existed {
|
||||
first[k] = v
|
||||
}
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
func LoadBaseIdentifier(ast *descriptorpb.FileDescriptorProto) map[string]*Symbol {
|
||||
ret := make(NameSpace, len(ast.GetEnumType())+len(ast.GetMessageType())+len(ast.GetExtension())+len(ast.GetService()))
|
||||
|
||||
ret["true"] = &ConstTrue
|
||||
ret["false"] = &ConstFalse
|
||||
ret[`""`] = &ConstEmptyString
|
||||
ret["bool"] = &Symbol{
|
||||
Type: model.TypeBool,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["uint32"] = &Symbol{
|
||||
Type: model.TypeUint32,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["uint64"] = &Symbol{
|
||||
Type: model.TypeUint64,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["fixed32"] = &Symbol{
|
||||
Type: model.TypeUint32,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["fixed64"] = &Symbol{
|
||||
Type: model.TypeUint64,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["int32"] = &Symbol{
|
||||
Type: model.TypeInt32,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["int64"] = &Symbol{
|
||||
Type: model.TypeInt64,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["sint32"] = &Symbol{
|
||||
Type: model.TypeInt32,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["sint64"] = &Symbol{
|
||||
Type: model.TypeInt64,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["sfixed32"] = &Symbol{
|
||||
Type: model.TypeInt32,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["sfixed64"] = &Symbol{
|
||||
Type: model.TypeInt64,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["double"] = &Symbol{
|
||||
Type: model.TypeFloat64,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["float"] = &Symbol{
|
||||
Type: model.TypeFloat32,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["string"] = &Symbol{
|
||||
Type: model.TypeString,
|
||||
Scope: ast,
|
||||
}
|
||||
ret["bytes"] = &Symbol{
|
||||
Type: model.TypeBinary,
|
||||
Scope: ast,
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (resolver *Resolver) LoadOne(ast *descriptorpb.FileDescriptorProto) (NameSpace, error) {
|
||||
ret := LoadBaseIdentifier(ast)
|
||||
space := util.BaseName(ast.GetPackage(), "")
|
||||
prefix := "." + space
|
||||
|
||||
for _, e := range ast.GetEnumType() {
|
||||
name := strings.TrimLeft(e.GetName(), prefix)
|
||||
ret[e.GetName()] = &Symbol{
|
||||
Name: name,
|
||||
Space: space,
|
||||
IsValue: false,
|
||||
Value: e,
|
||||
Scope: ast,
|
||||
Type: model.NewEnumType(name, model.CategoryEnum),
|
||||
}
|
||||
for _, ee := range e.GetValue() {
|
||||
name := strings.TrimLeft(ee.GetName(), prefix)
|
||||
ret[ee.GetName()] = &Symbol{
|
||||
Name: name,
|
||||
Space: space,
|
||||
IsValue: true,
|
||||
Value: ee,
|
||||
Scope: ast,
|
||||
Type: model.NewCategoryType(model.TypeInt, model.CategoryEnum),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, mt := range ast.GetMessageType() {
|
||||
name := strings.TrimLeft(mt.GetName(), prefix)
|
||||
ret[mt.GetName()] = &Symbol{
|
||||
Name: name,
|
||||
Space: space,
|
||||
IsValue: false,
|
||||
Value: mt,
|
||||
Scope: ast,
|
||||
Type: model.NewStructType(name, model.CategoryStruct),
|
||||
}
|
||||
|
||||
for _, nt := range mt.GetNestedType() {
|
||||
ntname := name + "_" + nt.GetName()
|
||||
ret[name+"."+nt.GetName()] = &Symbol{
|
||||
Name: ntname,
|
||||
Space: space,
|
||||
IsValue: false,
|
||||
Value: nt,
|
||||
Scope: ast,
|
||||
Type: model.NewStructType(ntname, model.CategoryStruct),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range ast.GetService() {
|
||||
name := strings.TrimLeft(s.GetName(), prefix)
|
||||
ret[s.GetName()] = &Symbol{
|
||||
Name: name,
|
||||
Space: space,
|
||||
IsValue: false,
|
||||
Value: s,
|
||||
Scope: ast,
|
||||
Type: model.NewFuncType(name, model.CategoryService),
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) GetFiles() FileInfos {
|
||||
return resolver.files
|
||||
}
|
153
protobuf/tag_test.go
Normal file
153
protobuf/tag_test.go
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/protobuf/compiler/protogen"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/pluginpb"
|
||||
)
|
||||
|
||||
func TestTagGenerate(t *testing.T) {
|
||||
type TagStruct struct {
|
||||
Annotation string
|
||||
GeneratedTag string
|
||||
ActualTag string
|
||||
}
|
||||
|
||||
tagList := []TagStruct{
|
||||
{
|
||||
Annotation: "query",
|
||||
GeneratedTag: "protobuf:\"bytes,1,opt,name=QueryTag\" json:\"QueryTag,omitempty\" query:\"query\"",
|
||||
},
|
||||
{
|
||||
Annotation: "raw_body",
|
||||
GeneratedTag: "protobuf:\"bytes,2,opt,name=RawBodyTag\" json:\"RawBodyTag,omitempty\" raw_body:\"raw_body\"",
|
||||
},
|
||||
{
|
||||
Annotation: "path",
|
||||
GeneratedTag: "protobuf:\"bytes,3,opt,name=PathTag\" json:\"PathTag,omitempty\" path:\"path\"",
|
||||
},
|
||||
{
|
||||
Annotation: "form",
|
||||
GeneratedTag: "protobuf:\"bytes,4,opt,name=FormTag\" json:\"FormTag,omitempty\" form:\"form\"",
|
||||
},
|
||||
{
|
||||
Annotation: "cookie",
|
||||
GeneratedTag: "protobuf:\"bytes,5,opt,name=CookieTag\" json:\"CookieTag,omitempty\" cookie:\"cookie\"",
|
||||
},
|
||||
{
|
||||
Annotation: "header",
|
||||
GeneratedTag: "protobuf:\"bytes,6,opt,name=HeaderTag\" json:\"HeaderTag,omitempty\" header:\"header\"",
|
||||
},
|
||||
{
|
||||
Annotation: "body",
|
||||
GeneratedTag: "bytes,7,opt,name=BodyTag\" form:\"body\" json:\"body,omitempty\"",
|
||||
},
|
||||
{
|
||||
Annotation: "go.tag",
|
||||
GeneratedTag: "bytes,8,opt,name=GoTag\" json:\"json\" form:\"form\" goTag:\"tag\" header:\"header\" query:\"query\"",
|
||||
},
|
||||
{
|
||||
Annotation: "vd",
|
||||
GeneratedTag: "bytes,9,opt,name=VdTag\" json:\"VdTag,omitempty\" form:\"VdTag\" query:\"VdTag\" vd:\"$!='?'\"",
|
||||
},
|
||||
{
|
||||
Annotation: "non",
|
||||
GeneratedTag: "bytes,10,opt,name=DefaultTag\" json:\"DefaultTag,omitempty\" form:\"DefaultTag\" query:\"DefaultTag\"",
|
||||
},
|
||||
{
|
||||
Annotation: "query required",
|
||||
GeneratedTag: "bytes,11,req,name=ReqQuery\" json:\"ReqQuery,required\" query:\"query,required\"",
|
||||
},
|
||||
{
|
||||
Annotation: "query optional",
|
||||
GeneratedTag: "bytes,12,opt,name=OptQuery\" json:\"OptQuery,omitempty\" query:\"query\"",
|
||||
},
|
||||
{
|
||||
Annotation: "body required",
|
||||
GeneratedTag: "protobuf:\"bytes,13,req,name=ReqBody\" form:\"body,required\" json:\"body,required\"",
|
||||
},
|
||||
{
|
||||
Annotation: "body optional",
|
||||
GeneratedTag: "protobuf:\"bytes,14,opt,name=OptBody\" form:\"body\" json:\"body,omitempty\"",
|
||||
},
|
||||
{
|
||||
Annotation: "go.tag required",
|
||||
GeneratedTag: "protobuf:\"bytes,15,req,name=ReqGoTag\" query:\"ReqGoTag,required\" form:\"ReqGoTag,required\" json:\"json\"",
|
||||
},
|
||||
{
|
||||
Annotation: "go.tag optional",
|
||||
GeneratedTag: "bytes,16,opt,name=OptGoTag\" query:\"OptGoTag\" form:\"OptGoTag\" json:\"json\"",
|
||||
},
|
||||
{
|
||||
Annotation: "go tag cover query",
|
||||
GeneratedTag: "bytes,17,req,name=QueryGoTag\" json:\"QueryGoTag,required\" query:\"queryTag\"",
|
||||
},
|
||||
}
|
||||
|
||||
in, err := ioutil.ReadFile("./test_data/protobuf_tag_test.out")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := &pluginpb.CodeGeneratorRequest{}
|
||||
err = proto.Unmarshal(in, req)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal stdin request error: %v", err)
|
||||
}
|
||||
|
||||
opts := protogen.Options{}
|
||||
gen, err := opts.New(req)
|
||||
|
||||
for _, f := range gen.Files {
|
||||
if f.Proto.GetName() == "test_tag.proto" {
|
||||
fileInfo := newFileInfo(f)
|
||||
for _, message := range fileInfo.allMessages {
|
||||
for idx, field := range message.Fields {
|
||||
tags := structTags{
|
||||
{"protobuf", fieldProtobufTagValue(field)},
|
||||
}
|
||||
err = injectTagsToStructTags(field.Desc, &tags, true, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var actualTag string
|
||||
for i, tag := range tags {
|
||||
if i == 0 {
|
||||
actualTag = tag[0] + ":" + "\"" + tag[1] + "\""
|
||||
} else {
|
||||
actualTag = actualTag + " " + tag[0] + ":" + "\"" + tag[1] + "\""
|
||||
}
|
||||
}
|
||||
tagList[idx].ActualTag = actualTag
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range tagList {
|
||||
if !strings.Contains(tagList[i].ActualTag, tagList[i].GeneratedTag) {
|
||||
t.Fatalf("expected tag: '%s', but autual tag: '%s'", tagList[i].GeneratedTag, tagList[i].ActualTag)
|
||||
}
|
||||
}
|
||||
}
|
474
protobuf/tags.go
Normal file
474
protobuf/tags.go
Normal file
@ -0,0 +1,474 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/config"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/protobuf/api"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/runtime/protoimpl"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
)
|
||||
|
||||
var (
|
||||
jsonSnakeName = false
|
||||
unsetOmitempty = false
|
||||
protobufCamelJSONTagStyle = false
|
||||
)
|
||||
|
||||
func CheckTagOption(args *config.Argument) (ret []generator.Option) {
|
||||
if args == nil {
|
||||
return
|
||||
}
|
||||
if args.SnakeName {
|
||||
jsonSnakeName = true
|
||||
}
|
||||
if args.UnsetOmitempty {
|
||||
unsetOmitempty = true
|
||||
}
|
||||
if args.JSONEnumStr {
|
||||
ret = append(ret, generator.OptionMarshalEnumToText)
|
||||
}
|
||||
if args.ProtobufCamelJSONTag {
|
||||
protobufCamelJSONTagStyle = true
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func checkSnakeName(name string) string {
|
||||
if jsonSnakeName {
|
||||
name = util.ToSnakeCase(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
var (
|
||||
HttpMethodOptions = map[*protoimpl.ExtensionInfo]string{
|
||||
api.E_Get: "GET",
|
||||
api.E_Post: "POST",
|
||||
api.E_Put: "PUT",
|
||||
api.E_Patch: "PATCH",
|
||||
api.E_Delete: "DELETE",
|
||||
api.E_Options: "OPTIONS",
|
||||
api.E_Head: "HEAD",
|
||||
api.E_Any: "Any",
|
||||
}
|
||||
|
||||
BindingTags = map[*protoimpl.ExtensionInfo]string{
|
||||
api.E_Path: "path",
|
||||
api.E_Query: "query",
|
||||
api.E_Header: "header",
|
||||
api.E_Cookie: "cookie",
|
||||
api.E_Body: "json",
|
||||
// Do not change the relative order of "api.E_Form" and "api.E_Body", so that "api.E_Form" can overwrite the form tag generated by "api.E_Body"
|
||||
api.E_Form: "form",
|
||||
api.E_FormCompatible: "form",
|
||||
api.E_RawBody: "raw_body",
|
||||
}
|
||||
|
||||
ValidatorTags = map[*protoimpl.ExtensionInfo]string{api.E_Vd: "vd"}
|
||||
|
||||
SerializerOptions = map[*protoimpl.ExtensionInfo]string{api.E_Serializer: "serializer"}
|
||||
)
|
||||
|
||||
type httpOption struct {
|
||||
method string
|
||||
path string
|
||||
}
|
||||
|
||||
type httpOptions []httpOption
|
||||
|
||||
func (s httpOptions) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s httpOptions) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s httpOptions) Less(i, j int) bool {
|
||||
return s[i].method < s[j].method
|
||||
}
|
||||
|
||||
func getAllOptions(extensions map[*protoimpl.ExtensionInfo]string, opts ...protoreflect.ProtoMessage) map[string]interface{} {
|
||||
out := map[string]interface{}{}
|
||||
for _, opt := range opts {
|
||||
for e, t := range extensions {
|
||||
if proto.HasExtension(opt, e) {
|
||||
v := proto.GetExtension(opt, e)
|
||||
out[t] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func checkFirstOptions(extensions map[*protoimpl.ExtensionInfo]string, opts ...protoreflect.ProtoMessage) (string, interface{}) {
|
||||
for _, opt := range opts {
|
||||
for e, t := range extensions {
|
||||
if proto.HasExtension(opt, e) {
|
||||
v := proto.GetExtension(opt, e)
|
||||
return t, v
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func checkFirstOption(ext *protoimpl.ExtensionInfo, opts ...protoreflect.ProtoMessage) interface{} {
|
||||
for _, opt := range opts {
|
||||
if proto.HasExtension(opt, ext) {
|
||||
v := proto.GetExtension(opt, ext)
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkOption(ext *protoimpl.ExtensionInfo, opts ...protoreflect.ProtoMessage) (ret []interface{}) {
|
||||
for _, opt := range opts {
|
||||
if proto.HasExtension(opt, ext) {
|
||||
v := proto.GetExtension(opt, ext)
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tag(k string, v interface{}) model.Tag {
|
||||
return model.Tag{
|
||||
Key: k,
|
||||
Value: fmt.Sprintf("%v", v),
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------For Compiler---------------------------
|
||||
|
||||
func defaultBindingTags(f *descriptorpb.FieldDescriptorProto) []model.Tag {
|
||||
opts := f.GetOptions()
|
||||
out := make([]model.Tag, 3)
|
||||
if v := checkFirstOption(api.E_Body, opts); v != nil {
|
||||
val := getJsonValue(f, v.(string))
|
||||
out[0] = tag("json", val)
|
||||
} else {
|
||||
out[0] = jsonTag(f)
|
||||
}
|
||||
if v := checkFirstOption(api.E_Query, opts); v != nil {
|
||||
val := checkRequire(f, v.(string))
|
||||
out[1] = tag(BindingTags[api.E_Query], val)
|
||||
} else {
|
||||
val := checkRequire(f, checkSnakeName(f.GetName()))
|
||||
out[1] = tag(BindingTags[api.E_Query], val)
|
||||
}
|
||||
if v := checkFirstOption(api.E_Form, opts); v != nil {
|
||||
val := checkRequire(f, v.(string))
|
||||
out[2] = tag(BindingTags[api.E_Form], val)
|
||||
} else {
|
||||
val := checkRequire(f, checkSnakeName(f.GetName()))
|
||||
out[2] = tag(BindingTags[api.E_Form], val)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func jsonTag(f *descriptorpb.FieldDescriptorProto) (ret model.Tag) {
|
||||
ret.Key = "json"
|
||||
ret.Value = checkSnakeName(f.GetJsonName())
|
||||
if v := checkFirstOption(api.E_JsConv, f.GetOptions()); v != nil {
|
||||
ret.Value += ",string"
|
||||
} else if v := checkFirstOption(api.E_JsConvCompatible, f.GetOptions()); v != nil {
|
||||
ret.Value += ",string"
|
||||
}
|
||||
if !unsetOmitempty && f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL {
|
||||
ret.Value += ",omitempty"
|
||||
} else if f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {
|
||||
ret.Value += ",required"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func injectTagsToModel(f *descriptorpb.FieldDescriptorProto, gf *model.Field, needDefault bool) error {
|
||||
as := f.GetOptions()
|
||||
|
||||
tags := gf.Tags
|
||||
if tags == nil {
|
||||
tags = make([]model.Tag, 0, 4)
|
||||
}
|
||||
|
||||
// binding tags
|
||||
if needDefault {
|
||||
tags = append(tags, defaultBindingTags(f)...)
|
||||
}
|
||||
for k, v := range BindingTags {
|
||||
if vv := checkFirstOption(k, as); vv != nil {
|
||||
tags.Remove(v)
|
||||
if v == "json" {
|
||||
vv = getJsonValue(f, vv.(string))
|
||||
} else {
|
||||
vv = checkRequire(f, vv.(string))
|
||||
}
|
||||
tags = append(tags, tag(v, vv))
|
||||
}
|
||||
}
|
||||
|
||||
// validator tags
|
||||
for k, v := range ValidatorTags {
|
||||
for _, vv := range checkOption(k, as) {
|
||||
tags = append(tags, tag(v, vv))
|
||||
}
|
||||
}
|
||||
|
||||
// go.tags
|
||||
for _, v := range checkOption(api.E_GoTag, as) {
|
||||
gts := util.SplitGoTags(v.(string))
|
||||
for _, gt := range gts {
|
||||
sp := strings.SplitN(gt, ":", 2)
|
||||
if len(sp) != 2 {
|
||||
return fmt.Errorf("invalid go tag: %s", v)
|
||||
}
|
||||
vv, err := strconv.Unquote(sp[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid go.tag value: %s, err: %v", sp[1], err.Error())
|
||||
}
|
||||
key := sp[0]
|
||||
tags.Remove(key)
|
||||
tags = append(tags, model.Tag{
|
||||
Key: key,
|
||||
Value: vv,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(tags)
|
||||
gf.Tags = tags
|
||||
return nil
|
||||
}
|
||||
|
||||
func getJsonValue(f *descriptorpb.FieldDescriptorProto, val string) string {
|
||||
if v := checkFirstOption(api.E_JsConv, f.GetOptions()); v != nil {
|
||||
val += ",string"
|
||||
} else if v := checkFirstOption(api.E_JsConvCompatible, f.GetOptions()); v != nil {
|
||||
val += ",string"
|
||||
}
|
||||
if !unsetOmitempty && f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL {
|
||||
val += ",omitempty"
|
||||
} else if f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {
|
||||
val += ",required"
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func checkRequire(f *descriptorpb.FieldDescriptorProto, val string) string {
|
||||
if f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {
|
||||
val += ",required"
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
//-------------------------For plugin---------------------------------
|
||||
|
||||
func m2s(mt model.Tag) (ret [2]string) {
|
||||
ret[0] = mt.Key
|
||||
ret[1] = mt.Value
|
||||
return ret
|
||||
}
|
||||
|
||||
func reflectJsonTag(f protoreflect.FieldDescriptor) (ret model.Tag) {
|
||||
ret.Key = "json"
|
||||
if protobufCamelJSONTagStyle {
|
||||
ret.Value = checkSnakeName(f.JSONName())
|
||||
} else {
|
||||
ret.Value = checkSnakeName(string(f.Name()))
|
||||
}
|
||||
if v := checkFirstOption(api.E_Body, f.Options()); v != nil {
|
||||
ret.Value += ",string"
|
||||
}
|
||||
if descriptorpb.FieldDescriptorProto_Label(f.Cardinality()) == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {
|
||||
ret.Value += ",required"
|
||||
} else if !unsetOmitempty {
|
||||
ret.Value += ",omitempty"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func defaultBindingStructTags(f protoreflect.FieldDescriptor) []model.Tag {
|
||||
opts := f.Options()
|
||||
out := make([]model.Tag, 3)
|
||||
bindingTags := []*protoimpl.ExtensionInfo{
|
||||
api.E_Path,
|
||||
api.E_Query,
|
||||
api.E_Form,
|
||||
api.E_FormCompatible,
|
||||
api.E_Header,
|
||||
api.E_Cookie,
|
||||
api.E_Body,
|
||||
api.E_RawBody,
|
||||
}
|
||||
// If the user provides an annotation, return json tag directly
|
||||
for _, tag := range bindingTags {
|
||||
if vv := checkFirstOption(tag, opts); vv != nil {
|
||||
out[0] = reflectJsonTag(f)
|
||||
return out[:1]
|
||||
}
|
||||
}
|
||||
|
||||
if v := checkFirstOption(api.E_Body, opts); v != nil {
|
||||
val := getStructJsonValue(f, v.(string))
|
||||
out[0] = tag("json", val)
|
||||
} else {
|
||||
t := reflectJsonTag(f)
|
||||
t.IsDefault = true
|
||||
out[0] = t
|
||||
}
|
||||
if v := checkFirstOption(api.E_Query, opts); v != nil {
|
||||
val := checkStructRequire(f, v.(string))
|
||||
out[1] = tag(BindingTags[api.E_Query], val)
|
||||
} else {
|
||||
val := checkStructRequire(f, checkSnakeName(string(f.Name())))
|
||||
t := tag(BindingTags[api.E_Query], val)
|
||||
t.IsDefault = true
|
||||
out[1] = t
|
||||
}
|
||||
if v := checkFirstOption(api.E_Form, opts); v != nil {
|
||||
val := checkStructRequire(f, v.(string))
|
||||
out[2] = tag(BindingTags[api.E_Form], val)
|
||||
} else {
|
||||
if v := checkFirstOption(api.E_FormCompatible, opts); v != nil { // compatible form_compatible
|
||||
val := checkStructRequire(f, v.(string))
|
||||
t := tag(BindingTags[api.E_Form], val)
|
||||
t.IsDefault = true
|
||||
out[2] = t
|
||||
} else {
|
||||
val := checkStructRequire(f, checkSnakeName(string(f.Name())))
|
||||
t := tag(BindingTags[api.E_Form], val)
|
||||
t.IsDefault = true
|
||||
out[2] = t
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func injectTagsToStructTags(f protoreflect.FieldDescriptor, out *structTags, needDefault bool, rmTags RemoveTags) error {
|
||||
as := f.Options()
|
||||
// binding tags
|
||||
tags := model.Tags(make([]model.Tag, 0, 6))
|
||||
|
||||
if needDefault {
|
||||
tags = append(tags, defaultBindingStructTags(f)...)
|
||||
}
|
||||
for k, v := range BindingTags {
|
||||
if vv := checkFirstOption(k, as); vv != nil {
|
||||
tags.Remove(v)
|
||||
// body annotation will generate "json" & "form" tag for protobuf
|
||||
if v == "json" {
|
||||
formVal := vv
|
||||
vv = getStructJsonValue(f, vv.(string))
|
||||
formVal = checkStructRequire(f, formVal.(string))
|
||||
tags = append(tags, tag("form", formVal))
|
||||
} else {
|
||||
vv = checkStructRequire(f, vv.(string))
|
||||
}
|
||||
tags = append(tags, tag(v, vv))
|
||||
}
|
||||
}
|
||||
|
||||
// validator tags
|
||||
for k, v := range ValidatorTags {
|
||||
if vv := checkFirstOption(k, as); vv != nil {
|
||||
tags = append(tags, tag(v, vv))
|
||||
}
|
||||
}
|
||||
|
||||
if v := checkFirstOption(api.E_GoTag, as); v != nil {
|
||||
gts := util.SplitGoTags(v.(string))
|
||||
for _, gt := range gts {
|
||||
sp := strings.SplitN(gt, ":", 2)
|
||||
if len(sp) != 2 {
|
||||
return fmt.Errorf("invalid go tag: %s", v)
|
||||
}
|
||||
vv, err := strconv.Unquote(sp[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid go.tag value: %s, err: %v", sp[1], err.Error())
|
||||
}
|
||||
key := sp[0]
|
||||
tags.Remove(key)
|
||||
tags = append(tags, model.Tag{
|
||||
Key: key,
|
||||
Value: vv,
|
||||
})
|
||||
}
|
||||
}
|
||||
disableTag := false
|
||||
if vv := checkFirstOption(api.E_None, as); vv != nil {
|
||||
if strings.EqualFold(vv.(string), "true") {
|
||||
disableTag = true
|
||||
}
|
||||
} else if vv := checkFirstOption(api.E_NoneCompatible, as); vv != nil {
|
||||
if strings.EqualFold(vv.(string), "true") {
|
||||
disableTag = true
|
||||
}
|
||||
}
|
||||
for _, t := range tags {
|
||||
if t.IsDefault && rmTags.Exist(t.Key) {
|
||||
tags.Remove(t.Key)
|
||||
}
|
||||
}
|
||||
// protobuf tag as first
|
||||
sort.Sort(tags[1:])
|
||||
for _, t := range tags {
|
||||
if disableTag {
|
||||
*out = append(*out, [2]string{t.Key, "-"})
|
||||
} else {
|
||||
*out = append(*out, m2s(t))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStructJsonValue(f protoreflect.FieldDescriptor, val string) string {
|
||||
if v := checkFirstOption(api.E_JsConv, f.Options()); v != nil {
|
||||
val += ",string"
|
||||
} else if v := checkFirstOption(api.E_JsConvCompatible, f.Options()); v != nil {
|
||||
val += ",string"
|
||||
}
|
||||
|
||||
if descriptorpb.FieldDescriptorProto_Label(f.Cardinality()) == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {
|
||||
val += ",required"
|
||||
} else if !unsetOmitempty {
|
||||
val += ",omitempty"
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func checkStructRequire(f protoreflect.FieldDescriptor, val string) string {
|
||||
if descriptorpb.FieldDescriptorProto_Label(f.Cardinality()) == descriptorpb.FieldDescriptorProto_LABEL_REQUIRED {
|
||||
val += ",required"
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
BIN
protobuf/test_data/protobuf_tag_test.out
Normal file
BIN
protobuf/test_data/protobuf_tag_test.out
Normal file
Binary file not shown.
32
protobuf/test_data/test_tag.proto
Normal file
32
protobuf/test_data/test_tag.proto
Normal file
@ -0,0 +1,32 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package test;
|
||||
|
||||
option go_package = "cloudwego.hertz.hz";
|
||||
|
||||
import "api.proto";
|
||||
|
||||
message MultiTagReq {
|
||||
// basic feature
|
||||
optional string QueryTag = 1 [(api.query)="query"];
|
||||
optional string RawBodyTag = 2 [(api.raw_body)="raw_body"];
|
||||
optional string PathTag = 3 [(api.path)="path"];
|
||||
optional string FormTag = 4 [(api.form)="form"];
|
||||
optional string CookieTag = 5 [(api.cookie)="cookie"];
|
||||
optional string HeaderTag = 6 [(api.header)="header"];
|
||||
optional string BodyTag = 7 [(api.body)="body"];
|
||||
optional string GoTag = 8 [(api.go_tag)="json:\"json\" query:\"query\" form:\"form\" header:\"header\" goTag:\"tag\""];
|
||||
optional string VdTag = 9 [(api.vd)="$!='?'"];
|
||||
optional string DefaultTag = 10;
|
||||
|
||||
// optional / required
|
||||
required string ReqQuery = 11 [(api.query)="query"];
|
||||
optional string OptQuery = 12 [(api.query)="query"];
|
||||
required string ReqBody = 13 [(api.body)="body"];
|
||||
optional string OptBody = 14 [(api.body)="body"];
|
||||
required string ReqGoTag = 15 [(api.go_tag)="json:\"json\""];
|
||||
optional string OptGoTag = 16 [(api.go_tag)="json:\"json\""];
|
||||
|
||||
// gotag cover feature
|
||||
required string QueryGoTag = 17 [(api.query)="query", (api.go_tag)="query:\"queryTag\""];
|
||||
}
|
105
test_hz_unix.sh
Normal file
105
test_hz_unix.sh
Normal file
@ -0,0 +1,105 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# const value define
|
||||
moduleName="github.com/cloudwego/hertz/cmd/hz/test"
|
||||
curDir=`pwd`
|
||||
thriftIDL=$curDir"/testdata/thrift/psm.thrift"
|
||||
protobuf2IDL=$curDir"/testdata/protobuf2/psm/psm.proto"
|
||||
proto2Search=$curDir"/testdata/protobuf2"
|
||||
protobuf3IDL=$curDir"/testdata/protobuf3/psm/psm.proto"
|
||||
proto3Search=$curDir"/testdata/protobuf3"
|
||||
protoSearch="/usr/local/include"
|
||||
|
||||
judge_exit() {
|
||||
code=$1
|
||||
if [ $code != 0 ]; then
|
||||
exit $code
|
||||
fi
|
||||
}
|
||||
|
||||
compile_hz() {
|
||||
go build -o hz
|
||||
judge_exit "$?"
|
||||
}
|
||||
|
||||
install_dependent_tools() {
|
||||
# install thriftgo
|
||||
go install github.com/cloudwego/thriftgo@latest
|
||||
|
||||
# install protoc
|
||||
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip
|
||||
unzip -d protoc-3.19.4-linux-x86_64 protoc-3.19.4-linux-x86_64.zip
|
||||
cp protoc-3.19.4-linux-x86_64/bin/protoc /usr/local/bin/protoc
|
||||
cp -r protoc-3.19.4-linux-x86_64/include/google /usr/local/include/google
|
||||
}
|
||||
|
||||
test_thrift() {
|
||||
mkdir -p test
|
||||
cd test
|
||||
../hz new --idl=$thriftIDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router
|
||||
judge_exit "$?"
|
||||
go mod tidy && go build .
|
||||
judge_exit "$?"
|
||||
../hz update --idl=$thriftIDL
|
||||
judge_exit "$?"
|
||||
../hz model --idl=$thriftIDL --model_dir=hertz_model
|
||||
judge_exit "$?"
|
||||
../hz client --idl=$thriftIDL --client_dir=hertz_client
|
||||
judge_exit "$?"
|
||||
cd ..
|
||||
rm -rf test
|
||||
}
|
||||
|
||||
test_protobuf2() {
|
||||
# test protobuf2
|
||||
mkdir -p test
|
||||
cd test
|
||||
../hz new -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router
|
||||
judge_exit "$?"
|
||||
go mod tidy && go build .
|
||||
judge_exit "$?"
|
||||
../hz update -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL
|
||||
judge_exit "$?"
|
||||
../hz model -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --model_dir=hertz_model
|
||||
judge_exit "$?"
|
||||
../hz client -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --client_dir=hertz_client
|
||||
judge_exit "$?"
|
||||
cd ..
|
||||
rm -rf test
|
||||
}
|
||||
|
||||
test_protobuf3() {
|
||||
# test protobuf2
|
||||
mkdir -p test
|
||||
cd test
|
||||
../hz new -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router
|
||||
judge_exit "$?"
|
||||
go mod tidy && go build .
|
||||
judge_exit "$?"
|
||||
../hz update -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL
|
||||
judge_exit "$?"
|
||||
../hz model -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --model_dir=hertz_model
|
||||
judge_exit "$?"
|
||||
../hz client -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --client_dir=hertz_client
|
||||
judge_exit "$?"
|
||||
cd ..
|
||||
rm -rf test
|
||||
}
|
||||
|
||||
main() {
|
||||
compile_hz
|
||||
judge_exit "$?"
|
||||
install_dependent_tools
|
||||
judge_exit "$?"
|
||||
echo "test thrift......"
|
||||
test_thrift
|
||||
judge_exit "$?"
|
||||
echo "test protobuf2......"
|
||||
test_protobuf2
|
||||
judge_exit "$?"
|
||||
echo "test protobuf3......"
|
||||
test_protobuf3
|
||||
judge_exit "$?"
|
||||
echo "hz execute success"
|
||||
}
|
||||
main
|
101
test_hz_windows.sh
Normal file
101
test_hz_windows.sh
Normal file
@ -0,0 +1,101 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# const value define
|
||||
moduleName="github.com/cloudwego/hertz/cmd/hz/test"
|
||||
curDir=`pwd`
|
||||
thriftIDL=$curDir"/testdata/thrift/psm.thrift"
|
||||
protobuf2IDL=$curDir"/testdata/protobuf2/psm/psm.proto"
|
||||
proto2Search=$curDir"/testdata/protobuf2"
|
||||
protobuf3IDL=$curDir"/testdata/protobuf3/psm/psm.proto"
|
||||
proto3Search=$curDir"/testdata/protobuf3"
|
||||
protoSearch=$curDir"/testdata/include"
|
||||
|
||||
judge_exit() {
|
||||
code=$1
|
||||
if [ $code != 0 ]; then
|
||||
exit $code
|
||||
fi
|
||||
}
|
||||
|
||||
compile_hz() {
|
||||
go install .
|
||||
judge_exit "$?"
|
||||
}
|
||||
|
||||
install_dependent_tools() {
|
||||
# install thriftgo
|
||||
go install github.com/cloudwego/thriftgo@latest
|
||||
}
|
||||
|
||||
test_thrift() {
|
||||
# test thrift
|
||||
mkdir -p test
|
||||
cd test
|
||||
hz new --idl=$thriftIDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router
|
||||
judge_exit "$?"
|
||||
go mod tidy && go build .
|
||||
judge_exit "$?"
|
||||
hz update --idl=$thriftIDL
|
||||
judge_exit "$?"
|
||||
hz model --idl=$thriftIDL --model_dir=hertz_model
|
||||
judge_exit "$?"
|
||||
hz client --idl=$thriftIDL --client_dir=hertz_client
|
||||
judge_exit "$?"
|
||||
cd ..
|
||||
rm -rf test
|
||||
}
|
||||
|
||||
test_protobuf2() {
|
||||
# test protobuf2
|
||||
mkdir -p test
|
||||
cd test
|
||||
hz new -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router
|
||||
judge_exit "$?"
|
||||
go mod tidy && go build .
|
||||
judge_exit "$?"
|
||||
hz update -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL
|
||||
judge_exit "$?"
|
||||
hz model -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --model_dir=hertz_model
|
||||
judge_exit "$?"
|
||||
hz client -I=$protoSearch -I=$proto2Search --idl=$protobuf2IDL --client_dir=hertz_client
|
||||
judge_exit "$?"
|
||||
cd ..
|
||||
rm -rf test
|
||||
}
|
||||
|
||||
test_protobuf3() {
|
||||
# test protobuf2
|
||||
mkdir -p test
|
||||
cd test
|
||||
hz new -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --mod=$moduleName -f --model_dir=hertz_model --handler_dir=hertz_handler --router_dir=hertz_router
|
||||
judge_exit "$?"
|
||||
go mod tidy && go build .
|
||||
judge_exit "$?"
|
||||
hz update -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL
|
||||
judge_exit "$?"
|
||||
hz model -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --model_dir=hertz_model
|
||||
judge_exit "$?"
|
||||
hz client -I=$protoSearch -I=$proto3Search --idl=$protobuf3IDL --client_dir=hertz_client
|
||||
judge_exit "$?"
|
||||
cd ..
|
||||
rm -rf test
|
||||
}
|
||||
|
||||
main() {
|
||||
compile_hz
|
||||
judge_exit "$?"
|
||||
install_dependent_tools
|
||||
judge_exit "$?"
|
||||
# todo: add thrift test when thriftgo fixed windows
|
||||
echo "test thrift......"
|
||||
test_thrift
|
||||
judge_exit "$?"
|
||||
echo "test protobuf2......"
|
||||
test_protobuf2
|
||||
judge_exit "$?"
|
||||
echo "test protobuf3......"
|
||||
test_protobuf3
|
||||
judge_exit "$?"
|
||||
echo "hz execute success"
|
||||
}
|
||||
main
|
65
testdata/protobuf2/api.proto
vendored
Normal file
65
testdata/protobuf2/api.proto
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package api;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/api";
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional string raw_body = 50101;
|
||||
optional string query = 50102;
|
||||
optional string header = 50103;
|
||||
optional string cookie = 50104;
|
||||
optional string body = 50105;
|
||||
optional string path = 50106;
|
||||
optional string vd = 50107;
|
||||
optional string form = 50108;
|
||||
optional string js_conv = 50109;
|
||||
optional string file_name = 50110;
|
||||
optional string none = 50111;
|
||||
|
||||
// 50131~50160 used to extend field option by hz
|
||||
optional string form_compatible = 50131;
|
||||
optional string js_conv_compatible = 50132;
|
||||
optional string file_name_compatible = 50133;
|
||||
optional string none_compatible = 50134;
|
||||
|
||||
optional string go_tag = 51001;
|
||||
}
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
optional string get = 50201;
|
||||
optional string post = 50202;
|
||||
optional string put = 50203;
|
||||
optional string delete = 50204;
|
||||
optional string patch = 50205;
|
||||
optional string options = 50206;
|
||||
optional string head = 50207;
|
||||
optional string any = 50208;
|
||||
optional string gen_path = 50301; // The path specified by the user when the client code is generated, with a higher priority than api_version
|
||||
optional string api_version = 50302; // Specify the value of the :version variable in path when the client code is generated
|
||||
optional string tag = 50303; // rpc tag, can be multiple, separated by commas
|
||||
optional string name = 50304; // Name of rpc
|
||||
optional string api_level = 50305; // Interface Level
|
||||
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
|
||||
|
||||
// 50331~50360 used to extend method option by hz
|
||||
optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method
|
||||
}
|
||||
|
||||
extend google.protobuf.EnumValueOptions {
|
||||
optional int32 http_code = 50401;
|
||||
|
||||
// 50431~50460 used to extend enum option by hz
|
||||
}
|
||||
|
||||
extend google.protobuf.ServiceOptions {
|
||||
optional string base_domain = 50402;
|
||||
|
||||
// 50731~50760 used to extend service option by hz
|
||||
optional string base_domain_compatible = 50731;
|
||||
}
|
12
testdata/protobuf2/other/other.proto
vendored
Normal file
12
testdata/protobuf2/other/other.proto
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package hertz.other;
|
||||
|
||||
import "other/other_base.proto";
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/other";
|
||||
|
||||
message OtherType {
|
||||
optional string IsBaseString = 1;
|
||||
optional OtherBaseType IsOtherBaseType = 2;
|
||||
}
|
9
testdata/protobuf2/other/other_base.proto
vendored
Normal file
9
testdata/protobuf2/other/other_base.proto
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package hertz.other;
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/other";
|
||||
|
||||
message OtherBaseType {
|
||||
optional string IsOtherBaseTypeString = 1;
|
||||
}
|
14
testdata/protobuf2/psm/base.proto
vendored
Normal file
14
testdata/protobuf2/psm/base.proto
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package base;
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/psm";
|
||||
|
||||
message Base {
|
||||
optional string IsBaseString = 1;
|
||||
}
|
||||
|
||||
enum BaseEnumType {
|
||||
TWEET = 0;
|
||||
RETWEET = 1;
|
||||
}
|
155
testdata/protobuf2/psm/psm.proto
vendored
Normal file
155
testdata/protobuf2/psm/psm.proto
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package psm;
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/psm";
|
||||
|
||||
import "api.proto";
|
||||
import "base.proto";
|
||||
import "other/other.proto";
|
||||
|
||||
enum EnumType {
|
||||
TWEET = 0;
|
||||
RETWEET = 1;
|
||||
}
|
||||
message UnusedMessageType {
|
||||
optional string IsUnusedMessageType = 1;
|
||||
}
|
||||
|
||||
message BaseType {
|
||||
optional base.Base IsBaseType = 1;
|
||||
}
|
||||
|
||||
message MultiTypeReq {
|
||||
// basic type (leading comments)
|
||||
optional bool IsBoolOpt = 1;
|
||||
required bool IsBoolReq = 2;
|
||||
optional int32 IsInt32Opt = 3;
|
||||
required int32 IsInt32Req = 4;
|
||||
optional int64 IsInt64Opt = 5;
|
||||
optional uint32 IsUInt32Opt = 6;
|
||||
optional uint64 IsUInt64Opt = 7;
|
||||
optional sint32 IsSInt32Opt = 8;
|
||||
optional sint64 IsSInt64Opt = 9;
|
||||
optional fixed32 IsFix32Opt = 10;
|
||||
optional fixed64 IsFix64Opt = 11;
|
||||
optional sfixed32 IsSFix32Opt = 12;
|
||||
optional sfixed64 IsSFix64Opt = 13;
|
||||
optional double IsDoubleOpt = 14;
|
||||
required double IsDoubleReq = 15;
|
||||
optional float IsFloatOpt = 16;
|
||||
optional string IsStringOpt = 17;
|
||||
required string IsStringReq = 18;
|
||||
optional bytes IsBytesOpt = 19;
|
||||
optional bytes IsBytesReq = 20;
|
||||
|
||||
// slice
|
||||
repeated string IsRepeatedString = 21;
|
||||
repeated BaseType IsRepeatedBaseType = 22;
|
||||
|
||||
// map
|
||||
map<string, string> IsStringMap = 23;
|
||||
map<string, BaseType> IsBaseTypeMap = 24;
|
||||
|
||||
// oneof
|
||||
// multiple comments
|
||||
oneof TestOneof {
|
||||
string IsOneofString = 25;
|
||||
BaseType IsOneofBaseType = 26;
|
||||
int32 IsOneofInt = 100;
|
||||
bool IsOneofBool = 101;
|
||||
double IsOneoDouble = 102;
|
||||
bytes IsOneofBytes = 103;
|
||||
}
|
||||
|
||||
// this is oneof2, one field in oneof
|
||||
oneof TestOneof2 {
|
||||
string IsOneof2String = 104;
|
||||
}
|
||||
|
||||
message NestedMessageType {
|
||||
optional string IsNestedString = 1;
|
||||
optional BaseType IsNestedBaseType = 2;
|
||||
repeated BaseType IsNestedRepeatedBaseType = 3;
|
||||
// nested oneof
|
||||
oneof NestedMsgOneof {
|
||||
string IsNestedMsgOneofString = 4;
|
||||
EnumType IsNestedMsgOneofEnumType = 5;
|
||||
}
|
||||
}
|
||||
// nested message
|
||||
optional NestedMessageType IsNestedType = 27;
|
||||
|
||||
// other dependency
|
||||
optional base.Base IsCurrentPackageBase = 28;
|
||||
optional hertz.other.OtherType IsOtherType = 29;
|
||||
|
||||
// enum
|
||||
optional EnumType IsEnumTypeOpt = 30;
|
||||
required EnumType IsEnumTypeReq = 31;
|
||||
repeated EnumType IsEnumTypeList = 32;
|
||||
optional base.BaseEnumType IsBaseEnumType = 33;
|
||||
}
|
||||
|
||||
message MultiTagReq {
|
||||
optional string QueryTag = 1 [(api.query) = "query", (api.none) = "true"];
|
||||
optional string RawBodyTag = 2 [(api.raw_body) = "raw_body"];
|
||||
optional string CookieTag = 3 [(api.cookie) = "cookie"];
|
||||
optional string BodyTag = 4 [(api.body) = "body"];
|
||||
optional string PathTag = 5 [(api.path) = "path"];
|
||||
optional string VdTag = 6 [(api.vd) = "$!='?'"];
|
||||
optional string FormTag = 7 [(api.form) = "form"];
|
||||
optional string DefaultTag = 8 [(api.go_tag) = "FFF:\"fff\" json:\"json\""];
|
||||
}
|
||||
|
||||
message CompatibleAnnoReq {
|
||||
optional string FormCompatibleTag = 1 [(api.form_compatible) = "form"];
|
||||
optional string FilenameCompatibleTag = 2 [(api.file_name_compatible) = "file_name"];
|
||||
optional string NoneCompatibleTag = 3 [(api.none_compatible) = "true"];
|
||||
optional string JsConvCompatibleTag = 4 [(api.js_conv_compatible) = "true"];
|
||||
}
|
||||
|
||||
message Resp {
|
||||
optional string Resp = 1;
|
||||
}
|
||||
|
||||
message MultiNameStyleMessage {
|
||||
optional string hertz = 1;
|
||||
optional string Hertz = 2;
|
||||
optional string hertz_demo = 3;
|
||||
optional string hertz_demo_idl = 4;
|
||||
optional string hertz_Idl = 5;
|
||||
optional string hertzDemo = 6;
|
||||
optional string h = 7;
|
||||
optional string H = 8;
|
||||
optional string hertz_ = 9;
|
||||
}
|
||||
|
||||
service Hertz {
|
||||
rpc Method1(MultiTypeReq) returns(Resp) {
|
||||
option (api.get) = "/company/department/group/user:id/name";
|
||||
}
|
||||
rpc Method2(MultiTypeReq) returns(Resp) {
|
||||
option (api.post) = "/company/department/group/user:id/sex";
|
||||
}
|
||||
rpc Method3(MultiTypeReq) returns(Resp) {
|
||||
option (api.put) = "/company/department/group/user:id/number";
|
||||
}
|
||||
rpc Method4(MultiTypeReq) returns(Resp) {
|
||||
option (api.delete) = "/company/department/group/user:id/age";
|
||||
}
|
||||
|
||||
|
||||
rpc Method5(MultiTagReq) returns(Resp) {
|
||||
option (api.options) = "/school/class/student/name";
|
||||
}
|
||||
rpc Method6(MultiTagReq) returns(Resp) {
|
||||
option (api.head) = "/school/class/student/number";
|
||||
}
|
||||
rpc Method7(MultiTagReq) returns(Resp) {
|
||||
option (api.patch) = "/school/class/student/sex";
|
||||
}
|
||||
rpc Method8(MultiTagReq) returns(Resp) {
|
||||
option (api.any) = "/school/class/student/grade/*subjects";
|
||||
}
|
||||
}
|
65
testdata/protobuf3/api.proto
vendored
Normal file
65
testdata/protobuf3/api.proto
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package api;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/api";
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional string raw_body = 50101;
|
||||
optional string query = 50102;
|
||||
optional string header = 50103;
|
||||
optional string cookie = 50104;
|
||||
optional string body = 50105;
|
||||
optional string path = 50106;
|
||||
optional string vd = 50107;
|
||||
optional string form = 50108;
|
||||
optional string js_conv = 50109;
|
||||
optional string file_name = 50110;
|
||||
optional string none = 50111;
|
||||
|
||||
// 50131~50160 used to extend field option by hz
|
||||
optional string form_compatible = 50131;
|
||||
optional string js_conv_compatible = 50132;
|
||||
optional string file_name_compatible = 50133;
|
||||
optional string none_compatible = 50134;
|
||||
|
||||
optional string go_tag = 51001;
|
||||
}
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
optional string get = 50201;
|
||||
optional string post = 50202;
|
||||
optional string put = 50203;
|
||||
optional string delete = 50204;
|
||||
optional string patch = 50205;
|
||||
optional string options = 50206;
|
||||
optional string head = 50207;
|
||||
optional string any = 50208;
|
||||
optional string gen_path = 50301; // The path specified by the user when the client code is generated, with a higher priority than api_version
|
||||
optional string api_version = 50302; // Specify the value of the :version variable in path when the client code is generated
|
||||
optional string tag = 50303; // rpc tag, can be multiple, separated by commas
|
||||
optional string name = 50304; // Name of rpc
|
||||
optional string api_level = 50305; // Interface Level
|
||||
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
|
||||
|
||||
// 50331~50360 used to extend method option by hz
|
||||
optional string handler_path_compatible = 50331; // handler_path specifies the path to generate the method
|
||||
}
|
||||
|
||||
extend google.protobuf.EnumValueOptions {
|
||||
optional int32 http_code = 50401;
|
||||
|
||||
// 50431~50460 used to extend enum option by hz
|
||||
}
|
||||
|
||||
extend google.protobuf.ServiceOptions {
|
||||
optional string base_domain = 50402;
|
||||
|
||||
// 50731~50760 used to extend service option by hz
|
||||
optional string base_domain_compatible = 50731;
|
||||
}
|
12
testdata/protobuf3/other/other.proto
vendored
Normal file
12
testdata/protobuf3/other/other.proto
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package hertz.other;
|
||||
|
||||
import "other/other_base.proto";
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/other";
|
||||
|
||||
message OtherType {
|
||||
optional string IsBaseString = 1;
|
||||
optional OtherBaseType IsOtherBaseType = 2;
|
||||
}
|
9
testdata/protobuf3/other/other_base.proto
vendored
Normal file
9
testdata/protobuf3/other/other_base.proto
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package hertz.other;
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/other";
|
||||
|
||||
message OtherBaseType {
|
||||
optional string IsOtherBaseTypeString = 1;
|
||||
}
|
9
testdata/protobuf3/psm/base.proto
vendored
Normal file
9
testdata/protobuf3/psm/base.proto
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package base;
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/psm";
|
||||
|
||||
message Base {
|
||||
optional string IsBaseString = 1;
|
||||
}
|
133
testdata/protobuf3/psm/psm.proto
vendored
Normal file
133
testdata/protobuf3/psm/psm.proto
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package psm;
|
||||
|
||||
option go_package = "github.com/cloudwego/hertz/cmd/hz/test/hertz_model/psm";
|
||||
|
||||
import "api.proto";
|
||||
import "base.proto";
|
||||
import "other/other.proto";
|
||||
|
||||
enum EnumType {
|
||||
TWEET = 0;
|
||||
RETWEET = 1;
|
||||
}
|
||||
message UnusedMessageType {
|
||||
optional string IsUnusedMessageType = 1;
|
||||
}
|
||||
|
||||
message BaseType {
|
||||
optional base.Base IsBaseType = 1;
|
||||
}
|
||||
|
||||
message MultiTypeReq {
|
||||
// basic type (leading comments)
|
||||
optional bool IsBoolOpt = 1;
|
||||
optional int32 IsInt32Opt = 3;
|
||||
int64 IsInt64Default = 5;
|
||||
optional uint32 IsUInt32Opt = 6;
|
||||
uint64 IsUInt64Default = 7;
|
||||
optional sint32 IsSInt32Opt = 8;
|
||||
sint64 IsSInt64Default = 9;
|
||||
optional fixed32 IsFix32Opt = 10;
|
||||
optional fixed64 IsFix64Opt = 11;
|
||||
optional sfixed32 IsSFix32Opt = 12;
|
||||
optional sfixed64 IsSFix64Opt = 13;
|
||||
optional double IsDoubleOpt = 14;
|
||||
optional float IsFloatOpt = 16;
|
||||
optional string IsStringOpt = 17;
|
||||
optional bytes IsBytesOpt = 19;
|
||||
bytes IsBytesDefault = 20;
|
||||
|
||||
// slice
|
||||
repeated string IsRepeatedString = 21;
|
||||
repeated BaseType IsRepeatedBaseType = 22;
|
||||
|
||||
// map
|
||||
map<string, string> IsStringMap = 23;
|
||||
map<string, BaseType> IsBaseTypeMap = 24;
|
||||
|
||||
// oneof
|
||||
oneof TestOneof {
|
||||
string IsOneofString = 25;
|
||||
BaseType IsOneofBaseTypeString = 26;
|
||||
}
|
||||
|
||||
oneof TestOneof2 {
|
||||
string IsOneofString2 = 100;
|
||||
}
|
||||
|
||||
// nested message
|
||||
message NestedMessageType {
|
||||
oneof NestedOneof {
|
||||
string YYY = 4;
|
||||
string GGG = 5;
|
||||
}
|
||||
optional string IsNestedString = 1;
|
||||
optional BaseType IsNestedBaseType = 2;
|
||||
repeated BaseType IsNestedRepeatedBaseType = 3;
|
||||
}
|
||||
optional NestedMessageType IsNestedType = 27;
|
||||
|
||||
// other dependency
|
||||
optional base.Base IsCurrentPackageBase = 28;
|
||||
optional hertz.other.OtherType IsOtherType = 29;
|
||||
|
||||
// enum
|
||||
optional EnumType IsEnumTypeOpt = 30;
|
||||
EnumType IsEnumTypeDefault = 31;
|
||||
}
|
||||
|
||||
message MultiTagReq {
|
||||
optional string QueryTag = 1 [(api.query) = "query", (api.none) = "true"];
|
||||
optional string RawBodyTag = 2 [(api.raw_body)="raw_body"];
|
||||
optional string CookieTag = 3 [(api.cookie)="cookie"];
|
||||
optional string BodyTag = 4 [(api.body)="body"];
|
||||
optional string PathTag = 5 [(api.path)="path"];
|
||||
optional string VdTag = 6 [(api.vd)="$!='?'"];
|
||||
optional string DefaultTag = 7;
|
||||
oneof TestOneof {
|
||||
string IsOneofString = 25;
|
||||
BaseType IsOneofBaseTypeString = 26;
|
||||
}
|
||||
}
|
||||
|
||||
message CompatibleAnnoReq {
|
||||
optional string FormCompatibleTag = 1 [(api.form_compatible) = "form"];
|
||||
optional string FilenameCompatibleTag = 2 [(api.file_name_compatible) = "file_name"];
|
||||
optional string NoneCompatibleTag = 3 [(api.none_compatible) = "true"];
|
||||
optional string JsConvCompatibleTag = 4 [(api.js_conv_compatible) = "true"];
|
||||
}
|
||||
|
||||
message Resp {
|
||||
optional string Resp = 1;
|
||||
}
|
||||
|
||||
service Hertz {
|
||||
rpc Method1(MultiTypeReq) returns(Resp) {
|
||||
option (api.get)="/company/department/group/user:id/name";
|
||||
}
|
||||
rpc Method2(MultiTypeReq) returns(Resp) {
|
||||
option (api.post)="/company/department/group/user:id/sex";
|
||||
}
|
||||
rpc Method3(MultiTypeReq) returns(Resp) {
|
||||
option (api.put)="/company/department/group/user:id/number";
|
||||
}
|
||||
rpc Method4(MultiTypeReq) returns(Resp) {
|
||||
option (api.delete)="/company/department/group/user:id/age";
|
||||
}
|
||||
|
||||
|
||||
rpc Method5(MultiTagReq) returns(Resp) {
|
||||
option (api.options)="/school/class/student/name";
|
||||
}
|
||||
rpc Method6(MultiTagReq) returns(Resp) {
|
||||
option (api.head)="/school/class/student/number";
|
||||
}
|
||||
rpc Method7(MultiTagReq) returns(Resp) {
|
||||
option (api.patch)="/school/class/student/sex";
|
||||
}
|
||||
rpc Method8(MultiTagReq) returns(Resp) {
|
||||
option (api.any)="/school/class/student/grade/*subjects";
|
||||
}
|
||||
}
|
13
testdata/thrift/common.thrift
vendored
Normal file
13
testdata/thrift/common.thrift
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
namespace go toutiao.middleware.hertz
|
||||
|
||||
struct CommonType {
|
||||
1: required string IsCommonString;
|
||||
2: optional string TTT;
|
||||
3: required bool HHH;
|
||||
4: required Base GGG;
|
||||
}
|
||||
|
||||
struct Base {
|
||||
1: optional string AAA;
|
||||
2: optional i32 BBB;
|
||||
}
|
5
testdata/thrift/data/basic_data.thrift
vendored
Normal file
5
testdata/thrift/data/basic_data.thrift
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
namespace go toutiao.middleware.hertz_data
|
||||
|
||||
struct BasicDataType {
|
||||
1: optional string IsBasicDataString;
|
||||
}
|
7
testdata/thrift/data/data.thrift
vendored
Normal file
7
testdata/thrift/data/data.thrift
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
include "basic_data.thrift"
|
||||
|
||||
namespace go toutiao.middleware.hertz_data
|
||||
|
||||
struct DataType {
|
||||
1: optional basic_data.BasicDataType IsDataString;
|
||||
}
|
122
testdata/thrift/psm.thrift
vendored
Normal file
122
testdata/thrift/psm.thrift
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
include "common.thrift"
|
||||
include "data/data.thrift"
|
||||
|
||||
namespace go toutiao.middleware.hertz
|
||||
|
||||
const string STRING_CONST = "hertz";
|
||||
|
||||
enum EnumType {
|
||||
TWEET,
|
||||
RETWEET = 2,
|
||||
}
|
||||
|
||||
typedef i32 MyInteger
|
||||
|
||||
struct BaseType {
|
||||
1: string GoTag = "test" (go.tag="json:\"go\" goTag:\"tag\"");
|
||||
2: optional string IsBaseString = "test";
|
||||
3: optional common.CommonType IsDepCommonType = {"IsCommonString":"test", "TTT":"test", "HHH":true, "GGG": {"AAA":"test","BBB":32}};
|
||||
4: optional EnumType IsBaseTypeEnum = 1;
|
||||
}
|
||||
|
||||
typedef common.CommonType FFF
|
||||
|
||||
typedef BaseType MyBaseType
|
||||
|
||||
struct MultiTypeReq {
|
||||
// basic type (leading comments)
|
||||
1: optional bool IsBoolOpt = true; // trailing comments
|
||||
2: required bool IsBoolReq;
|
||||
3: optional byte IsByteOpt = 8;
|
||||
4: required byte IsByteReq;
|
||||
//5: optional i8 IsI8Opt; // unsupported i8, suggest byte
|
||||
//6: required i8 IsI8Req = 5; // default
|
||||
7: optional i16 IsI16Opt = 16;
|
||||
8: optional i32 IsI32Opt;
|
||||
9: optional i64 IsI64Opt;
|
||||
10: optional double IsDoubleOpt;
|
||||
11: required double IsDoubleReq;
|
||||
12: optional string IsStringOpt = "test";
|
||||
13: required string IsStringReq;
|
||||
|
||||
14: optional list<string> IsList;
|
||||
22: required list<string> IsListReq;
|
||||
15: optional set<string> IsSet;
|
||||
16: optional map<string, string> IsMap;
|
||||
21: optional map<string, BaseType> IsStructMap;
|
||||
|
||||
// struct type
|
||||
17: optional BaseType IsBaseType; // use struct name
|
||||
18: optional MyBaseType IsMyBaseType; // use typedef for struct
|
||||
19: optional common.CommonType IsCommonType = {"IsCommonString": "fffff"};
|
||||
20: optional data.DataType IsDataType; // multi-dependent struct
|
||||
}
|
||||
|
||||
typedef data.DataType IsMyDataType
|
||||
|
||||
struct MultiTagReq {
|
||||
1: string QueryTag (api.query="query");
|
||||
2: string RawBodyTag (api.raw_body="raw_body");
|
||||
3: string PathTag (api.path="path");
|
||||
4: string FormTag (api.form="form");
|
||||
5: string CookieTag (api.cookie="cookie");
|
||||
6: string HeaderTag (api.header="header");
|
||||
7: string ProtobufTag (api.protobuf="protobuf");
|
||||
8: string BodyTag (api.body="body");
|
||||
9: string GoTag (go.tag="json:\"go\" goTag:\"tag\"");
|
||||
10: string VdTag (api.vd="$!='?'");
|
||||
11: string DefaultTag;
|
||||
}
|
||||
|
||||
struct Resp {
|
||||
1: string Resp = "this is Resp";
|
||||
}
|
||||
|
||||
struct MultiNameStyleReq {
|
||||
1: optional string hertz;
|
||||
2: optional string Hertz;
|
||||
3: optional string hertz_demo;
|
||||
4: optional string hertz_demo_idl;
|
||||
5: optional string hertz_Idl;
|
||||
6: optional string hertzDemo;
|
||||
7: optional string h;
|
||||
8: optional string H;
|
||||
9: optional string hertz_;
|
||||
}
|
||||
|
||||
struct MultiDefaultReq {
|
||||
1: optional bool IsBoolOpt = true;
|
||||
2: required bool IsBoolReq = false;
|
||||
3: optional i32 IsI32Opt = 32;
|
||||
4: required i32 IsI32Req = 32;
|
||||
5: optional string IsStringOpt = "test";
|
||||
6: required string IsStringReq = "test";
|
||||
|
||||
14: optional list<string> IsListOpt = ["test", "ttt", "sdsds"];
|
||||
22: required list<string> IsListReq = ["test", "ttt", "sdsds"];
|
||||
15: optional set<string> IsSet = ["test", "ttt", "sdsds"];
|
||||
16: optional map<string, string> IsMapOpt = {"test": "ttt", "ttt": "lll"};
|
||||
17: required map<string, string> IsMapReq = {"test": "ttt", "ttt": "lll"};
|
||||
21: optional map<string, BaseType> IsStructMapOpt = {"test": {"GoTag":"fff", "IsBaseTypeEnum":1, "IsBaseString":"ddd", "IsDepCommonType": {"IsCommonString":"fffffff", "TTT":"ttt", "HHH":true, "GGG": {"AAA":"test","BBB":32}}}};
|
||||
25: required map<string, BaseType> IsStructMapReq = {"test": {"GoTag":"fff", "IsBaseTypeEnum":1, "IsBaseString":"ddd", "IsDepCommonType": {"IsCommonString":"fffffff", "TTT":"ttt", "HHH":true, "GGG": {"AAA":"test","BBB":32}}}};
|
||||
|
||||
23: optional common.CommonType IsDepCommonTypeOpt = {"IsCommonString":"fffffff", "TTT":"ttt", "HHH":true, "GGG": {"AAA":"test","BBB":32}};
|
||||
24: required common.CommonType IsDepCommonTypeReq = {"IsCommonString":"fffffff", "TTT":"ttt", "HHH":true, "GGG": {"AAA":"test","BBB":32}};
|
||||
}
|
||||
|
||||
typedef map<string, string> IsTypedefContainer
|
||||
|
||||
service Hertz {
|
||||
Resp Method1(1: MultiTypeReq request) (api.get="/company/department/group/user:id/name", api.handler_path="v1");
|
||||
Resp Method2(1: MultiTagReq request) (api.post="/company/department/group/user:id/sex", api.handler_path="v1");
|
||||
Resp Method3(1: BaseType request) (api.put="/company/department/group/user:id/number", api.handler_path="v1");
|
||||
Resp Method4(1: data.DataType request) (api.delete="/company/department/group/user:id/age", api.handler_path="v1");
|
||||
|
||||
Resp Method5(1: MultiTypeReq request) (api.options="/school/class/student/name", api.handler_path="v2");
|
||||
Resp Method6(1: MultiTagReq request) (api.head="/school/class/student/number", api.handler_path="v2");
|
||||
Resp Method7(1: MultiTagReq request) (api.patch="/school/class/student/sex", api.handler_path="v2");
|
||||
Resp Method8(1: BaseType request) (api.any="/school/class/student/grade/*subjects", api.handler_path="v2");
|
||||
|
||||
Resp Method9(1: IsTypedefContainer request) (api.get="/typedef/container", api.handler_path="v2");
|
||||
Resp Method10(1: map<string, string> request) (api.get="/container", api.handler_path="v2");
|
||||
}
|
916
thrift/ast.go
Normal file
916
thrift/ast.go
Normal file
@ -0,0 +1,916 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package thrift
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/config"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
"github.com/cloudwego/thriftgo/generator/golang"
|
||||
"github.com/cloudwego/thriftgo/generator/golang/styles"
|
||||
"github.com/cloudwego/thriftgo/parser"
|
||||
"github.com/cloudwego/thriftgo/semantic"
|
||||
)
|
||||
|
||||
/*---------------------------Import-----------------------------*/
|
||||
|
||||
func getGoPackage(ast *parser.Thrift, pkgMap map[string]string) string {
|
||||
filePackage := ast.GetFilename()
|
||||
if opt, ok := pkgMap[filePackage]; ok {
|
||||
return opt
|
||||
} else {
|
||||
goPackage := ast.GetNamespaceOrReferenceName("go")
|
||||
if goPackage != "" {
|
||||
return util.SplitPackage(goPackage, "")
|
||||
}
|
||||
// If namespace is not declared, the file name (without the extension) is used as the package name
|
||||
return util.SplitPackage(filePackage, ".thrift")
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------Service-----------------------------*/
|
||||
|
||||
func astToService(ast *parser.Thrift, resolver *Resolver, args *config.Argument) ([]*generator.Service, error) {
|
||||
ss := ast.GetServices()
|
||||
out := make([]*generator.Service, 0, len(ss))
|
||||
var models model.Models
|
||||
extendServices := getExtendServices(ast)
|
||||
for _, s := range ss {
|
||||
// if the service is extended, it is not processed
|
||||
if extendServices.exist(s.Name) && args.EnableExtends {
|
||||
logs.Debugf("%s is extended, so skip it\n", s.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
resolver.ExportReferred(true, false)
|
||||
service := &generator.Service{
|
||||
Name: s.GetName(),
|
||||
}
|
||||
service.BaseDomain = ""
|
||||
domainAnno := getAnnotation(s.Annotations, ApiBaseDomain)
|
||||
if len(domainAnno) == 1 {
|
||||
if args.CmdType == meta.CmdClient {
|
||||
service.BaseDomain = domainAnno[0]
|
||||
}
|
||||
}
|
||||
service.ServiceGroup = ""
|
||||
groupAnno := getAnnotation(s.Annotations, ApiServiceGroup)
|
||||
if len(groupAnno) == 1 {
|
||||
if args.CmdType != meta.CmdClient {
|
||||
service.ServiceGroup = groupAnno[0]
|
||||
}
|
||||
}
|
||||
service.ServiceGenDir = ""
|
||||
serviceGenDirAnno := getAnnotation(s.Annotations, ApiServiceGenDir)
|
||||
if len(serviceGenDirAnno) == 1 {
|
||||
if args.CmdType != meta.CmdClient {
|
||||
service.ServiceGenDir = serviceGenDirAnno[0]
|
||||
}
|
||||
}
|
||||
ms := s.GetFunctions()
|
||||
if len(s.Extends) != 0 && args.EnableExtends {
|
||||
// all the services that are extended to the current service
|
||||
extendsFuncs, err := getAllExtendFunction(s, ast, resolver, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parser extend function failed, err=%v", err)
|
||||
}
|
||||
ms = append(ms, extendsFuncs...)
|
||||
}
|
||||
methods := make([]*generator.HttpMethod, 0, len(ms))
|
||||
clientMethods := make([]*generator.ClientMethod, 0, len(ms))
|
||||
servicePathAnno := getAnnotation(s.Annotations, ApiServicePath)
|
||||
servicePath := ""
|
||||
if len(servicePathAnno) > 0 {
|
||||
servicePath = servicePathAnno[0]
|
||||
}
|
||||
for _, m := range ms {
|
||||
rs := getAnnotations(m.Annotations, HttpMethodAnnotations)
|
||||
if len(rs) == 0 {
|
||||
continue
|
||||
}
|
||||
httpAnnos := httpAnnotations{}
|
||||
for k, v := range rs {
|
||||
httpAnnos = append(httpAnnos, httpAnnotation{
|
||||
method: k,
|
||||
path: v,
|
||||
})
|
||||
}
|
||||
// turn the map into a slice and sort it to make sure getting the results in the same order every time
|
||||
sort.Sort(httpAnnos)
|
||||
handlerOutDir := servicePath
|
||||
genPaths := getAnnotation(m.Annotations, ApiGenPath)
|
||||
if len(genPaths) == 1 {
|
||||
handlerOutDir = genPaths[0]
|
||||
} else if len(genPaths) > 0 {
|
||||
return nil, fmt.Errorf("too many 'api.handler_path' for %s", m.Name)
|
||||
}
|
||||
|
||||
hmethod, path := httpAnnos[0].method, httpAnnos[0].path
|
||||
if len(path) == 0 || path[0] == "" {
|
||||
return nil, fmt.Errorf("invalid api.%s for %s.%s: %s", hmethod, s.Name, m.Name, path)
|
||||
}
|
||||
|
||||
var reqName, reqRawName, reqPackage string
|
||||
if len(m.Arguments) >= 1 {
|
||||
if len(m.Arguments) > 1 {
|
||||
logs.Warnf("function '%s' has more than one argument, but only the first can be used in hertz now", m.GetName())
|
||||
}
|
||||
var err error
|
||||
reqName, err = resolver.ResolveTypeName(m.Arguments[0].GetType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.Contains(reqName, ".") && !m.Arguments[0].GetType().Category.IsContainerType() {
|
||||
// If reqName contains "." , then it must be of the form "pkg.name".
|
||||
// so reqRawName='name', reqPackage='pkg'
|
||||
names := strings.Split(reqName, ".")
|
||||
if len(names) != 2 {
|
||||
return nil, fmt.Errorf("request name: %s is wrong", reqName)
|
||||
}
|
||||
reqRawName = names[1]
|
||||
reqPackage = names[0]
|
||||
}
|
||||
}
|
||||
var respName, respRawName, respPackage string
|
||||
if !m.Oneway {
|
||||
var err error
|
||||
respName, err = resolver.ResolveTypeName(m.GetFunctionType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.Contains(respName, ".") && !m.GetFunctionType().Category.IsContainerType() {
|
||||
names := strings.Split(respName, ".")
|
||||
if len(names) != 2 {
|
||||
return nil, fmt.Errorf("response name: %s is wrong", respName)
|
||||
}
|
||||
// If respName contains "." , then it must be of the form "pkg.name".
|
||||
// so respRawName='name', respPackage='pkg'
|
||||
respRawName = names[1]
|
||||
respPackage = names[0]
|
||||
}
|
||||
}
|
||||
|
||||
sr, _ := util.GetFirstKV(getAnnotations(m.Annotations, SerializerTags))
|
||||
method := &generator.HttpMethod{
|
||||
Name: util.CamelString(m.GetName()),
|
||||
HTTPMethod: hmethod,
|
||||
RequestTypeName: reqName,
|
||||
RequestTypeRawName: reqRawName,
|
||||
RequestTypePackage: reqPackage,
|
||||
ReturnTypeName: respName,
|
||||
ReturnTypeRawName: respRawName,
|
||||
ReturnTypePackage: respPackage,
|
||||
Path: path[0],
|
||||
Serializer: sr,
|
||||
OutputDir: handlerOutDir,
|
||||
GenHandler: true,
|
||||
// Annotations: m.Annotations,
|
||||
}
|
||||
refs := resolver.ExportReferred(false, true)
|
||||
method.Models = make(map[string]*model.Model, len(refs))
|
||||
for _, ref := range refs {
|
||||
if v, ok := method.Models[ref.Model.PackageName]; ok && (v.Package != ref.Model.Package) {
|
||||
return nil, fmt.Errorf("Package name: %s redeclared in %s and %s ", ref.Model.PackageName, v.Package, ref.Model.Package)
|
||||
}
|
||||
method.Models[ref.Model.PackageName] = ref.Model
|
||||
}
|
||||
models.MergeMap(method.Models)
|
||||
methods = append(methods, method)
|
||||
for idx, anno := range httpAnnos {
|
||||
for i := 0; i < len(anno.path); i++ {
|
||||
if idx == 0 && i == 0 { // idx==0 && i==0 has been added above
|
||||
continue
|
||||
}
|
||||
newMethod, err := newHTTPMethod(s, m, method, i, anno)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
methods = append(methods, newMethod)
|
||||
}
|
||||
}
|
||||
if args.CmdType == meta.CmdClient {
|
||||
clientMethod := &generator.ClientMethod{}
|
||||
clientMethod.HttpMethod = method
|
||||
rt, err := resolver.ResolveIdentifier(m.Arguments[0].GetType().GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = parseAnnotationToClient(clientMethod, m.Arguments[0].GetType(), rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientMethods = append(clientMethods, clientMethod)
|
||||
}
|
||||
}
|
||||
|
||||
service.ClientMethods = clientMethods
|
||||
service.Methods = methods
|
||||
service.Models = models
|
||||
out = append(out, service)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func newHTTPMethod(s *parser.Service, m *parser.Function, method *generator.HttpMethod, i int, anno httpAnnotation) (*generator.HttpMethod, error) {
|
||||
newMethod := *method
|
||||
hmethod, path := anno.method, anno.path
|
||||
if path[i] == "" {
|
||||
return nil, fmt.Errorf("invalid api.%s for %s.%s: %s", hmethod, s.Name, m.Name, path[i])
|
||||
}
|
||||
newMethod.HTTPMethod = hmethod
|
||||
newMethod.Path = path[i]
|
||||
newMethod.GenHandler = false
|
||||
return &newMethod, nil
|
||||
}
|
||||
|
||||
func parseAnnotationToClient(clientMethod *generator.ClientMethod, p *parser.Type, symbol ResolvedSymbol) error {
|
||||
if p == nil {
|
||||
return fmt.Errorf("get type failed for parse annotatoon to client")
|
||||
}
|
||||
typeName := p.GetName()
|
||||
if strings.Contains(typeName, ".") {
|
||||
ret := strings.Split(typeName, ".")
|
||||
typeName = ret[len(ret)-1]
|
||||
}
|
||||
scope, err := golang.BuildScope(thriftgoUtil, symbol.Scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can not build scope for %s", p.Name)
|
||||
}
|
||||
thriftgoUtil.SetRootScope(scope)
|
||||
st := scope.StructLike(typeName)
|
||||
if st == nil {
|
||||
logs.Infof("the type '%s' for method '%s' is base type, so skip parse client info\n")
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
hasBodyAnnotation bool
|
||||
hasFormAnnotation bool
|
||||
)
|
||||
for _, field := range st.Fields() {
|
||||
hasAnnotation := false
|
||||
isStringFieldType := false
|
||||
if field.GetType().String() == "string" {
|
||||
isStringFieldType = true
|
||||
}
|
||||
if anno := getAnnotation(field.Annotations, AnnotationQuery); len(anno) > 0 {
|
||||
hasAnnotation = true
|
||||
query := checkSnakeName(anno[0])
|
||||
clientMethod.QueryParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", query, field.GoName().String())
|
||||
}
|
||||
|
||||
if anno := getAnnotation(field.Annotations, AnnotationPath); len(anno) > 0 {
|
||||
hasAnnotation = true
|
||||
path := checkSnakeName(anno[0])
|
||||
if isStringFieldType {
|
||||
clientMethod.PathParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", path, field.GoName().String())
|
||||
} else {
|
||||
clientMethod.PathParamsCode += fmt.Sprintf("%q: fmt.Sprint(req.Get%s()),\n", path, field.GoName().String())
|
||||
}
|
||||
}
|
||||
|
||||
if anno := getAnnotation(field.Annotations, AnnotationHeader); len(anno) > 0 {
|
||||
hasAnnotation = true
|
||||
header := checkSnakeName(anno[0])
|
||||
if isStringFieldType {
|
||||
clientMethod.HeaderParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", header, field.GoName().String())
|
||||
} else {
|
||||
clientMethod.HeaderParamsCode += fmt.Sprintf("%q: fmt.Sprint(req.Get%s()),\n", header, field.GoName().String())
|
||||
}
|
||||
}
|
||||
|
||||
if anno := getAnnotation(field.Annotations, AnnotationForm); len(anno) > 0 {
|
||||
hasAnnotation = true
|
||||
form := checkSnakeName(anno[0])
|
||||
hasFormAnnotation = true
|
||||
if isStringFieldType {
|
||||
clientMethod.FormValueCode += fmt.Sprintf("%q: req.Get%s(),\n", form, field.GoName().String())
|
||||
} else {
|
||||
clientMethod.FormValueCode += fmt.Sprintf("%q: fmt.Sprint(req.Get%s()),\n", form, field.GoName().String())
|
||||
}
|
||||
}
|
||||
|
||||
if anno := getAnnotation(field.Annotations, AnnotationBody); len(anno) > 0 {
|
||||
hasAnnotation = true
|
||||
hasBodyAnnotation = true
|
||||
}
|
||||
|
||||
if anno := getAnnotation(field.Annotations, AnnotationFileName); len(anno) > 0 {
|
||||
hasAnnotation = true
|
||||
fileName := checkSnakeName(anno[0])
|
||||
hasFormAnnotation = true
|
||||
clientMethod.FormFileCode += fmt.Sprintf("%q: req.Get%s(),\n", fileName, field.GoName().String())
|
||||
}
|
||||
if !hasAnnotation && strings.EqualFold(clientMethod.HTTPMethod, "get") {
|
||||
clientMethod.QueryParamsCode += fmt.Sprintf("%q: req.Get%s(),\n", checkSnakeName(field.GetName()), field.GoName().String())
|
||||
}
|
||||
}
|
||||
clientMethod.BodyParamsCode = meta.SetBodyParam
|
||||
if hasBodyAnnotation && hasFormAnnotation {
|
||||
clientMethod.FormValueCode = ""
|
||||
clientMethod.FormFileCode = ""
|
||||
}
|
||||
if !hasBodyAnnotation && hasFormAnnotation {
|
||||
clientMethod.BodyParamsCode = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type extendServiceList []string
|
||||
|
||||
func (svr extendServiceList) exist(serviceName string) bool {
|
||||
for _, s := range svr {
|
||||
if s == serviceName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getExtendServices(ast *parser.Thrift) (res extendServiceList) {
|
||||
for a := range ast.DepthFirstSearch() {
|
||||
for _, svc := range a.Services {
|
||||
if len(svc.Extends) > 0 {
|
||||
res = append(res, svc.Extends)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getAllExtendFunction(svc *parser.Service, ast *parser.Thrift, resolver *Resolver, args *config.Argument) (res []*parser.Function, err error) {
|
||||
if len(svc.Extends) == 0 {
|
||||
return
|
||||
}
|
||||
parts := semantic.SplitType(svc.Extends)
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
if resolver.mainPkg.Ast.Filename == ast.Filename { // extended current service for master IDL
|
||||
extendSvc, found := ast.GetService(parts[0])
|
||||
if found {
|
||||
funcs := extendSvc.GetFunctions()
|
||||
// determine if it still has extends
|
||||
extendFuncs, err := getAllExtendFunction(extendSvc, ast, resolver, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, append(funcs, extendFuncs...)...)
|
||||
}
|
||||
return res, nil
|
||||
} else { // extended current service for other IDL
|
||||
extendSvc, found := ast.GetService(parts[0])
|
||||
if found {
|
||||
base, err := addResolverDependency(resolver, ast, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
funcs := extendSvc.GetFunctions()
|
||||
for _, f := range funcs {
|
||||
processExtendsType(f, base)
|
||||
}
|
||||
extendFuncs, err := getAllExtendFunction(extendSvc, ast, resolver, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, append(funcs, extendFuncs...)...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
case 2:
|
||||
refAst, found := ast.GetReference(parts[0])
|
||||
base, err := addResolverDependency(resolver, refAst, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ff the service extends from other files, it has to resolve the dependencies of other files as well
|
||||
for _, dep := range refAst.Includes {
|
||||
_, err := addResolverDependency(resolver, dep.Reference, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if found {
|
||||
extendSvc, found := refAst.GetService(parts[1])
|
||||
if found {
|
||||
funcs := extendSvc.GetFunctions()
|
||||
for _, f := range funcs {
|
||||
processExtendsType(f, base)
|
||||
}
|
||||
extendFuncs, err := getAllExtendFunction(extendSvc, refAst, resolver, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, append(funcs, extendFuncs...)...)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func processExtendsType(f *parser.Function, base string) {
|
||||
// the method of other file is extended, and the package of req/resp needs to be changed
|
||||
// ex. base.thrift -> Resp Method(Req){}
|
||||
// base.Resp Method(base.Req){}
|
||||
if len(f.Arguments) > 0 {
|
||||
if f.Arguments[0].Type.Category.IsContainerType() {
|
||||
switch f.Arguments[0].Type.Category {
|
||||
case parser.Category_Set, parser.Category_List:
|
||||
if !strings.Contains(f.Arguments[0].Type.ValueType.Name, ".") && f.Arguments[0].Type.ValueType.Category.IsStruct() {
|
||||
f.Arguments[0].Type.ValueType.Name = base + "." + f.Arguments[0].Type.ValueType.Name
|
||||
}
|
||||
case parser.Category_Map:
|
||||
if !strings.Contains(f.Arguments[0].Type.ValueType.Name, ".") && f.Arguments[0].Type.ValueType.Category.IsStruct() {
|
||||
f.Arguments[0].Type.ValueType.Name = base + "." + f.Arguments[0].Type.ValueType.Name
|
||||
}
|
||||
if !strings.Contains(f.Arguments[0].Type.KeyType.Name, ".") && f.Arguments[0].Type.KeyType.Category.IsStruct() {
|
||||
f.Arguments[0].Type.KeyType.Name = base + "." + f.Arguments[0].Type.KeyType.Name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !strings.Contains(f.Arguments[0].Type.Name, ".") && f.Arguments[0].Type.Category.IsStruct() {
|
||||
f.Arguments[0].Type.Name = base + "." + f.Arguments[0].Type.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if f.FunctionType.Category.IsContainerType() {
|
||||
switch f.FunctionType.Category {
|
||||
case parser.Category_Set, parser.Category_List:
|
||||
if !strings.Contains(f.FunctionType.ValueType.Name, ".") && f.FunctionType.ValueType.Category.IsStruct() {
|
||||
f.FunctionType.ValueType.Name = base + "." + f.FunctionType.ValueType.Name
|
||||
}
|
||||
case parser.Category_Map:
|
||||
if !strings.Contains(f.FunctionType.ValueType.Name, ".") && f.FunctionType.ValueType.Category.IsStruct() {
|
||||
f.FunctionType.ValueType.Name = base + "." + f.FunctionType.ValueType.Name
|
||||
}
|
||||
if !strings.Contains(f.FunctionType.KeyType.Name, ".") && f.FunctionType.KeyType.Category.IsStruct() {
|
||||
f.FunctionType.KeyType.Name = base + "." + f.FunctionType.KeyType.Name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !strings.Contains(f.FunctionType.Name, ".") && f.FunctionType.Category.IsStruct() {
|
||||
f.FunctionType.Name = base + "." + f.FunctionType.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getUniqueResolveDependentName(name string, resolver *Resolver) string {
|
||||
rawName := name
|
||||
for i := 0; i < 10000; i++ {
|
||||
if _, exist := resolver.deps[name]; !exist {
|
||||
return name
|
||||
}
|
||||
name = rawName + fmt.Sprint(i)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func addResolverDependency(resolver *Resolver, ast *parser.Thrift, args *config.Argument) (string, error) {
|
||||
namespace, err := resolver.LoadOne(ast)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
baseName := util.BaseName(ast.Filename, ".thrift")
|
||||
if refPkg, exist := resolver.refPkgs[baseName]; !exist {
|
||||
resolver.deps[baseName] = namespace
|
||||
} else {
|
||||
if ast.Filename != refPkg.Ast.Filename {
|
||||
baseName = getUniqueResolveDependentName(baseName, resolver)
|
||||
resolver.deps[baseName] = namespace
|
||||
}
|
||||
}
|
||||
pkg := getGoPackage(ast, args.OptPkgMap)
|
||||
impt := ast.Filename
|
||||
pkgName := util.SplitPackageName(pkg, "")
|
||||
pkgName, err = util.GetPackageUniqueName(pkgName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ref := &PackageReference{baseName, impt, &model.Model{
|
||||
FilePath: ast.Filename,
|
||||
Package: pkg,
|
||||
PackageName: pkgName,
|
||||
}, ast, false}
|
||||
if _, exist := resolver.refPkgs[baseName]; !exist {
|
||||
resolver.refPkgs[baseName] = ref
|
||||
}
|
||||
|
||||
return baseName, nil
|
||||
}
|
||||
|
||||
/*---------------------------Model-----------------------------*/
|
||||
|
||||
var BaseThrift = parser.Thrift{}
|
||||
|
||||
var baseTypes = map[string]string{
|
||||
"bool": "bool",
|
||||
"byte": "int8",
|
||||
"i8": "int8",
|
||||
"i16": "int16",
|
||||
"i32": "int32",
|
||||
"i64": "int64",
|
||||
"double": "float64",
|
||||
"string": "string",
|
||||
"binary": "[]byte",
|
||||
}
|
||||
|
||||
func switchBaseType(typ *parser.Type) *model.Type {
|
||||
switch typ.Name {
|
||||
case "bool":
|
||||
return model.TypeBool
|
||||
case "byte":
|
||||
return model.TypeByte
|
||||
case "i8":
|
||||
return model.TypeInt8
|
||||
case "i16":
|
||||
return model.TypeInt16
|
||||
case "i32":
|
||||
return model.TypeInt32
|
||||
case "i64":
|
||||
return model.TypeInt64
|
||||
case "int":
|
||||
return model.TypeInt
|
||||
case "double":
|
||||
return model.TypeFloat64
|
||||
case "string":
|
||||
return model.TypeString
|
||||
case "binary":
|
||||
return model.TypeBinary
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBaseType(typ *model.Type, cg model.Category) *model.Type {
|
||||
cyp := *typ
|
||||
cyp.Category = cg
|
||||
return &cyp
|
||||
}
|
||||
|
||||
func newStructType(name string, cg model.Category) *model.Type {
|
||||
return &model.Type{
|
||||
Name: name,
|
||||
Scope: nil,
|
||||
Kind: model.KindStruct,
|
||||
Category: cg,
|
||||
Indirect: false,
|
||||
Extra: nil,
|
||||
HasNew: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newEnumType(name string, cg model.Category) *model.Type {
|
||||
return &model.Type{
|
||||
Name: name,
|
||||
Scope: &model.BaseModel,
|
||||
Kind: model.KindInt,
|
||||
Category: cg,
|
||||
}
|
||||
}
|
||||
|
||||
func newFuncType(name string, cg model.Category) *model.Type {
|
||||
return &model.Type{
|
||||
Name: name,
|
||||
Scope: nil,
|
||||
Kind: model.KindFunc,
|
||||
Category: cg,
|
||||
Indirect: false,
|
||||
Extra: nil,
|
||||
HasNew: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (resolver *Resolver) getFieldType(typ *parser.Type) (*model.Type, error) {
|
||||
if dt, _ := resolver.getBaseType(typ); dt != nil {
|
||||
return dt, nil
|
||||
}
|
||||
sb := resolver.Get(typ.Name)
|
||||
if sb != nil {
|
||||
return sb.Type, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown type: %s", typ.Name)
|
||||
}
|
||||
|
||||
type ResolvedSymbol struct {
|
||||
Base string
|
||||
Src string
|
||||
*Symbol
|
||||
}
|
||||
|
||||
func (rs ResolvedSymbol) Expression() string {
|
||||
base, err := NameStyle.Identify(rs.Base)
|
||||
if err != nil {
|
||||
logs.Warnf("%s naming style for %s failed, fall back to %s, please refer to the variable manually!", NameStyle.Name(), rs.Base, rs.Base)
|
||||
base = rs.Base
|
||||
}
|
||||
// base type no need to do name style
|
||||
if model.IsBaseType(rs.Type) {
|
||||
// base type mapping
|
||||
if val, exist := baseTypes[rs.Base]; exist {
|
||||
base = val
|
||||
}
|
||||
}
|
||||
if rs.Src != "" {
|
||||
if !rs.IsValue && model.IsBaseType(rs.Type) {
|
||||
return base
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", rs.Src, base)
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func astToModel(ast *parser.Thrift, rs *Resolver) (*model.Model, error) {
|
||||
main := rs.mainPkg.Model
|
||||
if main == nil {
|
||||
main = new(model.Model)
|
||||
}
|
||||
|
||||
// typedefs
|
||||
tds := ast.GetTypedefs()
|
||||
typdefs := make([]model.TypeDef, 0, len(tds))
|
||||
for _, t := range tds {
|
||||
td := model.TypeDef{
|
||||
Scope: main,
|
||||
Alias: t.Alias,
|
||||
}
|
||||
if bt, err := rs.ResolveType(t.Type); bt == nil || err != nil {
|
||||
return nil, fmt.Errorf("%s has no type definition, error: %s", t.String(), err)
|
||||
} else {
|
||||
td.Type = bt
|
||||
}
|
||||
typdefs = append(typdefs, td)
|
||||
}
|
||||
main.Typedefs = typdefs
|
||||
|
||||
// constants
|
||||
cts := ast.GetConstants()
|
||||
constants := make([]model.Constant, 0, len(cts))
|
||||
variables := make([]model.Variable, 0, len(cts))
|
||||
for _, c := range cts {
|
||||
ft, err := rs.ResolveType(c.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ft.Name == model.TypeBaseList.Name || ft.Name == model.TypeBaseMap.Name || ft.Name == model.TypeBaseSet.Name {
|
||||
resolveValue, err := rs.ResolveConstantValue(c.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vt := model.Variable{
|
||||
Scope: main,
|
||||
Name: c.Name,
|
||||
Type: ft,
|
||||
Value: resolveValue,
|
||||
}
|
||||
variables = append(variables, vt)
|
||||
} else {
|
||||
resolveValue, err := rs.ResolveConstantValue(c.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct := model.Constant{
|
||||
Scope: main,
|
||||
Name: c.Name,
|
||||
Type: ft,
|
||||
Value: resolveValue,
|
||||
}
|
||||
constants = append(constants, ct)
|
||||
}
|
||||
}
|
||||
main.Constants = constants
|
||||
main.Variables = variables
|
||||
|
||||
// Enums
|
||||
ems := ast.GetEnums()
|
||||
enums := make([]model.Enum, 0, len(ems))
|
||||
for _, e := range ems {
|
||||
em := model.Enum{
|
||||
Scope: main,
|
||||
Name: e.GetName(),
|
||||
GoType: "int64",
|
||||
}
|
||||
vs := make([]model.Constant, 0, len(e.Values))
|
||||
for _, ee := range e.Values {
|
||||
vs = append(vs, model.Constant{
|
||||
Scope: main,
|
||||
Name: ee.Name,
|
||||
Type: model.TypeInt64,
|
||||
Value: model.IntExpression{Src: int(ee.Value)},
|
||||
})
|
||||
}
|
||||
em.Values = vs
|
||||
enums = append(enums, em)
|
||||
}
|
||||
main.Enums = enums
|
||||
|
||||
// Structs
|
||||
sts := make([]*parser.StructLike, 0, len(ast.Structs))
|
||||
sts = append(sts, ast.Structs...)
|
||||
structs := make([]model.Struct, 0, len(ast.Structs)+len(ast.Unions)+len(ast.Exceptions))
|
||||
for _, st := range sts {
|
||||
s := model.Struct{
|
||||
Scope: main,
|
||||
Name: st.GetName(),
|
||||
Category: model.CategoryStruct,
|
||||
LeadingComments: removeCommentsSlash(st.GetReservedComments()),
|
||||
}
|
||||
|
||||
vs := make([]model.Field, 0, len(st.Fields))
|
||||
for _, f := range st.Fields {
|
||||
fieldName, _ := (&styles.ThriftGo{}).Identify(f.Name)
|
||||
isP, err := isPointer(f, rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolveType, err := rs.ResolveType(f.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
field := model.Field{
|
||||
Scope: &s,
|
||||
Name: fieldName,
|
||||
Type: resolveType,
|
||||
// IsSetDefault: f.IsSetDefault(),
|
||||
LeadingComments: removeCommentsSlash(f.GetReservedComments()),
|
||||
IsPointer: isP,
|
||||
}
|
||||
err = injectTags(f, &field, true, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs = append(vs, field)
|
||||
}
|
||||
checkDuplicatedFileName(vs)
|
||||
s.Fields = vs
|
||||
structs = append(structs, s)
|
||||
}
|
||||
|
||||
sts = make([]*parser.StructLike, 0, len(ast.Unions))
|
||||
sts = append(sts, ast.Unions...)
|
||||
for _, st := range sts {
|
||||
s := model.Struct{
|
||||
Scope: main,
|
||||
Name: st.GetName(),
|
||||
Category: model.CategoryUnion,
|
||||
LeadingComments: removeCommentsSlash(st.GetReservedComments()),
|
||||
}
|
||||
vs := make([]model.Field, 0, len(st.Fields))
|
||||
for _, f := range st.Fields {
|
||||
fieldName, _ := (&styles.ThriftGo{}).Identify(f.Name)
|
||||
isP, err := isPointer(f, rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolveType, err := rs.ResolveType(f.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
field := model.Field{
|
||||
Scope: &s,
|
||||
Name: fieldName,
|
||||
Type: resolveType,
|
||||
LeadingComments: removeCommentsSlash(f.GetReservedComments()),
|
||||
IsPointer: isP,
|
||||
}
|
||||
err = injectTags(f, &field, true, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs = append(vs, field)
|
||||
}
|
||||
checkDuplicatedFileName(vs)
|
||||
s.Fields = vs
|
||||
structs = append(structs, s)
|
||||
}
|
||||
|
||||
sts = make([]*parser.StructLike, 0, len(ast.Exceptions))
|
||||
sts = append(sts, ast.Exceptions...)
|
||||
for _, st := range sts {
|
||||
s := model.Struct{
|
||||
Scope: main,
|
||||
Name: st.GetName(),
|
||||
Category: model.CategoryException,
|
||||
LeadingComments: removeCommentsSlash(st.GetReservedComments()),
|
||||
}
|
||||
vs := make([]model.Field, 0, len(st.Fields))
|
||||
for _, f := range st.Fields {
|
||||
fieldName, _ := (&styles.ThriftGo{}).Identify(f.Name)
|
||||
isP, err := isPointer(f, rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolveType, err := rs.ResolveType(f.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
field := model.Field{
|
||||
Scope: &s,
|
||||
Name: fieldName,
|
||||
Type: resolveType,
|
||||
LeadingComments: removeCommentsSlash(f.GetReservedComments()),
|
||||
IsPointer: isP,
|
||||
}
|
||||
err = injectTags(f, &field, true, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs = append(vs, field)
|
||||
}
|
||||
checkDuplicatedFileName(vs)
|
||||
s.Fields = vs
|
||||
structs = append(structs, s)
|
||||
}
|
||||
main.Structs = structs
|
||||
|
||||
// In case of only the service refers another model, therefore scanning service is necessary
|
||||
ss := ast.GetServices()
|
||||
var err error
|
||||
for _, s := range ss {
|
||||
for _, m := range s.GetFunctions() {
|
||||
_, err = rs.ResolveType(m.GetFunctionType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, a := range m.GetArguments() {
|
||||
_, err = rs.ResolveType(a.GetType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return main, nil
|
||||
}
|
||||
|
||||
// removeCommentsSlash can remove double slash for comments with thrift
|
||||
func removeCommentsSlash(comments string) string {
|
||||
if comments == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return comments[2:]
|
||||
}
|
||||
|
||||
func isPointer(f *parser.Field, rs *Resolver) (bool, error) {
|
||||
typ, err := rs.ResolveType(f.GetType())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if typ == nil {
|
||||
return false, fmt.Errorf("can not get type: %s for %s", f.GetType(), f.GetName())
|
||||
}
|
||||
if typ.Kind == model.KindStruct || typ.Kind == model.KindMap || typ.Kind == model.KindSlice {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if f.GetRequiredness().IsOptional() {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getNewFieldName(fieldName string, fieldNameSet map[string]bool) string {
|
||||
if _, ex := fieldNameSet[fieldName]; ex {
|
||||
fieldName = fieldName + "_"
|
||||
return getNewFieldName(fieldName, fieldNameSet)
|
||||
}
|
||||
return fieldName
|
||||
}
|
||||
|
||||
func checkDuplicatedFileName(vs []model.Field) {
|
||||
fieldNameSet := make(map[string]bool)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
if _, ex := fieldNameSet[vs[i].Name]; ex {
|
||||
newName := getNewFieldName(vs[i].Name, fieldNameSet)
|
||||
fieldNameSet[newName] = true
|
||||
vs[i].Name = newName
|
||||
} else {
|
||||
fieldNameSet[vs[i].Name] = true
|
||||
}
|
||||
}
|
||||
}
|
445
thrift/plugin.go
Normal file
445
thrift/plugin.go
Normal file
@ -0,0 +1,445 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package thrift
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/config"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
"github.com/cloudwego/thriftgo/generator/backend"
|
||||
"github.com/cloudwego/thriftgo/generator/golang"
|
||||
"github.com/cloudwego/thriftgo/generator/golang/styles"
|
||||
"github.com/cloudwego/thriftgo/parser"
|
||||
thriftgo_plugin "github.com/cloudwego/thriftgo/plugin"
|
||||
)
|
||||
|
||||
type Plugin struct {
|
||||
req *thriftgo_plugin.Request
|
||||
args *config.Argument
|
||||
logger *logs.StdLogger
|
||||
rmTags []string
|
||||
}
|
||||
|
||||
func (plugin *Plugin) Run() int {
|
||||
plugin.setLogger()
|
||||
args := &config.Argument{}
|
||||
defer func() {
|
||||
if args == nil {
|
||||
return
|
||||
}
|
||||
if args.Verbose {
|
||||
verboseLog := plugin.recvVerboseLogger()
|
||||
if len(verboseLog) != 0 {
|
||||
fmt.Fprintf(os.Stderr, verboseLog)
|
||||
}
|
||||
} else {
|
||||
warning := plugin.recvWarningLogger()
|
||||
if len(warning) != 0 {
|
||||
fmt.Fprintf(os.Stderr, warning)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := plugin.handleRequest()
|
||||
if err != nil {
|
||||
logs.Errorf("handle request failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
|
||||
args, err = plugin.parseArgs()
|
||||
if err != nil {
|
||||
logs.Errorf("parse args failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
plugin.rmTags = args.RmTags
|
||||
if args.CmdType == meta.CmdModel {
|
||||
// check tag options for model mode
|
||||
CheckTagOption(plugin.args)
|
||||
res, err := plugin.GetResponse(nil, args.OutDir)
|
||||
if err != nil {
|
||||
logs.Errorf("get response failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
plugin.response(res)
|
||||
if err != nil {
|
||||
logs.Errorf("response failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
err = plugin.initNameStyle()
|
||||
if err != nil {
|
||||
logs.Errorf("init naming style failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
|
||||
options := CheckTagOption(plugin.args)
|
||||
|
||||
pkgInfo, err := plugin.getPackageInfo()
|
||||
if err != nil {
|
||||
logs.Errorf("get http package info failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
|
||||
customPackageTemplate := args.CustomizePackage
|
||||
pkg, err := args.GetGoPackage()
|
||||
if err != nil {
|
||||
logs.Errorf("get go package failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
handlerDir, err := args.GetHandlerDir()
|
||||
if err != nil {
|
||||
logs.Errorf("get handler dir failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
routerDir, err := args.GetRouterDir()
|
||||
if err != nil {
|
||||
logs.Errorf("get router dir failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
modelDir, err := args.GetModelDir()
|
||||
if err != nil {
|
||||
logs.Errorf("get model dir failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
clientDir, err := args.GetClientDir()
|
||||
if err != nil {
|
||||
logs.Errorf("get client dir failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
sg := generator.HttpPackageGenerator{
|
||||
ConfigPath: customPackageTemplate,
|
||||
HandlerDir: handlerDir,
|
||||
RouterDir: routerDir,
|
||||
ModelDir: modelDir,
|
||||
UseDir: args.Use,
|
||||
ClientDir: clientDir,
|
||||
TemplateGenerator: generator.TemplateGenerator{
|
||||
OutputDir: args.OutDir,
|
||||
Excludes: args.Excludes,
|
||||
},
|
||||
ProjPackage: pkg,
|
||||
Options: options,
|
||||
HandlerByMethod: args.HandlerByMethod,
|
||||
CmdType: args.CmdType,
|
||||
IdlClientDir: util.SubDir(modelDir, pkgInfo.Package),
|
||||
ForceClientDir: args.ForceClientDir,
|
||||
BaseDomain: args.BaseDomain,
|
||||
SnakeStyleMiddleware: args.SnakeStyleMiddleware,
|
||||
}
|
||||
if args.ModelBackend != "" {
|
||||
sg.Backend = meta.Backend(args.ModelBackend)
|
||||
}
|
||||
generator.SetDefaultTemplateConfig()
|
||||
|
||||
err = sg.Generate(pkgInfo)
|
||||
if err != nil {
|
||||
logs.Errorf("generate package failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
if len(args.Use) != 0 {
|
||||
err = sg.Persist()
|
||||
if err != nil {
|
||||
logs.Errorf("persist file failed within '-use' option: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
res := thriftgo_plugin.BuildErrorResponse(errors.New(meta.TheUseOptionMessage).Error())
|
||||
err = plugin.response(res)
|
||||
if err != nil {
|
||||
logs.Errorf("response failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
return 0
|
||||
}
|
||||
files, err := sg.GetFormatAndExcludedFiles()
|
||||
if err != nil {
|
||||
logs.Errorf("format file failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
res, err := plugin.GetResponse(files, sg.OutputDir)
|
||||
if err != nil {
|
||||
logs.Errorf("get response failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
err = plugin.response(res)
|
||||
if err != nil {
|
||||
logs.Errorf("response failed: %s", err.Error())
|
||||
return meta.PluginError
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (plugin *Plugin) setLogger() {
|
||||
plugin.logger = logs.NewStdLogger(logs.LevelInfo)
|
||||
plugin.logger.Defer = true
|
||||
plugin.logger.ErrOnly = true
|
||||
logs.SetLogger(plugin.logger)
|
||||
}
|
||||
|
||||
func (plugin *Plugin) recvWarningLogger() string {
|
||||
warns := plugin.logger.Warn()
|
||||
plugin.logger.Flush()
|
||||
logs.SetLogger(logs.NewStdLogger(logs.LevelInfo))
|
||||
return warns
|
||||
}
|
||||
|
||||
func (plugin *Plugin) recvVerboseLogger() string {
|
||||
info := plugin.logger.Out()
|
||||
warns := plugin.logger.Warn()
|
||||
verboseLog := string(info) + warns
|
||||
plugin.logger.Flush()
|
||||
logs.SetLogger(logs.NewStdLogger(logs.LevelInfo))
|
||||
return verboseLog
|
||||
}
|
||||
|
||||
func (plugin *Plugin) handleRequest() error {
|
||||
data, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read request failed: %s", err.Error())
|
||||
}
|
||||
req, err := thriftgo_plugin.UnmarshalRequest(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal request failed: %s", err.Error())
|
||||
}
|
||||
plugin.req = req
|
||||
// init thriftgo utils
|
||||
thriftgoUtil = golang.NewCodeUtils(backend.DummyLogFunc())
|
||||
thriftgoUtil.HandleOptions(req.GeneratorParameters)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) parseArgs() (*config.Argument, error) {
|
||||
if plugin.req == nil {
|
||||
return nil, fmt.Errorf("request is nil")
|
||||
}
|
||||
args := new(config.Argument)
|
||||
err := args.Unpack(plugin.req.PluginParameters)
|
||||
if err != nil {
|
||||
logs.Errorf("unpack args failed: %s", err.Error())
|
||||
}
|
||||
plugin.args = args
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// initNameStyle initializes the naming style based on the "naming_style" option for thrift.
|
||||
func (plugin *Plugin) initNameStyle() error {
|
||||
if len(plugin.args.ThriftOptions) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, opt := range plugin.args.ThriftOptions {
|
||||
parts := strings.SplitN(opt, "=", 2)
|
||||
if len(parts) == 2 && parts[0] == "naming_style" {
|
||||
NameStyle = styles.NewNamingStyle(parts[1])
|
||||
if NameStyle == nil {
|
||||
return fmt.Errorf(fmt.Sprintf("do not support \"%s\" naming style", parts[1]))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) getPackageInfo() (*generator.HttpPackage, error) {
|
||||
req := plugin.req
|
||||
args := plugin.args
|
||||
|
||||
ast := req.GetAST()
|
||||
if ast == nil {
|
||||
return nil, fmt.Errorf("no ast")
|
||||
}
|
||||
logs.Infof("Processing %s", ast.GetFilename())
|
||||
|
||||
pkgMap := args.OptPkgMap
|
||||
pkg := getGoPackage(ast, pkgMap)
|
||||
main := &model.Model{
|
||||
FilePath: ast.Filename,
|
||||
Package: pkg,
|
||||
PackageName: util.SplitPackageName(pkg, ""),
|
||||
}
|
||||
rs, err := NewResolver(ast, main, pkgMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new thrift resolver failed, err:%v", err)
|
||||
}
|
||||
err = rs.LoadAll(ast)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idlPackage := getGoPackage(ast, pkgMap)
|
||||
if idlPackage == "" {
|
||||
return nil, fmt.Errorf("go package for '%s' is not defined", ast.GetFilename())
|
||||
}
|
||||
|
||||
services, err := astToService(ast, rs, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var models model.Models
|
||||
for _, s := range services {
|
||||
models.MergeArray(s.Models)
|
||||
}
|
||||
|
||||
return &generator.HttpPackage{
|
||||
Services: services,
|
||||
IdlName: ast.GetFilename(),
|
||||
Package: idlPackage,
|
||||
Models: models,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) response(res *thriftgo_plugin.Response) error {
|
||||
data, err := thriftgo_plugin.MarshalResponse(res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal response failed: %s", err.Error())
|
||||
}
|
||||
_, err = os.Stdout.Write(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write response failed: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) InsertTag() ([]*thriftgo_plugin.Generated, error) {
|
||||
var res []*thriftgo_plugin.Generated
|
||||
|
||||
if plugin.args.NoRecurse {
|
||||
outPath := plugin.req.OutputPath
|
||||
packageName := getGoPackage(plugin.req.AST, nil)
|
||||
fileName := util.BaseNameAndTrim(plugin.req.AST.GetFilename()) + ".go"
|
||||
outPath = filepath.Join(outPath, packageName, fileName)
|
||||
for _, st := range plugin.req.AST.Structs {
|
||||
stName := st.GetName()
|
||||
for _, f := range st.Fields {
|
||||
fieldName := f.GetName()
|
||||
tagString, err := getTagString(f, plugin.rmTags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
insertPointer := "struct." + stName + "." + fieldName + "." + "tag"
|
||||
gen := &thriftgo_plugin.Generated{
|
||||
Content: tagString,
|
||||
Name: &outPath,
|
||||
InsertionPoint: &insertPointer,
|
||||
}
|
||||
res = append(res, gen)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
for ast := range plugin.req.AST.DepthFirstSearch() {
|
||||
outPath := plugin.req.OutputPath
|
||||
packageName := getGoPackage(ast, nil)
|
||||
fileName := util.BaseNameAndTrim(ast.GetFilename()) + ".go"
|
||||
outPath = filepath.Join(outPath, packageName, fileName)
|
||||
|
||||
for _, st := range ast.Structs {
|
||||
stName := st.GetName()
|
||||
for _, f := range st.Fields {
|
||||
fieldName := f.GetName()
|
||||
tagString, err := getTagString(f, plugin.rmTags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
insertPointer := "struct." + stName + "." + fieldName + "." + "tag"
|
||||
gen := &thriftgo_plugin.Generated{
|
||||
Content: tagString,
|
||||
Name: &outPath,
|
||||
InsertionPoint: &insertPointer,
|
||||
}
|
||||
res = append(res, gen)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (plugin *Plugin) GetResponse(files []generator.File, outputDir string) (*thriftgo_plugin.Response, error) {
|
||||
var contents []*thriftgo_plugin.Generated
|
||||
for _, file := range files {
|
||||
filePath := filepath.Join(outputDir, file.Path)
|
||||
content := &thriftgo_plugin.Generated{
|
||||
Content: file.Content,
|
||||
Name: &filePath,
|
||||
}
|
||||
contents = append(contents, content)
|
||||
}
|
||||
|
||||
insertTag, err := plugin.InsertTag()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contents = append(contents, insertTag...)
|
||||
|
||||
return &thriftgo_plugin.Response{
|
||||
Contents: contents,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTagString(f *parser.Field, rmTags []string) (string, error) {
|
||||
field := model.Field{}
|
||||
err := injectTags(f, &field, true, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
disableTag := false
|
||||
if v := getAnnotation(f.Annotations, AnnotationNone); len(v) > 0 {
|
||||
if strings.EqualFold(v[0], "true") {
|
||||
disableTag = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, rmTag := range rmTags {
|
||||
for _, t := range field.Tags {
|
||||
if t.IsDefault && strings.EqualFold(t.Key, rmTag) {
|
||||
field.Tags.Remove(t.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tagString string
|
||||
tags := field.Tags
|
||||
for idx, tag := range tags {
|
||||
value := tag.Value
|
||||
if disableTag {
|
||||
value = "-"
|
||||
}
|
||||
if idx == 0 {
|
||||
tagString += " " + tag.Key + ":\"" + value + "\"" + " "
|
||||
} else if idx == len(tags)-1 {
|
||||
tagString += tag.Key + ":\"" + value + "\""
|
||||
} else {
|
||||
tagString += tag.Key + ":\"" + value + "\"" + " "
|
||||
}
|
||||
}
|
||||
|
||||
return tagString, nil
|
||||
}
|
110
thrift/plugin_test.go
Normal file
110
thrift/plugin_test.go
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package thrift
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator"
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/thriftgo/plugin"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("../testdata/request_thrift.out")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := plugin.UnmarshalRequest(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
plu := new(Plugin)
|
||||
plu.setLogger()
|
||||
|
||||
plu.req = req
|
||||
|
||||
_, err = plu.parseArgs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
options := CheckTagOption(plu.args)
|
||||
|
||||
pkgInfo, err := plu.getPackageInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
args := plu.args
|
||||
customPackageTemplate := args.CustomizePackage
|
||||
pkg, err := args.GetGoPackage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
handlerDir, err := args.GetHandlerDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
routerDir, err := args.GetRouterDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
modelDir, err := args.GetModelDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clientDir, err := args.GetClientDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sg := generator.HttpPackageGenerator{
|
||||
ConfigPath: customPackageTemplate,
|
||||
HandlerDir: handlerDir,
|
||||
RouterDir: routerDir,
|
||||
ModelDir: modelDir,
|
||||
ClientDir: clientDir,
|
||||
TemplateGenerator: generator.TemplateGenerator{
|
||||
OutputDir: args.OutDir,
|
||||
},
|
||||
ProjPackage: pkg,
|
||||
Options: options,
|
||||
}
|
||||
if args.ModelBackend != "" {
|
||||
sg.Backend = meta.Backend(args.ModelBackend)
|
||||
}
|
||||
|
||||
err = sg.Generate(pkgInfo)
|
||||
if err != nil {
|
||||
t.Fatalf("generate package failed: %v", err)
|
||||
}
|
||||
files, err := sg.GetFormatAndExcludedFiles()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := plu.GetResponse(files, sg.OutputDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
plu.response(res)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
592
thrift/resolver.go
Normal file
592
thrift/resolver.go
Normal file
@ -0,0 +1,592 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package thrift
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/thriftgo/parser"
|
||||
)
|
||||
|
||||
var (
|
||||
ConstTrue = Symbol{
|
||||
IsValue: true,
|
||||
Type: model.TypeBool,
|
||||
Value: true,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ConstFalse = Symbol{
|
||||
IsValue: true,
|
||||
Type: model.TypeBool,
|
||||
Value: false,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ConstEmptyString = Symbol{
|
||||
IsValue: true,
|
||||
Type: model.TypeString,
|
||||
Value: "",
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
)
|
||||
|
||||
type PackageReference struct {
|
||||
IncludeBase string
|
||||
IncludePath string
|
||||
Model *model.Model
|
||||
Ast *parser.Thrift
|
||||
Referred bool
|
||||
}
|
||||
|
||||
func getReferPkgMap(pkgMap map[string]string, incs []*parser.Include, mainModel *model.Model) (map[string]*PackageReference, error) {
|
||||
var err error
|
||||
out := make(map[string]*PackageReference, len(pkgMap))
|
||||
pkgAliasMap := make(map[string]string, len(incs))
|
||||
// bugfix: add main package to avoid namespace conflict
|
||||
mainPkg := mainModel.Package
|
||||
mainPkgName := mainModel.PackageName
|
||||
mainPkgName, err = util.GetPackageUniqueName(mainPkgName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgAliasMap[mainPkg] = mainPkgName
|
||||
for _, inc := range incs {
|
||||
pkg := getGoPackage(inc.Reference, pkgMap)
|
||||
impt := inc.GetPath()
|
||||
base := util.BaseNameAndTrim(impt)
|
||||
pkgName := util.SplitPackageName(pkg, "")
|
||||
if pn, exist := pkgAliasMap[pkg]; exist {
|
||||
pkgName = pn
|
||||
} else {
|
||||
pkgName, err = util.GetPackageUniqueName(pkgName)
|
||||
pkgAliasMap[pkg] = pkgName
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get package unique name failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
out[base] = &PackageReference{base, impt, &model.Model{
|
||||
FilePath: inc.Path,
|
||||
Package: pkg,
|
||||
PackageName: pkgName,
|
||||
}, inc.Reference, false}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type Symbol struct {
|
||||
IsValue bool
|
||||
Type *model.Type
|
||||
Value interface{}
|
||||
Scope *parser.Thrift
|
||||
}
|
||||
|
||||
type NameSpace map[string]*Symbol
|
||||
|
||||
type Resolver struct {
|
||||
// idl symbols
|
||||
root NameSpace
|
||||
deps map[string]NameSpace
|
||||
|
||||
// exported models
|
||||
mainPkg PackageReference
|
||||
refPkgs map[string]*PackageReference
|
||||
}
|
||||
|
||||
func NewResolver(ast *parser.Thrift, model *model.Model, pkgMap map[string]string) (*Resolver, error) {
|
||||
pm, err := getReferPkgMap(pkgMap, ast.GetIncludes(), model)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get package map failed, err: %v", err)
|
||||
}
|
||||
file := ast.GetFilename()
|
||||
return &Resolver{
|
||||
root: make(NameSpace),
|
||||
deps: make(map[string]NameSpace),
|
||||
refPkgs: pm,
|
||||
mainPkg: PackageReference{
|
||||
IncludeBase: util.BaseNameAndTrim(file),
|
||||
IncludePath: ast.GetFilename(),
|
||||
Model: model,
|
||||
Ast: ast,
|
||||
Referred: false,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) GetRefModel(includeBase string) (*model.Model, error) {
|
||||
if includeBase == "" {
|
||||
return resolver.mainPkg.Model, nil
|
||||
}
|
||||
ref, ok := resolver.refPkgs[includeBase]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not found include %s", includeBase)
|
||||
}
|
||||
return ref.Model, nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) getBaseType(typ *parser.Type) (*model.Type, bool) {
|
||||
tt := switchBaseType(typ)
|
||||
if tt != nil {
|
||||
return tt, true
|
||||
}
|
||||
if typ.Name == "map" {
|
||||
t := *model.TypeBaseMap
|
||||
return &t, false
|
||||
}
|
||||
if typ.Name == "list" {
|
||||
t := *model.TypeBaseList
|
||||
return &t, false
|
||||
}
|
||||
if typ.Name == "set" {
|
||||
t := *model.TypeBaseList
|
||||
return &t, false
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (resolver *Resolver) ResolveType(typ *parser.Type) (*model.Type, error) {
|
||||
bt, base := resolver.getBaseType(typ)
|
||||
if bt != nil {
|
||||
if base {
|
||||
return bt, nil
|
||||
} else {
|
||||
if typ.Name == model.TypeBaseMap.Name {
|
||||
resolveKey, err := resolver.ResolveType(typ.KeyType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolveValue, err := resolver.ResolveType(typ.ValueType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bt.Extra = append(bt.Extra, resolveKey, resolveValue)
|
||||
} else if typ.Name == model.TypeBaseList.Name || typ.Name == model.TypeBaseSet.Name {
|
||||
resolveValue, err := resolver.ResolveType(typ.ValueType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bt.Extra = append(bt.Extra, resolveValue)
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid DefinitionType(%+v)", bt)
|
||||
}
|
||||
return bt, nil
|
||||
}
|
||||
}
|
||||
|
||||
id := typ.GetName()
|
||||
rs, err := resolver.ResolveIdentifier(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sb := rs.Symbol
|
||||
if sb == nil {
|
||||
return nil, fmt.Errorf("not found identifier %s", id)
|
||||
}
|
||||
return sb.Type, nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) ResolveConstantValue(constant *parser.ConstValue) (model.Literal, error) {
|
||||
switch constant.Type {
|
||||
case parser.ConstType_ConstInt:
|
||||
return model.IntExpression{Src: int(constant.TypedValue.GetInt())}, nil
|
||||
case parser.ConstType_ConstDouble:
|
||||
return model.DoubleExpression{Src: constant.TypedValue.GetDouble()}, nil
|
||||
case parser.ConstType_ConstLiteral:
|
||||
return model.StringExpression{Src: constant.TypedValue.GetLiteral()}, nil
|
||||
case parser.ConstType_ConstList:
|
||||
eleType, err := switchConstantType(constant.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := model.ListExpression{
|
||||
ElementType: eleType,
|
||||
}
|
||||
for _, i := range constant.TypedValue.List {
|
||||
elem, err := resolver.ResolveConstantValue(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Elements = append(ret.Elements, elem)
|
||||
}
|
||||
return ret, nil
|
||||
case parser.ConstType_ConstMap:
|
||||
keyType, err := switchConstantType(constant.TypedValue.Map[0].Key.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valueType, err := switchConstantType(constant.TypedValue.Map[0].Value.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := model.MapExpression{
|
||||
KeyType: keyType,
|
||||
ValueType: valueType,
|
||||
Elements: make(map[string]model.Literal, len(constant.TypedValue.Map)),
|
||||
}
|
||||
for _, v := range constant.TypedValue.Map {
|
||||
value, err := resolver.ResolveConstantValue(v.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Elements[v.Key.String()] = value
|
||||
}
|
||||
return ret, nil
|
||||
case parser.ConstType_ConstIdentifier:
|
||||
return resolver.ResolveIdentifier(*constant.TypedValue.Identifier)
|
||||
}
|
||||
return model.StringExpression{Src: constant.String()}, nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) ResolveIdentifier(id string) (ret ResolvedSymbol, err error) {
|
||||
sb := resolver.Get(id)
|
||||
if sb == nil {
|
||||
return ResolvedSymbol{}, fmt.Errorf("identifier '%s' not found", id)
|
||||
}
|
||||
ret.Symbol = sb
|
||||
ret.Base = id
|
||||
if sb.Scope == &BaseThrift {
|
||||
return
|
||||
}
|
||||
if sb.Scope == resolver.mainPkg.Ast {
|
||||
resolver.mainPkg.Referred = true
|
||||
ret.Src = resolver.mainPkg.Model.PackageName
|
||||
return
|
||||
}
|
||||
|
||||
sp := strings.SplitN(id, ".", 2)
|
||||
if ref, ok := resolver.refPkgs[sp[0]]; ok {
|
||||
ref.Referred = true
|
||||
ret.Base = sp[1]
|
||||
ret.Src = ref.Model.PackageName
|
||||
ret.Type.Scope = ref.Model
|
||||
} else {
|
||||
return ResolvedSymbol{}, fmt.Errorf("can't resolve identifier '%s'", id)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (resolver *Resolver) ResolveTypeName(typ *parser.Type) (string, error) {
|
||||
if typ.GetIsTypedef() {
|
||||
rt, err := resolver.ResolveIdentifier(typ.GetName())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return rt.Expression(), nil
|
||||
}
|
||||
switch typ.GetCategory() {
|
||||
case parser.Category_Map:
|
||||
keyType, err := resolver.ResolveTypeName(typ.GetKeyType())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if typ.GetKeyType().GetCategory().IsStruct() {
|
||||
keyType = "*" + keyType
|
||||
}
|
||||
valueType, err := resolver.ResolveTypeName(typ.GetValueType())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if typ.GetValueType().GetCategory().IsStruct() {
|
||||
valueType = "*" + valueType
|
||||
}
|
||||
return fmt.Sprintf("map[%s]%s", keyType, valueType), nil
|
||||
case parser.Category_List, parser.Category_Set:
|
||||
// list/set -> []element for thriftgo
|
||||
// valueType refers the element type for list/set
|
||||
elemType, err := resolver.ResolveTypeName(typ.GetValueType())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if typ.GetValueType().GetCategory().IsStruct() {
|
||||
elemType = "*" + elemType
|
||||
}
|
||||
return fmt.Sprintf("[]%s", elemType), err
|
||||
}
|
||||
rt, err := resolver.ResolveIdentifier(typ.GetName())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return rt.Expression(), nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) Get(name string) *Symbol {
|
||||
s, ok := resolver.root[name]
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
if strings.Contains(name, ".") {
|
||||
sp := strings.SplitN(name, ".", 2)
|
||||
if ref, ok := resolver.deps[sp[0]]; ok {
|
||||
if ss, ok := ref[sp[1]]; ok {
|
||||
return ss
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (resolver *Resolver) ExportReferred(all, needMain bool) (ret []*PackageReference) {
|
||||
for _, v := range resolver.refPkgs {
|
||||
if all {
|
||||
ret = append(ret, v)
|
||||
v.Referred = false
|
||||
} else if v.Referred {
|
||||
ret = append(ret, v)
|
||||
v.Referred = false
|
||||
}
|
||||
}
|
||||
if needMain && (all || resolver.mainPkg.Referred) {
|
||||
ret = append(ret, &resolver.mainPkg)
|
||||
}
|
||||
resolver.mainPkg.Referred = false
|
||||
return
|
||||
}
|
||||
|
||||
func (resolver *Resolver) LoadAll(ast *parser.Thrift) error {
|
||||
var err error
|
||||
resolver.root, err = resolver.LoadOne(ast)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load root package: %s", err)
|
||||
}
|
||||
|
||||
includes := ast.GetIncludes()
|
||||
astMap := make(map[string]NameSpace, len(includes))
|
||||
for _, dep := range includes {
|
||||
bName := util.BaseName(dep.Path, ".thrift")
|
||||
astMap[bName], err = resolver.LoadOne(dep.Reference)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load idl %s: %s", dep.Path, err)
|
||||
}
|
||||
}
|
||||
resolver.deps = astMap
|
||||
for _, td := range ast.Typedefs {
|
||||
name := td.GetAlias()
|
||||
if _, ex := resolver.root[name]; ex {
|
||||
if resolver.root[name].Type != nil {
|
||||
typ := newTypedefType(resolver.root[name].Type, name)
|
||||
resolver.root[name].Type = &typ
|
||||
continue
|
||||
}
|
||||
}
|
||||
sym := resolver.Get(td.Type.GetName())
|
||||
typ := newTypedefType(sym.Type, name)
|
||||
resolver.root[name].Type = &typ
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadBaseIdentifier() NameSpace {
|
||||
ret := make(NameSpace, 16)
|
||||
|
||||
ret["true"] = &ConstTrue
|
||||
ret["false"] = &ConstFalse
|
||||
ret[`""`] = &ConstEmptyString
|
||||
ret["bool"] = &Symbol{
|
||||
Type: model.TypeBool,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["byte"] = &Symbol{
|
||||
Type: model.TypeByte,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["i8"] = &Symbol{
|
||||
Type: model.TypeInt8,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["i16"] = &Symbol{
|
||||
Type: model.TypeInt16,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["i32"] = &Symbol{
|
||||
Type: model.TypeInt32,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["i64"] = &Symbol{
|
||||
Type: model.TypeInt64,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["int"] = &Symbol{
|
||||
Type: model.TypeInt,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["double"] = &Symbol{
|
||||
Type: model.TypeFloat64,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["string"] = &Symbol{
|
||||
Type: model.TypeString,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["binary"] = &Symbol{
|
||||
Type: model.TypeBinary,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["list"] = &Symbol{
|
||||
Type: model.TypeBaseList,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["set"] = &Symbol{
|
||||
Type: model.TypeBaseSet,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
ret["map"] = &Symbol{
|
||||
Type: model.TypeBaseMap,
|
||||
Scope: &BaseThrift,
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (resolver *Resolver) LoadOne(ast *parser.Thrift) (NameSpace, error) {
|
||||
ret := LoadBaseIdentifier()
|
||||
|
||||
for _, e := range ast.Enums {
|
||||
prefix := e.GetName()
|
||||
ret[prefix] = &Symbol{
|
||||
IsValue: false,
|
||||
Value: e,
|
||||
Scope: ast,
|
||||
Type: newEnumType(prefix, model.CategoryEnum),
|
||||
}
|
||||
for _, ee := range e.Values {
|
||||
name := prefix + "." + ee.GetName()
|
||||
if _, exist := ret[name]; exist {
|
||||
return nil, fmt.Errorf("duplicated identifier '%s' in %s", name, ast.Filename)
|
||||
}
|
||||
|
||||
ret[name] = &Symbol{
|
||||
IsValue: true,
|
||||
Value: ee,
|
||||
Scope: ast,
|
||||
Type: newBaseType(model.TypeInt, model.CategoryEnum),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range ast.Constants {
|
||||
name := e.GetName()
|
||||
if _, exist := ret[name]; exist {
|
||||
return nil, fmt.Errorf("duplicated identifier '%s' in %s", name, ast.Filename)
|
||||
}
|
||||
gt, _ := resolver.getBaseType(e.Type)
|
||||
ret[name] = &Symbol{
|
||||
IsValue: true,
|
||||
Value: e,
|
||||
Scope: ast,
|
||||
Type: gt,
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range ast.Structs {
|
||||
name := e.GetName()
|
||||
if _, exist := ret[name]; exist {
|
||||
return nil, fmt.Errorf("duplicated identifier '%s' in %s", name, ast.Filename)
|
||||
}
|
||||
ret[name] = &Symbol{
|
||||
IsValue: false,
|
||||
Value: e,
|
||||
Scope: ast,
|
||||
Type: newStructType(name, model.CategoryStruct),
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range ast.Unions {
|
||||
name := e.GetName()
|
||||
if _, exist := ret[name]; exist {
|
||||
return nil, fmt.Errorf("duplicated identifier '%s' in %s", name, ast.Filename)
|
||||
}
|
||||
ret[name] = &Symbol{
|
||||
IsValue: false,
|
||||
Value: e,
|
||||
Scope: ast,
|
||||
Type: newStructType(name, model.CategoryStruct),
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range ast.Exceptions {
|
||||
name := e.GetName()
|
||||
if _, exist := ret[name]; exist {
|
||||
return nil, fmt.Errorf("duplicated identifier '%s' in %s", name, ast.Filename)
|
||||
}
|
||||
ret[name] = &Symbol{
|
||||
IsValue: false,
|
||||
Value: e,
|
||||
Scope: ast,
|
||||
Type: newStructType(name, model.CategoryStruct),
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range ast.Services {
|
||||
name := e.GetName()
|
||||
if _, exist := ret[name]; exist {
|
||||
return nil, fmt.Errorf("duplicated identifier '%s' in %s", name, ast.Filename)
|
||||
}
|
||||
ret[name] = &Symbol{
|
||||
IsValue: false,
|
||||
Value: e,
|
||||
Scope: ast,
|
||||
Type: newFuncType(name, model.CategoryService),
|
||||
}
|
||||
}
|
||||
|
||||
for _, td := range ast.Typedefs {
|
||||
name := td.GetAlias()
|
||||
if _, exist := ret[name]; exist {
|
||||
return nil, fmt.Errorf("duplicated identifier '%s' in %s", name, ast.Filename)
|
||||
}
|
||||
gt, _ := resolver.getBaseType(td.Type)
|
||||
if gt == nil {
|
||||
sym := ret[td.Type.Name]
|
||||
if sym != nil {
|
||||
gt = sym.Type
|
||||
}
|
||||
}
|
||||
ret[name] = &Symbol{
|
||||
IsValue: false,
|
||||
Value: td,
|
||||
Scope: ast,
|
||||
Type: gt,
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func switchConstantType(constant parser.ConstType) (*model.Type, error) {
|
||||
switch constant {
|
||||
case parser.ConstType_ConstInt:
|
||||
return model.TypeInt, nil
|
||||
case parser.ConstType_ConstDouble:
|
||||
return model.TypeFloat64, nil
|
||||
case parser.ConstType_ConstLiteral:
|
||||
return model.TypeString, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown constant type %d", constant)
|
||||
}
|
||||
}
|
||||
|
||||
func newTypedefType(t *model.Type, name string) model.Type {
|
||||
tmp := t
|
||||
typ := *tmp
|
||||
typ.Name = name
|
||||
typ.Category = model.CategoryTypedef
|
||||
return typ
|
||||
}
|
129
thrift/tag_test.go
Normal file
129
thrift/tag_test.go
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package thrift
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/config"
|
||||
"github.com/cloudwego/thriftgo/plugin"
|
||||
)
|
||||
|
||||
func TestInsertTag(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("./test_data/thrift_tag_test.out")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req, err := plugin.UnmarshalRequest(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
plu := new(Plugin)
|
||||
plu.req = req
|
||||
plu.args = new(config.Argument)
|
||||
|
||||
type TagStruct struct {
|
||||
Annotation string
|
||||
GeneratedTag string
|
||||
ActualTag string
|
||||
}
|
||||
|
||||
tagList := []TagStruct{
|
||||
{
|
||||
Annotation: "query",
|
||||
GeneratedTag: "json:\"DefaultQueryTag\" query:\"query\"",
|
||||
},
|
||||
{
|
||||
Annotation: "raw_body",
|
||||
GeneratedTag: "json:\"RawBodyTag\" raw_body:\"raw_body\"",
|
||||
},
|
||||
{
|
||||
Annotation: "path",
|
||||
GeneratedTag: "json:\"PathTag\" path:\"path\"",
|
||||
},
|
||||
{
|
||||
Annotation: "form",
|
||||
GeneratedTag: "form:\"form\" json:\"FormTag\"",
|
||||
},
|
||||
{
|
||||
Annotation: "cookie",
|
||||
GeneratedTag: "cookie:\"cookie\" json:\"CookieTag\"",
|
||||
},
|
||||
{
|
||||
Annotation: "header",
|
||||
GeneratedTag: "header:\"header\" json:\"HeaderTag\"",
|
||||
},
|
||||
{
|
||||
Annotation: "body",
|
||||
GeneratedTag: "form:\"body\" json:\"body\"",
|
||||
},
|
||||
{
|
||||
Annotation: "go.tag",
|
||||
GeneratedTag: "",
|
||||
},
|
||||
{
|
||||
Annotation: "vd",
|
||||
GeneratedTag: "form:\"VdTag\" json:\"VdTag\" query:\"VdTag\" vd:\"$!='?'\"",
|
||||
},
|
||||
{
|
||||
Annotation: "non",
|
||||
GeneratedTag: "form:\"DefaultTag\" json:\"DefaultTag\" query:\"DefaultTag\"",
|
||||
},
|
||||
{
|
||||
Annotation: "query required",
|
||||
GeneratedTag: "json:\"ReqQuery,required\" query:\"query,required\"",
|
||||
},
|
||||
{
|
||||
Annotation: "query optional",
|
||||
GeneratedTag: "json:\"OptQuery,omitempty\" query:\"query\"",
|
||||
},
|
||||
{
|
||||
Annotation: "body required",
|
||||
GeneratedTag: "form:\"body,required\" json:\"body,required\"",
|
||||
},
|
||||
{
|
||||
Annotation: "body optional",
|
||||
GeneratedTag: "form:\"body\" json:\"body,omitempty\"",
|
||||
},
|
||||
{
|
||||
Annotation: "go.tag required",
|
||||
GeneratedTag: "form:\"ReqGoTag,required\" query:\"ReqGoTag,required\"",
|
||||
},
|
||||
{
|
||||
Annotation: "go.tag optional",
|
||||
GeneratedTag: "form:\"OptGoTag\" query:\"OptGoTag\"",
|
||||
},
|
||||
{
|
||||
Annotation: "go tag cover query",
|
||||
GeneratedTag: "form:\"QueryGoTag,required\" json:\"QueryGoTag,required\"",
|
||||
},
|
||||
}
|
||||
|
||||
tags, err := plu.InsertTag()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, tag := range tags {
|
||||
tagList[i].ActualTag = tag.Content
|
||||
if !strings.Contains(tagList[i].ActualTag, tagList[i].GeneratedTag) {
|
||||
t.Fatalf("expected tag: '%s', but autual tag: '%s'", tagList[i].GeneratedTag, tagList[i].ActualTag)
|
||||
}
|
||||
}
|
||||
}
|
370
thrift/tags.go
Normal file
370
thrift/tags.go
Normal file
@ -0,0 +1,370 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package thrift
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/config"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator"
|
||||
"github.com/cloudwego/hertz/cmd/hz/generator/model"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util"
|
||||
"github.com/cloudwego/thriftgo/parser"
|
||||
)
|
||||
|
||||
const (
|
||||
AnnotationQuery = "api.query"
|
||||
AnnotationForm = "api.form"
|
||||
AnnotationPath = "api.path"
|
||||
AnnotationHeader = "api.header"
|
||||
AnnotationCookie = "api.cookie"
|
||||
AnnotationBody = "api.body"
|
||||
AnnotationRawBody = "api.raw_body"
|
||||
AnnotationJsConv = "api.js_conv"
|
||||
AnnotationNone = "api.none"
|
||||
AnnotationFileName = "api.file_name"
|
||||
|
||||
AnnotationValidator = "api.vd"
|
||||
|
||||
AnnotationGoTag = "go.tag"
|
||||
)
|
||||
|
||||
const (
|
||||
ApiGet = "api.get"
|
||||
ApiPost = "api.post"
|
||||
ApiPut = "api.put"
|
||||
ApiPatch = "api.patch"
|
||||
ApiDelete = "api.delete"
|
||||
ApiOptions = "api.options"
|
||||
ApiHEAD = "api.head"
|
||||
ApiAny = "api.any"
|
||||
ApiPath = "api.path"
|
||||
ApiSerializer = "api.serializer"
|
||||
ApiGenPath = "api.handler_path"
|
||||
)
|
||||
|
||||
const (
|
||||
ApiBaseDomain = "api.base_domain"
|
||||
ApiServiceGroup = "api.service_group"
|
||||
ApiServiceGenDir = "api.service_gen_dir" // handler_dir for handler_by_service
|
||||
ApiServicePath = "api.service_path" // declare the path to the service's handler according to this annotation for handler_by_method
|
||||
)
|
||||
|
||||
var (
|
||||
HttpMethodAnnotations = map[string]string{
|
||||
ApiGet: "GET",
|
||||
ApiPost: "POST",
|
||||
ApiPut: "PUT",
|
||||
ApiPatch: "PATCH",
|
||||
ApiDelete: "DELETE",
|
||||
ApiOptions: "OPTIONS",
|
||||
ApiHEAD: "HEAD",
|
||||
ApiAny: "ANY",
|
||||
}
|
||||
|
||||
HttpMethodOptionAnnotations = map[string]string{
|
||||
ApiGenPath: "handler_path",
|
||||
}
|
||||
|
||||
BindingTags = map[string]string{
|
||||
AnnotationPath: "path",
|
||||
AnnotationQuery: "query",
|
||||
AnnotationHeader: "header",
|
||||
AnnotationCookie: "cookie",
|
||||
AnnotationBody: "json",
|
||||
AnnotationForm: "form",
|
||||
AnnotationRawBody: "raw_body",
|
||||
}
|
||||
|
||||
SerializerTags = map[string]string{
|
||||
ApiSerializer: "serializer",
|
||||
}
|
||||
|
||||
ValidatorTags = map[string]string{AnnotationValidator: "vd"}
|
||||
)
|
||||
|
||||
var (
|
||||
jsonSnakeName = false
|
||||
unsetOmitempty = false
|
||||
)
|
||||
|
||||
func CheckTagOption(args *config.Argument) []generator.Option {
|
||||
var ret []generator.Option
|
||||
if args == nil {
|
||||
return ret
|
||||
}
|
||||
if args.SnakeName {
|
||||
jsonSnakeName = true
|
||||
}
|
||||
if args.UnsetOmitempty {
|
||||
unsetOmitempty = true
|
||||
}
|
||||
if args.JSONEnumStr {
|
||||
ret = append(ret, generator.OptionMarshalEnumToText)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func checkSnakeName(name string) string {
|
||||
if jsonSnakeName {
|
||||
name = util.ToSnakeCase(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func getAnnotation(input parser.Annotations, target string) []string {
|
||||
if len(input) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, anno := range input {
|
||||
if strings.ToLower(anno.Key) == target {
|
||||
return anno.Values
|
||||
}
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
type httpAnnotation struct {
|
||||
method string
|
||||
path []string
|
||||
}
|
||||
|
||||
type httpAnnotations []httpAnnotation
|
||||
|
||||
func (s httpAnnotations) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s httpAnnotations) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s httpAnnotations) Less(i, j int) bool {
|
||||
return s[i].method < s[j].method
|
||||
}
|
||||
|
||||
func getAnnotations(input parser.Annotations, targets map[string]string) map[string][]string {
|
||||
if len(input) == 0 || len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := map[string][]string{}
|
||||
for k, t := range targets {
|
||||
var ret *parser.Annotation
|
||||
for _, anno := range input {
|
||||
if strings.ToLower(anno.Key) == k {
|
||||
ret = anno
|
||||
break
|
||||
}
|
||||
}
|
||||
if ret == nil {
|
||||
continue
|
||||
}
|
||||
out[t] = ret.Values
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func defaultBindingTags(f *parser.Field) []model.Tag {
|
||||
out := make([]model.Tag, 3)
|
||||
bindingTags := []string{
|
||||
AnnotationQuery,
|
||||
AnnotationForm,
|
||||
AnnotationPath,
|
||||
AnnotationHeader,
|
||||
AnnotationCookie,
|
||||
AnnotationBody,
|
||||
AnnotationRawBody,
|
||||
}
|
||||
|
||||
for _, tag := range bindingTags {
|
||||
if v := getAnnotation(f.Annotations, tag); len(v) > 0 {
|
||||
out[0] = jsonTag(f)
|
||||
return out[:1]
|
||||
}
|
||||
}
|
||||
|
||||
if v := getAnnotation(f.Annotations, AnnotationBody); len(v) > 0 {
|
||||
val := getJsonValue(f, v[0])
|
||||
out[0] = tag("json", val)
|
||||
} else {
|
||||
t := jsonTag(f)
|
||||
t.IsDefault = true
|
||||
out[0] = t
|
||||
}
|
||||
if v := getAnnotation(f.Annotations, AnnotationQuery); len(v) > 0 {
|
||||
val := checkRequire(f, v[0])
|
||||
out[1] = tag(BindingTags[AnnotationQuery], val)
|
||||
} else {
|
||||
val := checkRequire(f, checkSnakeName(f.Name))
|
||||
t := tag(BindingTags[AnnotationQuery], val)
|
||||
t.IsDefault = true
|
||||
out[1] = t
|
||||
}
|
||||
if v := getAnnotation(f.Annotations, AnnotationForm); len(v) > 0 {
|
||||
val := checkRequire(f, v[0])
|
||||
out[2] = tag(BindingTags[AnnotationForm], val)
|
||||
} else {
|
||||
val := checkRequire(f, checkSnakeName(f.Name))
|
||||
t := tag(BindingTags[AnnotationForm], val)
|
||||
t.IsDefault = true
|
||||
out[2] = t
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func jsonTag(f *parser.Field) (ret model.Tag) {
|
||||
ret.Key = "json"
|
||||
ret.Value = checkSnakeName(f.Name)
|
||||
|
||||
if v := getAnnotation(f.Annotations, AnnotationJsConv); len(v) > 0 {
|
||||
ret.Value += ",string"
|
||||
}
|
||||
if !unsetOmitempty && f.Requiredness == parser.FieldType_Optional {
|
||||
ret.Value += ",omitempty"
|
||||
} else if f.Requiredness == parser.FieldType_Required {
|
||||
ret.Value += ",required"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tag(k, v string) model.Tag {
|
||||
return model.Tag{
|
||||
Key: k,
|
||||
Value: v,
|
||||
}
|
||||
}
|
||||
|
||||
func annotationToTags(as parser.Annotations, targets map[string]string) (tags []model.Tag) {
|
||||
rets := getAnnotations(as, targets)
|
||||
for k, v := range rets {
|
||||
for _, vv := range v {
|
||||
tags = append(tags, model.Tag{
|
||||
Key: k,
|
||||
Value: vv,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func injectTags(f *parser.Field, gf *model.Field, needDefault, needGoTag bool) error {
|
||||
as := f.Annotations
|
||||
if as == nil {
|
||||
as = parser.Annotations{}
|
||||
}
|
||||
tags := gf.Tags
|
||||
if tags == nil {
|
||||
tags = make([]model.Tag, 0, len(as))
|
||||
}
|
||||
|
||||
if needDefault {
|
||||
tags = append(tags, defaultBindingTags(f)...)
|
||||
}
|
||||
|
||||
// binding tags
|
||||
bts := annotationToTags(as, BindingTags)
|
||||
for _, t := range bts {
|
||||
key := t.Key
|
||||
tags.Remove(key)
|
||||
if key == "json" {
|
||||
formVal := t.Value
|
||||
t.Value = getJsonValue(f, t.Value)
|
||||
formVal = checkRequire(f, formVal)
|
||||
tags = append(tags, tag("form", formVal))
|
||||
} else {
|
||||
t.Value = checkRequire(f, t.Value)
|
||||
}
|
||||
tags = append(tags, t)
|
||||
}
|
||||
|
||||
// validator tags
|
||||
tags = append(tags, annotationToTags(as, ValidatorTags)...)
|
||||
|
||||
// the tag defined by gotag with higher priority
|
||||
checkGoTag(as, &tags)
|
||||
|
||||
// go.tags for compiler mode
|
||||
if needGoTag {
|
||||
rets := getAnnotation(as, AnnotationGoTag)
|
||||
for _, v := range rets {
|
||||
gts := util.SplitGoTags(v)
|
||||
for _, gt := range gts {
|
||||
sp := strings.SplitN(gt, ":", 2)
|
||||
if len(sp) != 2 {
|
||||
return fmt.Errorf("invalid go tag: %s", v)
|
||||
}
|
||||
vv, err := strconv.Unquote(sp[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid go.tag value: %s, err: %v", sp[1], err.Error())
|
||||
}
|
||||
key := sp[0]
|
||||
tags.Remove(key)
|
||||
tags = append(tags, model.Tag{
|
||||
Key: key,
|
||||
Value: vv,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(tags)
|
||||
gf.Tags = tags
|
||||
return nil
|
||||
}
|
||||
|
||||
func getJsonValue(f *parser.Field, val string) string {
|
||||
if v := getAnnotation(f.Annotations, AnnotationJsConv); len(v) > 0 {
|
||||
val += ",string"
|
||||
}
|
||||
if !unsetOmitempty && f.Requiredness == parser.FieldType_Optional {
|
||||
val += ",omitempty"
|
||||
} else if f.Requiredness == parser.FieldType_Required {
|
||||
val += ",required"
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func checkRequire(f *parser.Field, val string) string {
|
||||
if f.Requiredness == parser.FieldType_Required {
|
||||
val += ",required"
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// checkGoTag removes the tag defined in gotag
|
||||
func checkGoTag(as parser.Annotations, tags *model.Tags) error {
|
||||
rets := getAnnotation(as, AnnotationGoTag)
|
||||
for _, v := range rets {
|
||||
gts := util.SplitGoTags(v)
|
||||
for _, gt := range gts {
|
||||
sp := strings.SplitN(gt, ":", 2)
|
||||
if len(sp) != 2 {
|
||||
return fmt.Errorf("invalid go tag: %s", v)
|
||||
}
|
||||
key := sp[0]
|
||||
tags.Remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
26
thrift/test_data/test_tag.thrift
Normal file
26
thrift/test_data/test_tag.thrift
Normal file
@ -0,0 +1,26 @@
|
||||
namespace go cloudwego.hertz.hz
|
||||
|
||||
struct MultiTagReq {
|
||||
// basic feature
|
||||
1: string DefaultQueryTag (api.query="query");
|
||||
2: string RawBodyTag (api.raw_body="raw_body");
|
||||
3: string PathTag (api.path="path");
|
||||
4: string FormTag (api.form="form");
|
||||
5: string CookieTag (api.cookie="cookie");
|
||||
6: string HeaderTag (api.header="header");
|
||||
7: string BodyTag (api.body="body");
|
||||
8: string GoTag (go.tag="json:\"json\" query:\"query\" form:\"form\" header:\"header\" goTag:\"tag\"");
|
||||
9: string VdTag (api.vd="$!='?'");
|
||||
10: string DefaultTag;
|
||||
|
||||
// optional / required
|
||||
11: required string ReqQuery (api.query="query");
|
||||
12: optional string OptQuery (api.query="query");
|
||||
13: required string ReqBody (api.body="body");
|
||||
14: optional string OptBody (api.body="body");
|
||||
15: required string ReqGoTag (go.tag="json:\"json\"");
|
||||
16: optional string OptGoTag (go.tag="json:\"json\"");
|
||||
|
||||
// gotag cover feature
|
||||
17: required string QueryGoTag (apt.query="query", go.tag="query:\"queryTag\"")
|
||||
}
|
BIN
thrift/test_data/thrift_tag_test.out
Normal file
BIN
thrift/test_data/thrift_tag_test.out
Normal file
Binary file not shown.
26
thrift/thriftgo_util.go
Normal file
26
thrift/thriftgo_util.go
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package thrift
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/thriftgo/generator/golang"
|
||||
"github.com/cloudwego/thriftgo/generator/golang/styles"
|
||||
)
|
||||
|
||||
var thriftgoUtil *golang.CodeUtils
|
||||
|
||||
var NameStyle = styles.NewNamingStyle("thriftgo")
|
65
util/ast.go
Normal file
65
util/ast.go
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func AddImport(file, alias, impt string) ([]byte, error) {
|
||||
fset := token.NewFileSet()
|
||||
path, _ := filepath.Abs(file)
|
||||
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not parse ast for file: %s, err: %v", path, err)
|
||||
}
|
||||
|
||||
return addImport(fset, f, alias, impt)
|
||||
}
|
||||
|
||||
func AddImportForContent(fileContent []byte, alias, impt string) ([]byte, error) {
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "", fileContent, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not parse ast for file: %s, err: %v", fileContent, err)
|
||||
}
|
||||
|
||||
return addImport(fset, f, alias, impt)
|
||||
}
|
||||
|
||||
func addImport(fset *token.FileSet, f *ast.File, alias, impt string) ([]byte, error) {
|
||||
added := astutil.AddNamedImport(fset, f, alias, impt)
|
||||
if !added {
|
||||
return nil, fmt.Errorf("can not add import \"%s\" for file: %s", impt, f.Name.Name)
|
||||
}
|
||||
var output []byte
|
||||
buffer := bytes.NewBuffer(output)
|
||||
err := format.Node(buffer, fset, f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not add import for file: %s, err: %v", f.Name.Name, err)
|
||||
}
|
||||
|
||||
return buffer.Bytes(), nil
|
||||
}
|
93
util/ast_test.go
Normal file
93
util/ast_test.go
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func TestAddImport(t *testing.T) {
|
||||
inserts := [][]string{
|
||||
{
|
||||
"ctx",
|
||||
"context",
|
||||
},
|
||||
{
|
||||
"",
|
||||
"context",
|
||||
},
|
||||
}
|
||||
files := [][]string{
|
||||
{
|
||||
`package foo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
`,
|
||||
`package foo
|
||||
|
||||
import (
|
||||
ctx "context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
`,
|
||||
},
|
||||
{
|
||||
`package foo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
`,
|
||||
`package foo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
`,
|
||||
},
|
||||
}
|
||||
for idx, file := range files {
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "", file[0], parser.ImportsOnly)
|
||||
if err != nil {
|
||||
t.Fatalf("can not parse ast for file")
|
||||
}
|
||||
astutil.AddNamedImport(fset, f, inserts[idx][0], inserts[idx][1])
|
||||
var output []byte
|
||||
buffer := bytes.NewBuffer(output)
|
||||
err = format.Node(buffer, fset, f)
|
||||
if err != nil {
|
||||
t.Fatalf("can add import for file")
|
||||
}
|
||||
if buffer.String() != file[1] {
|
||||
t.Fatalf("insert import fialed")
|
||||
}
|
||||
}
|
||||
}
|
436
util/data.go
Normal file
436
util/data.go
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
)
|
||||
|
||||
func CopyStringSlice(from, to *[]string) {
|
||||
n := len(*from)
|
||||
m := len(*to)
|
||||
if n > m {
|
||||
n = m
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
(*to)[i] = (*from)[i]
|
||||
}
|
||||
*to = (*to)[:n]
|
||||
}
|
||||
|
||||
func CopyString2StringMap(from, to map[string]string) {
|
||||
for k := range to {
|
||||
delete(to, k)
|
||||
}
|
||||
for k, v := range from {
|
||||
to[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func PackArgs(c interface{}) (res []string, err error) {
|
||||
t := reflect.TypeOf(c)
|
||||
v := reflect.ValueOf(c)
|
||||
if reflect.TypeOf(c).Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
v = v.Elem()
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return nil, errors.New("passed c must be struct or pointer of struct")
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
x := v.Field(i)
|
||||
n := f.Name
|
||||
|
||||
if x.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
switch x.Kind() {
|
||||
case reflect.Bool:
|
||||
if x.Bool() == false {
|
||||
continue
|
||||
}
|
||||
res = append(res, n+"="+fmt.Sprint(x.Bool()))
|
||||
case reflect.String:
|
||||
if x.String() == "" {
|
||||
continue
|
||||
}
|
||||
res = append(res, n+"="+x.String())
|
||||
case reflect.Slice:
|
||||
if x.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
ft := f.Type.Elem()
|
||||
if ft.Kind() != reflect.String {
|
||||
return nil, fmt.Errorf("slice field %v must be '[]string', err: %v", f.Name, err.Error())
|
||||
}
|
||||
var ss []string
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
ss = append(ss, x.Index(i).String())
|
||||
}
|
||||
res = append(res, n+"="+strings.Join(ss, ";"))
|
||||
case reflect.Map:
|
||||
if x.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
fk := f.Type.Key()
|
||||
if fk.Kind() != reflect.String {
|
||||
return nil, fmt.Errorf("map field %v must be 'map[string]string', err: %v", f.Name, err.Error())
|
||||
}
|
||||
fv := f.Type.Elem()
|
||||
if fv.Kind() != reflect.String {
|
||||
return nil, fmt.Errorf("map field %v must be 'map[string]string', err: %v", f.Name, err.Error())
|
||||
}
|
||||
var sk []string
|
||||
it := x.MapRange()
|
||||
for it.Next() {
|
||||
sk = append(sk, it.Key().String()+"="+it.Value().String())
|
||||
}
|
||||
res = append(res, n+"="+strings.Join(sk, ";"))
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported field type: %+v, err: %v", f, err.Error())
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func UnpackArgs(args []string, c interface{}) error {
|
||||
m, err := MapForm(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal args failed, err: %v", err.Error())
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(c).Elem()
|
||||
v := reflect.ValueOf(c).Elem()
|
||||
if t.Kind() != reflect.Struct {
|
||||
return errors.New("passed c must be struct or pointer of struct")
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
x := v.Field(i)
|
||||
n := f.Name
|
||||
values, ok := m[n]
|
||||
if !ok || len(values) == 0 || values[0] == "" {
|
||||
continue
|
||||
}
|
||||
switch x.Kind() {
|
||||
case reflect.Bool:
|
||||
if len(values) != 1 {
|
||||
return fmt.Errorf("field %s can't be assigned multi values: %v", n, values)
|
||||
}
|
||||
x.SetBool(values[0] == "true")
|
||||
case reflect.String:
|
||||
if len(values) != 1 {
|
||||
return fmt.Errorf("field %s can't be assigned multi values: %v", n, values)
|
||||
}
|
||||
x.SetString(values[0])
|
||||
case reflect.Slice:
|
||||
if len(values) != 1 {
|
||||
return fmt.Errorf("field %s can't be assigned multi values: %v", n, values)
|
||||
}
|
||||
ss := strings.Split(values[0], ";")
|
||||
if x.Type().Elem().Kind() == reflect.Int {
|
||||
n := reflect.MakeSlice(x.Type(), len(ss), len(ss))
|
||||
for i, s := range ss {
|
||||
val, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.Index(i).SetInt(val)
|
||||
}
|
||||
x.Set(n)
|
||||
} else {
|
||||
for _, s := range ss {
|
||||
val := reflect.Append(x, reflect.ValueOf(s))
|
||||
x.Set(val)
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
if len(values) != 1 {
|
||||
return fmt.Errorf("field %s can't be assigned multi values: %v", n, values)
|
||||
}
|
||||
ss := strings.Split(values[0], ";")
|
||||
out := make(map[string]string, len(ss))
|
||||
for _, s := range ss {
|
||||
sk := strings.SplitN(s, "=", 2)
|
||||
if len(sk) != 2 {
|
||||
return fmt.Errorf("map filed %v invalid key-value pair '%v'", n, s)
|
||||
}
|
||||
out[sk[0]] = sk[1]
|
||||
}
|
||||
x.Set(reflect.ValueOf(out))
|
||||
default:
|
||||
return fmt.Errorf("field %s has unsupported type %+v", n, f.Type)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MapForm(input []string) (map[string][]string, error) {
|
||||
out := make(map[string][]string, len(input))
|
||||
|
||||
for _, str := range input {
|
||||
parts := strings.SplitN(str, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid argument: '%s'", str)
|
||||
}
|
||||
key, val := parts[0], parts[1]
|
||||
out[key] = append(out[key], val)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func GetFirstKV(m map[string][]string) (string, []string) {
|
||||
for k, v := range m {
|
||||
return k, v
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func ToCamelCase(name string) string {
|
||||
return CamelString(name)
|
||||
}
|
||||
|
||||
func ToSnakeCase(name string) string {
|
||||
return SnakeString(name)
|
||||
}
|
||||
|
||||
// unifyPath will convert "\" to "/" in path if the os is windows
|
||||
func unifyPath(path string) string {
|
||||
if IsWindows() {
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// BaseName get base name for path. ex: "github.com/p.s.m" => "p.s.m"
|
||||
func BaseName(include, subFixToTrim string) string {
|
||||
include = unifyPath(include)
|
||||
subFixToTrim = unifyPath(subFixToTrim)
|
||||
last := include
|
||||
if id := strings.LastIndex(last, "/"); id >= 0 && id < len(last)-1 {
|
||||
last = last[id+1:]
|
||||
}
|
||||
if !strings.HasSuffix(last, subFixToTrim) {
|
||||
return last
|
||||
}
|
||||
return last[:len(last)-len(subFixToTrim)]
|
||||
}
|
||||
|
||||
func BaseNameAndTrim(include string) string {
|
||||
include = unifyPath(include)
|
||||
last := include
|
||||
if id := strings.LastIndex(last, "/"); id >= 0 && id < len(last)-1 {
|
||||
last = last[id+1:]
|
||||
}
|
||||
|
||||
if id := strings.LastIndex(last, "."); id != -1 {
|
||||
last = last[:id]
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
func SplitPackageName(pkg, subFixToTrim string) string {
|
||||
pkg = unifyPath(pkg)
|
||||
subFixToTrim = unifyPath(subFixToTrim)
|
||||
last := SplitPackage(pkg, subFixToTrim)
|
||||
if id := strings.LastIndex(last, "/"); id >= 0 && id < len(last)-1 {
|
||||
last = last[id+1:]
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
func SplitPackage(pkg, subFixToTrim string) string {
|
||||
pkg = unifyPath(pkg)
|
||||
subFixToTrim = unifyPath(subFixToTrim)
|
||||
last := strings.TrimSuffix(pkg, subFixToTrim)
|
||||
if id := strings.LastIndex(last, "/"); id >= 0 && id < len(last)-1 {
|
||||
last = last[id+1:]
|
||||
}
|
||||
return strings.ReplaceAll(last, ".", "/")
|
||||
}
|
||||
|
||||
func PathToImport(path, subFix string) string {
|
||||
path = strings.TrimSuffix(path, subFix)
|
||||
// path = RelativePath(path)
|
||||
return strings.ReplaceAll(path, string(filepath.Separator), "/")
|
||||
}
|
||||
|
||||
func ImportToPath(path, subFix string) string {
|
||||
// path = RelativePath(path)
|
||||
return strings.ReplaceAll(path, "/", string(filepath.Separator)) + subFix
|
||||
}
|
||||
|
||||
func ImportToPathAndConcat(path, subFix string) string {
|
||||
path = strings.TrimSuffix(path, subFix)
|
||||
path = strings.ReplaceAll(path, "/", string(filepath.Separator))
|
||||
if i := strings.LastIndex(path, string(filepath.Separator)); i >= 0 && i < len(path)-1 && strings.Contains(path[i+1:], ".") {
|
||||
base := strings.ReplaceAll(path[i+1:], ".", "_")
|
||||
dir := path[:i]
|
||||
return dir + string(filepath.Separator) + base
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func ToVarName(paths []string) string {
|
||||
ps := strings.Join(paths, "__")
|
||||
input := []byte(url.PathEscape(ps))
|
||||
out := make([]byte, 0, len(input))
|
||||
for i := 0; i < len(input); i++ {
|
||||
c := input[i]
|
||||
if c == ':' || c == '*' {
|
||||
continue
|
||||
}
|
||||
if (c >= '0' && c <= '9' && i != 0) || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') {
|
||||
out = append(out, c)
|
||||
} else {
|
||||
out = append(out, '_')
|
||||
}
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func SplitGoTags(input string) []string {
|
||||
out := make([]string, 0, 4)
|
||||
ns := len(input)
|
||||
|
||||
flag := false
|
||||
prev := 0
|
||||
i := 0
|
||||
for i = 0; i < ns; i++ {
|
||||
c := input[i]
|
||||
if c == '"' {
|
||||
flag = !flag
|
||||
}
|
||||
if !flag && c == ' ' {
|
||||
if prev < i {
|
||||
out = append(out, input[prev:i])
|
||||
}
|
||||
prev = i + 1
|
||||
}
|
||||
}
|
||||
if i != 0 && prev < i {
|
||||
out = append(out, input[prev:i])
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func SubPackage(mod, dir string) string {
|
||||
if dir == "" {
|
||||
return mod
|
||||
}
|
||||
return mod + "/" + PathToImport(dir, "")
|
||||
}
|
||||
|
||||
func SubDir(root, subPkg string) string {
|
||||
if root == "" {
|
||||
return ImportToPath(subPkg, "")
|
||||
}
|
||||
return filepath.Join(root, ImportToPath(subPkg, ""))
|
||||
}
|
||||
|
||||
var (
|
||||
uniquePackageName = map[string]bool{}
|
||||
uniqueMiddlewareName = map[string]bool{}
|
||||
uniqueHandlerPackageName = map[string]bool{}
|
||||
)
|
||||
|
||||
// GetPackageUniqueName can get a non-repeating variable name for package alias
|
||||
func GetPackageUniqueName(name string) (string, error) {
|
||||
name, err := getUniqueName(name, uniquePackageName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can not generate unique name for package '%s', err: %v", name, err)
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// GetMiddlewareUniqueName can get a non-repeating variable name for middleware name
|
||||
func GetMiddlewareUniqueName(name string) (string, error) {
|
||||
name, err := getUniqueName(name, uniqueMiddlewareName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can not generate routing group for path '%s', err: %v", name, err)
|
||||
}
|
||||
|
||||
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
|
||||
if _, exist := uniqueNameSet[uniqueName]; exist {
|
||||
for i := 0; i < 10000; i++ {
|
||||
uniqueName = uniqueName + fmt.Sprintf("%d", i)
|
||||
if _, exist := uniqueNameSet[uniqueName]; !exist {
|
||||
logs.Infof("There is a package name with the same name, change %s to %s", name, uniqueName)
|
||||
break
|
||||
}
|
||||
uniqueName = name
|
||||
if i == 9999 {
|
||||
return "", fmt.Errorf("there is too many same package for %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
uniqueNameSet[uniqueName] = true
|
||||
|
||||
return uniqueName, nil
|
||||
}
|
||||
|
||||
func SubPackageDir(path string) string {
|
||||
index := strings.LastIndex(path, "/")
|
||||
if index == -1 {
|
||||
return ""
|
||||
}
|
||||
return path[:index]
|
||||
}
|
||||
|
||||
var validFuncReg = regexp.MustCompile("[_0-9a-zA-Z]")
|
||||
|
||||
// ToGoFuncName converts a string to a function naming style for go
|
||||
func ToGoFuncName(s string) string {
|
||||
ss := []byte(s)
|
||||
for i := range ss {
|
||||
if !validFuncReg.Match([]byte{s[i]}) {
|
||||
ss[i] = '_'
|
||||
}
|
||||
}
|
||||
return string(ss)
|
||||
}
|
78
util/data_test.go
Normal file
78
util/data_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUniqueName(t *testing.T) {
|
||||
type UniqueName struct {
|
||||
Name string
|
||||
ExpectedName string
|
||||
ActualName string
|
||||
}
|
||||
|
||||
nameList := []UniqueName{
|
||||
{
|
||||
Name: "aaa",
|
||||
ExpectedName: "aaa",
|
||||
},
|
||||
{
|
||||
Name: "aaa",
|
||||
ExpectedName: "aaa0",
|
||||
},
|
||||
{
|
||||
Name: "aaa0",
|
||||
ExpectedName: "aaa00",
|
||||
},
|
||||
{
|
||||
Name: "aaa0",
|
||||
ExpectedName: "aaa01",
|
||||
},
|
||||
{
|
||||
Name: "aaa00",
|
||||
ExpectedName: "aaa000",
|
||||
},
|
||||
{
|
||||
Name: "aaa",
|
||||
ExpectedName: "aaa1",
|
||||
},
|
||||
{
|
||||
Name: "aaa",
|
||||
ExpectedName: "aaa2",
|
||||
},
|
||||
{
|
||||
Name: "aaa",
|
||||
ExpectedName: "aaa3",
|
||||
},
|
||||
{
|
||||
Name: "aaa",
|
||||
ExpectedName: "aaa4",
|
||||
},
|
||||
}
|
||||
for _, name := range nameList {
|
||||
name.ActualName, _ = getUniqueName(name.Name, uniquePackageName)
|
||||
if name.ActualName != name.ExpectedName {
|
||||
t.Errorf("%s name expected unique name '%s', actually get '%s'", name.Name, name.ExpectedName, name.ActualName)
|
||||
}
|
||||
}
|
||||
for _, name := range nameList {
|
||||
name.ActualName, _ = getUniqueName(name.Name, uniqueMiddlewareName)
|
||||
if name.ActualName != name.ExpectedName {
|
||||
t.Errorf("%s name expected unique name '%s', actually get '%s'", name.Name, name.ExpectedName, name.ActualName)
|
||||
}
|
||||
}
|
||||
}
|
138
util/env.go
Normal file
138
util/env.go
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
)
|
||||
|
||||
func GetGOPATH() (gopath string, err error) {
|
||||
ps := filepath.SplitList(os.Getenv("GOPATH"))
|
||||
if len(ps) > 0 {
|
||||
gopath = ps[0]
|
||||
}
|
||||
if gopath == "" {
|
||||
cmd := exec.Command("go", "env", "GOPATH")
|
||||
var out bytes.Buffer
|
||||
cmd.Stderr = &out
|
||||
cmd.Stdout = &out
|
||||
if err := cmd.Run(); err == nil {
|
||||
gopath = strings.Trim(out.String(), " \t\n\r")
|
||||
}
|
||||
}
|
||||
if gopath == "" {
|
||||
ps := GetBuildGoPaths()
|
||||
if len(ps) > 0 {
|
||||
gopath = ps[0]
|
||||
}
|
||||
}
|
||||
isExist, err := PathExist(gopath)
|
||||
if !isExist {
|
||||
return "", err
|
||||
}
|
||||
return strings.Replace(gopath, "/", string(os.PathSeparator), -1), nil
|
||||
}
|
||||
|
||||
// GetBuildGoPaths returns the list of Go path directories.
|
||||
func GetBuildGoPaths() []string {
|
||||
var all []string
|
||||
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
||||
if p == "" || p == build.Default.GOROOT {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(p, "~") {
|
||||
continue
|
||||
}
|
||||
all = append(all, p)
|
||||
}
|
||||
for k, v := range all {
|
||||
if strings.HasSuffix(v, "/") || strings.HasSuffix(v, string(os.PathSeparator)) {
|
||||
v = v[:len(v)-1]
|
||||
}
|
||||
all[k] = v
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
var goModReg = regexp.MustCompile(`^\s*module\s+(\S+)\s*`)
|
||||
|
||||
// SearchGoMod searches go.mod from the given directory (which must be an absolute path) to
|
||||
// the root directory. When the go.mod is found, its module name and path will be returned.
|
||||
func SearchGoMod(cwd string, recurse bool) (moduleName, path string, found bool) {
|
||||
for {
|
||||
path = filepath.Join(cwd, "go.mod")
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err == nil {
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
m := goModReg.FindStringSubmatch(line)
|
||||
if m != nil {
|
||||
return m[1], cwd, true
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("<module name not found in '%s'>", path), path, true
|
||||
}
|
||||
|
||||
if !os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
if !recurse {
|
||||
break
|
||||
}
|
||||
cwd = filepath.Dir(cwd)
|
||||
// the root directory will return itself by using "filepath.Dir()"; to prevent dead loops, so jump out
|
||||
if cwd == filepath.Dir(cwd) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func InitGoMod(module string) error {
|
||||
isExist, err := PathExist("go.mod")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isExist {
|
||||
return nil
|
||||
}
|
||||
gg, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := &exec.Cmd{
|
||||
Path: gg,
|
||||
Args: []string{"go", "mod", "init", module},
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func IsWindows() bool {
|
||||
return meta.SysType == meta.WindowsOS
|
||||
}
|
47
util/fs.go
Normal file
47
util/fs.go
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func PathExist(path string) (bool, error) {
|
||||
abPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = os.Stat(abPath)
|
||||
if err != nil {
|
||||
return os.IsExist(err), nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func RelativePath(path string) (string, error) {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret, _ := filepath.Rel(cwd, path)
|
||||
return ret, nil
|
||||
}
|
84
util/logs/api.go
Normal file
84
util/logs/api.go
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package logs
|
||||
|
||||
func init() {
|
||||
defaultLogger = NewStdLogger(LevelInfo)
|
||||
}
|
||||
|
||||
func SetLogger(logger Logger) {
|
||||
defaultLogger = logger
|
||||
}
|
||||
|
||||
const (
|
||||
LevelDebug = 1 + iota
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelError
|
||||
)
|
||||
|
||||
// TODO: merge with hertz logger package
|
||||
type Logger interface {
|
||||
Debugf(format string, v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
Warnf(format string, v ...interface{})
|
||||
Errorf(format string, v ...interface{})
|
||||
Flush()
|
||||
SetLevel(level int) error
|
||||
}
|
||||
|
||||
var defaultLogger Logger
|
||||
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
defaultLogger.Errorf(format, v...)
|
||||
}
|
||||
|
||||
func Warnf(format string, v ...interface{}) {
|
||||
defaultLogger.Warnf(format, v...)
|
||||
}
|
||||
|
||||
func Infof(format string, v ...interface{}) {
|
||||
defaultLogger.Infof(format, v...)
|
||||
}
|
||||
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
defaultLogger.Debugf(format, v...)
|
||||
}
|
||||
|
||||
func Error(format string, v ...interface{}) {
|
||||
defaultLogger.Errorf(format, v...)
|
||||
}
|
||||
|
||||
func Warn(format string, v ...interface{}) {
|
||||
defaultLogger.Warnf(format, v...)
|
||||
}
|
||||
|
||||
func Info(format string, v ...interface{}) {
|
||||
defaultLogger.Infof(format, v...)
|
||||
}
|
||||
|
||||
func Debug(format string, v ...interface{}) {
|
||||
defaultLogger.Debugf(format, v...)
|
||||
}
|
||||
|
||||
func Flush() {
|
||||
defaultLogger.Flush()
|
||||
}
|
||||
|
||||
func SetLevel(level int) {
|
||||
defaultLogger.SetLevel(level)
|
||||
}
|
141
util/logs/std.go
Normal file
141
util/logs/std.go
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type StdLogger struct {
|
||||
level int
|
||||
outLogger *log.Logger
|
||||
warnLogger *log.Logger
|
||||
errLogger *log.Logger
|
||||
out *bytes.Buffer
|
||||
warn *bytes.Buffer
|
||||
err *bytes.Buffer
|
||||
Defer bool
|
||||
ErrOnly bool
|
||||
}
|
||||
|
||||
func NewStdLogger(level int) *StdLogger {
|
||||
out := bytes.NewBuffer(nil)
|
||||
warn := bytes.NewBuffer(nil)
|
||||
err := bytes.NewBuffer(nil)
|
||||
return &StdLogger{
|
||||
level: level,
|
||||
outLogger: log.New(out, "[INFO]", log.Llongfile),
|
||||
warnLogger: log.New(warn, "[WARN]", log.Llongfile),
|
||||
errLogger: log.New(err, "[ERROR]", log.Llongfile),
|
||||
out: out,
|
||||
warn: warn,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) Debugf(format string, v ...interface{}) {
|
||||
if stdLogger.level > LevelDebug {
|
||||
return
|
||||
}
|
||||
stdLogger.outLogger.Output(3, fmt.Sprintf(format, v...))
|
||||
if !stdLogger.Defer {
|
||||
stdLogger.FlushOut()
|
||||
}
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) Infof(format string, v ...interface{}) {
|
||||
if stdLogger.level > LevelInfo {
|
||||
return
|
||||
}
|
||||
stdLogger.outLogger.Output(3, fmt.Sprintf(format, v...))
|
||||
if !stdLogger.Defer {
|
||||
stdLogger.FlushOut()
|
||||
}
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) Warnf(format string, v ...interface{}) {
|
||||
if stdLogger.level > LevelWarn {
|
||||
return
|
||||
}
|
||||
stdLogger.warnLogger.Output(3, fmt.Sprintf(format, v...))
|
||||
if !stdLogger.Defer {
|
||||
stdLogger.FlushErr()
|
||||
}
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) Errorf(format string, v ...interface{}) {
|
||||
if stdLogger.level > LevelError {
|
||||
return
|
||||
}
|
||||
stdLogger.errLogger.Output(3, fmt.Sprintf(format, v...))
|
||||
if !stdLogger.Defer {
|
||||
stdLogger.FlushErr()
|
||||
}
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) Flush() {
|
||||
stdLogger.FlushErr()
|
||||
if !stdLogger.ErrOnly {
|
||||
stdLogger.FlushOut()
|
||||
}
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) FlushOut() {
|
||||
os.Stderr.Write(stdLogger.out.Bytes())
|
||||
stdLogger.out.Reset()
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) Err() string {
|
||||
return string(stdLogger.err.Bytes())
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) Warn() string {
|
||||
return string(stdLogger.warn.Bytes())
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) FlushErr() {
|
||||
os.Stderr.Write(stdLogger.err.Bytes())
|
||||
stdLogger.err.Reset()
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) OutLines() []string {
|
||||
lines := bytes.Split(stdLogger.out.Bytes(), []byte("[INFO]"))
|
||||
var rets []string
|
||||
for _, line := range lines {
|
||||
rets = append(rets, string(line))
|
||||
}
|
||||
return rets
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) Out() []byte {
|
||||
return stdLogger.out.Bytes()
|
||||
}
|
||||
|
||||
func (stdLogger *StdLogger) SetLevel(level int) error {
|
||||
switch level {
|
||||
case LevelDebug, LevelInfo, LevelWarn, LevelError:
|
||||
break
|
||||
default:
|
||||
return errors.New("invalid log level")
|
||||
}
|
||||
stdLogger.level = level
|
||||
return nil
|
||||
}
|
99
util/string.go
Normal file
99
util/string.go
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Str2Bytes(in string) (out []byte) {
|
||||
op := (*reflect.SliceHeader)(unsafe.Pointer(&out))
|
||||
ip := (*reflect.StringHeader)(unsafe.Pointer(&in))
|
||||
op.Data = ip.Data
|
||||
op.Cap = ip.Len
|
||||
op.Len = ip.Len
|
||||
return
|
||||
}
|
||||
|
||||
func Bytes2Str(in []byte) (out string) {
|
||||
op := (*reflect.StringHeader)(unsafe.Pointer(&out))
|
||||
ip := (*reflect.SliceHeader)(unsafe.Pointer(&in))
|
||||
op.Data = ip.Data
|
||||
op.Len = ip.Len
|
||||
return
|
||||
}
|
||||
|
||||
// TrimLastChar can remove the last char for s
|
||||
func TrimLastChar(s string) string {
|
||||
r, size := utf8.DecodeLastRuneInString(s)
|
||||
if r == utf8.RuneError && (size == 0 || size == 1) {
|
||||
size = 0
|
||||
}
|
||||
return s[:len(s)-size]
|
||||
}
|
||||
|
||||
// AddSlashForComments can adjust the format of multi-line comments
|
||||
func AddSlashForComments(s string) string {
|
||||
s = strings.Replace(s, "\n", "\n//", -1)
|
||||
return s
|
||||
}
|
||||
|
||||
// CamelString converts the string 's' to a camel string
|
||||
func CamelString(s string) string {
|
||||
data := make([]byte, 0, len(s))
|
||||
j := false
|
||||
k := false
|
||||
num := len(s) - 1
|
||||
for i := 0; i <= num; i++ {
|
||||
d := s[i]
|
||||
if k == false && d >= 'A' && d <= 'Z' {
|
||||
k = true
|
||||
}
|
||||
if d >= 'a' && d <= 'z' && (j || k == false) {
|
||||
d = d - 32
|
||||
j = false
|
||||
k = true
|
||||
}
|
||||
if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' {
|
||||
j = true
|
||||
continue
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return Bytes2Str(data[:])
|
||||
}
|
||||
|
||||
// SnakeString converts the string 's' to a snake string
|
||||
func SnakeString(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
j := false
|
||||
for _, d := range Str2Bytes(s) {
|
||||
if d >= 'A' && d <= 'Z' {
|
||||
if j {
|
||||
data = append(data, '_')
|
||||
j = false
|
||||
}
|
||||
} else if d != '_' {
|
||||
j = true
|
||||
}
|
||||
data = append(data, d)
|
||||
}
|
||||
return strings.ToLower(Bytes2Str(data))
|
||||
}
|
151
util/tool_install.go
Normal file
151
util/tool_install.go
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/hertz/cmd/hz/meta"
|
||||
"github.com/cloudwego/hertz/cmd/hz/util/logs"
|
||||
gv "github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
const ThriftgoMiniVersion = "v0.2.0"
|
||||
|
||||
// QueryVersion will query the version of the corresponding executable.
|
||||
func QueryVersion(exe string) (version string, err error) {
|
||||
var buf strings.Builder
|
||||
cmd := &exec.Cmd{
|
||||
Path: exe,
|
||||
Args: []string{
|
||||
exe, "--version",
|
||||
},
|
||||
Stdin: os.Stdin,
|
||||
Stdout: &buf,
|
||||
Stderr: &buf,
|
||||
}
|
||||
err = cmd.Run()
|
||||
if err == nil {
|
||||
version = strings.Split(buf.String(), " ")[1]
|
||||
if strings.HasSuffix(version, "\n") {
|
||||
version = version[:len(version)-1]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ShouldUpdate will return "true" when current is lower than latest.
|
||||
func ShouldUpdate(current, latest string) bool {
|
||||
cv, err := gv.NewVersion(current)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
lv, err := gv.NewVersion(latest)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return cv.Compare(lv) < 0
|
||||
}
|
||||
|
||||
// InstallAndCheckThriftgo will automatically install thriftgo and judge whether it is installed successfully.
|
||||
func InstallAndCheckThriftgo() error {
|
||||
exe, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can not find tool 'go': %v", err)
|
||||
}
|
||||
var buf strings.Builder
|
||||
cmd := &exec.Cmd{
|
||||
Path: exe,
|
||||
Args: []string{
|
||||
exe, "install", "github.com/cloudwego/thriftgo@latest",
|
||||
},
|
||||
Stdin: os.Stdin,
|
||||
Stdout: &buf,
|
||||
Stderr: &buf,
|
||||
}
|
||||
|
||||
done := make(chan error)
|
||||
logs.Infof("installing thriftgo automatically")
|
||||
go func() {
|
||||
done <- cmd.Run()
|
||||
}()
|
||||
select {
|
||||
case err = <-done:
|
||||
if err != nil {
|
||||
return fmt.Errorf("can not install thriftgo, err: %v. Please install it manual, and make sure the version of thriftgo is greater than v0.2.0", cmd.Stderr)
|
||||
}
|
||||
case <-time.After(time.Second * 30):
|
||||
return fmt.Errorf("install thriftgo time out.Please install it manual, and make sure the version of thriftgo is greater than v0.2.0")
|
||||
}
|
||||
|
||||
exist, err := CheckCompiler(meta.TpCompilerThrift)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check %s exist failed, err: %v", meta.TpCompilerThrift, err)
|
||||
}
|
||||
if !exist {
|
||||
return fmt.Errorf("install thriftgo failed. Please install it manual, and make sure the version of thriftgo is greater than v0.2.0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckCompiler will check if the tool exists.
|
||||
func CheckCompiler(tool string) (bool, error) {
|
||||
path, err := exec.LookPath(tool)
|
||||
if err != nil {
|
||||
goPath, err := GetGOPATH()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get 'GOPATH' failed for find %s : %v", tool, path)
|
||||
}
|
||||
path = filepath.Join(goPath, "bin", tool)
|
||||
}
|
||||
|
||||
isExist, err := PathExist(path)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("can not check %s exist, err: %v", tool, err)
|
||||
}
|
||||
if !isExist {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckAndUpdateThriftgo checks the version of thriftgo and updates the tool to the latest version if its version is less than v0.2.0.
|
||||
func CheckAndUpdateThriftgo() error {
|
||||
path, err := exec.LookPath(meta.TpCompilerThrift)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can not find %s", meta.TpCompilerThrift)
|
||||
}
|
||||
curVersion, err := QueryVersion(path)
|
||||
logs.Infof("current thriftgo version is %s", curVersion)
|
||||
if ShouldUpdate(curVersion, ThriftgoMiniVersion) {
|
||||
logs.Infof(" current thriftgo version is less than v0.2.0, so update thriftgo version")
|
||||
err = InstallAndCheckThriftgo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("update thriftgo version failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
36
util/tool_install_test.go
Normal file
36
util/tool_install_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestQueryVersion(t *testing.T) {
|
||||
lowVersion := "v0.1.0"
|
||||
equalVersion := "v0.2.0"
|
||||
highVersion := "v0.3.0"
|
||||
|
||||
if ShouldUpdate(lowVersion, ThriftgoMiniVersion) {
|
||||
}
|
||||
|
||||
if ShouldUpdate(equalVersion, ThriftgoMiniVersion) {
|
||||
t.Fatal("should not be updated")
|
||||
}
|
||||
|
||||
if ShouldUpdate(highVersion, ThriftgoMiniVersion) {
|
||||
t.Fatal("should not be updated")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user