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
This commit is contained in:
Gerasimos (Makis) Maropoulos 2021-02-21 22:24:01 +02:00
parent bfbed2f841
commit c1b31ab102
No known key found for this signature in database
GPG Key ID: A771A828097B36C7
6 changed files with 131 additions and 21 deletions

View File

@ -28,7 +28,7 @@ The codebase for Dependency Injection, Internationalization and localization and
## Fixes and Improvements ## 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: - **New feature:** add the ability to set custom error handlers on path type parameters errors (existing or custom ones). Example Code:

View File

@ -10,9 +10,9 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"net/http"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -81,7 +81,6 @@ func (fi bindataFileInfo) Sys() interface{} {
return nil return nil
} }
type assetFile struct { type assetFile struct {
*bytes.Reader *bytes.Reader
name string name string
@ -326,12 +325,12 @@ type bintree struct {
} }
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"css": &bintree{nil, map[string]*bintree{ "css": {nil, map[string]*bintree{
"main.css": &bintree{cssMainCss, map[string]*bintree{}}, "main.css": {cssMainCss, map[string]*bintree{}},
}}, }},
"favicon.ico": &bintree{faviconIco, map[string]*bintree{}}, "favicon.ico": {faviconIco, map[string]*bintree{}},
"js": &bintree{nil, map[string]*bintree{ "js": {nil, map[string]*bintree{
"main.js": &bintree{jsMainJs, map[string]*bintree{}}, "main.js": {jsMainJs, map[string]*bintree{}},
}}, }},
}} }}

View File

@ -10,9 +10,9 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"net/http"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -81,7 +81,6 @@ func (fi bindataFileInfo) Sys() interface{} {
return nil return nil
} }
type assetFile struct { type assetFile struct {
*bytes.Reader *bytes.Reader
name string name string
@ -326,12 +325,12 @@ type bintree struct {
} }
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"css": &bintree{nil, map[string]*bintree{ "css": {nil, map[string]*bintree{
"main.css": &bintree{cssMainCss, map[string]*bintree{}}, "main.css": {cssMainCss, map[string]*bintree{}},
}}, }},
"favicon.ico": &bintree{faviconIco, map[string]*bintree{}}, "favicon.ico": {faviconIco, map[string]*bintree{}},
"js": &bintree{nil, map[string]*bintree{ "js": {nil, map[string]*bintree{
"main.js": &bintree{jsMainJs, map[string]*bintree{}}, "main.js": {jsMainJs, map[string]*bintree{}},
}}, }},
}} }}

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@ -323,6 +324,20 @@ func (api *APIBuilder) ConfigureContainer(builder ...func(*APIContainer)) *APICo
return api.apiBuilderDI 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. // 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 // 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 // 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. // 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. // 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: // Usage:
// app.PartyConfigure("/users", &api.UsersAPI{UserRepository: ..., ...}) // app.PartyConfigure("/users", &api.UsersAPI{UserRepository: ..., ...})
// Where UsersAPI looks like: // Where UsersAPI looks like:
@ -870,12 +890,23 @@ type PartyConfigurator interface {
// router.Get("/{id:uuid}", api.getUser) // 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 { func (api *APIBuilder) PartyConfigure(relativePath string, partyReg ...PartyConfigurator) Party {
child := api.Party(relativePath) child := api.Party(relativePath)
for _, p := range partyReg { for _, p := range partyReg {
if p != nil { if p == nil {
p.Configure(child) 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 return child
} }

View File

@ -27,6 +27,79 @@ type Party interface {
// //
// It returns the same `APIBuilder` featured with Dependency Injection. // It returns the same `APIBuilder` featured with Dependency Injection.
ConfigureContainer(builder ...func(*APIContainer)) *APIContainer 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 <T>, 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. // GetRelPath returns the current party's relative path.
// i.e: // i.e:
@ -91,6 +164,11 @@ type Party interface {
// It initializes a new children Party and executes the PartyConfigurator's Configure. // 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. // 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: // Usage:
// app.PartyConfigure("/users", &api.UsersAPI{UserRepository: ..., ...}) // app.PartyConfigure("/users", &api.UsersAPI{UserRepository: ..., ...})
// Where UsersAPI looks like: // Where UsersAPI looks like:
@ -99,6 +177,9 @@ type Party interface {
// router.Get("/{id:uuid}", api.getUser) // router.Get("/{id:uuid}", api.getUser)
// [...] // [...]
// } // }
// Usage with (static) dependencies:
// app.RegisterDependency(userRepo, ...)
// app.PartyConfigure("/users", &api.UsersAPI{})
PartyConfigure(relativePath string, partyReg ...PartyConfigurator) Party PartyConfigure(relativePath string, partyReg ...PartyConfigurator) Party
// Subdomain returns a new party which is responsible to register routes to // Subdomain returns a new party which is responsible to register routes to
// this specific "subdomain". // this specific "subdomain".

View File

@ -21,7 +21,7 @@ type (
// It's the exact type of return to bind, if declared to return <T>, otherwise nil. // It's the exact type of return to bind, if declared to return <T>, otherwise nil.
DestType reflect.Type DestType reflect.Type
Static bool 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. // not just assiginable to each other.
// Example of use case: depenendency like time.Time that we want to be bindable // 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. // 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 // d1 = Logger
// d2 = func(Logger) S1 // d2 = func(Logger) S1
// d2 should be static: it accepts dependencies that are static // 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) { if numIn == len(bindings) {
static := true static := true
for _, b := range bindings { for _, b := range bindings {