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 {