From c1b31ab1026d7b7a5cbff8ec601b0ebfd9b1757b Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Feb 2021 22:24:01 +0200 Subject: [PATCH] PartyConfigure: try to bind the struct's exported zero fields based on the registered dependencies (if any) If the PartyConfigurator value accepts only static dependencies then we have zero performance penalty, exactly like a Controller (structure) works --- HISTORY.md | 2 +- .../embedding-files-into-app/bindata.go | 15 ++-- .../bindata.go | 15 ++-- core/router/api_builder.go | 35 +++++++- core/router/party.go | 81 +++++++++++++++++++ hero/dependency.go | 4 +- 6 files changed, 131 insertions(+), 21 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 2c2d3959..6285a1e3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,7 +28,7 @@ The codebase for Dependency Injection, Internationalization and localization and ## Fixes and Improvements -- New `PartyConfigure(relativePath string, partyReg ...PartyConfigurator) Party` helper, registers a children Party like `Party` and `PartyFunc` but instead it accepts a structure value (useful when the api's dependencies amount are too much to pass on a function). +- New `PartyConfigure(relativePath string, partyReg ...PartyConfigurator) Party` helper, registers a children Party like `Party` and `PartyFunc` but instead it accepts a structure value which may contain one or more of the dependencies registered by `RegisterDependency` or `ConfigureContainer().RegisterDependency` methods and fills the unset/zero exported struct's fields respectfully (useful when the api's dependencies amount are too much to pass on a function). - **New feature:** add the ability to set custom error handlers on path type parameters errors (existing or custom ones). Example Code: diff --git a/_examples/file-server/embedding-files-into-app/bindata.go b/_examples/file-server/embedding-files-into-app/bindata.go index 1d9d13fa..b6f638f4 100644 --- a/_examples/file-server/embedding-files-into-app/bindata.go +++ b/_examples/file-server/embedding-files-into-app/bindata.go @@ -1,6 +1,6 @@ // Code generated by go-bindata. (@generated) DO NOT EDIT. - //Package main generated by go-bindata.// sources: +//Package main generated by go-bindata.// sources: // assets/css/main.css // assets/favicon.ico // assets/js/main.js @@ -10,9 +10,9 @@ import ( "bytes" "compress/gzip" "fmt" - "net/http" "io" "io/ioutil" + "net/http" "os" "path/filepath" "strings" @@ -81,7 +81,6 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } - type assetFile struct { *bytes.Reader name string @@ -326,12 +325,12 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ - "css": &bintree{nil, map[string]*bintree{ - "main.css": &bintree{cssMainCss, map[string]*bintree{}}, + "css": {nil, map[string]*bintree{ + "main.css": {cssMainCss, map[string]*bintree{}}, }}, - "favicon.ico": &bintree{faviconIco, map[string]*bintree{}}, - "js": &bintree{nil, map[string]*bintree{ - "main.js": &bintree{jsMainJs, map[string]*bintree{}}, + "favicon.ico": {faviconIco, map[string]*bintree{}}, + "js": {nil, map[string]*bintree{ + "main.js": {jsMainJs, map[string]*bintree{}}, }}, }} diff --git a/_examples/file-server/embedding-gzipped-files-into-app/bindata.go b/_examples/file-server/embedding-gzipped-files-into-app/bindata.go index 58f1d716..1c5b2f45 100644 --- a/_examples/file-server/embedding-gzipped-files-into-app/bindata.go +++ b/_examples/file-server/embedding-gzipped-files-into-app/bindata.go @@ -1,6 +1,6 @@ // Code generated by go-bindata. (@generated) DO NOT EDIT. - //Package main generated by go-bindata.// sources: +//Package main generated by go-bindata.// sources: // ../embedding-files-into-app/assets/css/main.css // ../embedding-files-into-app/assets/favicon.ico // ../embedding-files-into-app/assets/js/main.js @@ -10,9 +10,9 @@ import ( "bytes" "compress/gzip" "fmt" - "net/http" "io" "io/ioutil" + "net/http" "os" "path/filepath" "strings" @@ -81,7 +81,6 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } - type assetFile struct { *bytes.Reader name string @@ -326,12 +325,12 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ - "css": &bintree{nil, map[string]*bintree{ - "main.css": &bintree{cssMainCss, map[string]*bintree{}}, + "css": {nil, map[string]*bintree{ + "main.css": {cssMainCss, map[string]*bintree{}}, }}, - "favicon.ico": &bintree{faviconIco, map[string]*bintree{}}, - "js": &bintree{nil, map[string]*bintree{ - "main.js": &bintree{jsMainJs, map[string]*bintree{}}, + "favicon.ico": {faviconIco, map[string]*bintree{}}, + "js": {nil, map[string]*bintree{ + "main.js": {jsMainJs, map[string]*bintree{}}, }}, }} diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 7fe45e94..37f18c80 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "reflect" "runtime" "strings" "time" @@ -323,6 +324,20 @@ func (api *APIBuilder) ConfigureContainer(builder ...func(*APIContainer)) *APICo return api.apiBuilderDI } +// RegisterDependency calls the `ConfigureContainer.RegisterDependency` method +// with the provided value(s). See `HandleFunc` and `PartyConfigure` methods too. +func (api *APIBuilder) RegisterDependency(dependencies ...interface{}) { + diContainer := api.ConfigureContainer() + for i, dependency := range dependencies { + if dependency == nil { + api.logger.Warnf("Party: %s: nil dependency on position: %d", api.relativePath, i) + continue + } + + diContainer.RegisterDependency(dependency) + } +} + // HandleFunc registers a route on HTTP verb "method" and relative, to this Party, path. // It is like the `Handle` method but it accepts one or more "handlersFn" functions // that each one of them can accept any input arguments as the HTTP request and @@ -862,6 +877,11 @@ type PartyConfigurator interface { // It initializes a new children Party and executes the PartyConfigurator's Configure. // Useful when the api's dependencies amount are too much to pass on a function. // +// As an exception, if the end-developer registered one or more dependencies upfront through +// RegisterDependencies or ConfigureContainer.RegisterDependency methods +// and "p" is a pointer to a struct then try to bind the unset/zero exported fields +// to the registered dependencies, just like we do with Controllers. +// // Usage: // app.PartyConfigure("/users", &api.UsersAPI{UserRepository: ..., ...}) // Where UsersAPI looks like: @@ -870,12 +890,23 @@ type PartyConfigurator interface { // router.Get("/{id:uuid}", api.getUser) // [...] // } +// Usage with (static) dependencies: +// app.RegisterDependency(userRepo, ...) +// app.PartyConfigure("/users", &api.UsersAPI{}) func (api *APIBuilder) PartyConfigure(relativePath string, partyReg ...PartyConfigurator) Party { child := api.Party(relativePath) for _, p := range partyReg { - if p != nil { - p.Configure(child) + if p == nil { + continue } + + if len(api.apiBuilderDI.Container.Dependencies) > 0 { + if typ := reflect.TypeOf(p); typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct { + api.apiBuilderDI.Container.Struct(p, -1) + } + } + + p.Configure(child) } return child } diff --git a/core/router/party.go b/core/router/party.go index bd7cc44e..7b01c493 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -27,6 +27,79 @@ type Party interface { // // It returns the same `APIBuilder` featured with Dependency Injection. ConfigureContainer(builder ...func(*APIContainer)) *APIContainer + // RegisterDependency calls the `ConfigureContainer.RegisterDependency` method + // with the provided value(s). See `HandleFunc` and `PartyConfigure` methods too. + RegisterDependency(dependencies ...interface{}) + // HandleFunc registers a route on HTTP verb "method" and relative, to this Party, path. + // It is like the `Handle` method but it accepts one or more "handlersFn" functions + // that each one of them can accept any input arguments as the HTTP request and + // output a result as the HTTP response. Specifically, + // the input of the "handlersFn" can be any registered dependency + // (see ConfigureContainer().RegisterDependency) + // or leave the framework to parse the request and fill the values accordingly. + // The output of the "handlersFn" can be any output result: + // custom structs , string, []byte, int, error, + // a combination of the above, hero.Result(hero.View | hero.Response) and more. + // + // If more than one handler function is registered + // then the execution happens without the nessecity of the `Context.Next` method, + // simply, to stop the execution and not continue to the next "handlersFn" in chain + // you should return an `iris.ErrStopExecution`. + // + // Example Code: + // + // The client's request body and server's response body Go types. + // Could be any data structure. + // + // type ( + // request struct { + // Firstname string `json:"firstname"` + // Lastname string `json:"lastname"` + // } + // + // response struct { + // ID uint64 `json:"id"` + // Message string `json:"message"` + // } + // ) + // + // Register the route hander. + // + // HTTP VERB ROUTE PATH ROUTE HANDLER + // app.HandleFunc("PUT", "/users/{id:uint64}", updateUser) + // + // Code the route handler function. + // Path parameters and request body are binded + // automatically. + // The "id" uint64 binds to "{id:uint64}" route path parameter and + // the "input" binds to client request data such as JSON. + // + // func updateUser(id uint64, input request) response { + // // [custom logic...] + // + // return response{ + // ID:id, + // Message: "User updated successfully", + // } + // } + // + // Simulate a client request which sends data + // to the server and prints out the response. + // + // curl --request PUT -d '{"firstname":"John","lastname":"Doe"}' \ + // -H "Content-Type: application/json" \ + // http://localhost:8080/users/42 + // + // { + // "id": 42, + // "message": "User updated successfully" + // } + // + // See the `ConfigureContainer` for more features regrading + // the dependency injection, mvc and function handlers. + // + // This method is just a shortcut for the `ConfigureContainer().Handle` one. + HandleFunc(method, relativePath string, handlersFn ...interface{}) *Route // GetRelPath returns the current party's relative path. // i.e: @@ -91,6 +164,11 @@ type Party interface { // It initializes a new children Party and executes the PartyConfigurator's Configure. // Useful when the api's dependencies amount are too much to pass on a function. // + // As an exception, if the end-developer registered one or more dependencies upfront through + // RegisterDependencies or ConfigureContainer.RegisterDependency methods + // and "p" is a pointer to a struct then try to bind the unset/zero exported fields + // to the registered dependencies, just like we do with Controllers. + // // Usage: // app.PartyConfigure("/users", &api.UsersAPI{UserRepository: ..., ...}) // Where UsersAPI looks like: @@ -99,6 +177,9 @@ type Party interface { // router.Get("/{id:uuid}", api.getUser) // [...] // } + // Usage with (static) dependencies: + // app.RegisterDependency(userRepo, ...) + // app.PartyConfigure("/users", &api.UsersAPI{}) PartyConfigure(relativePath string, partyReg ...PartyConfigurator) Party // Subdomain returns a new party which is responsible to register routes to // this specific "subdomain". diff --git a/hero/dependency.go b/hero/dependency.go index 5d960ff6..9ecabbaa 100644 --- a/hero/dependency.go +++ b/hero/dependency.go @@ -21,7 +21,7 @@ type ( // It's the exact type of return to bind, if declared to return , otherwise nil. DestType reflect.Type Static bool - // If true then input and dependnecy DestType should be indedical, + // If true then input and dependency DestType should be indedical, // not just assiginable to each other. // Example of use case: depenendency like time.Time that we want to be bindable // only to time.Time inputs and not to a service with a `String() string` method that time.Time struct implements too. @@ -215,7 +215,7 @@ func fromDependentFunc(v reflect.Value, dest *Dependency, funcDependencies []*De // d1 = Logger // d2 = func(Logger) S1 // d2 should be static: it accepts dependencies that are static - // (note: we don't check the output argument(s) of this dependnecy). + // (note: we don't check the output argument(s) of this dependency). if numIn == len(bindings) { static := true for _, b := range bindings {