diff --git a/HISTORY.md b/HISTORY.md index fa7a16f6..4f9b67d7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -359,6 +359,10 @@ Response: Other Improvements: +- `*versioning.Group` type is a full `Party` now. + +- `Party.UseOnce` - either inserts a middleware, or on the basis of the middleware already existing, replace that existing middleware instead. + - Ability to register a view engine per group of routes or for the current chain of handlers through `Party.RegisterView` and `Context.ViewEngine` respectfully. - Add [Blocks](_examples/view/template_blocks_0) template engine. @@ -527,6 +531,8 @@ New Context Methods: Breaking Changes: +- `versioning.NewGroup(string)` now accepts a `Party` as its first input argument: `NewGroup(Party, string)`. +- `versioning.RegisterGroups` is **removed** as it is no longer necessary. - `Configuration.RemoteAddrHeaders` from `map[string]bool` to `[]string`. If you used `With(out)RemoteAddrHeader` then you are ready to proceed without any code changes for that one. - `ctx.Gzip(boolean)` replaced with `ctx.CompressWriter(boolean) error`. - `ctx.GzipReader(boolean) error` replaced with `ctx.CompressReader(boolean) error`. diff --git a/_examples/README.md b/_examples/README.md index 0faa8fb5..536a7567 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -101,6 +101,7 @@ * [The `urlpath` tmpl func](view/template_html_3/main.go) * [The `url` tmpl func](view/template_html_4/main.go) * [Inject Data Between Handlers](view/context-view-data/main.go) + * [Inject Engine Between Handlers](view/context-view-engine/main.go) * [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go) * [Write to a custom `io.Writer`](view/write-to) * [Blocks](view/template_blocks_0) diff --git a/_examples/mvc/versioned-controller/main.go b/_examples/mvc/versioned-controller/main.go index 1ba02951..9c8bb950 100644 --- a/_examples/mvc/versioned-controller/main.go +++ b/_examples/mvc/versioned-controller/main.go @@ -31,7 +31,7 @@ func newApp() *iris.Application { m.Handle(new(v1Controller), mvc.Version("1"), mvc.Deprecated(opts)) // 1 or 1.0, 1.0.0 ... m.Handle(new(v2Controller), mvc.Version("2.3")) // 2.3 or 2.3.0 m.Handle(new(v3Controller), mvc.Version(">=3, <4")) // 3, 3.x, 3.x.x ... - m.Handle(new(noVersionController)) + m.Handle(new(noVersionController)) // or if missing it will respond with 501 version not found. } return app diff --git a/_examples/routing/versioning/main.go b/_examples/routing/versioning/main.go index a3aa21c1..3502173f 100644 --- a/_examples/routing/versioning/main.go +++ b/_examples/routing/versioning/main.go @@ -42,9 +42,16 @@ func examplePerRoute(app *iris.Application) { // Headers[1] = Accept-Version: "2" func examplePerParty(app *iris.Application) { usersAPI := app.Party("/api/users") + // You can customize the way a version is extracting + // via middleware, for example: + // version url parameter, and, if it's missing we default it to "1". + usersAPI.Use(func(ctx iris.Context) { + versioning.SetVersion(ctx, ctx.URLParamDefault("version", "1")) + ctx.Next() + }) // version 1. - usersAPIV1 := versioning.NewGroup(">= 1, < 2") + usersAPIV1 := versioning.NewGroup(usersAPI, ">= 1, < 2") usersAPIV1.Get("/", func(ctx iris.Context) { ctx.Writef("v1 resource: /api/users handler") }) @@ -53,15 +60,13 @@ func examplePerParty(app *iris.Application) { }) // version 2. - usersAPIV2 := versioning.NewGroup(">= 2, < 3") + usersAPIV2 := versioning.NewGroup(usersAPI, ">= 2, < 3") usersAPIV2.Get("/", func(ctx iris.Context) { ctx.Writef("v2 resource: /api/users handler") }) usersAPIV2.Post("/", func(ctx iris.Context) { ctx.Writef("v2 resource: /api/users post handler") }) - - versioning.RegisterGroups(usersAPI, versioning.NotFoundHandler, usersAPIV1, usersAPIV2) } func catsVersionExactly1Handler(ctx iris.Context) { diff --git a/context/context.go b/context/context.go index ab4f11bd..c5a73183 100644 --- a/context/context.go +++ b/context/context.go @@ -303,7 +303,7 @@ var acquireGoroutines = func() interface{} { } func (ctx *Context) Go(fn func(cancelCtx stdContext.Context)) (running int) { - g := ctx.Values().GetOrSet(goroutinesContextKey, acquireGoroutines).(*goroutines) + g := ctx.values.GetOrSet(goroutinesContextKey, acquireGoroutines).(*goroutines) if fn != nil { g.wg.Add(1) @@ -613,6 +613,18 @@ func (ctx *Context) StopWithError(statusCode int, err error) { ctx.StopWithText(statusCode, err.Error()) } +// StopWithPlainError like `StopWithError` but it does NOT +// write anything to the response writer, it stores the error +// so any error handler matching the given "statusCode" can handle it by its own. +func (ctx *Context) StopWithPlainError(statusCode int, err error) { + if err == nil { + return + } + + ctx.SetErr(err) + ctx.StopWithStatus(statusCode) +} + // StopWithJSON stops the handlers chain, writes the status code // and sends a JSON response. // @@ -4459,7 +4471,7 @@ func (ctx *Context) Exec(method string, path string) { // backup the request path information backupPath := req.URL.Path backupMethod := req.Method - // don't backupValues := ctx.Values().ReadOnly() + // don't backupValues := ctx.values.ReadOnly() // set the request to be align with the 'againstRequestPath' req.RequestURI = path req.URL.Path = path @@ -4548,7 +4560,7 @@ func (ctx *Context) RegisterDependency(v interface{}) { val = reflect.ValueOf(v) } - cv := ctx.Values().Get(DependenciesContextKey) + cv := ctx.values.Get(DependenciesContextKey) if cv != nil { m, ok := cv.(DependenciesMap) if !ok { @@ -4559,7 +4571,7 @@ func (ctx *Context) RegisterDependency(v interface{}) { return } - ctx.Values().Set(DependenciesContextKey, DependenciesMap{ + ctx.values.Set(DependenciesContextKey, DependenciesMap{ val.Type(): val, }) } @@ -4567,7 +4579,7 @@ func (ctx *Context) RegisterDependency(v interface{}) { // UnregisterDependency removes a dependency based on its type. // Reports whether a dependency with that type was found and removed successfully. func (ctx *Context) UnregisterDependency(typ reflect.Type) bool { - cv := ctx.Values().Get(DependenciesContextKey) + cv := ctx.values.Get(DependenciesContextKey) if cv != nil { m, ok := cv.(DependenciesMap) if ok { @@ -4594,18 +4606,25 @@ const errorContextKey = "iris.context.error" // as a context value, it does nothing more. // Also, by-default this error's value is written to the client // on failures when no registered error handler is available (see `Party.On(Any)ErrorCode`). -// See `GetError` to retrieve it back. +// See `GetErr` to retrieve it back. +// +// To remove an error simply pass nil. // // Note that, if you want to stop the chain -// with an error see the `StopWithError` instead. +// with an error see the `StopWithError/StopWithPlainError` instead. func (ctx *Context) SetErr(err error) { - ctx.Values().Set(errorContextKey, err) + if err == nil { + ctx.values.Remove(errorContextKey) + return + } + + ctx.values.Set(errorContextKey, err) } // GetErr is a helper which retrieves // the error value stored by `SetErr`. func (ctx *Context) GetErr() error { - if v := ctx.Values().Get(errorContextKey); v != nil { + if v := ctx.values.Get(errorContextKey); v != nil { if err, ok := v.(error); ok { return err } diff --git a/core/router/api_builder.go b/core/router/api_builder.go index e3d7c775..78f56977 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -138,6 +138,8 @@ func overlapRoute(r *Route, next *Route) { return } + ctx.SetErr(nil) // clear any stored error. + // Set the route to the next one and execute it. ctx.SetCurrentRoute(next.ReadOnly) ctx.HandlerIndex(0) ctx.Do(nextHandlers) @@ -768,6 +770,25 @@ func (api *APIBuilder) Use(handlers ...context.Handler) { api.middleware = append(api.middleware, handlers...) } +// UseOnce either inserts a middleware, +// or on the basis of the middleware already existing, +// replace that existing middleware instead. +func (api *APIBuilder) UseOnce(handlers ...context.Handler) { +reg: + for _, handler := range handlers { + name := context.HandlerName(handler) + for i, registeredHandler := range api.middleware { + registeredName := context.HandlerName(registeredHandler) + if name == registeredName { + api.middleware[i] = handler // replace this handler with the new one. + continue reg // break and continue to the next handler. + } + } + + api.middleware = append(api.middleware, handler) // or just insert it. + } +} + // UseGlobal registers handlers that should run at the very beginning. // It prepends those handler(s) to all routes, // including all parties, subdomains. diff --git a/core/router/fs.go b/core/router/fs.go index 874cc2e1..a9224293 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -46,7 +46,7 @@ type Attachments struct { type DirCacheOptions struct { // Enable or disable cache. Enable bool - // Minimium content size for compression in bytes. + // Minimum content size for compression in bytes. CompressMinSize int64 // Ignore compress files that match this pattern. CompressIgnore *regexp.Regexp diff --git a/core/router/party.go b/core/router/party.go index 61b8e888..d72bfbac 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -76,9 +76,14 @@ type Party interface { // Use appends Handler(s) to the current Party's routes and child routes. // If the current Party is the root, then it registers the middleware to all child Parties' routes too. Use(middleware ...context.Handler) + // UseOnce either inserts a middleware, + // or on the basis of the middleware already existing, + // replace that existing middleware instead. + UseOnce(handlers ...context.Handler) // Done appends to the very end, Handler(s) to the current Party's routes and child routes. // The difference from .Use is that this/or these Handler(s) are being always running last. Done(handlers ...context.Handler) + // Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`, // and the execution rules. // Note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`. diff --git a/mvc/versioning.go b/mvc/versioning.go index 0e9085fd..c16c2889 100644 --- a/mvc/versioning.go +++ b/mvc/versioning.go @@ -25,15 +25,8 @@ import ( func Version(version string) OptionFunc { return func(c *ControllerActivator) { c.Router().SetRegisterRule(router.RouteOverlap) // required for this feature. - - c.Use(func(ctx *context.Context) { - if !versioning.Match(ctx, version) { - ctx.StopExecution() - return - } - - ctx.Next() - }) + // Note: Do not use a group, we need c.Use for the specific controller's routes. + c.Use(versioning.Handler(version)) } } diff --git a/versioning/group.go b/versioning/group.go index 09d13010..171d2af9 100644 --- a/versioning/group.go +++ b/versioning/group.go @@ -1,185 +1,46 @@ package versioning import ( - "net/http" - "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/core/router" ) -type ( - vroute struct { - method string - path string - versions Map - } +// Group is a group of version-based routes. +// One version per one or more routes. +type Group struct { + router.Party - // Group is a group of version-based routes. - // One version per one or more routes. - Group struct { - version string - extraMethods []string - routes []vroute - - deprecation DeprecationOptions - } -) + // Information not currently in-use. + version string + deprecation DeprecationOptions +} // NewGroup returns a ptr to Group based on the given "version". +// It sets the API Version for the "r" Party. // // See `Handle` and `RegisterGroups` for more. -func NewGroup(version string) *Group { +func NewGroup(r router.Party, version string) *Group { + // Note that this feature alters the RouteRegisterRule to RouteOverlap + // the RouteOverlap rule does not contain any performance downside + // but it's good to know that if you registered other mode, this wanna change it. + r.SetRegisterRule(router.RouteOverlap) + r.UseOnce(Handler(version)) // this is required in order to not populate this middleware to the next group. + return &Group{ + Party: r, version: version, } } // Deprecated marks this group and all its versioned routes // as deprecated versions of that endpoint. -// It can be called in the end just before `RegisterGroups` -// or first by `NewGroup(...).Deprecated(...)`. It returns itself. func (g *Group) Deprecated(options DeprecationOptions) *Group { - // if `Deprecated` is called in the end. - for _, r := range g.routes { - r.versions[g.version] = Deprecated(r.versions[g.version], options) - } - - // store the options if called before registering any versioned routes. + // store it for future use, e.g. collect all deprecated APIs and notify the developer. g.deprecation = options - return g -} - -// AllowMethods can be called before `Handle/Get/Post...` -// to tell the underline router that all routes should be registered -// to these "methods" as well. -func (g *Group) AllowMethods(methods ...string) *Group { - g.extraMethods = append(g.extraMethods, methods...) - return g -} - -func (g *Group) addVRoute(method, path string, handler context.Handler) { - for _, r := range g.routes { // check if route already exists. - if r.method == method && r.path == path { - return - } - } - - g.routes = append(g.routes, vroute{ - method: method, - path: path, - versions: Map{g.version: handler}, + g.Party.UseOnce(func(ctx *context.Context) { + WriteDeprecated(ctx, options) + ctx.Next() }) -} - -// Handle registers a versioned route to the group. -// A call of `RegisterGroups` is necessary in order to register the actual routes -// when the group is complete. -// -// `RegisterGroups` for more. -func (g *Group) Handle(method string, path string, handler context.Handler) { - if g.deprecation.ShouldHandle() { // if `Deprecated` called first. - handler = Deprecated(handler, g.deprecation) - } - - methods := append(g.extraMethods, method) - - for _, method := range methods { - g.addVRoute(method, path, handler) - } -} - -// None registers an "offline" versioned route -// see `context#ExecRoute(routeName)` and routing examples. -func (g *Group) None(path string, handler context.Handler) { - g.Handle(router.MethodNone, path, handler) -} - -// Get registers a versioned route for the Get http method. -func (g *Group) Get(path string, handler context.Handler) { - g.Handle(http.MethodGet, path, handler) -} - -// Post registers a versioned route for the Post http method. -func (g *Group) Post(path string, handler context.Handler) { - g.Handle(http.MethodPost, path, handler) -} - -// Put registers a versioned route for the Put http method -func (g *Group) Put(path string, handler context.Handler) { - g.Handle(http.MethodPut, path, handler) -} - -// Delete registers a versioned route for the Delete http method. -func (g *Group) Delete(path string, handler context.Handler) { - g.Handle(http.MethodDelete, path, handler) -} - -// Connect registers a versioned route for the Connect http method. -func (g *Group) Connect(path string, handler context.Handler) { - g.Handle(http.MethodConnect, path, handler) -} - -// Head registers a versioned route for the Head http method. -func (g *Group) Head(path string, handler context.Handler) { - g.Handle(http.MethodHead, path, handler) -} - -// Options registers a versioned route for the Options http method. -func (g *Group) Options(path string, handler context.Handler) { - g.Handle(http.MethodOptions, path, handler) -} - -// Patch registers a versioned route for the Patch http method. -func (g *Group) Patch(path string, handler context.Handler) { - g.Handle(http.MethodPatch, path, handler) -} - -// Trace registers a versioned route for the Trace http method. -func (g *Group) Trace(path string, handler context.Handler) { - g.Handle(http.MethodTrace, path, handler) -} - -// Any registers a versioned route for ALL of the http methods -// (Get,Post,Put,Head,Patch,Options,Connect,Delete). -func (g *Group) Any(registeredPath string, handler context.Handler) { - g.Get(registeredPath, handler) - g.Post(registeredPath, handler) - g.Put(registeredPath, handler) - g.Delete(registeredPath, handler) - g.Connect(registeredPath, handler) - g.Head(registeredPath, handler) - g.Options(registeredPath, handler) - g.Patch(registeredPath, handler) - g.Trace(registeredPath, handler) -} - -// RegisterGroups registers one or more groups to an `iris.Party` or to the root router. -// See `NewGroup` and `NotFoundHandler` too. -func RegisterGroups(r router.Party, notFoundHandler context.Handler, groups ...*Group) (actualRoutes []*router.Route) { - var total []vroute - for _, g := range groups { - inner: - for _, r := range g.routes { - for i, tr := range total { - if tr.method == r.method && tr.path == r.path { - total[i].versions[g.version] = r.versions[g.version] - continue inner - } - } - - total = append(total, r) - } - } - - for _, vr := range total { - if notFoundHandler != nil { - vr.versions[NotFound] = notFoundHandler - } - - route := r.Handle(vr.method, vr.path, NewMatcher(vr.versions)) - actualRoutes = append(actualRoutes, route) - } - - return + return g } diff --git a/versioning/version.go b/versioning/version.go index d5ea0b74..b5436d43 100644 --- a/versioning/version.go +++ b/versioning/version.go @@ -1,6 +1,7 @@ package versioning import ( + "errors" "strings" "github.com/kataras/iris/v12/context" @@ -31,6 +32,10 @@ const ( NotFound = "iris.api.version.notfound" ) +// ErrNotFound reports whether a requested version +// does not match with any of the server's implemented ones. +var ErrNotFound = errors.New("version not found") + // NotFoundHandler is the default version not found handler that // is executed from `NewMatcher` when no version is registered as available to dispatch a resource. var NotFoundHandler = func(ctx *context.Context) { @@ -46,8 +51,7 @@ var NotFoundHandler = func(ctx *context.Context) { recognize the request method and is not capable of supporting it for any resource. */ - ctx.StatusCode(501) - ctx.WriteString("version not found") + ctx.StopWithPlainError(501, ErrNotFound) } // GetVersion returns the current request version. diff --git a/versioning/versioning.go b/versioning/versioning.go index 682636e8..fb4d702f 100644 --- a/versioning/versioning.go +++ b/versioning/versioning.go @@ -44,6 +44,22 @@ func Match(ctx *context.Context, expectedVersion string) bool { return true } +// Handler returns a handler which stop the execution +// when the given "version" does not match with the requested one. +func Handler(version string) context.Handler { + return func(ctx *context.Context) { + if !Match(ctx, version) { + // Any overlapped handler + // can just clear the status code + // and the error to ignore this (see `NewGroup`). + NotFoundHandler(ctx) + return + } + + ctx.Next() + } +} + // Map is a map of versions targets to a handlers, // a handler per version or constraint, the key can be something like ">1, <=2" or just "1". type Map map[string]context.Handler diff --git a/versioning/versioning_test.go b/versioning/versioning_test.go index 2281f196..536b39bc 100644 --- a/versioning/versioning_test.go +++ b/versioning/versioning_test.go @@ -79,7 +79,7 @@ func TestNewGroup(t *testing.T) { userAPI := app.Party("/api/user") // [... static serving, middlewares and etc goes here]. - userAPIV10 := versioning.NewGroup("1.0").Deprecated(versioning.DefaultDeprecationOptions) + userAPIV10 := versioning.NewGroup(userAPI, "1.0").Deprecated(versioning.DefaultDeprecationOptions) // V10middlewareResponse := "m1" // userAPIV10.Use(func(ctx iris.Context) { // println("exec userAPIV10.Use - midl1") @@ -97,7 +97,7 @@ func TestNewGroup(t *testing.T) { // }) userAPIV10.Get("/", sendHandler(v10Response)) - userAPIV2 := versioning.NewGroup(">= 2, < 3") + userAPIV2 := versioning.NewGroup(userAPI, ">= 2, < 3") // V2middlewareResponse := "m2" // userAPIV2.Use(func(ctx iris.Context) { // println("exec userAPIV2.Use - midl1") @@ -113,8 +113,6 @@ func TestNewGroup(t *testing.T) { userAPIV2.Post("/", sendHandler(v2Response)) userAPIV2.Put("/other", sendHandler(v2Response)) - versioning.RegisterGroups(userAPI, versioning.NotFoundHandler, userAPIV10, userAPIV2) - e := httptest.New(t, app) ex := e.GET("/api/user").WithHeader(versioning.AcceptVersionHeaderKey, "1").Expect() diff --git a/view/README.md b/view/README.md index a515008a..17599f3d 100644 --- a/view/README.md +++ b/view/README.md @@ -17,25 +17,7 @@ Parse using embedded assets, Layouts and Party-specific layout, Template Funcs, | 7 | Jet | [CloudyKit/jet](https://github.com/CloudyKit/jet) | | 8 | Ace | [yosssi/ace](https://github.com/yosssi/ace) | -## Examples - -- [Overview](https://github.com/kataras/iris/blob/master/_examples/view/overview/main.go) -- [Hi](https://github.com/kataras/iris/blob/master/_examples/view/template_html_0/main.go) -- [A simple Layout](https://github.com/kataras/iris/blob/master/_examples/view/template_html_1/main.go) -- [Layouts: `yield` and `render` tmpl funcs](https://github.com/kataras/iris/blob/master/_examples/view/template_html_2/main.go) -- [The `urlpath` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_3/main.go) -- [The `url` tmpl func](https://github.com/kataras/iris/blob/master/_examples/view/template_html_4/main.go) -- [Inject Data Between Handlers](https://github.com/kataras/iris/blob/master/_examples/view/context-view-data/main.go) -- [Embedding Templates Into App Executable File](https://github.com/kataras/iris/blob/master/_examples/view/embedding-templates-into-app/main.go) -- [Blocks](https://github.com/kataras/iris/blob/master/_examples/view/template_blocks_0) -- [Blocks Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_blocks_1_embedded) -- [Greeting with Pug (Jade)`](view/template_pug_0) -- [Pug (Jade) Actions`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_1) -- [Pug (Jade) Includes`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_2) -- [Pug (Jade) Extends`](https://github.com/kataras/iris/blob/master/_examples/view/template_pug_3) -- [Jet](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_0) -- [Jet Embedded](https://github.com/kataras/iris/blob/master/_examples/view/template_jet_1_embedded) -- [Ace](https://github.com/kataras/iris/blob/master/_examples/view/template_ace_0) +[List of Examples](https://github.com/kataras/iris/tree/master/_examples/view). You can serve [quicktemplate](https://github.com/valyala/quicktemplate) files too, simply by using the `Context.ResponseWriter`, take a look at the [iris/_examples/view/quicktemplate](https://github.com/kataras/iris/tree/master/_examples/view/quicktemplate) example. diff --git a/view/amber.go b/view/amber.go index 9d1d153f..0bdecb41 100644 --- a/view/amber.go +++ b/view/amber.go @@ -26,7 +26,10 @@ type AmberEngine struct { templateCache map[string]*template.Template } -var _ Engine = (*AmberEngine)(nil) +var ( + _ Engine = (*AmberEngine)(nil) + _ EngineFuncer = (*AmberEngine)(nil) +) // Amber creates and returns a new amber view engine. // The given "extension" MUST begin with a dot. diff --git a/view/blocks.go b/view/blocks.go index 04e1e3dc..e03beae2 100644 --- a/view/blocks.go +++ b/view/blocks.go @@ -24,7 +24,10 @@ type BlocksEngine struct { Engine *blocks.Blocks } -var _ Engine = (*BlocksEngine)(nil) +var ( + _ Engine = (*BlocksEngine)(nil) + _ EngineFuncer = (*BlocksEngine)(nil) +) // WrapBlocks wraps an initialized blocks engine and returns its Iris adapter. // See `Blocks` package-level function too. @@ -53,9 +56,8 @@ func (s *BlocksEngine) Ext() string { // - url func(routeName string, args ...string) string // - urlpath func(routeName string, args ...string) string // - tr func(lang, key string, args ...interface{}) string -func (s *BlocksEngine) AddFunc(funcName string, funcBody interface{}) *BlocksEngine { +func (s *BlocksEngine) AddFunc(funcName string, funcBody interface{}) { s.Engine.Funcs(template.FuncMap{funcName: funcBody}) - return s } // AddLayoutFunc adds a template function for templates that are marked as layouts. diff --git a/view/django.go b/view/django.go index b0305c05..e77f8860 100644 --- a/view/django.go +++ b/view/django.go @@ -106,7 +106,10 @@ type DjangoEngine struct { templateCache map[string]*pongo2.Template } -var _ Engine = (*DjangoEngine)(nil) +var ( + _ Engine = (*DjangoEngine)(nil) + _ EngineFuncer = (*DjangoEngine)(nil) +) // Django creates and returns a new django view engine. // The given "extension" MUST begin with a dot. diff --git a/view/handlebars.go b/view/handlebars.go index f6557b8d..45f8b99b 100644 --- a/view/handlebars.go +++ b/view/handlebars.go @@ -27,7 +27,10 @@ type HandlebarsEngine struct { templateCache map[string]*raymond.Template } -var _ Engine = (*HandlebarsEngine)(nil) +var ( + _ Engine = (*HandlebarsEngine)(nil) + _ EngineFuncer = (*HandlebarsEngine)(nil) +) // Handlebars creates and returns a new handlebars view engine. // The given "extension" MUST begin with a dot. diff --git a/view/html.go b/view/html.go index 0001354f..b6723735 100644 --- a/view/html.go +++ b/view/html.go @@ -35,7 +35,10 @@ type HTMLEngine struct { // } -var _ Engine = (*HTMLEngine)(nil) +var ( + _ Engine = (*HTMLEngine)(nil) + _ EngineFuncer = (*HTMLEngine)(nil) +) var emptyFuncs = template.FuncMap{ "yield": func(binding interface{}) (string, error) { @@ -175,12 +178,10 @@ func (s *HTMLEngine) AddLayoutFunc(funcName string, funcBody interface{}) *HTMLE // - urlpath func(routeName string, args ...string) string // - render func(fullPartialName string) (template.HTML, error). // - tr func(lang, key string, args ...interface{}) string -func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) *HTMLEngine { +func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) { s.rmu.Lock() s.funcs[funcName] = funcBody s.rmu.Unlock() - - return s } // SetFuncs overrides the template funcs with the given "funcMap". diff --git a/view/jet.go b/view/jet.go index 6ad10b96..afab0c79 100644 --- a/view/jet.go +++ b/view/jet.go @@ -36,7 +36,10 @@ type JetEngine struct { jetDataContextKey string } -var _ Engine = (*JetEngine)(nil) +var ( + _ Engine = (*JetEngine)(nil) + _ EngineFuncer = (*JetEngine)(nil) +) // jet library does not export or give us any option to modify them via Set // (unless we parse the files by ourselves but this is not a smart choice).