├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cli ├── cmd │ ├── add │ │ ├── add.go │ │ ├── method.go │ │ └── plugin.go │ ├── cmd.go │ ├── default.go │ ├── describe │ │ └── describe.go │ └── init │ │ └── init.go ├── main.go └── pkg │ └── scaffold │ ├── generator │ ├── generator.go │ └── options.go │ ├── injector │ ├── injector.go │ ├── options.go │ └── types.go │ └── templates │ ├── docker.go │ ├── gitignore.go │ ├── main.go │ ├── makefile.go │ ├── module.go │ ├── proto.go │ ├── script.go │ └── service.go ├── docs ├── lastbackend.toolkit.flow.drawio ├── lastbackend.toolkit.flow.png ├── lastbackend.toolkit.schema.drawio └── lastbackend.toolkit.schema.png ├── examples ├── README.md ├── gateway │ ├── apis │ │ └── server.proto │ ├── config │ │ └── config.go │ ├── gen │ │ └── server │ │ │ ├── client │ │ │ └── server.pb.toolkit.rpc.go │ │ │ ├── server.pb.go │ │ │ ├── server.pb.validate.go │ │ │ ├── server_grpc.pb.go │ │ │ └── server_service.pb.toolkit.go │ ├── generate.go │ ├── main.go │ ├── middleware │ │ ├── example.go │ │ └── request.go │ └── scripts │ │ └── generate.sh ├── helloworld │ ├── apis │ │ └── helloworld.proto │ ├── gen │ │ ├── helloworld.pb.go │ │ └── helloworld_grpc.pb.go │ ├── generate.go │ ├── main.go │ └── scripts │ │ └── generate.sh ├── http │ ├── apis │ │ └── server.proto │ ├── gen │ │ └── server │ │ │ ├── client │ │ │ └── server.pb.toolkit.rpc.go │ │ │ ├── server.pb.go │ │ │ ├── server.pb.validate.go │ │ │ ├── server_grpc.pb.go │ │ │ └── server_service.pb.toolkit.go │ ├── generate.go │ ├── main.go │ ├── middleware │ │ ├── example.go │ │ └── request.go │ └── scripts │ │ └── generate.sh ├── service │ ├── apis │ │ ├── example.proto │ │ └── ptypes │ │ │ └── messages.proto │ ├── config │ │ └── config.go │ ├── gen │ │ ├── client │ │ │ ├── demo │ │ │ │ └── demo.go │ │ │ └── example.pb.toolkit.rpc.go │ │ ├── example.pb.go │ │ ├── example.pb.validate.go │ │ ├── example_grpc.pb.go │ │ ├── example_service.pb.toolkit.go │ │ ├── ptypes │ │ │ ├── messages.pb.go │ │ │ └── messages.pb.validate.go │ │ └── tests │ │ │ └── example.pb.toolkit.mockery.go │ ├── generate.go │ ├── internal │ │ ├── controller │ │ │ └── controller.go │ │ ├── repository │ │ │ └── repository.go │ │ └── server │ │ │ ├── handler.go │ │ │ ├── interceptor.go │ │ │ ├── middleware.go │ │ │ ├── server.go │ │ │ └── server_test.go │ ├── mage.go │ ├── magefile.go │ ├── main.go │ ├── scripts │ │ └── generate.sh │ └── test-results │ │ └── junit │ │ └── unit-tests.xml └── wss │ ├── apis │ └── server.proto │ ├── gen │ └── server │ │ ├── client │ │ └── server.pb.toolkit.rpc.go │ │ ├── server.pb.go │ │ ├── server.pb.validate.go │ │ ├── server_grpc.pb.go │ │ └── server_service.pb.toolkit.go │ ├── generate.go │ ├── main.go │ ├── middleware │ ├── example.go │ └── request.go │ ├── scripts │ └── generate.sh │ └── swagger │ ├── protoc-gen-openapiv2 │ └── options │ │ ├── annotations.swagger.json │ │ └── openapiv2.swagger.json │ ├── server.swagger.json │ └── validate │ └── validate.swagger.json ├── generate.go ├── go.mod ├── go.sum ├── hack └── generate.sh ├── interface.go ├── pkg ├── client │ ├── client.go │ ├── grpc │ │ ├── client.go │ │ ├── codec.go │ │ ├── options.go │ │ ├── pool.go │ │ ├── resolver │ │ │ ├── file │ │ │ │ ├── file.go │ │ │ │ └── table.go │ │ │ ├── filter.go │ │ │ ├── local │ │ │ │ ├── local.go │ │ │ │ └── table.go │ │ │ ├── options.go │ │ │ ├── resolver.go │ │ │ └── route │ │ │ │ └── route.go │ │ ├── selector │ │ │ ├── random.go │ │ │ ├── roundrobin.go │ │ │ └── selector.go │ │ └── stream.go │ └── http │ │ └── client.go ├── context │ ├── context.go │ └── metadata │ │ └── metadata.go ├── runtime │ ├── controller │ │ ├── client.go │ │ ├── config.go │ │ ├── controller.go │ │ ├── help.go │ │ ├── package.go │ │ ├── plugin.go │ │ ├── server.go │ │ ├── service.go │ │ ├── tools.go │ │ └── util.go │ ├── interface.go │ ├── logger │ │ ├── empty │ │ │ └── empty.go │ │ ├── logger.go │ │ └── zap │ │ │ └── zap.go │ ├── meta │ │ └── meta.go │ └── options.go ├── server │ ├── grpc │ │ ├── grpc.go │ │ ├── interceptor.go │ │ └── options.go │ ├── http │ │ ├── client.go │ │ ├── converter.go │ │ ├── cors.go │ │ ├── errors │ │ │ ├── errors.go │ │ │ └── http.go │ │ ├── handler.go │ │ ├── marshaler.go │ │ ├── marshaler │ │ │ ├── formpb │ │ │ │ └── formpb.go │ │ │ ├── jsonpb │ │ │ │ └── jsonpb.go │ │ │ ├── marshaler.go │ │ │ └── util │ │ │ │ └── convert_types.go │ │ ├── middleware.go │ │ ├── mux.go │ │ ├── options.go │ │ ├── server.go │ │ └── websockets │ │ │ ├── client.go │ │ │ ├── event.go │ │ │ └── manager.go │ └── server.go ├── tools │ ├── metrics │ │ └── metrics.go │ ├── probes │ │ ├── probes.go │ │ └── server │ │ │ └── server.go │ └── traces │ │ └── traces.go └── util │ ├── addr │ └── addr.go │ ├── backoff │ └── backoff.go │ ├── converter │ ├── converter.go │ └── query.go │ ├── filesystem │ └── filesystem.go │ ├── net │ └── net.go │ ├── parser │ └── parser.go │ ├── strings │ └── strings.go │ ├── tls │ └── tls.go │ └── types │ └── types.go ├── protoc-gen-toolkit ├── descriptor │ ├── descriptor.go │ ├── services.go │ └── types.go ├── generator │ └── generator.go ├── gentoolkit │ ├── gentoolkit.go │ ├── template.go │ ├── templates │ │ ├── client.go │ │ ├── header.go │ │ ├── message.go │ │ ├── plugin.go │ │ ├── server.go │ │ ├── service.go │ │ └── test.go │ └── utils.go ├── main.go └── toolkit │ └── options │ ├── annotations.pb.go │ ├── annotations.pb.validate.go │ └── annotations.proto └── toolkit.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Develop tools 2 | /.vscode/ 3 | /.idea/ 4 | 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Folders 13 | _obj 14 | _test 15 | _build 16 | 17 | # Architecture specific extensions/prefixes 18 | *.[568vq] 19 | [568vq].out 20 | 21 | *.cgo1.go 22 | *.cgo2.c 23 | _cgo_defun.c 24 | _cgo_gotypes.go 25 | _cgo_export.* 26 | 27 | # Test binary, build with `go test -c` 28 | *.test 29 | 30 | # Output of the go coverage tool, specifically when used with LiteIDE 31 | *.out 32 | 33 | # vim temp files 34 | *~ 35 | *.swp 36 | *.swo 37 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 2m 3 | modules-download-mode: readonly 4 | 5 | linters: 6 | enable: 7 | - deadcode 8 | - errcheck 9 | - gosimple 10 | - gofmt 11 | - govet 12 | - staticcheck 13 | - unused 14 | - typecheck 15 | - unconvert 16 | 17 | issues: 18 | # Independently of option `exclude` we use default exclude patterns, 19 | # it can be disabled by this option. 20 | # To list all excluded by default patterns execute `golangci-lint run --help`. 21 | # Default: true. 22 | exclude-use-default: true 23 | # If set to true exclude and exclude-rules regular expressions become case-sensitive. 24 | # Default: false 25 | exclude-case-sensitive: false 26 | # The list of ids of default excludes to include or disable. 27 | # Default: [] 28 | exclude: 29 | - EXC0002 # disable excluding of issues about comments from golint. 30 | # Maximum issues count per one linter. 31 | # Set to 0 to disable. 32 | # Default: 50 33 | max-issues-per-linter: 0 34 | # Maximum count of issues with the same text. 35 | # Set to 0 to disable. 36 | # Default: 3 37 | max-same-issues: 0 38 | # Fix found issues (if it's supported by the linter). 39 | fix: true 40 | 41 | skip-files: 42 | - ".*\\.pb\\.go$" 43 | - ".*\\.pb\\.*\\.go$" 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lastbackend/toolkit/e0774c46bc5aa97b349ccb594dd8d83edabaef61/CHANGELOG.md -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | 3 | ### Code reviews 4 | 5 | All submissions, including submissions by project members, require review. 6 | 7 | 8 | ==== Coding Style 9 | 10 | 1. All code should be formatted with `gofmt` 11 | 2. All code should follow the guidelines covered in Effective Go and Go Code Review Comments. 12 | 3. Comment the code. Tell us the why, the history and the context. 13 | 4. Variable name length should not be too long. 14 | 15 | Great materials to read: 16 | 17 | * https://golang.org/doc/effective_go.html[Effective Go] 18 | * https://blog.golang.org[The Go Blog] 19 | * https://github.com/golang/go/wiki/CodeReviewComments[Code Review Comments] 20 | 21 | Great tools to use: 22 | 23 | - https://github.com/kisielk/errcheck[ErrCheck] - a program for checking for unchecked errors in Go programs. 24 | - https://github.com/golang/lint[GoLint] - a linter for Go source code. 25 | 26 | ==== Reporting issues 27 | 28 | 1. Tell us version of Toolkit you use 29 | 2. Include the steps required to reproduce the problem, if possible 30 | -------------------------------------------------------------------------------- /cli/cmd/add/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package add 18 | 19 | import ( 20 | tcli "github.com/lastbackend/toolkit/cli/cmd" 21 | "github.com/urfave/cli/v2" 22 | ) 23 | 24 | func init() { 25 | tcli.Register(&cli.Command{ 26 | Name: "add", 27 | Usage: "Add new entity to service template", 28 | Subcommands: []*cli.Command{ 29 | { 30 | Name: "method", 31 | Usage: `Add a new method to service template 32 | Ex: 33 | ` + tcli.App().Name + ` add method --expose-http=get:/demo --proxy=helloworld:/helloworld.Greeter/SayHello Example 34 | ` + tcli.App().Name + ` add method --expose-ws=/demo Example 35 | ` + tcli.App().Name + ` add method --subscribe-ws --proxy=prime:/helloworld.Greeter/SayHello Example 36 | ` + tcli.App().Name + ` add method Example 37 | `, 38 | Action: AddMethod, 39 | Flags: methodFlags, 40 | }, 41 | { 42 | Name: "plugin", 43 | Usage: `Add a new plugin to service template 44 | Ex: 45 | ` + tcli.App().Name + ` add plugin --prefix=redis redis 46 | `, 47 | Action: AddPlugin, 48 | Flags: pluginFlags, 49 | }, 50 | }, 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /cli/cmd/add/plugin.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package add 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "path" 23 | "strings" 24 | 25 | "github.com/lastbackend/toolkit/cli/pkg/scaffold/injector" 26 | "github.com/urfave/cli/v2" 27 | ) 28 | 29 | var pluginFlags = []cli.Flag{ 30 | &cli.StringFlag{ 31 | Name: "path", 32 | Usage: "Path to proto directory", 33 | Value: "./proto", 34 | }, 35 | &cli.StringFlag{ 36 | Name: "prefix", 37 | Usage: "Set prefix for plugin", 38 | }, 39 | } 40 | 41 | func AddPlugin(ctx *cli.Context) error { 42 | arg := ctx.Args().First() 43 | if len(arg) == 0 { 44 | return cli.ShowSubcommandHelp(ctx) 45 | } 46 | 47 | workdir := ctx.String("path") 48 | if path.IsAbs(workdir) { 49 | fmt.Println("must provide a relative path as service name") 50 | return nil 51 | } 52 | 53 | if _, err := os.Stat(workdir); os.IsNotExist(err) { 54 | return fmt.Errorf("%s not exists", workdir) 55 | } 56 | 57 | po := injector.PluginOption{ 58 | Prefix: ctx.String("prefix"), 59 | Name: strings.ToLower(arg), 60 | } 61 | 62 | i := injector.New( 63 | injector.Workdir(workdir), 64 | injector.Plugin(po), 65 | ) 66 | 67 | if err := i.Inject(); err != nil { 68 | return err 69 | } 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /cli/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cmd 18 | 19 | import ( 20 | "os" 21 | "runtime/debug" 22 | 23 | "github.com/urfave/cli/v2" 24 | ) 25 | 26 | type Option func(o *Options) 27 | 28 | type Options struct { 29 | Name string 30 | Description string 31 | Version string 32 | } 33 | 34 | type CLI interface { 35 | App() *cli.App 36 | Options() Options 37 | Run() error 38 | } 39 | 40 | type cmd struct { 41 | app *cli.App 42 | opts Options 43 | } 44 | 45 | func (c *cmd) App() *cli.App { 46 | return c.app 47 | } 48 | 49 | func (c *cmd) Options() Options { 50 | return c.opts 51 | } 52 | 53 | func (c *cmd) Run() error { 54 | return c.app.Run(os.Args) 55 | } 56 | 57 | func NewCLI(opts ...Option) CLI { 58 | options := Options{} 59 | 60 | for _, o := range opts { 61 | o(&options) 62 | } 63 | 64 | if len(options.Name) == 0 { 65 | options.Name = name 66 | } 67 | if len(options.Description) == 0 { 68 | options.Description = description 69 | } 70 | if len(options.Version) == 0 { 71 | if bi, ok := debug.ReadBuildInfo(); ok { 72 | options.Version = bi.Main.Version 73 | } else { 74 | options.Version = version 75 | } 76 | } 77 | 78 | c := new(cmd) 79 | c.app = cli.NewApp() 80 | c.opts = options 81 | c.app.Name = c.opts.Name 82 | c.app.Usage = c.opts.Description 83 | c.app.Version = c.opts.Version 84 | c.app.EnableBashCompletion = true 85 | c.app.HideVersion = len(options.Version) == 0 86 | 87 | return c 88 | } 89 | -------------------------------------------------------------------------------- /cli/cmd/default.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cmd 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/urfave/cli/v2" 24 | ) 25 | 26 | var ( 27 | DefaultCLI = NewCLI() 28 | 29 | name = os.Args[0] 30 | description = "The Go Last.Backend Toolkit CLI tool" 31 | version = "latest" 32 | ) 33 | 34 | func Register(commands ...*cli.Command) { 35 | app := DefaultCLI.App() 36 | app.Commands = append(app.Commands, commands...) 37 | } 38 | 39 | func App() *cli.App { 40 | return DefaultCLI.App() 41 | } 42 | 43 | func Run() { 44 | if err := DefaultCLI.Run(); err != nil { 45 | fmt.Println(err.Error()) 46 | os.Exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cli/cmd/describe/describe.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package add 18 | 19 | import ( 20 | "fmt" 21 | "github.com/lastbackend/toolkit/pkg/util/filesystem" 22 | "os" 23 | "text/tabwriter" 24 | 25 | "github.com/jhump/protoreflect/desc/protoparse" 26 | tcli "github.com/lastbackend/toolkit/cli/cmd" 27 | "github.com/urfave/cli/v2" 28 | ) 29 | 30 | var flags = []cli.Flag{ 31 | &cli.StringFlag{ 32 | Name: "path", 33 | Usage: "Path to proto dir", 34 | Value: "./proto", 35 | }, 36 | } 37 | 38 | func init() { 39 | tcli.Register(&cli.Command{ 40 | Name: "describe", 41 | Usage: "Get describe service info", 42 | Action: Describe, 43 | Flags: flags, 44 | }) 45 | } 46 | 47 | func Describe(ctx *cli.Context) error { 48 | files, err := filesystem.WalkMatch(ctx.String("path"), "*.proto") 49 | if err != nil { 50 | return err 51 | } 52 | 53 | parser := protoparse.Parser{} 54 | desc, err := parser.ParseFilesButDoNotLink(files...) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | w := new(tabwriter.Writer) 60 | w.Init(os.Stdout, 8, 8, 0, '\t', 0) 61 | defer w.Flush() 62 | 63 | for _, d := range desc { 64 | for _, svc := range d.Service { 65 | fmt.Fprintf(w, "\n Service: %s\n", svc.GetName()) 66 | for index, meth := range svc.Method { 67 | if !meth.GetClientStreaming() && !meth.GetClientStreaming() { 68 | fmt.Fprintf(w, "\n %d. rpc %s(ctx context.Context, req *%s) returns (resp *%s, err error) \t", index+1, meth.GetName(), meth.GetInputType(), meth.GetOutputType()) 69 | } else if meth.GetClientStreaming() { 70 | fmt.Fprintf(w, "\n %d. rpc %s(req *%s) returns (stream *%s, error) \t", index+1, meth.GetName(), meth.GetInputType(), meth.GetOutputType()) 71 | } else { 72 | fmt.Fprintf(w, "\n %d. rpc %s(stream *%s) returns error \t", index+1, meth.GetName(), meth.GetInputType()) 73 | } 74 | } 75 | fmt.Fprintf(w, "\n") 76 | } 77 | } 78 | fmt.Fprintf(w, "\n") 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /cli/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/lastbackend/toolkit/cli/cmd" 21 | 22 | // register commands 23 | _ "github.com/lastbackend/toolkit/cli/cmd/add" 24 | _ "github.com/lastbackend/toolkit/cli/cmd/describe" 25 | _ "github.com/lastbackend/toolkit/cli/cmd/init" 26 | ) 27 | 28 | func main() { 29 | cmd.Run() 30 | } 31 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/generator/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package generator 18 | 19 | import ( 20 | "os" 21 | "path/filepath" 22 | "text/template" 23 | 24 | ustrings "github.com/lastbackend/toolkit/pkg/util/strings" 25 | ) 26 | 27 | type Generator interface { 28 | Generate([]File) error 29 | Options() Options 30 | } 31 | 32 | type generator struct { 33 | opts Options 34 | } 35 | 36 | type File struct { 37 | Path string 38 | Template string 39 | FileMode os.FileMode 40 | } 41 | 42 | func (g *generator) Generate(files []File) error { 43 | for _, file := range files { 44 | fp := filepath.Join(g.opts.Workdir, file.Path) 45 | dir := filepath.Dir(fp) 46 | 47 | if file.Template == "" { 48 | dir = fp 49 | } 50 | 51 | if _, err := os.Stat(dir); os.IsNotExist(err) { 52 | if err := os.MkdirAll(dir, 0755); err != nil { 53 | return err 54 | } 55 | } 56 | 57 | if file.Template == "" { 58 | continue 59 | } 60 | 61 | fn := template.FuncMap{ 62 | "title": ustrings.Title, 63 | "lower": ustrings.ToLower, 64 | "upper": ustrings.ToUpper, 65 | "camel": ustrings.ToCamel, 66 | "git": ustrings.GitParse, 67 | "dehyphen": ustrings.DeHyphenFunc, 68 | "lowerhyphen": ustrings.LowerHyphenFunc, 69 | "tohyphen": ustrings.ToHyphen, 70 | } 71 | 72 | t, err := template.New(fp).Funcs(fn).Parse(file.Template) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | f, err := os.Create(fp) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | if file.FileMode != 0 { 83 | if err := f.Chmod(file.FileMode); err != nil { 84 | return err 85 | } 86 | } 87 | 88 | err = t.Execute(f, g.opts) 89 | if err != nil { 90 | return err 91 | } 92 | } 93 | 94 | return nil 95 | } 96 | 97 | func (g *generator) Options() Options { 98 | return g.opts 99 | } 100 | 101 | func New(opts ...Option) Generator { 102 | var options Options 103 | for _, o := range opts { 104 | o(&options) 105 | } 106 | return &generator{ 107 | opts: options, 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/generator/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package generator 18 | 19 | type Options struct { 20 | Service string 21 | Vendor string 22 | Workdir string 23 | 24 | RedisPlugin bool 25 | RabbitMQPlugin bool 26 | PostgresPGPlugin bool 27 | PostgresGORMPlugin bool 28 | PostgresPlugin bool 29 | CentrifugePlugin bool 30 | annotationImport bool 31 | } 32 | 33 | func (o Options) AnnotationImport() bool { 34 | return o.annotationImport 35 | } 36 | 37 | type Option func(o *Options) 38 | 39 | func Service(s string) Option { 40 | return func(o *Options) { 41 | o.Service = s 42 | } 43 | } 44 | func Vendor(s string) Option { 45 | return func(o *Options) { 46 | o.Vendor = s 47 | } 48 | } 49 | 50 | func Workdir(d string) Option { 51 | return func(o *Options) { 52 | o.Workdir = d 53 | } 54 | } 55 | 56 | func RedisPlugin(b bool) Option { 57 | return func(o *Options) { 58 | if b { 59 | o.annotationImport = true 60 | } 61 | o.RedisPlugin = b 62 | } 63 | } 64 | 65 | func RabbitMQPlugin(b bool) Option { 66 | return func(o *Options) { 67 | if b { 68 | o.annotationImport = true 69 | } 70 | o.RabbitMQPlugin = b 71 | } 72 | } 73 | 74 | func PostgresPGPlugin(b bool) Option { 75 | return func(o *Options) { 76 | if b { 77 | o.annotationImport = true 78 | } 79 | o.PostgresPGPlugin = b 80 | } 81 | } 82 | 83 | func PostgresGORMPlugin(b bool) Option { 84 | return func(o *Options) { 85 | if b { 86 | o.annotationImport = true 87 | } 88 | o.PostgresGORMPlugin = b 89 | } 90 | } 91 | 92 | func PostgresPlugin(b bool) Option { 93 | return func(o *Options) { 94 | if b { 95 | o.annotationImport = true 96 | } 97 | o.PostgresPlugin = b 98 | } 99 | } 100 | 101 | func CentrifugePlugin(b bool) Option { 102 | return func(o *Options) { 103 | if b { 104 | o.annotationImport = true 105 | } 106 | o.CentrifugePlugin = b 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/injector/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package injector 18 | 19 | type Options struct { 20 | Workdir string 21 | Methods []MethodOption 22 | Plugins []PluginOption 23 | } 24 | 25 | type Option func(o *Options) 26 | 27 | func Workdir(d string) Option { 28 | return func(o *Options) { 29 | o.Workdir = d 30 | } 31 | } 32 | 33 | type MethodOption struct { 34 | Name string 35 | ExposeHTTP *ExposeHTTP 36 | ExposeWS *ExposeWS 37 | RPCProxy *RpcProxy 38 | SubscriptionWS bool 39 | } 40 | 41 | func Method(m MethodOption) Option { 42 | return func(o *Options) { 43 | if o.Methods == nil { 44 | o.Methods = make([]MethodOption, 0) 45 | } 46 | o.Methods = append(o.Methods, m) 47 | } 48 | } 49 | 50 | type PluginOption struct { 51 | Name string 52 | Prefix string 53 | } 54 | 55 | func Plugin(p PluginOption) Option { 56 | return func(o *Options) { 57 | if o.Plugins == nil { 58 | o.Plugins = make([]PluginOption, 0) 59 | } 60 | o.Plugins = append(o.Plugins, p) 61 | } 62 | } 63 | 64 | type ImportOption struct { 65 | Path string 66 | } 67 | 68 | type MessageOption struct { 69 | Name string 70 | } 71 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/injector/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package injector 18 | 19 | type ExposeHTTP struct { 20 | Method string 21 | Path string 22 | } 23 | 24 | type ExposeWS struct { 25 | Method string 26 | Path string 27 | } 28 | 29 | type SubscriptionWS struct { 30 | Service string 31 | Method string 32 | } 33 | 34 | type RpcProxy struct { 35 | Service string 36 | Method string 37 | } 38 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/templates/docker.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // Dockerfile is the Dockerfile template used for new services. 20 | var Dockerfile = `# Script generated by toolkit CLI. 21 | FROM golang:alpine as builder 22 | 23 | # Set necessary environmet variables needed for our image 24 | ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 25 | 26 | # Configure golang 27 | ENV GOPATH /go 28 | ENV GOROOT /usr/local/go 29 | ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH 30 | 31 | # Install dependencies 32 | RUN apk --update add --no-cache ca-certificates \ 33 | linux-headers \ 34 | gcc \ 35 | libtool \ 36 | musl-dev 37 | 38 | RUN set -ex \ 39 | && apk add --no-cache --virtual .build-deps \ 40 | bash \ 41 | git \ 42 | curl \ 43 | protoc \ 44 | make \ 45 | \ 46 | && rm -rf /*.patch 47 | 48 | WORKDIR $GOPATH/src/{{.Service}} 49 | 50 | # Build Go binary 51 | COPY scripts ./scripts 52 | COPY Makefile go.mod go.sum ./ 53 | 54 | RUN make init && go mod download 55 | 56 | COPY . . 57 | 58 | RUN make tidy build 59 | 60 | 61 | # Deployment container 62 | FROM golang:alpine 63 | 64 | COPY --from=builder /go/src/{{.Service}}/{{.Service}} /{{.Service}} 65 | 66 | ENTRYPOINT ["/{{.Service}}"] 67 | ` 68 | 69 | // DockerIgnore is the .dockerignore template used for new services. 70 | var DockerIgnore = `.gitignore 71 | Dockerfile 72 | ` 73 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/templates/gitignore.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // GitIgnore is the .gitignore template used for new services. 20 | var GitIgnore = `# This file generated by toolkit CLI. 21 | # Binaries for programs and plugins 22 | # Develop tools 23 | /.vscode/ 24 | /.idea/ 25 | 26 | # Binaries for programs and plugins 27 | *.exe 28 | *.exe~ 29 | *.dll 30 | *.so 31 | *.dylib 32 | 33 | # Folders 34 | _obj 35 | _test 36 | _build 37 | 38 | # Architecture specific extensions/prefixes 39 | *.[568vq] 40 | [568vq].out 41 | 42 | *.cgo1.go 43 | *.cgo2.c 44 | _cgo_defun.c 45 | _cgo_gotypes.go 46 | _cgo_export.* 47 | 48 | # Test binary, build with 'go test -c' 49 | *.test 50 | 51 | # Output of the go coverage tool, specifically when used with LiteIDE 52 | *.out 53 | 54 | # vim temp files 55 | *~ 56 | *.swp 57 | *.swo 58 | 59 | # Ignore the service binary to vcs 60 | {{.Service}} 61 | ` 62 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/templates/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // Main is the main template used for new services. 20 | var Main = `package main 21 | 22 | import ( 23 | "context" 24 | "os" 25 | 26 | servicepb "{{.Vendor}}{{lower .Service}}/gen" 27 | "{{.Vendor}}{{lower .Service}}/config" 28 | "{{.Vendor}}{{lower .Service}}/internal/server" 29 | ) 30 | 31 | func main() { 32 | 33 | app, err := servicepb.NewExampleService("example", 34 | runtime.WithVersion("0.1.0"), 35 | runtime.WithDescription("Example microservice"), 36 | runtime.WithEnvPrefix("LB"), 37 | ) 38 | if err != nil { 39 | fmt.Println(err) 40 | } 41 | 42 | // Config management 43 | cfg := config.New() 44 | 45 | if err := app.RegisterConfig(cfg); err != nil { 46 | app.Log().Error(err) 47 | return 48 | } 49 | 50 | // Add packages 51 | app.RegisterPackage(repository.NewRepository, controller.NewController) 52 | 53 | svc.SetServer(server.NewServer) 54 | 55 | if err := app.Start(context.Background()); err != nil { 56 | app.Log().Errorf("could not run the service %v", err) 57 | os.Exit(1) 58 | return 59 | } 60 | 61 | // time.Sleep(10 * time.Second) 62 | // app.Stop(context.Background()) 63 | 64 | app.Log().Info("graceful stop") 65 | } 66 | ` 67 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/templates/makefile.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // Makefile is the Makefile template used for new services. 20 | var Makefile = `GOPATH:=$(shell go env GOPATH) 21 | 22 | .PHONY: init 23 | init: 24 | @sh ./scripts/bootstrap.sh 25 | 26 | .PHONY: proto 27 | proto: 28 | @sh ./scripts/generate.sh 29 | 30 | .PHONY: update 31 | update: 32 | @go get -u 33 | 34 | .PHONY: tidy 35 | tidy: 36 | @go mod tidy 37 | 38 | .PHONY: build 39 | build: 40 | @go build -o {{.Service}} *.go 41 | 42 | .PHONY: test 43 | test: 44 | @go test -v ./... -cover 45 | 46 | .PHONY: gotest 47 | gotest: 48 | @gotestsum --format testname 49 | 50 | .PHONY: lint 51 | lint: 52 | @golangci-lint run -v 53 | ` 54 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/templates/module.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // Module is the go.mod template used for new services. 20 | var Module = `module {{.Vendor}}{{.Service}} 21 | 22 | go 1.19 23 | 24 | {{ if not .Vendor }} 25 | replace {{lower .Service}} => ./ 26 | {{ end }} 27 | ` 28 | -------------------------------------------------------------------------------- /cli/pkg/scaffold/templates/service.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // ServiceConfig is the config template used for new services. 20 | var ServiceConfig = `package config 21 | 22 | type Config struct { 23 | Nested NestedConfig 24 | } 25 | 26 | type NestedConfig struct { 27 | Demo string 28 | } 29 | 30 | func New() *Config { 31 | c := new(Config) 32 | return c 33 | } 34 | ` 35 | 36 | // ServiceServer is the server template used for new services. 37 | var ServiceServer = `package server 38 | 39 | import ( 40 | "context" 41 | 42 | "{{.Vendor}}{{lower .Service}}/config" 43 | servicepb "{{.Vendor}}{{lower .Service}}/gen" 44 | typespb "{{.Vendor}}{{lower .Service}}/gen" 45 | ) 46 | 47 | type Handlers struct { 48 | svc servicepb.Service 49 | cfg *config.Config 50 | } 51 | 52 | func (h Handlers) HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest) (*typespb.HelloWorldResponse, error) { 53 | //TODO implement me 54 | panic("implement me") 55 | } 56 | 57 | func NewServer(svc servicepb.Service, cfg *config.Config) servicepb.{{camel .Service}}RpcServer { 58 | return &Handlers{ 59 | svc: svc, 60 | cfg: cfg, 61 | } 62 | } 63 | ` 64 | -------------------------------------------------------------------------------- /docs/lastbackend.toolkit.flow.drawio: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/lastbackend.toolkit.flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lastbackend/toolkit/e0774c46bc5aa97b349ccb594dd8d83edabaef61/docs/lastbackend.toolkit.flow.png -------------------------------------------------------------------------------- /docs/lastbackend.toolkit.schema.drawio: -------------------------------------------------------------------------------- 1 | 7V1Lk6M2EP41Pk6K9+M4O7NJDtmqTe0h2dxkkDG7GLFCnhnn10cYgQHhGXmwaTnei8sSL9H9datfEgv7YfPyG0XF+hOJcbawjPhlYT8uLCvwDf5bdezqDj+w6o6EpnHdZR46vqT/YtEprku2aYzL3omMkIylRb8zInmOI9brQ5SS5/5pK5L1n1qgBEsdXyKUyb1/pTFbi9ey/EP/7zhN1s2TTS+sj2xQc7J4k3KNYvLc6bI/LuwHSgir/21eHnBW0a6hS33dr0eOtgOjOGcqF/yJnv8Jf7j32eOduQ0+hX8Rl96Z7ejYrnllHHMKiCahbE0SkqPs46H3AyXbPMbVfQ3eOpzzByEF7zR55zfM2E6wE20Z4V1rtsnEUT5kuvtbXL9vfK0av7hN8/Gle/BxJ1oxKtf751Y3WZGciSeYAW/XL1KN/iiFRFdJtjTCr5FFIA3RBLNXznO8lpFcADDZYD5afiHFGWLpU38gSEAxac87cIv/EQw7hXn1fZ9QthVPqkTje8oWlpfxYX9YUv4vqf6VmD6l/JWHvD5wsqLo8zpl+EuB9rR55vLc55p4HKYMv7xOYpki4gJbwE1oA7NRD88H2Wohue7IlWdciIa+C4H/M0LVVoSqb0FC1ZagyhU2hxEDR2RoDCDpQEPS80FVsnmaSj6n1rUUsewa4yydSe1aEpiLbJukeQkOZtfrg9kNoMFshbcKZkcRzOZUxSwu/UxSPsYWCq3lLaAQ2gMW1wMTVw243A5jgpQ4kpTEiKElKuGtEN/t08ZzocXEhjXDAcXEvXExcSUxWVLyHVPthMS3wIXElIhyI0Li3biQeJKQlLuS4Y1+QhJCC4l3swaXP5eQTPMefAnLjKK8LDjVtYOzaRgutNKH8YZfUtbGJ/n/Dpp56wDmqrHrIhtYBgJVGYANXAaSDFBckuxJA7MntHTT6GYoUWvL0gw+2jDUFoEHTSo7+Kks2jTZmzognKgDxo3FoQAF4YDbtRaTjMU3rU7pRhe2Opv36Fqde2aAC55EYnDBs2AED37GVZc2UKuzuXE3AYOitX6hOPiIdZP/uT0oW6pQ9kGhbElQTmgRgSN5qJQ18J2cWw2YWapZ8clQPmK6OAMsmO7MtouccF8zVmgoJD60kNgwATMtfYa3BUs5q3kZ58IL+vgJh7C4tFzJWc1vCCcaxi3aGD2cYHk/BUtdsJTzoC6o8SWnKylaLlO2+aGdBGiQsAQpLrxWCVBOcjqgEiDnIgtSsoRi+IDsUAI0qGtxfkqAugSoZjCPwWAmCZAzmBTHqX7wh48leTAmEDyUHVUHHLaU1xmvS6ckyzQw6aVqXhMcz/at4lnV7wXGs+yecoB+r9a46QZmB3ydhXuzYFb1NSeDeTyI45hDveb2b3HhII4jO7HV+jgNNb4Nvj7Okd2d7RLTu4Tw261ewClmDZdvgVPMtW5VrYTXMUfK5VTc5lulCTiWJek3oLHc3PjmsNwETjRfAd4Ms2vvUfKUxvD2nh3qZu/5t5oNd1WdF2Awy85Llq5wtIsy/eCsgZ2h1TJxa048K/svoJkCHyT2DUFn2L0l/KsKsja5iSZPoZKbWKVZ9kAyQvdvZ6+CCEcR7y9ZtVS0c2QZuI5bXdHbKAcCEnVkFWwqkT18kt+VfNTwa6yGVj78hiOuVsXb/pwziWrOGXgmgZ3qu7P71+5BjRgEOwWZ1lX5Fq9uo3b6ZAPB78nzy/7Se0rRrnNCUYWcy86dB5FrfxBttEQo+dgSpuH5rtD1B6zVIzhreNuVQ7b7yY/Al/8O5z7LhK7+9S2JWFGWghPKHxaQgxsJpnFVZvYbWi1GOFiNajUvCvBydUETOlRdo+1MLaGeyG+t4gvnnNIUmA/Cb9DKXtPQygu4BX6D+hSmcVUbxfwf+A0aEjFHtpGQAJBw/hZHzRexxzZaNqcbp5o1HHRvh9HdMbumreU8vwWoQJeTzD9EIwFOb8SdWq2scXcq9pae66maj6/xWCZ/k4NTJbZxMSvSBAmKz689XpfBN5VKcJnlc+ag8soeMvrClVeBXG87W+XVaRLTlBdpt2F5IGdJ1yiP56lXfhcN7UA7GsrpAQitNIMaUS3DD0Bt0WBkf8fZPlhwFr0wNrXOjGl5VU53KYMhB6g1JaUNvsohkLenm61C8DyzFHjhYDBaZHlA40QTe0Y0gq8hazwmkDUdZ6GhA74MNZS3v0JFkaURn9xILtFx/2GSHrH6vkBOcjxwHEQXytIk582IU5AD3f5Q0Zc/JrsXBzZpHO+thzHu9C2KvZ8iBjXmt1yOgeawdlPmnz3qN7qX4p+skTX41NH7hEM3w8E0YXPX3YqPr4tu7E+XSOBcRniNcrgAIYgTdD0B4akwUC1tgIYByLLIM7PzqHY5Q+3SVByo7vIwGQd6xgDb5181wGbAiWpeCVpfwGxydBDwk+T76mDQmOK6w8CQXSwZF3l8X32Td9H6Sz12DKT1M2Lch8r3PZZht3xovsNrjXtMp30R9jQbvps4G82b1X1TNfR7d0wf+m1S0vTdqp03D18qrk8/fO7Z/vgf -------------------------------------------------------------------------------- /docs/lastbackend.toolkit.schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lastbackend/toolkit/e0774c46bc5aa97b349ccb594dd8d83edabaef61/docs/lastbackend.toolkit.schema.png -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ### 1. GRPC service 4 | 5 | ---- 6 | 7 | Follow these run example: 8 | 9 | Run the GRPC service: 10 | 11 | ```console 12 | go run service/main.go 13 | ``` 14 | 15 | if you see it: 16 | ```console 17 | ... 18 | server listening at [::]:50005 19 | ... 20 | ``` 21 | 22 | _Great, you have succeeded!_ 23 | 24 | 25 | ### 2. HTTP Gateway 26 | 27 | ---- 28 | 29 | Follow these run example: 30 | 31 | Run the Gateway server: 32 | 33 | ```console 34 | env GTW_GATEWAY_SERVER_PORT=8082 go run main.go 35 | ``` 36 | 37 | Run gRPC Hello World Server 38 | 39 | ```console 40 | go run helloworld/main.go 41 | ``` 42 | 43 | Make a request to the Hello World Server 44 | 45 | ```console 46 | curl -i -X POST http://localhost:8082/hello -H 'Content-Type: application/json' --data '{"name":"world"}' 47 | 48 | Response: 49 | HTTP/1.1 200 OK 50 | Content-Type: application/json 51 | Date: Wed, 01 Feb 2023 20:40:03 GMT 52 | Content-Length: 25 53 | 54 | {"message":"Hello world"} 55 | ``` 56 | 57 | Call custom handler 58 | 59 | ```console 60 | curl -i -X GET http://localhost:8080/health -H 'Content-Type: application/json' 61 | 62 | Response: 63 | HTTP/1.1 200 OK 64 | Content-Type: application/json 65 | Date: Wed, 01 Feb 2023 20:44:57 GMT 66 | Content-Length: 15 67 | 68 | {"alive": true} 69 | ``` 70 | 71 | 72 | ### 3. WSS Proxy 73 | 74 | ---- 75 | 76 | Follow these run example: 77 | 78 | Run the WSS server: 79 | 80 | ```console 81 | go run wss/main.go 82 | ``` 83 | 84 | Run gRPC Hello World Server 85 | 86 | ```console 87 | go run helloworld/main.go 88 | ``` 89 | 90 | Make a request to the Hello World Server 91 | 92 | ```console 93 | curl -i -X POST http://localhost:8080/hello -H 'Content-Type: application/json' --data '{"name":"world"}' 94 | 95 | Response: 96 | HTTP/1.1 200 OK 97 | Content-Type: application/json 98 | Date: Wed, 01 Feb 2023 20:40:03 GMT 99 | Content-Length: 24 100 | 101 | {"message":"Hello world"} 102 | ``` 103 | 104 | Call wss events handler 105 | 106 | ```console 107 | websocat ws://localhost:8008/events 108 | {"type":"HelloWorld","payload":{"name":"world"}} 109 | {"message":"Hello world"} 110 | ``` 111 | 112 | 113 | ### 4. HTTP server 114 | 115 | ---- 116 | 117 | Follow these run example: 118 | 119 | Run the HTTP server: 120 | 121 | ```console 122 | env LB_HTTP_SERVER_PORT=8082 go run http/main.go 123 | ``` 124 | 125 | Make a request to the HTTP Server 126 | 127 | ```console 128 | curl -X GET 'http://localhost:8082/health' 129 | 130 | Response: 131 | HTTP/1.1 200 OK 132 | Content-Type: application/json 133 | Date: Wed, 01 Feb 2023 20:44:57 GMT 134 | Content-Length: 15 135 | 136 | {"alive": true} 137 | ``` 138 | -------------------------------------------------------------------------------- /examples/gateway/apis/server.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package gateway; 4 | 5 | option go_package = "github.com/lastbackend/toolkit/examples/gateway/gen/server;serverpb"; 6 | 7 | import "google/api/annotations.proto"; 8 | 9 | import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto"; 10 | import "github.com/lastbackend/toolkit/examples/helloworld/apis/helloworld.proto"; 11 | 12 | // ===================================================== 13 | // HTTP proxy methods 14 | // ===================================================== 15 | 16 | // Example methods 17 | service ProxyGateway { 18 | option (toolkit.runtime) = { 19 | servers: [GRPC, HTTP] 20 | }; 21 | option (toolkit.server) = { 22 | middlewares: [] 23 | }; 24 | rpc HelloWorld(helloworld.HelloRequest) returns (helloworld.HelloReply) { 25 | option (toolkit.route) = { 26 | middlewares: [], 27 | http_proxy: { 28 | service: "helloworld", 29 | method: "/helloworld.Greeter/SayHello", 30 | } 31 | }; 32 | option (google.api.http) = { 33 | post: "/hello" 34 | }; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /examples/gateway/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | type Config struct { 20 | Demo string 21 | } 22 | 23 | func New() *Config { 24 | c := new(Config) 25 | return c 26 | } 27 | -------------------------------------------------------------------------------- /examples/gateway/gen/server/client/server.pb.toolkit.rpc.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT. 2 | // source: github.com/lastbackend/toolkit/examples/gateway/apis/server.proto 3 | 4 | package serverpb 5 | 6 | import ( 7 | context "context" 8 | 9 | "github.com/lastbackend/toolkit/examples/helloworld/gen" 10 | client "github.com/lastbackend/toolkit/pkg/client" 11 | emptypb "google.golang.org/protobuf/types/known/emptypb" 12 | ) 13 | 14 | // Suppress "imported and not used" errors 15 | var _ context.Context 16 | var _ emptypb.Empty 17 | 18 | // Client gRPC API for ProxyGateway service 19 | func NewProxyGatewayRPCClient(service string, c client.GRPCClient) ProxyGatewayRPCClient { 20 | return &proxygatewayGrpcRPCClient{service, c} 21 | } 22 | 23 | // Client gRPC API for ProxyGateway service 24 | type ProxyGatewayRPCClient interface { 25 | HelloWorld(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) 26 | } 27 | 28 | type proxygatewayGrpcRPCClient struct { 29 | service string 30 | cli client.GRPCClient 31 | } 32 | 33 | func (c *proxygatewayGrpcRPCClient) HelloWorld(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) { 34 | resp := new(servicepb.HelloReply) 35 | if err := c.cli.Call(ctx, c.service, ProxyGateway_HelloWorldMethod, req, resp, opts...); err != nil { 36 | return nil, err 37 | } 38 | return resp, nil 39 | } 40 | 41 | func (proxygatewayGrpcRPCClient) mustEmbedUnimplementedProxyGatewayClient() {} 42 | 43 | // Client methods for ProxyGateway service 44 | const ( 45 | ProxyGateway_HelloWorldMethod = "/gateway.ProxyGateway/HelloWorld" 46 | ) 47 | -------------------------------------------------------------------------------- /examples/gateway/gen/server/server.pb.validate.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-validate. DO NOT EDIT. 2 | // source: github.com/lastbackend/toolkit/examples/gateway/apis/server.proto 3 | 4 | package serverpb 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/mail" 12 | "net/url" 13 | "regexp" 14 | "sort" 15 | "strings" 16 | "time" 17 | "unicode/utf8" 18 | 19 | "google.golang.org/protobuf/types/known/anypb" 20 | ) 21 | 22 | // ensure the imports are used 23 | var ( 24 | _ = bytes.MinRead 25 | _ = errors.New("") 26 | _ = fmt.Print 27 | _ = utf8.UTFMax 28 | _ = (*regexp.Regexp)(nil) 29 | _ = (*strings.Reader)(nil) 30 | _ = net.IPv4len 31 | _ = time.Duration(0) 32 | _ = (*url.URL)(nil) 33 | _ = (*mail.Address)(nil) 34 | _ = anypb.Any{} 35 | _ = sort.Sort 36 | ) 37 | -------------------------------------------------------------------------------- /examples/gateway/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate ./scripts/generate.sh 4 | -------------------------------------------------------------------------------- /examples/gateway/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | "os" 25 | 26 | "github.com/lastbackend/toolkit" 27 | servicepb "github.com/lastbackend/toolkit/examples/gateway/gen/server" 28 | typespb "github.com/lastbackend/toolkit/examples/helloworld/gen" 29 | "github.com/lastbackend/toolkit/pkg/runtime" 30 | ) 31 | 32 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { 33 | w.Header().Set("Content-Type", "application/json") 34 | w.WriteHeader(http.StatusOK) 35 | if _, err := io.WriteString(w, `{"alive": true}`); err != nil { 36 | fmt.Println(err) 37 | } 38 | } 39 | func main() { 40 | // define service with name and options 41 | app, err := servicepb.NewProxyGatewayService("gateway", 42 | runtime.WithVersion("0.1.0"), 43 | runtime.WithDescription("Example gateway microservice"), 44 | runtime.WithEnvPrefix("GTW"), 45 | ) 46 | if err != nil { 47 | fmt.Println(err) 48 | } 49 | 50 | // Add server 51 | app.Server().HTTP().AddHandler(http.MethodGet, "/health", HealthCheckHandler) 52 | 53 | app.Server().GRPC().SetService(newServer) 54 | 55 | // Logger settings 56 | app.Log().Info("Run microservice") 57 | 58 | // Service run 59 | if err := app.Start(context.Background()); err != nil { 60 | app.Log().Errorf("could not run the service %v", err) 61 | os.Exit(1) 62 | return 63 | } 64 | 65 | app.Log().Info("graceful stop") 66 | } 67 | 68 | type Handlers struct { 69 | app toolkit.Service 70 | } 71 | 72 | // Implement server 73 | func newServer(app toolkit.Service) servicepb.ProxyGatewayRpcServer { 74 | return &Handlers{ 75 | app: app, 76 | } 77 | } 78 | 79 | func (h *Handlers) HelloWorld(ctx context.Context, req *typespb.HelloRequest) (*typespb.HelloReply, error) { 80 | return &typespb.HelloReply{ 81 | Message: "hello", 82 | }, nil 83 | } 84 | -------------------------------------------------------------------------------- /examples/gateway/middleware/example.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package middleware 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | ) 23 | 24 | // ExampleMiddleware - show request info middleware 25 | func ExampleMiddleware(h http.Handler) http.Handler { 26 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | fmt.Println("Call: ExampleMiddleware") 28 | h.ServeHTTP(w, r) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /examples/gateway/middleware/request.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "strings" 11 | "sync/atomic" 12 | ) 13 | 14 | // KeyPEMBlock to use when setting the request UserID. 15 | type ctxKeyRequestID int 16 | 17 | // RequestIDKey is the key that holds th unique request UserID in a request context. 18 | const RequestIDKey ctxKeyRequestID = 0 19 | 20 | var ( 21 | // prefix is const prefix for request UserID 22 | prefix string 23 | 24 | // reqID is counter for request UserID 25 | reqID uint64 26 | ) 27 | 28 | // init Initializes constant part of request UserID 29 | func init() { 30 | hostname, err := os.Hostname() 31 | if hostname == "" || err != nil { 32 | hostname = "localhost" 33 | } 34 | var buf [12]byte 35 | var b64 string 36 | for len(b64) < 10 { 37 | _, _ = rand.Read(buf[:]) 38 | b64 = base64.StdEncoding.EncodeToString(buf[:]) 39 | b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) 40 | } 41 | 42 | prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) 43 | } 44 | 45 | // RequestID is a middleware that injects a request UserID into the context of each 46 | // request. A request UserID is a string of the form "host.example.com/random-0001", 47 | // where "random" is a base62 random string that uniquely identifies this go 48 | // process, and where the last number is an atomically incremented request 49 | // counter. 50 | func RequestID(h http.Handler) http.Handler { 51 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 52 | id := atomic.AddUint64(&reqID, 1) 53 | ctx := r.Context() 54 | ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, id)) 55 | h.ServeHTTP(w, r.WithContext(ctx)) 56 | }) 57 | } 58 | 59 | // GetReqID returns a request UserID from the given context if one is present. 60 | // Returns the empty string if a request UserID cannot be found. 61 | func GetReqID(ctx context.Context) string { 62 | if ctx == nil { 63 | return "" 64 | } 65 | if reqID, ok := ctx.Value(RequestIDKey).(string); ok { 66 | return reqID 67 | } 68 | return "" 69 | } 70 | -------------------------------------------------------------------------------- /examples/gateway/scripts/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/gateway 4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE 5 | PROTO_DIR=$ROOT_DIR/apis 6 | 7 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete 8 | 9 | mkdir -p $PROTO_DIR/google/api 10 | mkdir -p $PROTO_DIR/validate 11 | 12 | curl -s -f -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; } 13 | curl -s -f -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto || { echo "Error: Request failed"; exit 1; } 14 | curl -s -f -o $PROTO_DIR/validate/validate.proto -L https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/main/validate/validate.proto || { echo "Error: Request failed"; exit 1; } 15 | 16 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/router/options) 17 | 18 | # Generate for toolkit service 19 | for PROTO in $PROTOS; do 20 | protoc \ 21 | -I. \ 22 | -I$GOPATH/src \ 23 | -I$PROTO_DIR \ 24 | -I$(dirname $PROTO) \ 25 | --validate_out=lang=go:$GOPATH/src \ 26 | --go_out=:$GOPATH/src \ 27 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \ 28 | --toolkit_out=$GOPATH/src \ 29 | $PROTO 30 | done 31 | 32 | rm -r $PROTO_DIR/google 33 | rm -r $PROTO_DIR/validate 34 | 35 | echo "Generation is ok" 36 | -------------------------------------------------------------------------------- /examples/helloworld/apis/helloworld.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package helloworld; 4 | 5 | option go_package = "github.com/lastbackend/toolkit/examples/helloworld/gen;servicepb"; 6 | 7 | // The greeting service definition. 8 | service Greeter { 9 | // Sends a greeting 10 | rpc SayHello (HelloRequest) returns (HelloReply) {} 11 | } 12 | 13 | // The request message containing the user's name. 14 | message HelloRequest { 15 | string name = 1; 16 | } 17 | 18 | // The response message containing the greetings 19 | message HelloReply { 20 | string message = 1; 21 | } 22 | -------------------------------------------------------------------------------- /examples/helloworld/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate ./scripts/generate.sh 4 | -------------------------------------------------------------------------------- /examples/helloworld/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "net" 25 | 26 | pb "github.com/lastbackend/toolkit/examples/helloworld/gen" 27 | "google.golang.org/grpc" 28 | ) 29 | 30 | var ( 31 | port = flag.Int("port", 9000, "The server port") 32 | ) 33 | 34 | // server is used to implement lastbackend.helloworld.GreeterServer. 35 | type server struct { 36 | pb.UnimplementedGreeterServer 37 | } 38 | 39 | // SayHello implements lastbackend.helloworld.GreeterServer 40 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 41 | log.Printf("Received: %v", in.GetName()) 42 | return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil 43 | } 44 | 45 | func main() { 46 | flag.Parse() 47 | 48 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 49 | if err != nil { 50 | log.Fatalf("failed to listen: %v", err) 51 | } 52 | 53 | s := grpc.NewServer() 54 | 55 | pb.RegisterGreeterServer(s, &server{}) 56 | 57 | log.Printf("server listening at %v", lis.Addr()) 58 | 59 | if err := s.Serve(lis); err != nil { 60 | log.Fatalf("failed to serve: %v", err) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/helloworld/scripts/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/helloworld 4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE 5 | PROTO_DIR=$ROOT_DIR/apis 6 | 7 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete 8 | 9 | mkdir -p $PROTO_DIR/google/api 10 | 11 | curl -s -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto 12 | curl -s -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto 13 | 14 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/validate) 15 | 16 | # Generate GRPC service 17 | for PROTO in $PROTOS; do 18 | protoc \ 19 | -I. \ 20 | -I$GOPATH/src \ 21 | -I$PROTO_DIR \ 22 | -I$(dirname $PROTO) \ 23 | --go_out=:$GOPATH/src \ 24 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \ 25 | $PROTO 26 | done 27 | 28 | rm -r $PROTO_DIR/google 29 | -------------------------------------------------------------------------------- /examples/http/apis/server.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package http; 4 | 5 | option go_package = "github.com/lastbackend/toolkit/examples/http/gen/server;serverpb"; 6 | 7 | import "google/api/annotations.proto"; 8 | 9 | import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto"; 10 | 11 | // Messages 12 | 13 | // The request message containing the user's name. 14 | message HelloRequest { 15 | string name = 1; 16 | } 17 | 18 | // The response message containing the greetings 19 | message HelloResponse { 20 | string message = 1; 21 | } 22 | 23 | // ===================================================== 24 | // HTTP methods 25 | // ===================================================== 26 | 27 | // Example methods 28 | service Http { 29 | option (toolkit.runtime) = { 30 | servers: [HTTP] 31 | }; 32 | option (toolkit.server) = { 33 | middlewares: [] 34 | }; 35 | 36 | rpc HelloWorld(HelloRequest) returns (HelloResponse) { 37 | option (google.api.http) = { 38 | get: "/hello" 39 | }; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /examples/http/gen/server/client/server.pb.toolkit.rpc.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT. 2 | // source: github.com/lastbackend/toolkit/examples/http/apis/server.proto 3 | 4 | package serverpb 5 | 6 | import ( 7 | context "context" 8 | 9 | "github.com/lastbackend/toolkit/examples/http/gen/server" 10 | client "github.com/lastbackend/toolkit/pkg/client" 11 | emptypb "google.golang.org/protobuf/types/known/emptypb" 12 | ) 13 | 14 | // Suppress "imported and not used" errors 15 | var _ context.Context 16 | var _ emptypb.Empty 17 | 18 | // Client gRPC API for Http service 19 | func NewHttpRPCClient(service string, c client.GRPCClient) HttpRPCClient { 20 | return &httpGrpcRPCClient{service, c} 21 | } 22 | 23 | // Client gRPC API for Http service 24 | type HttpRPCClient interface { 25 | HelloWorld(ctx context.Context, req *serverpb.HelloRequest, opts ...client.GRPCCallOption) (*serverpb.HelloResponse, error) 26 | } 27 | 28 | type httpGrpcRPCClient struct { 29 | service string 30 | cli client.GRPCClient 31 | } 32 | 33 | func (c *httpGrpcRPCClient) HelloWorld(ctx context.Context, req *serverpb.HelloRequest, opts ...client.GRPCCallOption) (*serverpb.HelloResponse, error) { 34 | resp := new(serverpb.HelloResponse) 35 | if err := c.cli.Call(ctx, c.service, Http_HelloWorldMethod, req, resp, opts...); err != nil { 36 | return nil, err 37 | } 38 | return resp, nil 39 | } 40 | 41 | func (httpGrpcRPCClient) mustEmbedUnimplementedHttpClient() {} 42 | 43 | // Client methods for Http service 44 | const ( 45 | Http_HelloWorldMethod = "/http.Http/HelloWorld" 46 | ) 47 | -------------------------------------------------------------------------------- /examples/http/gen/server/server_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc v4.25.2 5 | // source: github.com/lastbackend/toolkit/examples/http/apis/server.proto 6 | 7 | package serverpb 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Http_HelloWorld_FullMethodName = "/http.Http/HelloWorld" 23 | ) 24 | 25 | // HttpClient is the client API for Http service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type HttpClient interface { 29 | HelloWorld(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) 30 | } 31 | 32 | type httpClient struct { 33 | cc grpc.ClientConnInterface 34 | } 35 | 36 | func NewHttpClient(cc grpc.ClientConnInterface) HttpClient { 37 | return &httpClient{cc} 38 | } 39 | 40 | func (c *httpClient) HelloWorld(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { 41 | out := new(HelloResponse) 42 | err := c.cc.Invoke(ctx, Http_HelloWorld_FullMethodName, in, out, opts...) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return out, nil 47 | } 48 | 49 | // HttpServer is the server API for Http service. 50 | // All implementations should embed UnimplementedHttpServer 51 | // for forward compatibility 52 | type HttpServer interface { 53 | HelloWorld(context.Context, *HelloRequest) (*HelloResponse, error) 54 | } 55 | 56 | // UnimplementedHttpServer should be embedded to have forward compatible implementations. 57 | type UnimplementedHttpServer struct { 58 | } 59 | 60 | func (UnimplementedHttpServer) HelloWorld(context.Context, *HelloRequest) (*HelloResponse, error) { 61 | return nil, status.Errorf(codes.Unimplemented, "method HelloWorld not implemented") 62 | } 63 | 64 | // UnsafeHttpServer may be embedded to opt out of forward compatibility for this service. 65 | // Use of this interface is not recommended, as added methods to HttpServer will 66 | // result in compilation errors. 67 | type UnsafeHttpServer interface { 68 | mustEmbedUnimplementedHttpServer() 69 | } 70 | 71 | func RegisterHttpServer(s grpc.ServiceRegistrar, srv HttpServer) { 72 | s.RegisterService(&Http_ServiceDesc, srv) 73 | } 74 | 75 | func _Http_HelloWorld_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 76 | in := new(HelloRequest) 77 | if err := dec(in); err != nil { 78 | return nil, err 79 | } 80 | if interceptor == nil { 81 | return srv.(HttpServer).HelloWorld(ctx, in) 82 | } 83 | info := &grpc.UnaryServerInfo{ 84 | Server: srv, 85 | FullMethod: Http_HelloWorld_FullMethodName, 86 | } 87 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 88 | return srv.(HttpServer).HelloWorld(ctx, req.(*HelloRequest)) 89 | } 90 | return interceptor(ctx, in, info, handler) 91 | } 92 | 93 | // Http_ServiceDesc is the grpc.ServiceDesc for Http service. 94 | // It's only intended for direct use with grpc.RegisterService, 95 | // and not to be introspected or modified (even as a copy) 96 | var Http_ServiceDesc = grpc.ServiceDesc{ 97 | ServiceName: "http.Http", 98 | HandlerType: (*HttpServer)(nil), 99 | Methods: []grpc.MethodDesc{ 100 | { 101 | MethodName: "HelloWorld", 102 | Handler: _Http_HelloWorld_Handler, 103 | }, 104 | }, 105 | Streams: []grpc.StreamDesc{}, 106 | Metadata: "github.com/lastbackend/toolkit/examples/http/apis/server.proto", 107 | } 108 | -------------------------------------------------------------------------------- /examples/http/gen/server/server_service.pb.toolkit.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT. 2 | // source: github.com/lastbackend/toolkit/examples/http/apis/server.proto 3 | 4 | package serverpb 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "io" 10 | "net/http" 11 | 12 | toolkit "github.com/lastbackend/toolkit" 13 | client "github.com/lastbackend/toolkit/pkg/client" 14 | runtime "github.com/lastbackend/toolkit/pkg/runtime" 15 | controller "github.com/lastbackend/toolkit/pkg/runtime/controller" 16 | tk_http "github.com/lastbackend/toolkit/pkg/server/http" 17 | errors "github.com/lastbackend/toolkit/pkg/server/http/errors" 18 | tk_ws "github.com/lastbackend/toolkit/pkg/server/http/websockets" 19 | emptypb "google.golang.org/protobuf/types/known/emptypb" 20 | ) 21 | 22 | // This is a compile-time assertion to ensure that this generated file 23 | // is compatible with the toolkit package it is being compiled against and 24 | // suppress "imported and not used" errors 25 | var ( 26 | _ context.Context 27 | _ emptypb.Empty 28 | _ http.Handler 29 | _ errors.Err 30 | _ io.Reader 31 | _ json.Marshaler 32 | _ tk_ws.Client 33 | _ tk_http.Handler 34 | _ client.GRPCClient 35 | ) 36 | 37 | // Definitions 38 | 39 | // Service Http define 40 | type serviceHttp struct { 41 | runtime runtime.Runtime 42 | } 43 | 44 | func NewHttpService(name string, opts ...runtime.Option) (_ toolkit.Service, err error) { 45 | app := new(serviceHttp) 46 | 47 | app.runtime, err = controller.NewRuntime(context.Background(), name, opts...) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | // loop over plugins and initialize plugin instance 53 | 54 | // loop over plugins and register plugin in toolkit 55 | 56 | // create new Http HTTP server 57 | app.runtime.Server().HTTPNew(name, nil) 58 | 59 | app.runtime.Server().HTTP().AddHandler(http.MethodGet, "/hello", app.handlerHTTPHttpHelloWorld) 60 | 61 | return app.runtime.Service(), nil 62 | } 63 | 64 | // Define services for Http HTTP server 65 | 66 | type HttpHTTPService interface { 67 | HelloWorld(ctx context.Context, req *HelloRequest) (*HelloResponse, error) 68 | } 69 | 70 | // Define HTTP handlers for Router HTTP server 71 | 72 | func (s *serviceHttp) handlerHTTPHttpHelloWorld(w http.ResponseWriter, r *http.Request) { 73 | ctx, cancel := context.WithCancel(r.Context()) 74 | defer cancel() 75 | 76 | var protoRequest HelloRequest 77 | var protoResponse *HelloResponse 78 | 79 | _, om := tk_http.GetMarshaler(s.runtime.Server().HTTP(), r) 80 | 81 | if err := r.ParseForm(); err != nil { 82 | errors.HTTP.InternalServerError(w) 83 | return 84 | } 85 | 86 | if err := tk_http.ParseRequestQueryParametersToProto(&protoRequest, r.Form); err != nil { 87 | errors.HTTP.InternalServerError(w) 88 | return 89 | } 90 | 91 | headers, err := tk_http.PrepareHeaderFromRequest(r) 92 | if err != nil { 93 | errors.HTTP.InternalServerError(w) 94 | return 95 | } 96 | 97 | ctx = tk_http.NewIncomingContext(ctx, headers) 98 | 99 | protoResponse, err = s.runtime.Server().HTTP().GetService().(HttpHTTPService).HelloWorld(ctx, &protoRequest) 100 | if err != nil { 101 | errors.GrpcErrorHandlerFunc(w, err) 102 | return 103 | } 104 | 105 | buf, err := om.Marshal(protoResponse) 106 | if err != nil { 107 | errors.HTTP.InternalServerError(w) 108 | return 109 | } 110 | 111 | w.Header().Set("Content-Type", om.ContentType()) 112 | if proceed, err := tk_http.HandleGRPCResponse(w, r, headers); err != nil || !proceed { 113 | return 114 | } 115 | 116 | if _, err = w.Write(buf); err != nil { 117 | return 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /examples/http/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate ./scripts/generate.sh 4 | -------------------------------------------------------------------------------- /examples/http/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler/jsonpb" 23 | "google.golang.org/protobuf/encoding/protojson" 24 | "io" 25 | "net/http" 26 | "os" 27 | 28 | servicepb "github.com/lastbackend/toolkit/examples/http/gen/server" 29 | "github.com/lastbackend/toolkit/pkg/runtime" 30 | ) 31 | 32 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { 33 | w.Header().Set("Content-Type", "application/json") 34 | w.WriteHeader(http.StatusOK) 35 | if _, err := io.WriteString(w, `{"alive": true}`); err != nil { 36 | fmt.Println(err) 37 | } 38 | } 39 | 40 | func main() { 41 | // define service with name and options 42 | app, err := servicepb.NewHttpService("http", 43 | runtime.WithVersion("0.1.0"), 44 | runtime.WithDescription("Example http server"), 45 | runtime.WithEnvPrefix("LB"), 46 | ) 47 | if err != nil { 48 | fmt.Println(err) 49 | } 50 | 51 | // Register marshaller 52 | jsonPb := &jsonpb.JSONPb{ 53 | UnmarshalOptions: protojson.UnmarshalOptions{ 54 | DiscardUnknown: true, 55 | }, 56 | } 57 | 58 | if err = app.Server().HTTP().UseMarshaler("application/json", jsonPb); err != nil { 59 | app.Log().Errorf("could not use json marshaler %v", err) 60 | os.Exit(1) 61 | return 62 | } 63 | 64 | // Add server 65 | app.Server().HTTP().AddHandler(http.MethodGet, "/health", HealthCheckHandler) 66 | 67 | // Logger settings 68 | app.Log().Info("Run http server") 69 | 70 | // Service run 71 | if err := app.Start(context.Background()); err != nil { 72 | app.Log().Errorf("could not run the service %v", err) 73 | os.Exit(1) 74 | return 75 | } 76 | 77 | app.Log().Info("graceful stop") 78 | } 79 | -------------------------------------------------------------------------------- /examples/http/middleware/example.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package middleware 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | ) 23 | 24 | // ExampleMiddleware - show request info middleware 25 | func ExampleMiddleware(h http.Handler) http.Handler { 26 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | fmt.Println("Call: ExampleMiddleware") 28 | h.ServeHTTP(w, r) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /examples/http/middleware/request.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "strings" 11 | "sync/atomic" 12 | ) 13 | 14 | // KeyPEMBlock to use when setting the request UserID. 15 | type ctxKeyRequestID int 16 | 17 | // RequestIDKey is the key that holds th unique request UserID in a request context. 18 | const RequestIDKey ctxKeyRequestID = 0 19 | 20 | var ( 21 | // prefix is const prefix for request UserID 22 | prefix string 23 | 24 | // reqID is counter for request UserID 25 | reqID uint64 26 | ) 27 | 28 | // init Initializes constant part of request UserID 29 | func init() { 30 | hostname, err := os.Hostname() 31 | if hostname == "" || err != nil { 32 | hostname = "localhost" 33 | } 34 | var buf [12]byte 35 | var b64 string 36 | for len(b64) < 10 { 37 | _, _ = rand.Read(buf[:]) 38 | b64 = base64.StdEncoding.EncodeToString(buf[:]) 39 | b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) 40 | } 41 | 42 | prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) 43 | } 44 | 45 | // RequestID is a middleware that injects a request UserID into the context of each 46 | // request. A request UserID is a string of the form "host.example.com/random-0001", 47 | // where "random" is a base62 random string that uniquely identifies this go 48 | // process, and where the last number is an atomically incremented request 49 | // counter. 50 | func RequestID(h http.Handler) http.Handler { 51 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 52 | id := atomic.AddUint64(&reqID, 1) 53 | ctx := r.Context() 54 | ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, id)) 55 | h.ServeHTTP(w, r.WithContext(ctx)) 56 | }) 57 | } 58 | 59 | // GetReqID returns a request UserID from the given context if one is present. 60 | // Returns the empty string if a request UserID cannot be found. 61 | func GetReqID(ctx context.Context) string { 62 | if ctx == nil { 63 | return "" 64 | } 65 | if reqID, ok := ctx.Value(RequestIDKey).(string); ok { 66 | return reqID 67 | } 68 | return "" 69 | } 70 | -------------------------------------------------------------------------------- /examples/http/scripts/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/http 4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE 5 | PROTO_DIR=$ROOT_DIR/apis 6 | 7 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete 8 | 9 | mkdir -p $PROTO_DIR/google/api 10 | mkdir -p $PROTO_DIR/validate 11 | 12 | curl -s -f -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; } 13 | curl -s -f -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto || { echo "Error: Request failed"; exit 1; } 14 | curl -s -f -o $PROTO_DIR/validate/validate.proto -L https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/main/validate/validate.proto || { echo "Error: Request failed"; exit 1; } 15 | 16 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/router/options) 17 | 18 | # Generate for toolkit service 19 | for PROTO in $PROTOS; do 20 | protoc \ 21 | -I. \ 22 | -I$GOPATH/src \ 23 | -I$PROTO_DIR \ 24 | -I$(dirname $PROTO) \ 25 | --validate_out=lang=go:$GOPATH/src \ 26 | --go_out=:$GOPATH/src \ 27 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \ 28 | --toolkit_out=$GOPATH/src \ 29 | $PROTO 30 | done 31 | 32 | rm -r $PROTO_DIR/google 33 | rm -r $PROTO_DIR/validate 34 | 35 | echo "Generation is ok" 36 | -------------------------------------------------------------------------------- /examples/service/apis/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package lastbackend.example; 4 | 5 | option go_package = "github.com/lastbackend/toolkit/examples/service/gen;servicepb"; 6 | 7 | import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto"; 8 | import "github.com/lastbackend/toolkit/examples/service/apis/ptypes/messages.proto"; 9 | 10 | // ===================================================== 11 | // Generate mocks 12 | // ===================================================== 13 | option (toolkit.tests_spec) = { 14 | mockery: { 15 | package: "github.com/lastbackend/toolkit/examples/service/tests/service_mocks" 16 | } 17 | }; 18 | 19 | // ===================================================== 20 | // Install clients 21 | // ===================================================== 22 | option (toolkit.services) = { 23 | service: "example", 24 | package: "github.com/lastbackend/toolkit/examples/service/gen/client" 25 | }; 26 | 27 | option (toolkit.plugins) = { 28 | prefix: "pgsql" 29 | plugin: "postgres_gorm" 30 | }; 31 | 32 | option (toolkit.plugins) = { 33 | prefix: "redis" 34 | plugin: "redis" 35 | }; 36 | 37 | 38 | // ===================================================== 39 | // RPC methods 40 | // ===================================================== 41 | service Example { 42 | option (toolkit.runtime) = { 43 | servers: [GRPC, HTTP] 44 | }; 45 | option (toolkit.server) = { 46 | middlewares: [ 47 | "request_id" 48 | ] 49 | }; 50 | 51 | // Example methods 52 | rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse) {} 53 | }; 54 | 55 | service Sample { 56 | option (toolkit.runtime).plugins = { 57 | prefix: "redis2" 58 | plugin: "redis" 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /examples/service/apis/ptypes/messages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package lastbackend.example; 4 | 5 | option go_package = "github.com/lastbackend/toolkit/examples/service/gen/ptypes;typespb"; 6 | 7 | import "validate/validate.proto"; 8 | 9 | 10 | // ============================================================================ 11 | // Request ==================================================================== 12 | // ============================================================================ 13 | 14 | message HelloWorldRequest { 15 | string name = 1 [(validate.rules).string.min_len = 1]; 16 | string type = 2 [(validate.rules).string.max_len = 1024]; 17 | map data = 3 [(validate.rules).map.min_pairs = 1]; 18 | } 19 | 20 | // ============================================================================ 21 | // Response =================================================================== 22 | // ============================================================================ 23 | 24 | message HelloWorldResponse { 25 | string id = 1; 26 | string name = 2; 27 | string type = 3; 28 | map data = 4; 29 | int64 created_at = 5; 30 | int64 updated_at = 6; 31 | } 32 | -------------------------------------------------------------------------------- /examples/service/config/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | type Config struct { 20 | DEMO string `env:"DEMO" required:"true" envDefault:"BAR"` 21 | Nested NestedConfig `envPrefix:"NESTED_"` 22 | } 23 | 24 | type NestedConfig struct { 25 | Demo string `env:"DEMO"` 26 | } 27 | 28 | func New() *Config { 29 | c := new(Config) 30 | return c 31 | } 32 | -------------------------------------------------------------------------------- /examples/service/gen/client/demo/demo.go: -------------------------------------------------------------------------------- 1 | package demo 2 | -------------------------------------------------------------------------------- /examples/service/gen/client/example.pb.toolkit.rpc.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT. 2 | // source: github.com/lastbackend/toolkit/examples/service/apis/example.proto 3 | 4 | package servicepb 5 | 6 | import ( 7 | context "context" 8 | 9 | "github.com/lastbackend/toolkit/examples/service/gen/ptypes" 10 | client "github.com/lastbackend/toolkit/pkg/client" 11 | emptypb "google.golang.org/protobuf/types/known/emptypb" 12 | ) 13 | 14 | // Suppress "imported and not used" errors 15 | var _ context.Context 16 | var _ emptypb.Empty 17 | 18 | // Client gRPC API for Example service 19 | func NewExampleRPCClient(service string, c client.GRPCClient) ExampleRPCClient { 20 | return &exampleGrpcRPCClient{service, c} 21 | } 22 | 23 | // Client gRPC API for Example service 24 | type ExampleRPCClient interface { 25 | HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest, opts ...client.GRPCCallOption) (*typespb.HelloWorldResponse, error) 26 | } 27 | 28 | // Client gRPC API for Sample service 29 | func NewSampleRPCClient(service string, c client.GRPCClient) SampleRPCClient { 30 | return &sampleGrpcRPCClient{service, c} 31 | } 32 | 33 | // Client gRPC API for Sample service 34 | type SampleRPCClient interface { 35 | } 36 | 37 | type exampleGrpcRPCClient struct { 38 | service string 39 | cli client.GRPCClient 40 | } 41 | 42 | func (c *exampleGrpcRPCClient) HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest, opts ...client.GRPCCallOption) (*typespb.HelloWorldResponse, error) { 43 | resp := new(typespb.HelloWorldResponse) 44 | if err := c.cli.Call(ctx, c.service, Example_HelloWorldMethod, req, resp, opts...); err != nil { 45 | return nil, err 46 | } 47 | return resp, nil 48 | } 49 | 50 | func (exampleGrpcRPCClient) mustEmbedUnimplementedExampleClient() {} 51 | 52 | type sampleGrpcRPCClient struct { 53 | service string 54 | cli client.GRPCClient 55 | } 56 | 57 | func (sampleGrpcRPCClient) mustEmbedUnimplementedSampleClient() {} 58 | 59 | // Client methods for Example service 60 | const ( 61 | Example_HelloWorldMethod = "/lastbackend.example.Example/HelloWorld" 62 | ) 63 | -------------------------------------------------------------------------------- /examples/service/gen/example.pb.validate.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-validate. DO NOT EDIT. 2 | // source: github.com/lastbackend/toolkit/examples/service/apis/example.proto 3 | 4 | package servicepb 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/mail" 12 | "net/url" 13 | "regexp" 14 | "sort" 15 | "strings" 16 | "time" 17 | "unicode/utf8" 18 | 19 | "google.golang.org/protobuf/types/known/anypb" 20 | ) 21 | 22 | // ensure the imports are used 23 | var ( 24 | _ = bytes.MinRead 25 | _ = errors.New("") 26 | _ = fmt.Print 27 | _ = utf8.UTFMax 28 | _ = (*regexp.Regexp)(nil) 29 | _ = (*strings.Reader)(nil) 30 | _ = net.IPv4len 31 | _ = time.Duration(0) 32 | _ = (*url.URL)(nil) 33 | _ = (*mail.Address)(nil) 34 | _ = anypb.Any{} 35 | _ = sort.Sort 36 | ) 37 | -------------------------------------------------------------------------------- /examples/service/gen/tests/example.pb.toolkit.mockery.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT. 2 | // source: github.com/lastbackend/toolkit/examples/service/apis/example.proto 3 | 4 | package servicepb 5 | 6 | // Warning: You have no mock in provided directory. Please check mockery docs for mocks generation. 7 | -------------------------------------------------------------------------------- /examples/service/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate ./scripts/generate.sh 4 | -------------------------------------------------------------------------------- /examples/service/internal/controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "github.com/lastbackend/toolkit/pkg/client" 6 | "time" 7 | 8 | "github.com/lastbackend/toolkit" 9 | "github.com/lastbackend/toolkit/examples/service/config" 10 | servicepb "github.com/lastbackend/toolkit/examples/service/gen" 11 | typespb "github.com/lastbackend/toolkit/examples/service/gen/ptypes" 12 | "github.com/lastbackend/toolkit/examples/service/internal/repository" 13 | "github.com/lastbackend/toolkit/pkg/runtime/logger" 14 | ) 15 | 16 | type Controller struct { 17 | app toolkit.Service 18 | log logger.Logger 19 | cfg *config.Config 20 | repo *repository.Repository 21 | services servicepb.ExampleServices 22 | } 23 | 24 | func (c *Controller) OnStart(ctx context.Context) error { 25 | c.log.Info("> service controller: on start") 26 | c.Call(ctx) 27 | return nil 28 | } 29 | 30 | func (c *Controller) Call(_ context.Context) error { 31 | 32 | header := make(map[string]string) 33 | header["x-req-id"] = time.Now().String() 34 | 35 | resp, err := c.services.Example().HelloWorld(context.Background(), &typespb.HelloWorldRequest{ 36 | Name: "name", 37 | Type: "type", 38 | Data: nil, 39 | }, client.GRPCOptionHeaders(header)) 40 | if err != nil { 41 | c.log.Error(err.Error()) 42 | return err 43 | } 44 | 45 | c.log.Info("> service response from server:> name:", resp.Name) 46 | return nil 47 | } 48 | 49 | func (c *Controller) OnStop(context.Context) error { 50 | c.log.Info("> service controller: on stop") 51 | return nil 52 | } 53 | 54 | func NewController(app toolkit.Service, cfg *config.Config, repo *repository.Repository, services servicepb.ExampleServices) *Controller { 55 | app.Log().Info("> service controller ----") 56 | ctrl := &Controller{app: app, log: app.Log(), cfg: cfg, repo: repo, services: services} 57 | return ctrl 58 | } 59 | -------------------------------------------------------------------------------- /examples/service/internal/repository/repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/lastbackend/toolkit" 7 | "github.com/lastbackend/toolkit-plugins/postgres_gorm" 8 | servicepb "github.com/lastbackend/toolkit/examples/service/gen" 9 | "github.com/lastbackend/toolkit/pkg/runtime/logger" 10 | ) 11 | 12 | // The Repository represents Model. 13 | type Repository struct { 14 | postgres_gorm.Plugin 15 | log logger.Logger 16 | } 17 | 18 | type DemoStruct struct { 19 | Id int 20 | Count int 21 | } 22 | 23 | func (r *Repository) Get(_ context.Context) *DemoStruct { 24 | val := new(DemoStruct) 25 | r.DB().First(val) 26 | return val 27 | } 28 | 29 | // NewRepository Model with given configurations. 30 | func NewRepository(app toolkit.Service, psql servicepb.PgsqlPlugin) *Repository { 31 | repo := &Repository{psql, app.Log()} 32 | app.Log().Info("repository init") 33 | return repo 34 | } 35 | -------------------------------------------------------------------------------- /examples/service/internal/server/handler.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "net/http" 4 | 5 | // ExampleHTTPServerHandler server definitions 6 | func ExampleHTTPServerHandler(w http.ResponseWriter, r *http.Request) { 7 | _, _ = w.Write([]byte("ok")) 8 | } 9 | -------------------------------------------------------------------------------- /examples/service/internal/server/interceptor.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "github.com/lastbackend/toolkit/pkg/runtime" 6 | "github.com/lastbackend/toolkit/pkg/server" 7 | "google.golang.org/grpc" 8 | "time" 9 | ) 10 | 11 | type ExampleGRPCServerInterceptor struct { 12 | server.GRPCInterceptor 13 | name server.KindInterceptor 14 | r runtime.Runtime 15 | } 16 | 17 | func NewExampleGRPCServerInterceptor(r runtime.Runtime) server.GRPCInterceptor { 18 | i := ExampleGRPCServerInterceptor{r: r} 19 | return &i 20 | } 21 | 22 | func (i *ExampleGRPCServerInterceptor) Kind() server.KindInterceptor { 23 | return i.name 24 | } 25 | 26 | func (i *ExampleGRPCServerInterceptor) Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 27 | i.r.Log().Info("Log: 1") 28 | time.Sleep(time.Second) 29 | 30 | i.r.Log().Info("Logging") 31 | 32 | res, err := handler(ctx, req) 33 | 34 | i.r.Log().Info("Log: 2") 35 | time.Sleep(time.Second) 36 | 37 | return res, err 38 | } 39 | -------------------------------------------------------------------------------- /examples/service/internal/server/middleware.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | servicepb "github.com/lastbackend/toolkit/examples/service/gen" 8 | typespb "github.com/lastbackend/toolkit/examples/service/gen/ptypes" 9 | "github.com/lastbackend/toolkit/pkg/runtime" 10 | "github.com/lastbackend/toolkit/pkg/server" 11 | ) 12 | 13 | type Data string 14 | 15 | const MWAuthenticate server.KindMiddleware = "mwauthenticate" 16 | 17 | type ExampleHTTPServerMiddleware struct { 18 | server.DefaultHttpServerMiddleware 19 | name server.KindMiddleware 20 | runtime runtime.Runtime 21 | service servicepb.ExampleServices 22 | } 23 | 24 | func (e *ExampleHTTPServerMiddleware) Apply(h http.HandlerFunc) http.HandlerFunc { 25 | return func(w http.ResponseWriter, r *http.Request) { 26 | e.runtime.Log().Info("Call: ExampleMiddleware") 27 | resp, _ := e.service.Example().HelloWorld(context.Background(), &typespb.HelloWorldRequest{ 28 | Name: "middleware", 29 | Type: "test", 30 | Data: nil, 31 | }) 32 | 33 | e.runtime.Log().Info("middleware resp response:> ", resp.Name) 34 | // Set example data to request context 35 | ctx := context.WithValue(r.Context(), Data("test-data"), "example context data") 36 | 37 | h.ServeHTTP(w, r.WithContext(ctx)) 38 | } 39 | } 40 | 41 | func (e *ExampleHTTPServerMiddleware) Kind() server.KindMiddleware { 42 | return e.name 43 | } 44 | 45 | func RegisterExampleHTTPServerMiddleware(runtime runtime.Runtime, service servicepb.ExampleServices) server.HttpServerMiddleware { 46 | return &ExampleHTTPServerMiddleware{name: MWAuthenticate, runtime: runtime, service: service} 47 | } 48 | -------------------------------------------------------------------------------- /examples/service/internal/server/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package server 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "google.golang.org/grpc" 23 | "google.golang.org/grpc/codes" 24 | "google.golang.org/grpc/metadata" 25 | "google.golang.org/grpc/status" 26 | "github.com/lastbackend/toolkit/examples/service/internal/repository" 27 | 28 | "github.com/lastbackend/toolkit" 29 | "github.com/lastbackend/toolkit/examples/service/config" 30 | servicepb "github.com/lastbackend/toolkit/examples/service/gen" 31 | typespb "github.com/lastbackend/toolkit/examples/service/gen/ptypes" 32 | ) 33 | 34 | type Handlers struct { 35 | servicepb.ExampleRpcServer 36 | 37 | app toolkit.Service 38 | cfg *config.Config 39 | repo *repository.Repository 40 | } 41 | 42 | func (h Handlers) HelloWorld(ctx context.Context, req *typespb.HelloWorldRequest) (*typespb.HelloWorldResponse, error) { 43 | h.app.Log().Info("ExamplseRpcServer: HelloWorld: call") 44 | 45 | md, ok := metadata.FromIncomingContext(ctx) 46 | if !ok { 47 | return nil, status.Errorf(codes.DataLoss, "failed to get metadata") 48 | } 49 | 50 | demo := h.repo.Get(ctx) 51 | 52 | resp := typespb.HelloWorldResponse{ 53 | Id: fmt.Sprintf("%d", demo.Id), 54 | Name: fmt.Sprintf("%s: %d", req.Name, demo.Count), 55 | Type: req.Type, 56 | } 57 | 58 | if len(md["x-req-id"]) > 0 { 59 | header := metadata.New(map[string]string{"x-response-id": md["x-req-id"][0]}) 60 | grpc.SendHeader(ctx, header) 61 | } 62 | 63 | return &resp, nil 64 | } 65 | 66 | func NewServer(app toolkit.Service, cfg *config.Config, repo *repository.Repository) servicepb.ExampleRpcServer { 67 | return &Handlers{ 68 | repo: repo, 69 | app: app, 70 | cfg: cfg, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/service/internal/server/server_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net" 8 | "testing" 9 | 10 | "github.com/lastbackend/toolkit/pkg/runtime/controller" 11 | 12 | "google.golang.org/grpc/credentials/insecure" 13 | 14 | servicepb "github.com/lastbackend/toolkit/examples/service/gen" 15 | ptypes "github.com/lastbackend/toolkit/examples/service/gen/ptypes" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/codes" 18 | "google.golang.org/grpc/status" 19 | "google.golang.org/grpc/test/bufconn" 20 | ) 21 | 22 | type ExampleServer struct { 23 | servicepb.UnimplementedExampleServer 24 | } 25 | 26 | func dialer() func(context.Context, string) (net.Conn, error) { 27 | listener := bufconn.Listen(1024 * 1024) 28 | 29 | runtime, _ := controller.NewRuntime(context.Background(), "test") 30 | runtime.Server().GRPC().RegisterService(NewServer(runtime.Service(), nil, nil)) 31 | 32 | go func() { 33 | runtime.Server().GRPC().Start(context.Background()) 34 | }() 35 | 36 | return func(context.Context, string) (net.Conn, error) { 37 | return listener.Dial() 38 | } 39 | } 40 | 41 | func TestExampleServer_HelloWorld(t *testing.T) { 42 | tests := []struct { 43 | name string 44 | amount float32 45 | res *ptypes.HelloWorldResponse 46 | errCode codes.Code 47 | errMsg string 48 | }{ 49 | { 50 | "empty hello world response", 51 | -1.11, 52 | nil, 53 | codes.InvalidArgument, 54 | fmt.Sprintf("cannot deposit %v", -1.11), 55 | }, 56 | { 57 | "spaced hello world response", 58 | 0.00, 59 | &ptypes.HelloWorldResponse{ 60 | Id: "1", 61 | Name: "", 62 | Type: "", 63 | Data: nil, 64 | CreatedAt: 0, 65 | UpdatedAt: 0, 66 | }, 67 | codes.OK, 68 | "", 69 | }, 70 | } 71 | 72 | ctx := context.Background() 73 | 74 | conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer())) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | defer conn.Close() 79 | 80 | client := servicepb.NewExampleClient(conn) 81 | 82 | for _, tt := range tests { 83 | t.Run(tt.name, func(t *testing.T) { 84 | request := &ptypes.HelloWorldRequest{} 85 | 86 | response, err := client.HelloWorld(ctx, request) 87 | if response != nil { 88 | if tt.res == nil { 89 | t.Error("rsponse: expected nil. received: ", response) 90 | } else { 91 | if response.Id != tt.res.Id { 92 | t.Error("response: expected", tt.res.Id, "received", response.Id) 93 | } 94 | } 95 | } 96 | 97 | if err != nil { 98 | if er, ok := status.FromError(err); ok { 99 | if er.Code() != tt.errCode { 100 | t.Error("error code: expected", codes.InvalidArgument, "received", er.Code()) 101 | } 102 | if er.Message() != tt.errMsg { 103 | t.Error("error message: expected", tt.errMsg, "received", er.Message()) 104 | } 105 | } 106 | } 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /examples/service/mage.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/magefile/mage/mage" 10 | ) 11 | 12 | func main() { os.Exit(mage.Main()) } 13 | -------------------------------------------------------------------------------- /examples/service/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "github.com/lastbackend/toolkit/examples/service/config" 23 | servicepb "github.com/lastbackend/toolkit/examples/service/gen" 24 | "github.com/lastbackend/toolkit/examples/service/internal/controller" 25 | "github.com/lastbackend/toolkit/examples/service/internal/repository" 26 | "github.com/lastbackend/toolkit/examples/service/internal/server" 27 | "github.com/lastbackend/toolkit/pkg/runtime" 28 | "github.com/lastbackend/toolkit/pkg/server/http" 29 | "os" 30 | "time" 31 | ) 32 | 33 | func main() { 34 | // define service with name and options 35 | app, err := servicepb.NewExampleService("example", 36 | runtime.WithVersion("0.1.0"), 37 | runtime.WithDescription("Example microservice"), 38 | runtime.WithEnvPrefix("LB"), 39 | ) 40 | if err != nil { 41 | fmt.Println(err) 42 | } 43 | 44 | // Config management 45 | cfg := config.New() 46 | 47 | if err := app.RegisterConfig(cfg); err != nil { 48 | app.Log().Error(err) 49 | return 50 | } 51 | 52 | // Add packages 53 | app.RegisterPackage(repository.NewRepository, controller.NewController) 54 | 55 | // Add server 56 | app.Server().GRPC().SetService(server.NewServer) 57 | app.Server().GRPC().SetInterceptor(server.NewExampleGRPCServerInterceptor) 58 | 59 | app.Server().HTTPNew("", nil) 60 | app.Server().HTTP().SetMiddleware(server.RegisterExampleHTTPServerMiddleware) 61 | app.Server().HTTP().AddHandler(http.MethodGet, "/", server.ExampleHTTPServerHandler, http.WithMiddleware(server.MWAuthenticate)) 62 | 63 | app.RegisterOnStartHook(func(ctx context.Context) error { 64 | time.Sleep(3 * time.Second) 65 | app.Log().Info("call gracefully stop") 66 | app.Stop(ctx, fmt.Errorf("test error")) 67 | return nil 68 | }) 69 | 70 | //go func() { 71 | // time.Sleep(5 * time.Second) 72 | // app.Log().Info("call gracefully stop") 73 | // app.Stop(context.Background()) 74 | //}() 75 | 76 | // Service run 77 | if err := app.Start(context.Background()); err != nil { 78 | app.Log().Errorf("could not run the service %v", err) 79 | os.Exit(1) 80 | return 81 | } 82 | 83 | // time.Sleep(10 * time.Second) 84 | // app.Stop(context.Background()) 85 | 86 | app.Log().Info("graceful stop") 87 | } 88 | -------------------------------------------------------------------------------- /examples/service/scripts/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/service 4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE 5 | PROTO_DIR=$ROOT_DIR/apis 6 | 7 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete 8 | 9 | mkdir -p $PROTO_DIR/google/api 10 | mkdir -p $PROTO_DIR/validate 11 | 12 | curl -s -f -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; } 13 | curl -s -f -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto || { echo "Error: Request failed"; exit 1; } 14 | curl -s -f -o $PROTO_DIR/validate/validate.proto -L https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/main/validate/validate.proto || { echo "Error: Request failed"; exit 1; } 15 | 16 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/validate) 17 | 18 | # Generate for toolkit service 19 | for PROTO in $PROTOS; do 20 | protoc \ 21 | -I. \ 22 | -I$GOPATH/src \ 23 | -I$PROTO_DIR \ 24 | -I$(dirname $PROTO) \ 25 | --validate_out=lang=go:$GOPATH/src \ 26 | --go_out=:$GOPATH/src \ 27 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \ 28 | --toolkit_out=$GOPATH/src \ 29 | $PROTO 30 | done 31 | 32 | rm -r $PROTO_DIR/google 33 | rm -r $PROTO_DIR/validate 34 | 35 | echo "Generation is ok" 36 | -------------------------------------------------------------------------------- /examples/wss/apis/server.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package gateway; 4 | 5 | option go_package = "github.com/lastbackend/toolkit/examples/wss/gen/server;serverpb"; 6 | 7 | import "google/api/annotations.proto"; 8 | import "protoc-gen-openapiv2/options/annotations.proto"; 9 | 10 | import "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options/annotations.proto"; 11 | import "github.com/lastbackend/toolkit/examples/helloworld/apis/helloworld.proto"; 12 | 13 | // ===================================================== 14 | // Swagger spec 15 | // ===================================================== 16 | 17 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { 18 | info: { 19 | title: "Websocket proxy server" 20 | version: "1.0" 21 | contact: { 22 | name: "Last.Backend" 23 | url: "https://lastbackend.com" 24 | email: "teams@lastbackend.com" 25 | }; 26 | }; 27 | host: 'lastbackend.com' 28 | schemes: [HTTP, HTTPS] 29 | consumes: "application/json" 30 | produces: "application/json" 31 | responses: { 32 | key: "500" 33 | value: { 34 | description: "Internal Server Error" 35 | schema: { 36 | json_schema: { 37 | type: OBJECT, 38 | read_only: true, 39 | example: "{\"code\": 500, \"status\": \"Internal Server Error\", \"message\": \"Internal Server Error\"}" 40 | }; 41 | }; 42 | }; 43 | }, 44 | }; 45 | 46 | // ===================================================== 47 | // HTTP proxy methods 48 | // ===================================================== 49 | 50 | // Example methods 51 | service Router { 52 | option (toolkit.runtime) = { 53 | servers: [HTTP, WEBSOCKET_PROXY, WEBSOCKET] 54 | plugins: [{ 55 | prefix: "redis1" 56 | plugin: "redis" 57 | }] 58 | }; 59 | option (toolkit.server) = { 60 | middlewares: [ 61 | "example" 62 | ] 63 | }; 64 | 65 | rpc Subscribe(SubscribeRequest) returns (SubscribeResponse) { 66 | option (toolkit.route).websocket = true; 67 | option (google.api.http) = { 68 | get: "/events" 69 | }; 70 | }; 71 | rpc SayHello(helloworld.HelloRequest) returns (helloworld.HelloReply) { 72 | option (toolkit.route).websocket_proxy = { 73 | service: "helloworld" 74 | method: "/helloworld.Greeter/SayHello" 75 | }; 76 | }; 77 | rpc HelloWorld(helloworld.HelloRequest) returns (helloworld.HelloReply) { 78 | option (toolkit.route) = { 79 | middlewares: ["example"] 80 | http_proxy: { 81 | service: "helloworld" 82 | method: "/helloworld.Greeter/SayHello" 83 | }; 84 | }; 85 | option (google.api.http) = { 86 | post: "/hello" 87 | }; 88 | }; 89 | } 90 | 91 | message SubscribeRequest {} 92 | 93 | message SubscribeResponse {} 94 | -------------------------------------------------------------------------------- /examples/wss/gen/server/client/server.pb.toolkit.rpc.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-toolkit. DO NOT EDIT. 2 | // source: github.com/lastbackend/toolkit/examples/wss/apis/server.proto 3 | 4 | package serverpb 5 | 6 | import ( 7 | context "context" 8 | 9 | "github.com/lastbackend/toolkit/examples/helloworld/gen" 10 | client "github.com/lastbackend/toolkit/pkg/client" 11 | emptypb "google.golang.org/protobuf/types/known/emptypb" 12 | ) 13 | 14 | // Suppress "imported and not used" errors 15 | var _ context.Context 16 | var _ emptypb.Empty 17 | 18 | // Client gRPC API for Router service 19 | func NewRouterRPCClient(service string, c client.GRPCClient) RouterRPCClient { 20 | return &routerGrpcRPCClient{service, c} 21 | } 22 | 23 | // Client gRPC API for Router service 24 | type RouterRPCClient interface { 25 | SayHello(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) 26 | 27 | HelloWorld(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) 28 | } 29 | 30 | type routerGrpcRPCClient struct { 31 | service string 32 | cli client.GRPCClient 33 | } 34 | 35 | func (c *routerGrpcRPCClient) SayHello(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) { 36 | resp := new(servicepb.HelloReply) 37 | if err := c.cli.Call(ctx, c.service, Router_SayHelloMethod, req, resp, opts...); err != nil { 38 | return nil, err 39 | } 40 | return resp, nil 41 | } 42 | 43 | func (c *routerGrpcRPCClient) HelloWorld(ctx context.Context, req *servicepb.HelloRequest, opts ...client.GRPCCallOption) (*servicepb.HelloReply, error) { 44 | resp := new(servicepb.HelloReply) 45 | if err := c.cli.Call(ctx, c.service, Router_HelloWorldMethod, req, resp, opts...); err != nil { 46 | return nil, err 47 | } 48 | return resp, nil 49 | } 50 | 51 | func (routerGrpcRPCClient) mustEmbedUnimplementedRouterClient() {} 52 | 53 | // Client methods for Router service 54 | const ( 55 | Router_SubscribeMethod = "/gateway.Router/Subscribe" 56 | 57 | Router_SayHelloMethod = "/gateway.Router/SayHello" 58 | 59 | Router_HelloWorldMethod = "/gateway.Router/HelloWorld" 60 | ) 61 | -------------------------------------------------------------------------------- /examples/wss/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate ./scripts/generate.sh 4 | -------------------------------------------------------------------------------- /examples/wss/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | "os" 25 | 26 | servicepb "github.com/lastbackend/toolkit/examples/wss/gen/server" 27 | "github.com/lastbackend/toolkit/examples/wss/middleware" 28 | "github.com/lastbackend/toolkit/pkg/runtime" 29 | tk_http "github.com/lastbackend/toolkit/pkg/server/http" 30 | "github.com/lastbackend/toolkit/pkg/server/http/websockets" 31 | ) 32 | 33 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { 34 | w.Header().Set("Content-Type", "application/json") 35 | w.WriteHeader(http.StatusOK) 36 | if _, err := io.WriteString(w, `{"alive": true}`); err != nil { 37 | fmt.Println(err) 38 | } 39 | } 40 | 41 | func TestWSHandler(ctx context.Context, event websockets.Event, c *websockets.Client) error { 42 | fmt.Println("Event:", event.Type, string(event.Payload)) 43 | fmt.Println("Context:", ctx.Value("test-data")) 44 | return c.WriteMessage(websockets.TextMessage, event.Payload) 45 | } 46 | 47 | func main() { 48 | 49 | // define service with name and options 50 | app, err := servicepb.NewRouterService("wss", 51 | runtime.WithVersion("0.1.0"), 52 | runtime.WithDescription("Example router microservice"), 53 | runtime.WithEnvPrefix("WSS"), 54 | ) 55 | if err != nil { 56 | fmt.Println(err) 57 | } 58 | 59 | // Logger settings 60 | app.Log().Info("Run microservice") 61 | 62 | // Add middleware 63 | app.Server().HTTP().SetMiddleware(middleware.RegisterExampleMiddleware) 64 | app.Server().HTTP().SetMiddleware(middleware.RegisterRequestID) 65 | 66 | // set middleware as global middleware 67 | app.Server().HTTP().UseMiddleware("request_id") 68 | app.Server().HTTP().Subscribe("event:name", TestWSHandler) 69 | 70 | // add handler to default http server 71 | app.Server().HTTP(). 72 | AddHandler(http.MethodGet, "/health", HealthCheckHandler, tk_http.WithMiddleware("example")) 73 | 74 | // Service run 75 | if err := app.Start(context.Background()); err != nil { 76 | app.Log().Errorf("could not run the service %v", err) 77 | os.Exit(1) 78 | return 79 | } 80 | 81 | app.Log().Info("graceful stop") 82 | } 83 | -------------------------------------------------------------------------------- /examples/wss/middleware/example.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package middleware 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net/http" 23 | 24 | "github.com/lastbackend/toolkit/pkg/runtime" 25 | "github.com/lastbackend/toolkit/pkg/server" 26 | ) 27 | 28 | // RegisterExampleMiddleware - show request info middleware 29 | func RegisterExampleMiddleware(runtime runtime.Runtime) server.HttpServerMiddleware { 30 | return &ExampleMiddleware{name: "example", runtime: runtime} 31 | } 32 | 33 | type ExampleMiddleware struct { 34 | name server.KindMiddleware 35 | runtime runtime.Runtime 36 | } 37 | 38 | func (e *ExampleMiddleware) Apply(h http.HandlerFunc) http.HandlerFunc { 39 | return func(w http.ResponseWriter, r *http.Request) { 40 | fmt.Println("Call: ExampleMiddleware") 41 | 42 | // Set example data to request context 43 | ctx := context.WithValue(r.Context(), "test-data", "example context data") 44 | 45 | h.ServeHTTP(w, r.WithContext(ctx)) 46 | } 47 | } 48 | 49 | func (e *ExampleMiddleware) Order() int { 50 | return 0 51 | } 52 | 53 | func (e *ExampleMiddleware) Kind() server.KindMiddleware { 54 | return e.name 55 | } 56 | -------------------------------------------------------------------------------- /examples/wss/middleware/request.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | "github.com/lastbackend/toolkit/pkg/runtime" 9 | "github.com/lastbackend/toolkit/pkg/server" 10 | "net/http" 11 | "os" 12 | "strings" 13 | "sync/atomic" 14 | ) 15 | 16 | // KeyPEMBlock to use when setting the request UserID. 17 | type ctxKeyRequestID int 18 | 19 | // RequestIDKey is the key that holds th unique request UserID in a request context. 20 | const RequestIDKey ctxKeyRequestID = 0 21 | 22 | var ( 23 | // prefix is const prefix for request UserID 24 | prefix string 25 | 26 | // reqID is counter for request UserID 27 | reqID uint64 28 | ) 29 | 30 | // init Initializes constant part of request UserID 31 | func init() { 32 | hostname, err := os.Hostname() 33 | if hostname == "" || err != nil { 34 | hostname = "localhost" 35 | } 36 | var buf [12]byte 37 | var b64 string 38 | for len(b64) < 10 { 39 | _, _ = rand.Read(buf[:]) 40 | b64 = base64.StdEncoding.EncodeToString(buf[:]) 41 | b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) 42 | } 43 | 44 | prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) 45 | } 46 | 47 | // GetReqID returns a request UserID from the given context if one is present. 48 | // Returns the empty string if a request UserID cannot be found. 49 | func GetReqID(ctx context.Context) string { 50 | if ctx == nil { 51 | return "" 52 | } 53 | if reqID, ok := ctx.Value(RequestIDKey).(string); ok { 54 | return reqID 55 | } 56 | return "" 57 | } 58 | 59 | // RegisterRequestID is a middleware that injects a request UserID into the context of each 60 | // request. A request UserID is a string of the form "host.example.com/random-0001", 61 | // where "random" is a base62 random string that uniquely identifies this go 62 | // process, and where the last number is an atomically incremented request 63 | // counter. 64 | func RegisterRequestID(runtime runtime.Runtime) server.HttpServerMiddleware { 65 | return &ExampleMiddleware{name: "request_id", runtime: runtime} 66 | } 67 | 68 | type RequestID struct { 69 | name server.KindMiddleware 70 | runtime runtime.Runtime 71 | } 72 | 73 | func (e *RequestID) Apply(h http.HandlerFunc) http.HandlerFunc { 74 | return func(w http.ResponseWriter, r *http.Request) { 75 | id := atomic.AddUint64(&reqID, 1) 76 | ctx := r.Context() 77 | ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, id)) 78 | h.ServeHTTP(w, r.WithContext(ctx)) 79 | } 80 | } 81 | 82 | func (e *RequestID) Order() int { 83 | return 0 84 | } 85 | 86 | func (e *RequestID) Kind() server.KindMiddleware { 87 | return e.name 88 | } 89 | -------------------------------------------------------------------------------- /examples/wss/scripts/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | SOURCE_PACKAGE=github.com/lastbackend/toolkit/examples/wss 4 | ROOT_DIR=$GOPATH/src/$SOURCE_PACKAGE 5 | PROTO_DIR=$ROOT_DIR/apis 6 | SWAGGER_DIR_NAME=swagger 7 | 8 | find $ROOT_DIR -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete 9 | 10 | rm -rf ./${SWAGGER_DIR_NAME} 11 | 12 | mkdir -p ${SWAGGER_DIR_NAME} 13 | mkdir -p $PROTO_DIR/google/api 14 | mkdir -p $PROTO_DIR/validate 15 | mkdir -p $PROTO_DIR/protoc-gen-openapiv2/options 16 | 17 | curl -s -f -o $PROTO_DIR/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; } 18 | curl -s -f -o $PROTO_DIR/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto || { echo "Error: Request failed"; exit 1; } 19 | curl -s -f -o $PROTO_DIR/validate/validate.proto -L https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/main/validate/validate.proto || { echo "Error: Request failed"; exit 1; } 20 | curl -s -f -o $PROTO_DIR/protoc-gen-openapiv2/options/annotations.proto -L https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-openapiv2/options/annotations.proto || { echo "Error: Request failed"; exit 1; } 21 | curl -s -f -o $PROTO_DIR/protoc-gen-openapiv2/options/openapiv2.proto -L https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-openapiv2/options/openapiv2.proto || { echo "Error: Request failed"; exit 1; } 22 | 23 | PROTOS=$(find $PROTO_DIR -type f -name '*.proto' | grep -v $PROTO_DIR/google/api | grep -v $PROTO_DIR/router/options) 24 | 25 | # Generate for toolkit service 26 | for PROTO in $PROTOS; do 27 | protoc \ 28 | -I. \ 29 | -I$GOPATH/src \ 30 | -I$PROTO_DIR \ 31 | -I$(dirname $PROTO) \ 32 | --openapiv2_out ./${SWAGGER_DIR_NAME} --openapiv2_opt logtostderr=true \ 33 | --validate_out=lang=go:$GOPATH/src \ 34 | --go_out=:$GOPATH/src \ 35 | --go-grpc_out=require_unimplemented_servers=false:$GOPATH/src \ 36 | --toolkit_out=$GOPATH/src \ 37 | $PROTO 38 | done 39 | 40 | mv ./${SWAGGER_DIR_NAME}/$SOURCE_PACKAGE/apis/* ./${SWAGGER_DIR_NAME}/ 41 | 42 | rm -rf ./${SWAGGER_DIR_NAME}/github.com 43 | rm -r $PROTO_DIR/google 44 | rm -r $PROTO_DIR/validate 45 | rm -r $PROTO_DIR/protoc-gen-openapiv2 46 | 47 | echo "Generation is ok" 48 | -------------------------------------------------------------------------------- /examples/wss/swagger/protoc-gen-openapiv2/options/annotations.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "github.com/lastbackend/toolkit/examples/wss/apis/protoc-gen-openapiv2/options/annotations.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/wss/swagger/protoc-gen-openapiv2/options/openapiv2.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "github.com/lastbackend/toolkit/examples/wss/apis/protoc-gen-openapiv2/options/openapiv2.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/wss/swagger/validate/validate.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "github.com/lastbackend/toolkit/examples/wss/apis/validate/validate.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | package toolkit 2 | 3 | //go:generate ./hack/generate.sh 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lastbackend/toolkit 2 | 3 | go 1.21.5 4 | 5 | toolchain go1.21.6 6 | 7 | require ( 8 | github.com/caarlos0/env/v7 v7.1.0 9 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be 10 | github.com/envoyproxy/protoc-gen-validate v1.0.3 11 | github.com/fatih/color v1.16.0 12 | github.com/fatih/structs v1.1.0 13 | github.com/google/uuid v1.5.0 14 | github.com/gorilla/mux v1.8.1 15 | github.com/gorilla/websocket v1.5.1 16 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 17 | github.com/improbable-eng/grpc-web v0.15.0 18 | github.com/jedib0t/go-pretty/v6 v6.5.3 19 | github.com/jhump/protoreflect v1.15.4 20 | github.com/lastbackend/toolkit-plugins/postgres_gorm v0.0.0-20240114174800-797efec18f22 21 | github.com/lastbackend/toolkit-plugins/redis v0.0.0-20240114174800-797efec18f22 22 | github.com/magefile/mage v1.15.0 23 | github.com/pkg/errors v0.9.1 24 | github.com/urfave/cli/v2 v2.27.1 25 | go.uber.org/fx v1.20.1 26 | go.uber.org/zap v1.26.0 27 | golang.org/x/net v0.20.0 28 | golang.org/x/sync v0.6.0 29 | golang.org/x/text v0.14.0 30 | google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 31 | google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 32 | google.golang.org/grpc v1.60.1 33 | google.golang.org/protobuf v1.35.1 34 | ) 35 | 36 | require ( 37 | github.com/bufbuild/protocompile v0.7.1 // indirect 38 | github.com/cenkalti/backoff/v4 v4.1.2 // indirect 39 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 40 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 41 | github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect 42 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 43 | github.com/go-pg/pg/v10 v10.12.0 // indirect 44 | github.com/go-pg/zerochecker v0.2.0 // indirect 45 | github.com/golang-migrate/migrate/v4 v4.17.0 // indirect 46 | github.com/golang/protobuf v1.5.3 // indirect 47 | github.com/hashicorp/errwrap v1.1.0 // indirect 48 | github.com/hashicorp/go-multierror v1.1.1 // indirect 49 | github.com/jackc/pgpassfile v1.0.0 // indirect 50 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 51 | github.com/jackc/pgx/v5 v5.4.3 // indirect 52 | github.com/jinzhu/inflection v1.0.0 // indirect 53 | github.com/jinzhu/now v1.1.5 // indirect 54 | github.com/klauspost/compress v1.15.11 // indirect 55 | github.com/lib/pq v1.10.9 // indirect 56 | github.com/mattn/go-colorable v0.1.13 // indirect 57 | github.com/mattn/go-isatty v0.0.20 // indirect 58 | github.com/mattn/go-runewidth v0.0.15 // indirect 59 | github.com/redis/go-redis/v9 v9.4.0 // indirect 60 | github.com/rivo/uniseg v0.2.0 // indirect 61 | github.com/rs/cors v1.7.0 // indirect 62 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 63 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect 64 | github.com/vmihailenco/bufpool v0.1.11 // indirect 65 | github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect 66 | github.com/vmihailenco/tagparser v0.1.2 // indirect 67 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 68 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 69 | go.uber.org/atomic v1.11.0 // indirect 70 | go.uber.org/dig v1.17.0 // indirect 71 | go.uber.org/multierr v1.11.0 // indirect 72 | golang.org/x/crypto v0.18.0 // indirect 73 | golang.org/x/sys v0.16.0 // indirect 74 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect 75 | gorm.io/driver/postgres v1.5.4 // indirect 76 | gorm.io/gorm v1.25.5 // indirect 77 | mellium.im/sasl v0.3.1 // indirect 78 | nhooyr.io/websocket v1.8.6 // indirect 79 | ) 80 | -------------------------------------------------------------------------------- /hack/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | find . -type d -name example -prune -type f \( -name '*.pb.go' -o -name '*.pb.*.go' \) -delete 4 | 5 | mkdir -p proto/google/api 6 | 7 | curl -s -f -o proto/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto || { echo "Error: Request failed"; exit 1; } 8 | 9 | PROTOS=$(find . -type f -name '*.proto' | grep -v examples | grep -v proto/google/api | grep -v proto/toolkit ) 10 | 11 | for PROTO in $PROTOS; do 12 | protoc \ 13 | -I. \ 14 | -I$GOPATH/src \ 15 | -I$(dirname $PROTO) \ 16 | --go_out=paths=source_relative:. \ 17 | $PROTO 18 | done 19 | 20 | rm -r proto 21 | 22 | echo "Generation is ok" 23 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package toolkit 18 | 19 | import ( 20 | "context" 21 | "github.com/lastbackend/toolkit/pkg/client" 22 | "github.com/lastbackend/toolkit/pkg/runtime/logger" 23 | "github.com/lastbackend/toolkit/pkg/runtime/meta" 24 | "github.com/lastbackend/toolkit/pkg/server" 25 | ) 26 | 27 | type Service interface { 28 | Meta() *meta.Meta 29 | 30 | Log() logger.Logger 31 | Client() Client 32 | Server() Server 33 | 34 | RegisterConfig(config ...any) error 35 | RegisterPlugin(constructor ...any) 36 | RegisterPackage(constructor ...any) 37 | 38 | Start(ctx context.Context) error 39 | Stop(ctx context.Context, err error) 40 | 41 | RegisterOnStartHook(...func(ctx context.Context) error) 42 | RegisterOnStartSyncHook(...func(ctx context.Context) error) 43 | 44 | RegisterOnStopHook(...func(ctx context.Context) error) 45 | RegisterOnStopSyncHook(...func(ctx context.Context) error) 46 | } 47 | 48 | type Client interface { 49 | GRPC() client.GRPCClient 50 | HTTP() client.HTTPClient 51 | } 52 | 53 | type Server interface { 54 | HTTP() server.HTTPServer 55 | GRPC() server.GRPCServer 56 | 57 | HTTPGet(name string) server.HTTPServer 58 | HTTPNew(name string, options *server.HTTPServerOptions) server.HTTPServer 59 | 60 | GRPCGet(name string) server.GRPCServer 61 | GRPCNew(name string, options *server.GRPCServerOptions) server.GRPCServer 62 | } 63 | 64 | type Config any 65 | 66 | type Package any 67 | 68 | type PackageItem struct { 69 | Index int 70 | Source any 71 | } 72 | 73 | type Plugin any 74 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver" 6 | "google.golang.org/grpc" 7 | "time" 8 | ) 9 | 10 | type GRPCStream grpc.ClientStream 11 | type GRPCClient interface { 12 | GetResolver() resolver.Resolver 13 | SetResolver(resolver resolver.Resolver) 14 | Conn(pool string) (grpc.ClientConnInterface, error) 15 | Call(ctx context.Context, service, method string, req, rsp interface{}, opts ...GRPCCallOption) error 16 | Stream(ctx context.Context, service, method string, body interface{}, opts ...GRPCCallOption) (grpc.ClientStream, error) 17 | } 18 | 19 | type GRPCCallOption func(*GRPCCallOptions) 20 | 21 | type GRPCCallOptions struct { 22 | Backoff GRPCBackoffFunc 23 | Retries time.Duration 24 | RequestTimeout time.Duration 25 | Context context.Context 26 | Headers map[string]string 27 | MaxCallSendMsgSize int 28 | MaxCallRecvMsgSize int 29 | MaxRetryRPCBufferSize int 30 | CallContentSubtype string 31 | } 32 | 33 | func GRPCOptionHeaders(h map[string]string) GRPCCallOption { 34 | return func(o *GRPCCallOptions) { 35 | o.Headers = h 36 | } 37 | } 38 | 39 | func GRPCOptionMaxCallSendMsgSize(bytes int) GRPCCallOption { 40 | return func(o *GRPCCallOptions) { 41 | o.MaxCallSendMsgSize = bytes 42 | } 43 | } 44 | 45 | func GRPCOptionMaxCallRecvMsgSize(bytes int) GRPCCallOption { 46 | return func(o *GRPCCallOptions) { 47 | o.MaxCallRecvMsgSize = bytes 48 | } 49 | } 50 | 51 | func GRPCOptionRequestTimeout(timeout time.Duration) GRPCCallOption { 52 | return func(o *GRPCCallOptions) { 53 | o.RequestTimeout = timeout 54 | } 55 | } 56 | 57 | type GRPCBackoffFunc func(ctx context.Context, req *GRPCRequest, attempts int) (time.Duration, error) 58 | type GRPCRetryFunc func(ctx context.Context, req *GRPCRequest, retryCount int, err error) (bool, error) 59 | 60 | type GRPCRequest struct { 61 | service string 62 | method string 63 | headers map[string]string 64 | body interface{} 65 | } 66 | 67 | func NewGRPCRequest(method, service string, body interface{}, headers map[string]string) *GRPCRequest { 68 | r := new(GRPCRequest) 69 | r.service = method 70 | r.method = service 71 | r.body = body 72 | if headers == nil { 73 | headers = make(map[string]string, 0) 74 | } 75 | r.headers = headers 76 | return r 77 | } 78 | 79 | func (r *GRPCRequest) Service() string { 80 | return r.service 81 | } 82 | 83 | func (r *GRPCRequest) Method() string { 84 | return r.method 85 | } 86 | 87 | func (r *GRPCRequest) Body() interface{} { 88 | return r.body 89 | } 90 | 91 | func (r *GRPCRequest) Headers() map[string]string { 92 | return r.headers 93 | } 94 | 95 | type HTTPClient interface { 96 | Get() error 97 | Post() error 98 | } 99 | -------------------------------------------------------------------------------- /pkg/client/grpc/codec.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package grpc 18 | 19 | import ( 20 | "google.golang.org/protobuf/proto" 21 | ) 22 | 23 | type Codec interface { 24 | Marshal(v interface{}) ([]byte, error) 25 | Unmarshal(data []byte, v interface{}) error 26 | Name() string 27 | } 28 | 29 | type protoCodec struct{} 30 | 31 | func (protoCodec) Marshal(v interface{}) ([]byte, error) { 32 | return proto.Marshal(v.(proto.Message)) 33 | } 34 | 35 | func (protoCodec) Unmarshal(data []byte, v interface{}) error { 36 | return proto.Unmarshal(data, v.(proto.Message)) 37 | } 38 | 39 | func (protoCodec) Name() string { 40 | return "proto" 41 | } 42 | -------------------------------------------------------------------------------- /pkg/client/grpc/resolver/file/file.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package file 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver" 23 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route" 24 | "github.com/lastbackend/toolkit/pkg/runtime" 25 | "github.com/lastbackend/toolkit/pkg/util/addr" 26 | "os" 27 | "path" 28 | ) 29 | 30 | const ( 31 | prefix string = "resolver" 32 | defaultFileName string = ".lb.resolver" 33 | ) 34 | 35 | type Plugin interface { 36 | Print() 37 | } 38 | 39 | type Resolver struct { 40 | prefix string 41 | runtime runtime.Runtime 42 | table *table 43 | opts Config 44 | } 45 | 46 | type Config struct { 47 | Cache bool 48 | File string `env:"FILEPATH" comment:"Filepath to resolver storage file. default(.lb.resolver in home dir)"` 49 | } 50 | 51 | type Options struct { 52 | Name string 53 | } 54 | 55 | func NewResolver(runtime runtime.Runtime) resolver.Resolver { 56 | 57 | var err error 58 | 59 | r := &Resolver{ 60 | prefix: prefix, 61 | runtime: runtime, 62 | } 63 | 64 | if err := runtime.Config().Parse(&r.opts, prefix); err != nil { 65 | runtime.Log().Errorf("Can not parse config %s: %s", prefix, err.Error()) 66 | } 67 | 68 | if r.opts.File == "" { 69 | dirname, err := os.UserHomeDir() 70 | if err != nil { 71 | runtime.Log().Errorf("Can not obtain default home dir: %s", prefix, err.Error()) 72 | return nil 73 | } 74 | r.opts.File = path.Join(dirname, defaultFileName) 75 | } 76 | 77 | if r.table, err = newTable(r.opts.File, runtime.Log()); err != nil { 78 | return nil 79 | } 80 | 81 | return r 82 | } 83 | 84 | func (c *Resolver) OnStart(ctx context.Context) error { 85 | c.runtime.Log().Info("resolver file on-start call") 86 | 87 | ip, err := addr.DetectIP() 88 | if err != nil { 89 | c.runtime.Log().Errorf("Can not detect local ip: %s", err.Error()) 90 | return err 91 | } 92 | 93 | srvs := c.runtime.Server().GRPCList() 94 | for name, srv := range srvs { 95 | c.table.Create(route.Route{ 96 | Service: name, 97 | Address: fmt.Sprintf("%s:%d", ip, srv.Info().Port), 98 | }) 99 | } 100 | return nil 101 | } 102 | 103 | func (c *Resolver) Lookup(service string, opts ...resolver.LookupOption) (route.List, error) { 104 | q := resolver.NewLookup(opts...) 105 | routes, err := c.table.Find(service) 106 | if err != nil { 107 | return nil, err 108 | } 109 | routes = resolver.Filter(routes, q) 110 | if len(routes) == 0 { 111 | return nil, route.ErrRouteNotFound 112 | } 113 | return routes, nil 114 | } 115 | 116 | func (c *Resolver) Table() resolver.Table { 117 | return c.table 118 | } 119 | 120 | func (c *Resolver) Print() { 121 | return 122 | } 123 | -------------------------------------------------------------------------------- /pkg/client/grpc/resolver/filter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package resolver 18 | 19 | import ( 20 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route" 21 | ) 22 | 23 | func isMatch(route route.Route, address string) bool { 24 | match := func(a, b string) bool { 25 | if a == "*" || b == "*" || a == b { 26 | return true 27 | } 28 | return false 29 | } 30 | type matcher struct { 31 | a string 32 | b string 33 | } 34 | values := []matcher{ 35 | {address, route.Address}, 36 | } 37 | for _, v := range values { 38 | if !match(v.a, v.b) { 39 | return false 40 | } 41 | } 42 | return true 43 | } 44 | 45 | func Filter(routes []route.Route, opts LookupOptions) []route.Route { 46 | address := opts.Address 47 | routeMap := make(map[string][]route.Route, 0) 48 | 49 | for _, r := range routes { 50 | if isMatch(r, address) { 51 | routeKey := r.Service 52 | routeMap[routeKey] = append(routeMap[routeKey], r) 53 | } 54 | } 55 | 56 | var results []route.Route 57 | 58 | for _, r := range routeMap { 59 | results = append(results, r...) 60 | } 61 | 62 | return results 63 | } 64 | -------------------------------------------------------------------------------- /pkg/client/grpc/resolver/local/local.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package local 18 | 19 | import ( 20 | "context" 21 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver" 22 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route" 23 | "github.com/lastbackend/toolkit/pkg/runtime" 24 | "strings" 25 | ) 26 | 27 | const ( 28 | prefix string = "resolver" 29 | separator string = "=" 30 | ) 31 | 32 | type Resolver struct { 33 | runtime runtime.Runtime 34 | table *table 35 | options *Options 36 | } 37 | 38 | type Options struct { 39 | Cache bool 40 | Endpoints []string `env:"ENDPOINTS" envSeparator:","` 41 | } 42 | 43 | func NewResolver(runtime runtime.Runtime) resolver.Resolver { 44 | 45 | opts := new(Options) 46 | if err := runtime.Config().Parse(opts, prefix); err != nil { 47 | runtime.Log().Errorf("Can not parse config %s: $s", prefix, err.Error()) 48 | } 49 | 50 | table := newTable() 51 | r := &Resolver{ 52 | table: table, 53 | options: opts, 54 | } 55 | 56 | for _, s := range opts.Endpoints { 57 | if s == "" { 58 | continue 59 | } 60 | 61 | parts := strings.Split(s, separator) 62 | if len(parts) < 2 { 63 | continue 64 | } 65 | 66 | table.Create(route.Route{ 67 | Service: parts[0], 68 | Address: parts[1], 69 | }) 70 | } 71 | 72 | return r 73 | } 74 | 75 | func (c *Resolver) OnStart(context.Context) error { 76 | return nil 77 | } 78 | 79 | func (c *Resolver) Lookup(service string, opts ...resolver.LookupOption) (route.List, error) { 80 | q := resolver.NewLookup(opts...) 81 | routes, err := c.table.Find(service) 82 | if err != nil { 83 | return nil, err 84 | } 85 | routes = resolver.Filter(routes, q) 86 | if len(routes) == 0 { 87 | return nil, route.ErrRouteNotFound 88 | } 89 | return routes, nil 90 | } 91 | 92 | func (c *Resolver) Table() resolver.Table { 93 | return c.table 94 | } 95 | -------------------------------------------------------------------------------- /pkg/client/grpc/resolver/local/table.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package local 18 | 19 | import ( 20 | rt "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route" 21 | "sync" 22 | "time" 23 | ) 24 | 25 | type table struct { 26 | sync.RWMutex 27 | x int 28 | routes map[string]map[string]*resolverRoute 29 | } 30 | 31 | type resolverRoute struct { 32 | route rt.Route 33 | updated time.Time 34 | } 35 | 36 | func newTable() *table { 37 | return &table{ 38 | routes: make(map[string]map[string]*resolverRoute, 0), 39 | } 40 | } 41 | 42 | func (t *table) Find(service string) ([]rt.Route, error) { 43 | var routes []rt.Route 44 | 45 | t.RLock() 46 | defer t.RUnlock() 47 | 48 | if len(service) > 0 { 49 | routeMap, ok := t.routes[service] 50 | if !ok { 51 | return nil, rt.ErrRouteNotFound 52 | } 53 | for _, rm := range routeMap { 54 | routes = append(routes, rm.route) 55 | } 56 | return routes, nil 57 | } 58 | for _, serviceRoutes := range t.routes { 59 | for _, sr := range serviceRoutes { 60 | routes = append(routes, sr.route) 61 | } 62 | } 63 | return routes, nil 64 | } 65 | 66 | func (t *table) Create(r rt.Route) error { 67 | service := r.Service 68 | sum := r.Hash() 69 | t.x = 100 70 | t.Lock() 71 | defer t.Unlock() 72 | 73 | if _, ok := t.routes[service]; !ok { 74 | t.routes[service] = make(map[string]*resolverRoute) 75 | } 76 | 77 | if _, ok := t.routes[service][sum]; ok { 78 | return rt.ErrDuplicateRoute 79 | } 80 | 81 | t.routes[service][sum] = &resolverRoute{r, time.Now()} 82 | 83 | return nil 84 | } 85 | 86 | func (t *table) Delete(r rt.Route) error { 87 | 88 | service := r.Service 89 | sum := r.Hash() 90 | 91 | t.Lock() 92 | defer t.Unlock() 93 | 94 | if _, ok := t.routes[service]; !ok { 95 | return rt.ErrRouteNotFound 96 | } 97 | 98 | if _, ok := t.routes[service][sum]; !ok { 99 | return rt.ErrRouteNotFound 100 | } 101 | 102 | delete(t.routes[service], sum) 103 | 104 | if len(t.routes[service]) == 0 { 105 | delete(t.routes, service) 106 | } 107 | return nil 108 | } 109 | 110 | func (t *table) Update(r rt.Route) error { 111 | 112 | service := r.Service 113 | sum := r.Hash() 114 | 115 | t.Lock() 116 | defer t.Unlock() 117 | 118 | if _, ok := t.routes[service]; !ok { 119 | t.routes[service] = make(map[string]*resolverRoute) 120 | } 121 | 122 | if _, ok := t.routes[service][sum]; !ok { 123 | t.routes[service][sum] = &resolverRoute{r, time.Now()} 124 | return nil 125 | } 126 | t.routes[service][sum] = &resolverRoute{r, time.Now()} 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /pkg/client/grpc/resolver/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package resolver 18 | 19 | func DefaultOptions() Options { 20 | return Options{ 21 | Cache: false, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/client/grpc/resolver/resolver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package resolver 18 | 19 | import ( 20 | "context" 21 | "github.com/lastbackend/toolkit/pkg/client/grpc/resolver/route" 22 | "github.com/pkg/errors" 23 | ) 24 | 25 | type ResolveType string 26 | 27 | func (r ResolveType) String() string { 28 | return string(r) 29 | } 30 | 31 | const ( 32 | LocalResolver ResolveType = "local" 33 | ConsulResolver ResolveType = "consul" 34 | ) 35 | 36 | var ( 37 | DefaultResolver Resolver 38 | ) 39 | 40 | var ( 41 | ErrResolverNotDetected = errors.New("resolver not detected") 42 | ErrNotAvailable = errors.New("not available") 43 | ) 44 | 45 | type Option func(*Options) 46 | 47 | type Options struct { 48 | Cache bool 49 | Endpoint string 50 | } 51 | 52 | type LookupOption func(*LookupOptions) 53 | 54 | type Resolver interface { 55 | OnStart(ctx context.Context) error 56 | Table() Table 57 | Lookup(service string, opts ...LookupOption) (route.List, error) 58 | } 59 | 60 | type Table interface { 61 | Create(route.Route) error 62 | Delete(route.Route) error 63 | Update(route.Route) error 64 | Find(service string) ([]route.Route, error) 65 | } 66 | 67 | type LookupOptions struct { 68 | Address string 69 | } 70 | 71 | func NewLookup(opts ...LookupOption) LookupOptions { 72 | qopts := LookupOptions{ 73 | Address: "*", 74 | } 75 | for _, o := range opts { 76 | o(&qopts) 77 | } 78 | return qopts 79 | } 80 | -------------------------------------------------------------------------------- /pkg/client/grpc/resolver/route/route.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package route 18 | 19 | import ( 20 | "crypto/sha256" 21 | "encoding/hex" 22 | "github.com/pkg/errors" 23 | ) 24 | 25 | var ( 26 | ErrRouteNotFound = errors.New("route not found") 27 | ErrDuplicateRoute = errors.New("duplicate route") 28 | ) 29 | 30 | type Route struct { 31 | Service string `json:"service"` 32 | Address string `json:"address"` 33 | } 34 | 35 | func (r *Route) Hash() string { 36 | hash := sha256.Sum256([]byte(r.Service + r.Address)) 37 | return hex.EncodeToString(hash[:]) 38 | } 39 | 40 | type List []Route 41 | 42 | func (r *List) Addresses() []string { 43 | list := make([]string, 0) 44 | for _, item := range *r { 45 | list = append(list, item.Address) 46 | } 47 | return list 48 | } 49 | -------------------------------------------------------------------------------- /pkg/client/grpc/selector/random.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package selector 18 | 19 | import ( 20 | "math/rand" 21 | ) 22 | 23 | func newRandomSelector() Selector { 24 | return new(random) 25 | } 26 | 27 | type random struct{} 28 | 29 | func (s *random) Select(routes []string) (Next, error) { 30 | if len(routes) == 0 { 31 | return nil, ErrNotAvailable 32 | } 33 | return func() string { 34 | if len(routes) == 1 { 35 | return routes[0] 36 | } 37 | return routes[rand.Intn(len(routes))] 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/client/grpc/selector/roundrobin.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package selector 18 | 19 | import ( 20 | "math/rand" 21 | ) 22 | 23 | func newRRSelector() Selector { 24 | return new(roundRobin) 25 | } 26 | 27 | type roundRobin struct{} 28 | 29 | func (s *roundRobin) Select(routes []string) (Next, error) { 30 | if len(routes) == 0 { 31 | return nil, ErrNotAvailable 32 | } 33 | i := rand.Intn(len(routes)) 34 | return func() string { 35 | route := routes[i%len(routes)] 36 | i++ 37 | return route 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/client/grpc/selector/selector.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package selector 18 | 19 | import ( 20 | "github.com/pkg/errors" 21 | ) 22 | 23 | type Type int 24 | 25 | const ( 26 | Random Type = iota 27 | RoundRobin 28 | ) 29 | 30 | var ( 31 | ErrSelectorNotDetected = errors.New("selector not detected") 32 | ErrNotAvailable = errors.New("not available") 33 | ) 34 | 35 | type Selector interface { 36 | Select([]string) (Next, error) 37 | } 38 | 39 | type Next func() string 40 | 41 | func New(t Type) (selector Selector, err error) { 42 | switch t { 43 | case Random: 44 | selector = newRandomSelector() 45 | case RoundRobin: 46 | selector = newRRSelector() 47 | default: 48 | err = ErrSelectorNotDetected 49 | } 50 | return selector, err 51 | } 52 | -------------------------------------------------------------------------------- /pkg/client/grpc/stream.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package grpc 18 | 19 | import ( 20 | "github.com/lastbackend/toolkit/pkg/client" 21 | "google.golang.org/grpc" 22 | 23 | "context" 24 | "sync" 25 | ) 26 | 27 | type stream struct { 28 | sync.RWMutex 29 | context context.Context 30 | 31 | grpc.ClientStream 32 | 33 | request *client.GRPCRequest 34 | conn *poolConn 35 | close func(err error) 36 | } 37 | 38 | func (s *stream) Context() context.Context { 39 | return s.context 40 | } 41 | 42 | func (s *stream) Headers() map[string]string { 43 | return s.request.Headers() 44 | } 45 | 46 | func (s *stream) SendMsg(msg interface{}) error { 47 | return s.ClientStream.SendMsg(msg) 48 | } 49 | 50 | func (s *stream) RecvMsg(msg interface{}) (err error) { 51 | return s.ClientStream.RecvMsg(msg) 52 | } 53 | 54 | func (s *stream) CloseSend() error { 55 | return s.ClientStream.CloseSend() 56 | } 57 | -------------------------------------------------------------------------------- /pkg/client/http/client.go: -------------------------------------------------------------------------------- 1 | package http 2 | -------------------------------------------------------------------------------- /pkg/context/context.go: -------------------------------------------------------------------------------- 1 | package context 2 | -------------------------------------------------------------------------------- /pkg/context/metadata/metadata.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | const emptyString = "" 8 | 9 | type MD map[string]string 10 | type mdKey struct{} 11 | 12 | func (m MD) Get(key string) (string, bool) { 13 | val, ok := m[key] 14 | if ok { 15 | return val, ok 16 | } 17 | val, ok = m[key] 18 | return val, ok 19 | } 20 | 21 | func (m MD) Set(key, val string) { 22 | m[key] = val 23 | } 24 | 25 | func (m MD) Del(key string) { 26 | delete(m, key) 27 | } 28 | 29 | func NewContext(ctx context.Context, md MD) context.Context { 30 | return context.WithValue(ctx, mdKey{}, md) 31 | } 32 | 33 | func DeleteContextValue(ctx context.Context, k string) context.Context { 34 | return SetContextValue(ctx, k, emptyString) 35 | } 36 | 37 | func SetContextValue(ctx context.Context, k, v string) context.Context { 38 | md, ok := LoadFromContext(ctx) 39 | if !ok { 40 | md = make(MD, 0) 41 | } 42 | if len(v) != 0 { 43 | md[k] = v 44 | } else { 45 | delete(md, k) 46 | } 47 | return context.WithValue(ctx, mdKey{}, md) 48 | } 49 | 50 | func GetContextValue(ctx context.Context, key string) (string, bool) { 51 | md, ok := LoadFromContext(ctx) 52 | if !ok { 53 | return emptyString, ok 54 | } 55 | val, ok := md[key] 56 | return val, ok 57 | } 58 | 59 | func LoadFromContext(ctx context.Context) (MD, bool) { 60 | md, ok := ctx.Value(mdKey{}).(MD) 61 | if !ok { 62 | return nil, ok 63 | } 64 | newMD := make(MD, len(md)) 65 | for k, v := range md { 66 | newMD[k] = v 67 | } 68 | return newMD, ok 69 | } 70 | 71 | func MergeContext(ctx context.Context, metadata MD, overwrite bool) context.Context { 72 | if ctx == nil { 73 | ctx = context.Background() 74 | } 75 | md, ok := ctx.Value(mdKey{}).(MD) 76 | if !ok { 77 | return ctx 78 | } 79 | cp := make(MD, len(md)) 80 | for k, v := range md { 81 | cp[k] = v 82 | } 83 | for k, v := range metadata { 84 | if _, ok := cp[k]; ok && !overwrite { 85 | continue 86 | } 87 | if len(v) != 0 { 88 | cp[k] = v 89 | } else { 90 | delete(cp, k) 91 | } 92 | } 93 | return context.WithValue(ctx, mdKey{}, cp) 94 | } 95 | -------------------------------------------------------------------------------- /pkg/runtime/controller/client.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "github.com/lastbackend/toolkit/pkg/client" 6 | "github.com/lastbackend/toolkit/pkg/client/grpc" 7 | "github.com/lastbackend/toolkit/pkg/runtime" 8 | "github.com/lastbackend/toolkit/pkg/runtime/logger" 9 | ) 10 | 11 | type clientManager struct { 12 | log logger.Logger 13 | grpc client.GRPCClient 14 | http client.HTTPClient 15 | } 16 | 17 | func (c *clientManager) GRPC() client.GRPCClient { 18 | return c.grpc 19 | } 20 | 21 | func (c *clientManager) HTTP() client.HTTPClient { 22 | return c.http 23 | } 24 | 25 | func newClientController(ctx context.Context, runtime runtime.Runtime) runtime.Client { 26 | cl := new(clientManager) 27 | 28 | cl.log = runtime.Log() 29 | cl.grpc = grpc.NewClient(ctx, runtime) 30 | 31 | return cl 32 | } 33 | -------------------------------------------------------------------------------- /pkg/runtime/controller/help.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "os" 5 | "text/template" 6 | ) 7 | 8 | const templateHelpText string = ` 9 | NAME: 10 | {{ .Name }} - {{ .Desc }} 11 | 12 | USAGE: 13 | {{ .Name }} [global options] command [command options] [arguments...] 14 | 15 | COMMANDS: 16 | help, h Shows a list of commands or help for one command 17 | 18 | GLOBAL OPTIONS: 19 | --help, -h show help 20 | --all show all available variables. by default show only required 21 | --yaml show environment variables as a yaml, table by default 22 | --without-comments disable printing envs comments 23 | 24 | ENVIRONMENT VARIABLES 25 | 26 | {{ .Envs }} 27 | 28 | ` 29 | 30 | func (c *controller) help() bool { 31 | 32 | var ( 33 | help, all, yaml, nocomments bool 34 | ) 35 | 36 | for _, arg := range os.Args { 37 | switch arg { 38 | case "-h": 39 | help = true 40 | case "--help": 41 | help = true 42 | case "h": 43 | help = true 44 | case "help": 45 | help = true 46 | case "--all": 47 | all = true 48 | case "--yaml": 49 | yaml = true 50 | case "--without-comments": 51 | nocomments = true 52 | } 53 | 54 | } 55 | 56 | if !help { 57 | return false 58 | } 59 | 60 | type data struct { 61 | Name string 62 | Desc string 63 | Envs string 64 | } 65 | 66 | tpl, err := template.New("help").Parse(templateHelpText) 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | var envs = c.Config().PrintTable(all, nocomments) 72 | 73 | if yaml { 74 | envs = c.Config().PrintYaml(all, nocomments) 75 | } 76 | 77 | if err := tpl.Execute(os.Stdout, data{ 78 | Name: c.meta.GetName(), 79 | Desc: c.meta.GetDescription(), 80 | Envs: envs, 81 | }); err != nil { 82 | panic(err) 83 | } 84 | 85 | return true 86 | } 87 | -------------------------------------------------------------------------------- /pkg/runtime/controller/service.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "github.com/lastbackend/toolkit" 6 | "github.com/lastbackend/toolkit/pkg/runtime" 7 | "github.com/lastbackend/toolkit/pkg/runtime/logger" 8 | "github.com/lastbackend/toolkit/pkg/runtime/meta" 9 | ) 10 | 11 | type service struct { 12 | toolkit.Service 13 | runtime runtime.Runtime 14 | start func(ctx context.Context, invoke ...interface{}) error 15 | } 16 | 17 | func (s *service) Meta() *meta.Meta { 18 | return s.runtime.Meta() 19 | } 20 | 21 | func (s *service) Log() logger.Logger { 22 | return s.runtime.Log() 23 | } 24 | 25 | func (s *service) Client() toolkit.Client { 26 | return s.runtime.Client() 27 | } 28 | 29 | func (s *service) Server() toolkit.Server { 30 | return s.runtime.Server() 31 | } 32 | 33 | func (s *service) RegisterConfig(items ...any) error { 34 | return s.runtime.Config().Provide(items...) 35 | } 36 | 37 | func (s *service) RegisterPackage(items ...any) { 38 | s.runtime.Package().Provide(items...) 39 | } 40 | 41 | func (s *service) RegisterPlugin(items ...any) { 42 | s.runtime.Plugin().Provide(items...) 43 | } 44 | 45 | func (s *service) Start(ctx context.Context) error { 46 | return s.runtime.Start(ctx) 47 | } 48 | 49 | func (s *service) Stop(ctx context.Context, err error) { 50 | s.runtime.Stop(ctx, err) 51 | } 52 | 53 | func (s *service) RegisterOnStartHook(fn ...func(ctx context.Context) error) { 54 | s.runtime.RegisterOnStartHook(fn...) 55 | } 56 | 57 | func (s *service) RegisterOnStopHook(fn ...func(ctx context.Context) error) { 58 | s.runtime.RegisterOnStopHook(fn...) 59 | } 60 | 61 | func (s *service) RegisterOnStartSyncHook(fn ...func(ctx context.Context) error) { 62 | s.runtime.RegisterOnStartSyncHook(fn...) 63 | } 64 | 65 | func (s *service) RegisterOnStopSyncHook(fn ...func(ctx context.Context) error) { 66 | s.runtime.RegisterOnStopSyncHook(fn...) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/runtime/controller/tools.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/lastbackend/toolkit/pkg/runtime" 7 | "github.com/lastbackend/toolkit/pkg/tools/metrics" 8 | "github.com/lastbackend/toolkit/pkg/tools/probes" 9 | "github.com/lastbackend/toolkit/pkg/tools/probes/server" 10 | "github.com/lastbackend/toolkit/pkg/tools/traces" 11 | ) 12 | 13 | type Tools struct { 14 | runtime.Tools 15 | 16 | runtime runtime.Runtime 17 | metrics metrics.Metrics 18 | probes probes.Probes 19 | traces traces.Traces 20 | } 21 | 22 | func (t *Tools) Probes() probes.Probes { 23 | return t.probes 24 | } 25 | 26 | func (t *Tools) Traces() traces.Traces { 27 | return t.traces 28 | } 29 | 30 | func (t *Tools) Metrics() metrics.Metrics { 31 | return t.metrics 32 | } 33 | 34 | func (t *Tools) OnStart(ctx context.Context) error { 35 | return t.probes.Start(ctx) 36 | } 37 | 38 | func newToolsRegistration(runtime runtime.Runtime) (runtime.Tools, error) { 39 | var ( 40 | tools = new(Tools) 41 | err error 42 | ) 43 | tools.runtime = runtime 44 | 45 | if tools.probes, err = server.NewProbesServer(runtime); err != nil { 46 | return nil, err 47 | } 48 | 49 | return tools, nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/runtime/controller/util.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | var ( 8 | _typeOfError reflect.Type = reflect.TypeOf((*error)(nil)).Elem() 9 | _nilError = reflect.Zero(_typeOfError) 10 | ) 11 | 12 | func build(target interface{}, kind interface{}) interface{} { 13 | 14 | paramTypes, remap := parameters(target) 15 | resultTypes, _ := currentResultTypes(target, kind) 16 | 17 | origFn := reflect.ValueOf(target) 18 | newFnType := reflect.FuncOf(paramTypes, resultTypes, false) 19 | newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value { 20 | args = remap(args) 21 | values := origFn.Call(args) 22 | 23 | for _, v := range values { 24 | values = append(values, v.Convert(reflect.TypeOf(kind).Elem())) 25 | } 26 | 27 | return values 28 | }) 29 | return newFn.Interface() 30 | } 31 | 32 | func buildPackage(target interface{}, kind interface{}) interface{} { 33 | 34 | paramTypes, remap := parameters(target) 35 | resultTypes, _ := currentResultTypes(target, kind) 36 | 37 | origFn := reflect.ValueOf(target) 38 | newFnType := reflect.FuncOf(paramTypes, resultTypes, false) 39 | newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value { 40 | args = remap(args) 41 | values := origFn.Call(args) 42 | 43 | for _, v := range values { 44 | rv := reflect.ValueOf(kind).Elem() 45 | rv.FieldByName("Source").Set(v) 46 | values = append(values, rv) 47 | } 48 | 49 | return values 50 | }) 51 | return newFn.Interface() 52 | } 53 | 54 | func currentResultTypes(target interface{}, kind interface{}) (resultTypes []reflect.Type, hasError bool) { 55 | ft := reflect.TypeOf(target) 56 | numOut := ft.NumOut() 57 | resultTypes = make([]reflect.Type, numOut) 58 | 59 | for i := 0; i < numOut; i++ { 60 | resultTypes[i] = ft.Out(i) 61 | if resultTypes[i] == _typeOfError && i == numOut-1 { 62 | hasError = true 63 | } 64 | } 65 | for i := 0; i < numOut; i++ { 66 | resultTypes = append(resultTypes, reflect.TypeOf(kind).Elem()) 67 | } 68 | return resultTypes, hasError 69 | } 70 | 71 | func parameters(target interface{}) ( 72 | types []reflect.Type, 73 | remap func([]reflect.Value) []reflect.Value, 74 | ) { 75 | ft := reflect.TypeOf(target) 76 | types = make([]reflect.Type, ft.NumIn()) 77 | for i := 0; i < ft.NumIn(); i++ { 78 | types[i] = ft.In(i) 79 | } 80 | 81 | return types, func(args []reflect.Value) []reflect.Value { 82 | return args 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /pkg/runtime/interface.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "github.com/caarlos0/env/v7" 6 | "github.com/lastbackend/toolkit" 7 | "github.com/lastbackend/toolkit/pkg/client" 8 | "github.com/lastbackend/toolkit/pkg/runtime/logger" 9 | "github.com/lastbackend/toolkit/pkg/runtime/meta" 10 | "github.com/lastbackend/toolkit/pkg/server" 11 | "github.com/lastbackend/toolkit/pkg/tools/metrics" 12 | "github.com/lastbackend/toolkit/pkg/tools/probes" 13 | "github.com/lastbackend/toolkit/pkg/tools/traces" 14 | ) 15 | 16 | type Runtime interface { 17 | Meta() *meta.Meta 18 | Log() logger.Logger 19 | 20 | Client() Client 21 | Package() Package 22 | Server() Server 23 | Config() Config 24 | Plugin() Plugin 25 | 26 | Tools() Tools 27 | 28 | Service() toolkit.Service 29 | 30 | Provide(constructor interface{}) 31 | Invoke(constructor interface{}) 32 | 33 | Start(ctx context.Context) error 34 | Stop(ctx context.Context, err error) 35 | 36 | RegisterOnStartHook(fn ...func(ctx context.Context) error) 37 | RegisterOnStartSyncHook(fn ...func(ctx context.Context) error) 38 | 39 | RegisterOnStopHook(fn ...func(ctx context.Context) error) 40 | RegisterOnStopSyncHook(fn ...func(ctx context.Context) error) 41 | } 42 | 43 | type Client interface { 44 | GRPC() client.GRPCClient 45 | HTTP() client.HTTPClient 46 | } 47 | 48 | type HTTPClient interface { 49 | Get() error 50 | Post() error 51 | } 52 | 53 | type Server interface { 54 | toolkit.Server 55 | 56 | Start(ctx context.Context) error 57 | Stop(ctx context.Context) error 58 | 59 | HTTPList() map[string]server.HTTPServer 60 | GRPCList() map[string]server.GRPCServer 61 | 62 | Provides() []interface{} 63 | Constructors() []interface{} 64 | } 65 | 66 | type Config interface { 67 | Provide(...any) error 68 | 69 | SetMeta(meta *meta.Meta) 70 | Parse(v interface{}, prefix string, opts ...env.Options) error 71 | Print(v interface{}, prefix string) 72 | PrintTable(all, nocomments bool) string 73 | PrintYaml(all, nocomments bool) string 74 | 75 | Configs() []any 76 | } 77 | 78 | type Plugin interface { 79 | Provide(constructor ...interface{}) 80 | 81 | Constructors() []interface{} 82 | Register(plugins []toolkit.Plugin) 83 | 84 | PreStart(ctx context.Context) error 85 | OnStart(ctx context.Context) error 86 | OnStop(ctx context.Context) error 87 | } 88 | 89 | type Package interface { 90 | Provide(constructor ...interface{}) 91 | 92 | Constructors() []interface{} 93 | Register(packages []toolkit.PackageItem) 94 | 95 | PreStart(ctx context.Context) error 96 | OnStart(ctx context.Context) error 97 | OnStop(ctx context.Context) error 98 | } 99 | 100 | type Tools interface { 101 | OnStart(ctx context.Context) error 102 | 103 | Metrics() metrics.Metrics 104 | Probes() probes.Probes 105 | Traces() traces.Traces 106 | } 107 | -------------------------------------------------------------------------------- /pkg/runtime/logger/empty/empty.go: -------------------------------------------------------------------------------- 1 | package empty 2 | 3 | import ( 4 | "github.com/lastbackend/toolkit/pkg/runtime/logger" 5 | "go.uber.org/fx/fxevent" 6 | ) 7 | 8 | type emptyLogger struct { 9 | logger.Logger 10 | } 11 | 12 | func (l *emptyLogger) WithFields(_ logger.Fields) logger.Logger { 13 | return l 14 | } 15 | 16 | func (l *emptyLogger) Init(_ logger.Options) logger.Logger { 17 | return l 18 | } 19 | 20 | func (l *emptyLogger) Options() logger.Options { 21 | return logger.Options{} 22 | } 23 | 24 | func (l *emptyLogger) Debug(_ ...interface{}) {} 25 | func (l *emptyLogger) Debugf(_ string, _ ...interface{}) {} 26 | func (l *emptyLogger) Info(_ ...interface{}) {} 27 | func (l *emptyLogger) Infof(_ string, _ ...interface{}) {} 28 | func (l *emptyLogger) Warn(_ ...interface{}) {} 29 | func (l *emptyLogger) Warnf(_ string, _ ...interface{}) {} 30 | func (l *emptyLogger) Error(_ ...interface{}) {} 31 | func (l *emptyLogger) Errorf(_ string, _ ...interface{}) {} 32 | func (l *emptyLogger) Panic(_ ...interface{}) {} 33 | func (l *emptyLogger) Panicf(_ string, _ ...interface{}) {} 34 | func (l *emptyLogger) Fatal(_ ...interface{}) {} 35 | func (l *emptyLogger) Fatalf(_ string, _ ...interface{}) {} 36 | func (l *emptyLogger) V(logger.Level) logger.Logger { 37 | return l 38 | } 39 | 40 | func (l *emptyLogger) Inject(_ func(level logger.Level)) { 41 | 42 | } 43 | 44 | func (l *emptyLogger) Fx() fxevent.Logger { 45 | return fxevent.NopLogger 46 | } 47 | 48 | func NewLogger() logger.Logger { 49 | return new(emptyLogger) 50 | } 51 | -------------------------------------------------------------------------------- /pkg/runtime/logger/logger.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package logger 18 | 19 | import ( 20 | "go.uber.org/fx/fxevent" 21 | "io" 22 | ) 23 | 24 | type Fields map[string]interface{} 25 | 26 | type Level int8 27 | 28 | const ( 29 | FatalLevel Level = iota 30 | PanicLevel 31 | ErrorLevel 32 | WarnLevel 33 | InfoLevel 34 | DebugLevel 35 | ) 36 | 37 | type Options struct { 38 | Verbose Level `env:"VERBOSE"` 39 | JSONFormat bool `env:"JSON_FORMAT"` 40 | CallerSkipCount int 41 | Fields Fields 42 | Out io.Writer 43 | Tags map[string]string 44 | } 45 | 46 | type Logger interface { 47 | Debug(args ...interface{}) 48 | Debugf(format string, args ...interface{}) 49 | Info(args ...interface{}) 50 | Infof(format string, args ...interface{}) 51 | Warn(args ...interface{}) 52 | Warnf(format string, args ...interface{}) 53 | Error(args ...interface{}) 54 | Errorf(format string, args ...interface{}) 55 | Panic(args ...interface{}) 56 | Panicf(format string, args ...interface{}) 57 | Fatal(args ...interface{}) 58 | Fatalf(format string, args ...interface{}) 59 | V(Level) Logger 60 | Inject(fn func(level Level)) 61 | Fx() fxevent.Logger 62 | } 63 | -------------------------------------------------------------------------------- /pkg/runtime/meta/meta.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | type Meta struct { 9 | name string 10 | version string 11 | description string 12 | prefix string 13 | } 14 | 15 | func (m *Meta) SetName(name string) *Meta { 16 | m.name = name 17 | return m 18 | } 19 | 20 | func (m *Meta) GetName() string { 21 | return m.name 22 | } 23 | 24 | func (m *Meta) GetSlug() string { 25 | slug := regexp.MustCompile(`[^_a-zA-Z0-9 ]+`).ReplaceAllString(m.name, "_") 26 | return slug 27 | } 28 | 29 | func (m *Meta) SetVersion(version string) *Meta { 30 | m.version = version 31 | return m 32 | } 33 | 34 | func (m *Meta) GetVersion() string { 35 | return m.version 36 | } 37 | 38 | func (m *Meta) SetDescription(description string) *Meta { 39 | m.description = description 40 | return m 41 | } 42 | 43 | func (m *Meta) GetDescription() string { 44 | return m.description 45 | } 46 | 47 | func (m *Meta) SetEnvPrefix(prefix string) *Meta { 48 | m.prefix = strings.ToUpper(regexp.MustCompile(`[^_a-zA-Z0-9 ]+`).ReplaceAllString(prefix, "")) 49 | return m 50 | } 51 | 52 | func (m *Meta) GetEnvPrefix() string { 53 | return m.prefix 54 | } 55 | -------------------------------------------------------------------------------- /pkg/runtime/options.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | const ( 4 | MetaOptionEnvPrefix = "envPrefix" 5 | MetaOptionVersion = "version" 6 | MetaOptionDescription = "description" 7 | ) 8 | 9 | type Option interface { 10 | Name() string 11 | Value() string 12 | } 13 | 14 | type Options []Option 15 | 16 | type option struct { 17 | name string 18 | value string 19 | } 20 | 21 | func (o *option) Name() string { 22 | return o.name 23 | } 24 | 25 | func (o *option) Value() string { 26 | return o.value 27 | } 28 | 29 | func WithEnvPrefix(prefix string) Option { 30 | return &option{name: MetaOptionEnvPrefix, value: prefix} 31 | } 32 | 33 | func WithVersion(version string) Option { 34 | return &option{name: MetaOptionVersion, value: version} 35 | } 36 | 37 | func WithDescription(description string) Option { 38 | return &option{name: MetaOptionDescription, value: description} 39 | } 40 | -------------------------------------------------------------------------------- /pkg/server/grpc/interceptor.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package grpc 18 | 19 | import ( 20 | "github.com/lastbackend/toolkit/pkg/runtime/logger" 21 | "github.com/lastbackend/toolkit/pkg/server" 22 | ) 23 | 24 | type Interceptors struct { 25 | log logger.Logger 26 | constructors []interface{} 27 | items map[server.KindInterceptor]server.GRPCInterceptor 28 | } 29 | 30 | func (i *Interceptors) AddConstructor(h interface{}) { 31 | i.constructors = append(i.constructors, h) 32 | } 33 | 34 | func (i *Interceptors) Add(h server.GRPCInterceptor) { 35 | i.items[h.Kind()] = h 36 | } 37 | 38 | func newInterceptors(log logger.Logger) *Interceptors { 39 | interceptors := Interceptors{ 40 | log: log, 41 | constructors: make([]interface{}, 0), 42 | items: make(map[server.KindInterceptor]server.GRPCInterceptor), 43 | } 44 | 45 | return &interceptors 46 | } 47 | -------------------------------------------------------------------------------- /pkg/server/grpc/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package grpc 18 | 19 | import ( 20 | "github.com/google/uuid" 21 | "github.com/improbable-eng/grpc-web/go/grpcweb" 22 | "google.golang.org/grpc" 23 | 24 | "crypto/tls" 25 | "time" 26 | ) 27 | 28 | const ( 29 | defaultPort = 9000 30 | defaultName = "go.toolkit.server" 31 | defaultRegisterInterval = time.Second * 30 32 | defaultRegisterTTL = time.Second * 90 33 | ) 34 | 35 | type Config struct { 36 | ID string 37 | Name string `env:"GRPC_SERVER_NAME" envDefault:"" comment:"Set GRPC server name"` 38 | 39 | Host string `env:"GRPC_SERVER_LISTEN" envDefault:"0.0.0.0" comment:"Set GRPC server listen host"` 40 | Port int `env:"GRPC_SERVER_PORT" envDefault:"9000" comment:"Set GRPC server listen port"` 41 | 42 | MaxConnSize int `env:"GRPC_SERVER_MAX_CONNECTION_SIZE" comment:"Sets the max simultaneous connections for server (default unlimited)"` 43 | MaxRecvMsgSize int `env:"GRPC_SERVER_MAX_RECEIVE_MESSAGE_SIZE" envDefault:"16777216" comment:"Sets the max message size in bytes the server can receive (default 16 MB)"` 44 | MaxSendMsgSize int `env:"GRPC_SERVER_MAX_SEND_MESSAGE_SIZE" envDefault:"16777216" comment:"Sets the max message size in bytes the server can send (default 16 MB)"` 45 | 46 | IsDisable bool `env:"GRPC_SERVER_DISABLED" envDefault:"false" comment:"GRPC server disable (default: false)"` 47 | 48 | GrpcOptions []grpc.ServerOption `env:"GRPC_SERVER_OPTIONS" envSeparator:"," comment:"Set GRPC server additional options (key=value,key2=value2)"` 49 | TLSConfig *tls.Config 50 | 51 | GRPCWebHost string `env:"GRPC_WEB_SERVER_LISTEN" envDefault:"0.0.0.0" comment:"Set GRPC WEB server listen host"` 52 | GRPCWebPort int `env:"GRPC_WEB_SERVER_PORT" comment:"Set GRPC WEB server listen host"` 53 | 54 | GrpcWebOptions []grpcweb.Option 55 | 56 | RegisterInterval time.Duration 57 | RegisterTTL time.Duration 58 | } 59 | 60 | func defaultOptions() Config { 61 | return Config{ 62 | ID: uuid.New().String(), 63 | Name: defaultName, 64 | Port: defaultPort, 65 | RegisterInterval: defaultRegisterInterval, 66 | RegisterTTL: defaultRegisterTTL, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pkg/server/http/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package http 18 | 19 | import ( 20 | "errors" 21 | "net/http" 22 | "strconv" 23 | ) 24 | 25 | func HandleGRPCResponse(w http.ResponseWriter, r *http.Request, headers map[string]string) (bool, error) { 26 | 27 | var statusCode, ok = headers["x-http-status-code"] 28 | if !ok { 29 | w.WriteHeader(http.StatusOK) 30 | return true, nil 31 | } 32 | 33 | i, err := strconv.Atoi(statusCode) 34 | if err != nil { 35 | w.WriteHeader(http.StatusInternalServerError) 36 | return false, err 37 | } 38 | 39 | if i < 200 || i > 505 { 40 | w.WriteHeader(http.StatusInternalServerError) 41 | return false, errors.New("invalid status code") 42 | } 43 | 44 | if i == 302 { 45 | if redirectUrl, ok := headers["x-http-redirect-uri"]; ok { 46 | http.Redirect(w, r, redirectUrl, http.StatusFound) 47 | return false, nil 48 | } 49 | } 50 | 51 | w.WriteHeader(i) 52 | return true, nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/server/http/converter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package http 18 | 19 | import ( 20 | "github.com/lastbackend/toolkit/pkg/util/converter" 21 | ) 22 | 23 | var ( 24 | NewReader = converter.NewReader 25 | ParseRequestQueryParametersToProto = converter.ParseRequestQueryParametersToProto 26 | ParseRequestUrlParametersToProto = converter.ParseRequestUrlParametersToProto 27 | SetRawBodyToProto = converter.SetRawBodyToProto 28 | PrepareHeaderFromRequest = converter.PrepareHeaderFromRequest 29 | ) 30 | -------------------------------------------------------------------------------- /pkg/server/http/cors.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/lastbackend/toolkit/pkg/server" 5 | "net/http" 6 | ) 7 | 8 | const corsMiddlewareKind server.KindMiddleware = "corsMiddleware" 9 | 10 | type corsMiddleware struct { 11 | server.DefaultHttpServerMiddleware 12 | handler http.HandlerFunc 13 | } 14 | 15 | func (corsMiddleware) Kind() server.KindMiddleware { 16 | return corsMiddlewareKind 17 | } 18 | 19 | func (corsMiddleware) Order() int { 20 | return 999 21 | } 22 | 23 | func (s *corsMiddleware) Apply(h http.HandlerFunc) http.HandlerFunc { 24 | return func(w http.ResponseWriter, r *http.Request) { 25 | s.handler(w, r) 26 | h.ServeHTTP(w, r) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/server/http/handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package http 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "net/http" 23 | "strings" 24 | 25 | "github.com/lastbackend/toolkit/pkg/context/metadata" 26 | "github.com/lastbackend/toolkit/pkg/server" 27 | grpc_md "google.golang.org/grpc/metadata" 28 | ) 29 | 30 | type HandleOption func(*HandleOptions) 31 | 32 | type HandleOptions struct { 33 | Middlewares server.HttpServerMiddleware 34 | } 35 | 36 | type Handler func(w http.ResponseWriter, r *http.Request) error 37 | 38 | func corsHandlerFunc(w http.ResponseWriter, r *http.Request) { 39 | w.Header().Add("Access-Control-Allow-Origin", r.Header.Get("Origin")) 40 | w.Header().Add("Access-Control-Allow-Credentials", "true") 41 | w.Header().Add("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,DELETE") 42 | w.Header().Add("Access-Control-Expose-Headers", "Content-Disposition") 43 | w.Header().Add("Access-Control-Allow-Headers", "Authorization,Content-Type,Origin,X-Tools-Name,X-Requested-With,Content-Name,Accept,Accept-Range,Range") 44 | w.Header().Add("Content-Type", "application/json") 45 | } 46 | 47 | func (s *httpServer) methodNotAllowedHandler() http.Handler { 48 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 | if s.opts.EnableCORS { 50 | corsHandlerFunc(w, r) 51 | } 52 | w.WriteHeader(http.StatusMethodNotAllowed) 53 | w.Write(makeResponse(http.StatusMethodNotAllowed, "Method Not Allowed")) 54 | }) 55 | } 56 | 57 | func (s *httpServer) methodNotFoundHandler() http.Handler { 58 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 59 | if s.opts.EnableCORS { 60 | corsHandlerFunc(w, r) 61 | } 62 | w.WriteHeader(http.StatusNotFound) 63 | w.Write(makeResponse(http.StatusNotFound, "Not Found")) 64 | }) 65 | } 66 | 67 | type response struct { 68 | Code int `json:"code"` 69 | Status string `json:"status"` 70 | Message string `json:"message"` 71 | } 72 | 73 | func makeResponse(code int, message string) []byte { 74 | r, _ := json.Marshal(response{ 75 | Code: code, 76 | Status: http.StatusText(code), 77 | Message: message, 78 | }) 79 | return r 80 | } 81 | 82 | func NewIncomingContext(ctx context.Context, headers map[string]string) context.Context { 83 | if headers == nil { 84 | headers = make(map[string]string) 85 | } 86 | if md, ok := metadata.LoadFromContext(ctx); ok { 87 | for k, v := range md { 88 | headers[strings.ToLower(k)] = v 89 | } 90 | return grpc_md.NewIncomingContext(ctx, grpc_md.New(md)) 91 | } 92 | return grpc_md.NewIncomingContext(ctx, grpc_md.New(headers)) 93 | } 94 | -------------------------------------------------------------------------------- /pkg/server/http/marshaler.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package http 18 | 19 | import ( 20 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler" 21 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler/formpb" 22 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler/jsonpb" 23 | "google.golang.org/protobuf/encoding/protojson" 24 | ) 25 | 26 | const ( 27 | ContentTypeForm = "application/x-www-form-urlencoded" 28 | ContentTypeJSON = "application/json" 29 | ) 30 | 31 | var ( 32 | DefaultMarshaler = &jsonpb.JSONPb{ 33 | MarshalOptions: defaultMarshalOptions, 34 | UnmarshalOptions: defaultUnmarshalOptions, 35 | } 36 | ) 37 | 38 | var ( 39 | defaultMarshalOptions = protojson.MarshalOptions{ 40 | EmitUnpopulated: true, // Include fields with default values in the JSON output 41 | UseProtoNames: true, // Use original proto field names (e.g., 'start_date') instead of CamelCase (e.g., 'StartDate') 42 | } 43 | defaultUnmarshalOptions = protojson.UnmarshalOptions{ 44 | DiscardUnknown: true, // Ignores unknown fields during deserialization 45 | } 46 | ) 47 | 48 | func GetMarshalerMap() map[string]marshaler.Marshaler { 49 | return map[string]marshaler.Marshaler{ 50 | ContentTypeJSON: &jsonpb.JSONPb{ 51 | MarshalOptions: defaultMarshalOptions, 52 | UnmarshalOptions: defaultUnmarshalOptions, 53 | }, 54 | ContentTypeForm: &formpb.FORMPb{ 55 | MarshalOptions: defaultMarshalOptions, 56 | UnmarshalOptions: defaultUnmarshalOptions, 57 | }, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/server/http/marshaler/marshaler.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package marshaler 18 | 19 | import ( 20 | "io" 21 | ) 22 | 23 | type Marshaler interface { 24 | Marshal(v interface{}) ([]byte, error) 25 | Unmarshal(data []byte, v interface{}) error 26 | NewDecoder(r io.Reader) Decoder 27 | NewEncoder(w io.Writer) Encoder 28 | ContentType() string 29 | } 30 | 31 | type Decoder interface { 32 | Decode(v interface{}) error 33 | } 34 | 35 | type Encoder interface { 36 | Encode(v interface{}) error 37 | } 38 | 39 | type DecoderFunc func(v interface{}) error 40 | 41 | func (f DecoderFunc) Decode(v interface{}) error { return f(v) } 42 | 43 | type EncoderFunc func(v interface{}) error 44 | 45 | func (f EncoderFunc) Encode(v interface{}) error { return f(v) } 46 | 47 | type Delimited interface { 48 | Delimiter() []byte 49 | } 50 | -------------------------------------------------------------------------------- /pkg/server/http/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package http 18 | 19 | import ( 20 | "crypto/tls" 21 | "github.com/lastbackend/toolkit/pkg/server" 22 | "net/http" 23 | ) 24 | 25 | const ( 26 | defaultAddress = ":8080" 27 | optionKindMiddleware server.HttpOptionKind = "middleware" 28 | optionKindExcludeGlobalMiddleware server.HttpOptionKind = "excludeGlobalMiddleware" 29 | ) 30 | 31 | const ( 32 | MethodGet = "GET" 33 | MethodHead = "HEAD" 34 | MethodPost = "POST" 35 | MethodPut = "PUT" 36 | MethodPatch = "PATCH" // RFC 5789 37 | MethodDelete = "DELETE" 38 | MethodConnect = "CONNECT" 39 | MethodOptions = "OPTIONS" 40 | MethodTrace = "TRACE" 41 | ) 42 | 43 | type optionMiddleware struct { 44 | kind server.HttpOptionKind 45 | middleware server.KindMiddleware 46 | } 47 | 48 | func (optionMiddleware) Kind() server.HttpOptionKind { 49 | return optionKindMiddleware 50 | } 51 | 52 | func WithMiddleware(middleware server.KindMiddleware) server.HTTPServerOption { 53 | return &optionMiddleware{kind: optionKindMiddleware, middleware: middleware} 54 | } 55 | 56 | type optionExcludeGlobalMiddleware struct { 57 | kind server.HttpOptionKind 58 | regexp string 59 | } 60 | 61 | func (optionExcludeGlobalMiddleware) Kind() server.HttpOptionKind { 62 | return optionKindExcludeGlobalMiddleware 63 | } 64 | 65 | func WithExcludeGlobalMiddleware(regexp string) server.HTTPServerOption { 66 | return &optionExcludeGlobalMiddleware{kind: optionKindMiddleware, regexp: regexp} 67 | } 68 | 69 | type Config struct { 70 | Id string 71 | 72 | Host string `env:"SERVER_LISTEN" envDefault:"0.0.0.0" comment:"Set HTTP server listen host"` 73 | Port int `env:"SERVER_PORT" envDefault:"8080" comment:"Set HTTP server listen port"` 74 | 75 | Prefix string 76 | 77 | EnableCORS bool `env:"SERVER_CORS_ENABLED" envDefault:"false" comment:"Enable Cross-Origin Resource Sharing header"` 78 | IsDisable bool 79 | 80 | TLSConfig *tls.Config 81 | } 82 | 83 | type Wrapper func(h http.Handler) http.Handler 84 | -------------------------------------------------------------------------------- /pkg/server/http/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package http 18 | -------------------------------------------------------------------------------- /pkg/server/http/websockets/event.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package websockets 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | ) 23 | 24 | type Event struct { 25 | Type string `json:"type"` 26 | Payload json.RawMessage `json:"payload"` 27 | } 28 | 29 | type EventHandler func(ctx context.Context, event Event, c *Client) error 30 | -------------------------------------------------------------------------------- /pkg/server/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package server 18 | 19 | import ( 20 | "context" 21 | "crypto/tls" 22 | "github.com/lastbackend/toolkit/pkg/server/http/marshaler" 23 | "github.com/lastbackend/toolkit/pkg/server/http/websockets" 24 | "google.golang.org/grpc" 25 | "net/http" 26 | ) 27 | 28 | type HTTPServer interface { 29 | Start(ctx context.Context) error 30 | Stop(ctx context.Context) error 31 | 32 | UseMiddleware(...KindMiddleware) 33 | UseMarshaler(contentType string, marshaler marshaler.Marshaler) error 34 | 35 | GetMiddlewares() []any 36 | SetMiddleware(middleware any) 37 | 38 | SetService(service interface{}) 39 | GetService() interface{} 40 | 41 | AddHandler(method, path string, h http.HandlerFunc, opts ...HTTPServerOption) 42 | 43 | GetConstructor() interface{} 44 | 45 | Subscribe(event string, h websockets.EventHandler) 46 | Info() ServerInfo 47 | 48 | ServerWS(w http.ResponseWriter, r *http.Request) 49 | SetCorsHandlerFunc(hf http.HandlerFunc) 50 | SetErrorHandlerFunc(hf func(http.ResponseWriter, error)) 51 | } 52 | 53 | type HTTPServerOptions struct { 54 | Host string 55 | Port int 56 | 57 | TLSConfig *tls.Config 58 | } 59 | 60 | type ServerInfo struct { 61 | Kind ServerKind 62 | Host string 63 | Port int 64 | 65 | TLSConfig *tls.Config 66 | } 67 | 68 | type HTTPServerHandler struct { 69 | Method string 70 | Path string 71 | Handler http.HandlerFunc 72 | Options []HTTPServerOption 73 | } 74 | 75 | type HttpOptionKind string 76 | 77 | type HTTPServerOption interface { 78 | Kind() HttpOptionKind 79 | } 80 | 81 | type DefaultHttpServerMiddleware struct{} 82 | 83 | func (DefaultHttpServerMiddleware) Order() int { 84 | return 0 85 | } 86 | 87 | type HttpServerMiddleware interface { 88 | Apply(h http.HandlerFunc) http.HandlerFunc 89 | Kind() KindMiddleware 90 | Order() int 91 | } 92 | 93 | type KindMiddleware string 94 | type KindInterceptor string 95 | 96 | type GRPCServer interface { 97 | Start(ctx context.Context) error 98 | Stop() error 99 | 100 | SetDescriptor(descriptor grpc.ServiceDesc) 101 | 102 | SetService(constructor interface{}) 103 | SetConstructor(fn interface{}) 104 | 105 | GetInterceptors() []interface{} 106 | SetInterceptor(interceptor any) 107 | 108 | GetService() interface{} 109 | GetConstructor() interface{} 110 | GetInterceptorsConstructor() interface{} 111 | 112 | RegisterService(service interface{}) 113 | Info() ServerInfo 114 | } 115 | 116 | type GRPCServerOptions struct { 117 | Host string 118 | Port int 119 | 120 | TLSConfig *tls.Config 121 | } 122 | 123 | type ServerKind string 124 | 125 | type GRPCInterceptor interface { 126 | Kind() KindInterceptor 127 | Order() int 128 | Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) 129 | } 130 | 131 | const ( 132 | ServerKindHTTPServer = "http" 133 | ServerKindGRPCServer = "grpc" 134 | ) 135 | -------------------------------------------------------------------------------- /pkg/tools/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | type Metrics interface { 4 | } 5 | -------------------------------------------------------------------------------- /pkg/tools/probes/probes.go: -------------------------------------------------------------------------------- 1 | package probes 2 | 3 | import "context" 4 | 5 | type ProbeKind int 6 | type HandleFunc func() error 7 | 8 | const ( 9 | ReadinessProbe ProbeKind = iota 10 | LivenessProbe 11 | ) 12 | 13 | type Probes interface { 14 | Start(ctx context.Context) error 15 | RegisterCheck(name string, kind ProbeKind, fn HandleFunc) error 16 | } 17 | -------------------------------------------------------------------------------- /pkg/tools/traces/traces.go: -------------------------------------------------------------------------------- 1 | package traces 2 | 3 | type Traces interface { 4 | } 5 | -------------------------------------------------------------------------------- /pkg/util/addr/addr.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package addr 18 | 19 | import ( 20 | "net" 21 | ) 22 | 23 | func addrToIP(addr net.Addr) net.IP { 24 | switch v := addr.(type) { 25 | case *net.IPAddr: 26 | return v.IP 27 | case *net.IPNet: 28 | return v.IP 29 | default: 30 | return nil 31 | } 32 | } 33 | 34 | func localIPs() []string { 35 | ifaces, err := net.Interfaces() 36 | if err != nil { 37 | return nil 38 | } 39 | 40 | var ipAddrs []string 41 | 42 | for _, iface := range ifaces { 43 | addrs, err := iface.Addrs() 44 | if err != nil { 45 | continue // ignore error 46 | } 47 | 48 | for _, addr := range addrs { 49 | if ip := addrToIP(addr); ip != nil { 50 | ipAddrs = append(ipAddrs, ip.String()) 51 | } 52 | } 53 | } 54 | 55 | return ipAddrs 56 | } 57 | 58 | func DetectIP() (string, error) { 59 | conn, err := net.Dial("udp", "127.0.0.1:9000") 60 | if err != nil { 61 | return "", err 62 | } 63 | 64 | defer conn.Close() 65 | addr := conn.LocalAddr().(*net.UDPAddr) 66 | return addr.IP.String(), nil 67 | } 68 | 69 | func IPs() []string { 70 | return localIPs() 71 | } 72 | -------------------------------------------------------------------------------- /pkg/util/backoff/backoff.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package backoff 18 | 19 | import ( 20 | "math" 21 | "sync/atomic" 22 | "time" 23 | ) 24 | 25 | const maxInt64 = float64(math.MaxInt64 - 512) 26 | 27 | type Backoff struct { 28 | attempt uint64 29 | // Factor are the multiplication factor 30 | Factor float64 31 | // Min and Max are the minimum and maximum counter values. 32 | Min, Max time.Duration 33 | } 34 | 35 | func (b *Backoff) Duration() time.Duration { 36 | d := b.ForAttempt(float64(atomic.AddUint64(&b.attempt, 1) - 1)) 37 | return d 38 | } 39 | 40 | func (b *Backoff) ForAttempt(attempt float64) time.Duration { 41 | min := b.Min 42 | if min <= 0 { 43 | min = 100 * time.Millisecond 44 | } 45 | max := b.Max 46 | if max <= 0 { 47 | max = 10 * time.Second 48 | } 49 | if min >= max { 50 | return max 51 | } 52 | 53 | factor := b.Factor 54 | if factor <= 0 { 55 | factor = 2 56 | } 57 | 58 | duration := float64(min) * math.Pow(factor, attempt) 59 | 60 | if duration > maxInt64 { 61 | return max 62 | } 63 | 64 | d := time.Duration(duration) 65 | if d < min { 66 | return min 67 | } 68 | if d > max { 69 | return max 70 | } 71 | return d 72 | } 73 | 74 | func (b *Backoff) Reset() { 75 | atomic.StoreUint64(&b.attempt, 0) 76 | } 77 | 78 | func (b *Backoff) Attempt() float64 { 79 | return float64(atomic.LoadUint64(&b.attempt)) 80 | } 81 | -------------------------------------------------------------------------------- /pkg/util/converter/converter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package converter 18 | 19 | import ( 20 | "encoding/base64" 21 | "fmt" 22 | "math" 23 | "reflect" 24 | "strconv" 25 | "strings" 26 | "time" 27 | ) 28 | 29 | func ToTrimRegexFromQueryParameter(v string) string { 30 | return strings.Split(v, ":")[0] 31 | } 32 | 33 | func StringToInt64(s string) int64 { 34 | i, _ := strconv.ParseInt(s, 10, 64) 35 | return i 36 | } 37 | 38 | func StringToInt(s string) int { 39 | i, _ := strconv.Atoi(s) 40 | return i 41 | } 42 | 43 | func StringToFloat(s string) float64 { 44 | i, _ := strconv.ParseFloat(s, 64) 45 | return i 46 | } 47 | 48 | func IntToString(i int) string { 49 | return strconv.Itoa(i) 50 | } 51 | 52 | func StringToBool(s string) bool { 53 | s = strings.ToLower(s) 54 | if s == "true" || s == "1" || s == "t" { 55 | return true 56 | } 57 | return false 58 | } 59 | 60 | func StringToUint(s string) uint { 61 | i, _ := strconv.ParseUint(s, 10, 64) 62 | return uint(i) 63 | } 64 | 65 | func BoolToString(str string) (bool, error) { 66 | switch str { 67 | case "": 68 | return false, nil 69 | case "1", "t", "T", "true", "TRUE", "True": 70 | return true, nil 71 | case "0", "f", "F", "false", "FALSE", "False": 72 | return false, nil 73 | } 74 | return false, fmt.Errorf("parse bool string: %s", str) 75 | } 76 | 77 | func Int64ToInt(i int64) int { 78 | return StringToInt(strconv.FormatInt(i, 10)) 79 | } 80 | 81 | func DecodeBase64(s string) string { 82 | buf, _ := base64.StdEncoding.DecodeString(s) 83 | return string(buf) 84 | } 85 | 86 | func EncodeBase64(s string) string { 87 | return base64.StdEncoding.EncodeToString([]byte(s)) 88 | } 89 | 90 | func HumanizeTimeDuration(duration time.Duration) string { 91 | hours := int64(math.Mod(duration.Hours(), 24)) 92 | minutes := int64(math.Mod(duration.Minutes(), 60)) 93 | seconds := int64(math.Mod(duration.Seconds(), 60)) 94 | 95 | chunks := []struct { 96 | amount int64 97 | }{ 98 | {hours}, 99 | {minutes}, 100 | {seconds}, 101 | } 102 | 103 | parts := make([]string, 0) 104 | 105 | for _, chunk := range chunks { 106 | switch chunk.amount { 107 | case 0: 108 | parts = append(parts, "00") 109 | continue 110 | default: 111 | if chunk.amount >= 10 { 112 | parts = append(parts, fmt.Sprintf("%d", chunk.amount)) 113 | } else { 114 | parts = append(parts, fmt.Sprintf("0%d", chunk.amount)) 115 | } 116 | } 117 | } 118 | 119 | return strings.Join(parts, ":") 120 | } 121 | 122 | func EnforcePtr(obj interface{}) (reflect.Value, error) { 123 | v := reflect.ValueOf(obj) 124 | if v.Kind() != reflect.Ptr { 125 | if v.Kind() == reflect.Invalid { 126 | return reflect.Value{}, fmt.Errorf("expected pointer, but got invalid kind") 127 | } 128 | return reflect.Value{}, fmt.Errorf("expected pointer, but got %v type", v.Type()) 129 | } 130 | if v.IsNil() { 131 | return reflect.Value{}, fmt.Errorf("expected pointer, but got nil") 132 | } 133 | return v.Elem(), nil 134 | } 135 | 136 | func ToBoolPointer(v bool) *bool { 137 | return &v 138 | } 139 | 140 | func ToStringPointer(v string) *string { 141 | return &v 142 | } 143 | 144 | func ToIntPointer(v int) *int { 145 | return &v 146 | } 147 | 148 | func ToInt64Pointer(v int64) *int64 { 149 | return &v 150 | } 151 | 152 | func ToDurationPointer(v time.Duration) *time.Duration { 153 | return &v 154 | } 155 | -------------------------------------------------------------------------------- /pkg/util/filesystem/filesystem.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package filesystem 18 | 19 | import ( 20 | "os" 21 | "path/filepath" 22 | ) 23 | 24 | func WalkMatch(root, pattern string) ([]string, error) { 25 | var matches []string 26 | err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 27 | if err != nil { 28 | return err 29 | } 30 | if info.IsDir() { 31 | return nil 32 | } 33 | if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil { 34 | return err 35 | } else if matched { 36 | matches = append(matches, path) 37 | } 38 | return nil 39 | }) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return matches, nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/util/net/net.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package net 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "net" 23 | "strconv" 24 | "strings" 25 | ) 26 | 27 | // HostPort format addr and port suitable for dial 28 | func HostPort(addr string, port interface{}) string { 29 | host := addr 30 | if strings.Count(addr, ":") > 0 { 31 | host = fmt.Sprintf("[%s]", addr) 32 | } 33 | // when port is blank or 0, host is a queue name 34 | if v, ok := port.(string); ok && v == "" { 35 | return host 36 | } else if v, ok := port.(int); ok && v == 0 && net.ParseIP(host) == nil { 37 | return host 38 | } 39 | 40 | return fmt.Sprintf("%s:%v", host, port) 41 | } 42 | 43 | // Listen takes addr:portmin-portmax and binds to the first available port 44 | // Example: Listen("localhost:5000-6000", fn) 45 | func Listen(addr string, fn func(string) (net.Listener, error)) (net.Listener, error) { 46 | 47 | if strings.Count(addr, ":") == 1 && strings.Count(addr, "-") == 0 { 48 | return fn(addr) 49 | } 50 | 51 | // host:port || host:min-max 52 | host, ports, err := net.SplitHostPort(addr) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | // try to extract port range 58 | prange := strings.Split(ports, "-") 59 | 60 | // single port 61 | if len(prange) < 2 { 62 | return fn(addr) 63 | } 64 | 65 | // we have a port range 66 | 67 | // extract min port 68 | min, err := strconv.Atoi(prange[0]) 69 | if err != nil { 70 | return nil, errors.New("unable to extract port range") 71 | } 72 | 73 | // extract max port 74 | max, err := strconv.Atoi(prange[1]) 75 | if err != nil { 76 | return nil, errors.New("unable to extract port range") 77 | } 78 | 79 | // range the ports 80 | for port := min; port <= max; port++ { 81 | // try bind to host:port 82 | ln, err := fn(HostPort(host, port)) 83 | if err == nil { 84 | return ln, nil 85 | } 86 | 87 | // hit max port 88 | if port == max { 89 | return nil, err 90 | } 91 | } 92 | 93 | // why are we here? 94 | return nil, fmt.Errorf("unable to bind to %s", addr) 95 | } 96 | -------------------------------------------------------------------------------- /pkg/util/parser/parser.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package parser 18 | 19 | import "strings" 20 | 21 | func ParseNameAndVendor(s string) (string, string) { 22 | i := strings.LastIndex(s, "/") 23 | if i == -1 { 24 | return s, "" 25 | } 26 | return s[i+1:], s[:i+1] 27 | } 28 | -------------------------------------------------------------------------------- /pkg/util/strings/strings.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package strings 18 | 19 | import ( 20 | "golang.org/x/text/cases" 21 | "golang.org/x/text/language" 22 | "strings" 23 | ) 24 | 25 | var uppercaseAcronym = map[string]string{ 26 | "ID": "id", 27 | } 28 | 29 | // ToCamelCase Converts a string to CamelCase 30 | func toCamelCase(s string, initCase bool) string { 31 | s = strings.TrimSpace(s) 32 | if s == "" { 33 | return s 34 | } 35 | if a, ok := uppercaseAcronym[s]; ok { 36 | s = a 37 | } 38 | 39 | n := strings.Builder{} 40 | n.Grow(len(s)) 41 | capNext := initCase 42 | for i, v := range []byte(s) { 43 | vIsCap := v >= 'A' && v <= 'Z' 44 | vIsLow := v >= 'a' && v <= 'z' 45 | if capNext { 46 | if vIsLow { 47 | v += 'A' 48 | v -= 'a' 49 | } 50 | } else if i == 0 { 51 | if vIsCap { 52 | v += 'a' 53 | v -= 'A' 54 | } 55 | } 56 | if vIsCap || vIsLow { 57 | n.WriteByte(v) 58 | capNext = false 59 | } else if vIsNum := v >= '0' && v <= '9'; vIsNum { 60 | n.WriteByte(v) 61 | capNext = true 62 | } else { 63 | capNext = v == '_' || v == ' ' || v == '-' || v == '.' 64 | } 65 | } 66 | return n.String() 67 | } 68 | 69 | // ToCamel converts a string to CamelCase 70 | func ToCamel(s string) string { 71 | return toCamelCase(s, true) 72 | } 73 | 74 | // ToLowerCamel converts a string to lowerCamelCase 75 | func ToLowerCamel(s string) string { 76 | return toCamelCase(s, false) 77 | } 78 | 79 | func Title(s string) string { 80 | return cases.Title(language.English).String(s) 81 | } 82 | 83 | func ToUpper(s string) string { 84 | return strings.ToUpper(s) 85 | } 86 | 87 | func ToLower(s string) string { 88 | return strings.ToLower(s) 89 | } 90 | 91 | func GitParse(s string) string { 92 | list := strings.Split(s, "/") 93 | return strings.Join(list[:2], "/") 94 | } 95 | 96 | func DeHyphenFunc(s string) string { 97 | return strings.ReplaceAll(s, "-", "") 98 | } 99 | 100 | func LowerHyphenFunc(s string) string { 101 | return strings.ReplaceAll(s, "-", "_") 102 | } 103 | 104 | func ToHyphen(s string) string { 105 | return strings.ReplaceAll(s, "-", "_") 106 | } 107 | -------------------------------------------------------------------------------- /pkg/util/tls/tls.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package tls 18 | 19 | import ( 20 | "bytes" 21 | "crypto/ecdsa" 22 | "crypto/elliptic" 23 | "crypto/rand" 24 | "crypto/tls" 25 | "crypto/x509" 26 | "crypto/x509/pkix" 27 | "encoding/pem" 28 | "math/big" 29 | "net" 30 | "time" 31 | ) 32 | 33 | func Certificate(host ...string) (tls.Certificate, error) { 34 | priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 35 | if err != nil { 36 | return tls.Certificate{}, err 37 | } 38 | 39 | notBefore := time.Now() 40 | notAfter := notBefore.Add(time.Hour * 24 * 365) 41 | 42 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 43 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 44 | if err != nil { 45 | return tls.Certificate{}, err 46 | } 47 | 48 | template := x509.Certificate{ 49 | SerialNumber: serialNumber, 50 | Subject: pkix.Name{ 51 | Organization: []string{"Acme Co"}, 52 | }, 53 | NotBefore: notBefore, 54 | NotAfter: notAfter, 55 | 56 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 57 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 58 | BasicConstraintsValid: true, 59 | } 60 | 61 | for _, h := range host { 62 | if ip := net.ParseIP(h); ip != nil { 63 | template.IPAddresses = append(template.IPAddresses, ip) 64 | } else { 65 | template.DNSNames = append(template.DNSNames, h) 66 | } 67 | } 68 | 69 | template.IsCA = true 70 | template.KeyUsage |= x509.KeyUsageCertSign 71 | 72 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 73 | if err != nil { 74 | return tls.Certificate{}, err 75 | } 76 | 77 | // create public key 78 | certOut := bytes.NewBuffer(nil) 79 | 80 | err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 81 | if err != nil { 82 | return tls.Certificate{}, err 83 | } 84 | 85 | // create private key 86 | keyOut := bytes.NewBuffer(nil) 87 | b, err := x509.MarshalECPrivateKey(priv) 88 | if err != nil { 89 | return tls.Certificate{}, err 90 | } 91 | err = pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) 92 | if err != nil { 93 | return tls.Certificate{}, err 94 | } 95 | 96 | return tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes()) 97 | } 98 | -------------------------------------------------------------------------------- /pkg/util/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "reflect" 4 | 5 | func Type(myvar interface{}) string { 6 | if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr { 7 | return "*" + t.Elem().PkgPath() + "." + t.Elem().Name() 8 | } else { 9 | return t.PkgPath() + "." + t.Name() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /protoc-gen-toolkit/generator/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package generator 18 | 19 | import ( 20 | "flag" 21 | 22 | "github.com/lastbackend/toolkit/protoc-gen-toolkit/descriptor" 23 | "github.com/lastbackend/toolkit/protoc-gen-toolkit/gentoolkit" 24 | "google.golang.org/protobuf/compiler/protogen" 25 | "google.golang.org/protobuf/types/pluginpb" 26 | ) 27 | 28 | type Generator struct { 29 | files []*descriptor.File 30 | } 31 | 32 | func Init() *Generator { 33 | g := new(Generator) 34 | g.files = make([]*descriptor.File, 0) 35 | return g 36 | } 37 | 38 | func (g *Generator) Run() error { 39 | var flagSet flag.FlagSet 40 | 41 | protogen.Options{ 42 | ParamFunc: flagSet.Set, 43 | }.Run(func(gen *protogen.Plugin) (err error) { 44 | 45 | gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) 46 | 47 | desc := descriptor.NewDescriptor() 48 | 49 | if err := desc.LoadFromPlugin(gen); err != nil { 50 | return err 51 | } 52 | 53 | if err := g.LoadFiles(gen, desc); err != nil { 54 | return err 55 | } 56 | 57 | // Generate generates a *.pb.toolkit.go file containing Toolkit service definitions. 58 | contentFiles, err := gentoolkit.New(desc).Generate(g.files) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | // Write generated files to disk 64 | for _, f := range contentFiles { 65 | genFile := gen.NewGeneratedFile(f.GetName(), protogen.GoImportPath(f.GoPkg.Path)) 66 | if _, err := genFile.Write([]byte(f.GetContent())); err != nil { 67 | return err 68 | } 69 | } 70 | 71 | return nil 72 | }) 73 | 74 | return nil 75 | } 76 | 77 | func (g *Generator) LoadFiles(gen *protogen.Plugin, desc *descriptor.Descriptor) (err error) { 78 | g.files = make([]*descriptor.File, 0) 79 | for _, f := range gen.Request.FileToGenerate { 80 | file, err := desc.FindFile(f) 81 | if err != nil { 82 | return err 83 | } 84 | g.files = append(g.files, file) 85 | } 86 | return err 87 | } 88 | -------------------------------------------------------------------------------- /protoc-gen-toolkit/gentoolkit/templates/header.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // HeaderTpl is the header template used for new services. 20 | var HeaderTpl = `// Code generated by protoc-gen-toolkit. DO NOT EDIT. 21 | // source: {{ .GetName }} 22 | 23 | package {{ .GoPkg.Name }} 24 | 25 | import ( 26 | {{ range $i := .Imports }}{{ if $i.Standard }}{{ $i | printf "%s\n" }}{{ end }}{{ end }} 27 | 28 | {{ range $i := .Imports }}{{ if not $i.Standard }}{{ $i | printf "%s\n" }}{{ end }}{{ end }} 29 | ) 30 | ` 31 | -------------------------------------------------------------------------------- /protoc-gen-toolkit/gentoolkit/templates/message.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // MessageTpl is the message template used for new services. 20 | var MessageTpl = `// Code generated by protoc-gen-toolkit. DO NOT EDIT. 21 | // source: {{ .GetName }} 22 | 23 | package {{ .GoPkg.Name }} 24 | 25 | // {{ .Message }} 26 | ` 27 | -------------------------------------------------------------------------------- /protoc-gen-toolkit/gentoolkit/templates/plugin.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // PluginDefineTpl is the plugin define template used for new services. 20 | var PluginDefineTpl = ` 21 | {{- range $type, $plugins := . }} 22 | {{- range $index, $plugin := $plugins }} 23 | type {{ $plugin.Prefix | ToCamel }}Plugin interface { 24 | {{ $type }}.Plugin 25 | } 26 | {{ end }} 27 | {{- end }} 28 | ` 29 | 30 | // PluginInitializeTpl is the plugin initialize template used for new services. 31 | var PluginInitializeTpl = ` 32 | {{- range $type, $plugins := . }} 33 | {{- range $index, $plugin := $plugins }} 34 | plugin_{{ $plugin.Prefix | ToLower }} := {{ $type }}.NewPlugin(app.runtime, &{{ $type }}.Options{Name: "{{ $plugin.Prefix | ToLower }}"}) 35 | {{- end }} 36 | {{- end -}} 37 | ` 38 | 39 | // PluginRegisterTpl is the plugin register template used for new services. 40 | var PluginRegisterTpl = ` 41 | {{- range $type, $plugins := . }} 42 | {{- range $index, $plugin := $plugins }} 43 | app.runtime.Plugin().Provide(func() {{ $plugin.Prefix | ToCamel }}Plugin { return plugin_{{ $plugin.Prefix | ToLower }} }) 44 | {{- end }} 45 | {{- end -}} 46 | ` 47 | -------------------------------------------------------------------------------- /protoc-gen-toolkit/gentoolkit/templates/test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package templates 18 | 19 | // TestTpl is the test template used for new services. 20 | var TestTpl = `// Suppress "imported and not used" errors 21 | var _ context.Context 22 | var _ emptypb.Empty 23 | var _ empty.Empty 24 | 25 | {{ range $svc := .Services }} 26 | // Mock server API for Api service 27 | type {{ $svc.GetName }}Stubs struct { 28 | {{ range $m := $svc.Methods }} 29 | {{ if and (not $m.GetServerStreaming) (not $m.GetClientStreaming) }} 30 | {{ $m.GetName }} []{{ $m.GetName }}Stub 31 | {{ else }} 32 | {{ end }} 33 | {{ end }} 34 | } 35 | 36 | func New{{ $svc.GetName }}Stubs() *{{ $svc.GetName }}Stubs { 37 | stubs:= new({{ $svc.GetName }}Stubs) 38 | {{ range $m := $svc.Methods }} 39 | {{ if and (not $m.GetServerStreaming) (not $m.GetClientStreaming) }} 40 | stubs.{{ $m.GetName }} = make([]{{ $m.GetName }}Stub,0) 41 | {{ end }} 42 | {{ end }} 43 | return stubs 44 | } 45 | 46 | func With{{ $svc.GetName }}Stubs(stubs *{{ $svc.GetName }}Stubs) servicepb.{{ $svc.GetName }}RPCClient{ 47 | 48 | rpc_mock := new(service_mocks.{{ $svc.GetName }}RPCClient) 49 | 50 | {{ range $m := $svc.Methods }} 51 | {{ if and (not $m.GetServerStreaming) (not $m.GetClientStreaming) }} 52 | for _, st := range stubs.{{ $m.GetName }} { 53 | resp := st.Response 54 | err := st.Error 55 | rpc_mock.On("{{ $m.GetName }}", st.Context, st.Request).Return( 56 | func(ctx context.Context, req *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}, opts ...client.GRPCCallOption) *{{ $m.ResponseType.GoType $m.Service.File.GoPkg.Path }} { 57 | return resp 58 | }, 59 | func(ctx context.Context, req *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}, opts ...client.GRPCCallOption) error { 60 | return err 61 | }, 62 | ) 63 | rpc_mock.On("{{ $m.GetName }}", st.Context, st.Request, st.CallOptions).Return( 64 | func(ctx context.Context, req *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}, opts ...client.GRPCCallOption) *{{ $m.ResponseType.GoType $m.Service.File.GoPkg.Path }} { 65 | return resp 66 | }, 67 | func(ctx context.Context, req *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }}, opts ...client.GRPCCallOption) error { 68 | return err 69 | }, 70 | ) 71 | } 72 | {{ end }} 73 | {{ end }} 74 | 75 | return rpc_mock 76 | } 77 | {{ end }} 78 | 79 | {{ range $svc := .Services }} 80 | {{ range $m := $svc.Methods }} 81 | type {{ $m.GetName }}Stub struct { 82 | {{ if and (not $m.GetServerStreaming) (not $m.GetClientStreaming) }} 83 | Context context.Context 84 | Request *{{ $m.RequestType.GoType $m.Service.File.GoPkg.Path }} 85 | Response *{{ $m.ResponseType.GoType $m.Service.File.GoPkg.Path }} 86 | CallOptions []client.GRPCCallOption 87 | Error error 88 | {{ end }} 89 | } 90 | {{ end }} 91 | {{ end }} 92 | ` 93 | -------------------------------------------------------------------------------- /protoc-gen-toolkit/gentoolkit/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package gentoolkit 18 | 19 | func camel(s string) string { 20 | if s == "" { 21 | return "" 22 | } 23 | t := make([]byte, 0, 32) 24 | i := 0 25 | if s[0] == '_' { 26 | // Need a capital letter; drop the '_'. 27 | t = append(t, 'X') 28 | i++ 29 | } 30 | // Invariant: if the next letter is lower case, it must be converted 31 | // to upper case. 32 | // That is, we process a word at a time, where words are marked by _ or 33 | // upper case letter. Digits are treated as words. 34 | for ; i < len(s); i++ { 35 | c := s[i] 36 | if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { 37 | continue // Skip the underscore in s. 38 | } 39 | if isASCIIDigit(c) { 40 | t = append(t, c) 41 | continue 42 | } 43 | // Assume we have a letter now - if not, it's a bogus identifier. 44 | // The next word is a sequence of characters that must start upper case. 45 | if isASCIILower(c) { 46 | c ^= ' ' // Make it a capital letter. 47 | } 48 | t = append(t, c) // Guaranteed not lower case. 49 | // Accept lower case sequence that follows. 50 | for i+1 < len(s) && isASCIILower(s[i+1]) { 51 | i++ 52 | t = append(t, s[i]) 53 | } 54 | } 55 | return string(t) 56 | } 57 | 58 | // And now lots of helper functions. 59 | 60 | // Is c an ASCII lower-case letter? 61 | func isASCIILower(c byte) bool { 62 | return 'a' <= c && c <= 'z' 63 | } 64 | 65 | // Is c an ASCII digit? 66 | func isASCIIDigit(c byte) bool { 67 | return '0' <= c && c <= '9' 68 | } 69 | -------------------------------------------------------------------------------- /protoc-gen-toolkit/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/lastbackend/toolkit/protoc-gen-toolkit/generator" 21 | 22 | "os" 23 | ) 24 | 25 | func main() { 26 | g := generator.Init() 27 | if err := g.Run(); err != nil { 28 | os.Exit(1) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /protoc-gen-toolkit/toolkit/options/annotations.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package toolkit; 4 | 5 | option go_package = "github.com/lastbackend/toolkit/protoc-gen-toolkit/toolkit/options;annotations"; 6 | 7 | import "google/protobuf/descriptor.proto"; 8 | 9 | message Plugin { 10 | string plugin = 1; 11 | string prefix = 2; 12 | } 13 | 14 | message Service { 15 | string service = 1; 16 | string package = 2; 17 | } 18 | 19 | extend google.protobuf.FileOptions { 20 | repeated Plugin plugins = 50001; 21 | repeated Service services = 50002; 22 | TestSpec tests_spec = 50004; 23 | } 24 | 25 | message Runtime { 26 | enum Server { 27 | GRPC = 0; 28 | HTTP = 1; 29 | WEBSOCKET_PROXY = 2; 30 | WEBSOCKET = 3; 31 | } 32 | repeated Plugin plugins = 1; 33 | repeated Server servers = 2; 34 | } 35 | 36 | extend google.protobuf.ServiceOptions { 37 | Runtime runtime = 70001; 38 | Server server = 70002; 39 | } 40 | 41 | message TestSpec { 42 | MockeryTestsSpec mockery = 1; 43 | } 44 | 45 | message MockeryTestsSpec { 46 | string package = 1; 47 | } 48 | 49 | extend google.protobuf.MethodOptions { 50 | Route route = 60001; 51 | } 52 | 53 | message Server { 54 | repeated string middlewares = 1; 55 | } 56 | 57 | message Route { 58 | repeated string middlewares = 1; 59 | repeated string exclude_global_middlewares = 2; 60 | oneof server { 61 | HttpProxy http_proxy = 3; 62 | WsProxy websocket_proxy = 4; 63 | bool websocket = 5; 64 | } 65 | } 66 | 67 | message HttpProxy { 68 | string service = 1; 69 | string method = 2; 70 | } 71 | 72 | message WsProxy { 73 | string service = 1; 74 | string method = 2; 75 | } 76 | -------------------------------------------------------------------------------- /toolkit.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2014] - [2023] The Last.Backend authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package toolkit 18 | --------------------------------------------------------------------------------