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
- 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:

View File

@ -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{}},
}},
}}

View File

@ -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{}},
}},
}}

View File

@ -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
}

View File

@ -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 <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.
// 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".

View File

@ -21,7 +21,7 @@ type (
// It's the exact type of return to bind, if declared to return <T>, 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 {