From 309b037e3b887bc564b1bb56a68f73419e1f09f3 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 12 Dec 2016 12:18:59 +0200 Subject: [PATCH] Update to 5.0.4 - Read HISTORY.md --- .github/ISSUE_TEMPLATE.md | 2 +- HISTORY.md | 38 ++++++++ README.md | 6 +- configuration.go | 1 - context.go | 189 ++++++++++++++++++-------------------- http.go | 3 +- iris.go | 148 +++++++++++------------------ iris/fs.go | 15 --- iris/main.go | 5 +- plugin_test.go | 33 +++---- template.go | 4 - 11 files changed, 204 insertions(+), 240 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f839f577..714311b8 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,4 @@ -- Version : **5.0.1** +- Version : **5.0.4** - Operating System: diff --git a/HISTORY.md b/HISTORY.md index 77cbb6d8..a5c6e885 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,44 @@ **How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`. +## 5.0.3 -> 5.0.4 + + +The use of `iris.BodyDecoder` as a custom decoder that you can implement to a type in order to be used as the decoder/binder for the request body and override the json.Unmarshal(`context.ReadJSON`) or xml.Unmarshal(`context.ReadXML`) was very useful and gave you some kind of **per-type-binder** extensibility. + + + + +**NEW** `context.UnmarshalBody`: **Per-service-binder**. Side by side with the `iris.BodyDecoder`. We now have a second way to pass a custom `Unmarshaler` to override the `json.Unmarshal` and `xml.Unmarshal`. + + If the object doesn't implements the `iris.BodyDecoder` but you still want to implement your own algorithm to parse []byte as an 'object' instead of the iris' defaults. + + ```go + type Unmarshaler interface { + Unmarshal(data []byte, v interface{}) error + } + + ``` +`context.ReadJSON & context.ReadXML` have been also refactored to work with this interface and the new `context.DeodeBody` function, look: + +```go +// ReadJSON reads JSON from request's body +// and binds it to a value of any json-valid type +func (ctx *Context) ReadJSON(jsonObject interface{}) error { + return ctx.UnmarshalBody(jsonObject, UnmarshalerFunc(json.Unmarshal)) +} + +// ReadXML reads XML from request's body +// and binds it to a value of any xml-valid type +func (ctx *Context) ReadXML(xmlObject interface{}) error { + return ctx.UnmarshalBody(xmlObject, UnmarshalerFunc(xml.Unmarshal)) +} + +``` + +Both `encoding/json` and `encoding/xml` standard packages have valid `Unmarshal function` so they can be used as `iris.Unmarshaller` (with the help of `iris.UnmarshallerFunc` which just converts the signature to the `iris.Unmarshaller` interface). You only have to implement one function and it will work with any 'object' passed to the `UnmarshalBody` even if the object doesn't implements the `iris.BodyDecoder`. + + ## 5.0.2 -> 5.0.3 - Fix `https relative redirect paths`, a very old issue, which I just saw, peaceful, again :) diff --git a/README.md b/README.md index 157d8b76..78f23262 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@
-Releases +Releases Examples @@ -36,7 +36,7 @@ ideally suited for
both experienced and novice developers.

Besides the fact that Iris is faster than any alternatives you may met before,
thanks to its fluent API, you don't have to be an expert to work with it.

-If you're coming from Node.js world, this is the expressjs alternative for the Go Programming Language. +If you're coming from Node.js world, this is the expressjs alternative for the Go Programming Language.

@@ -873,7 +873,7 @@ I recommend writing your API tests using this new library, [httpexpect](https:// Versioning ------------ -Current: **v5.0.3** +Current: **v5.0.4** Stable: **[v4 LTS](https://github.com/kataras/iris/tree/4.0.0#versioning)** diff --git a/configuration.go b/configuration.go index dfa4a74c..391f4924 100644 --- a/configuration.go +++ b/configuration.go @@ -617,7 +617,6 @@ var ( ) var ( - universe time.Time // 0001-01-01 00:00:00 +0000 UTC // CookieExpireNever the default cookie's life for sessions, unlimited (23 years) CookieExpireNever = time.Now().AddDate(23, 0, 0) ) diff --git a/context.go b/context.go index abbdbd72..879345dd 100644 --- a/context.go +++ b/context.go @@ -26,8 +26,6 @@ import ( ) const ( - // DefaultUserAgent default to 'iris' but it is not used anywhere yet - defaultUserAgent = "iris" // ContentType represents the header["Content-Type"] contentType = "Content-Type" // ContentLength represents the header["Content-Length"] @@ -78,14 +76,11 @@ const ( // errors var ( - errTemplateExecute = errors.New("Unable to execute a template. Trace: %s") - errFlashNotFound = errors.New("Unable to get flash message. Trace: Cookie does not exists") - errSessionNil = errors.New("Unable to set session, Config().Session.Provider is nil, please refer to the docs!") - errNoForm = errors.New("Request has no any valid form") - errWriteJSON = errors.New("Before JSON be written to the body, JSON Encoder returned an error. Trace: %s") - errRenderMarshalled = errors.New("Before +type Rendering, MarshalIndent returned an error. Trace: %s") - errReadBody = errors.New("While trying to read %s from the request body. Trace %s") - errServeContent = errors.New("While trying to serve content to the client. Trace %s") + errTemplateExecute = errors.New("Unable to execute a template. Trace: %s") + errFlashNotFound = errors.New("Unable to get flash message. Trace: Cookie does not exists") + errNoForm = errors.New("Request has no any valid form") + errReadBody = errors.New("While trying to read %s from the request body. Trace %s") + errServeContent = errors.New("While trying to serve content to the client. Trace %s") ) type ( @@ -282,22 +277,27 @@ func (ctx *Context) FormValues(name string) []string { // PostValuesAll returns all post data values with their keys // multipart, form data, get & post query arguments -func (ctx *Context) PostValuesAll() (valuesAll map[string][]string) { - reqCtx := ctx.RequestCtx - valuesAll = make(map[string][]string) +// +// NOTE: A check for nil is necessary for zero results +func (ctx *Context) PostValuesAll() map[string][]string { // first check if we have multipart form - multipartForm, err := reqCtx.MultipartForm() + multipartForm, err := ctx.MultipartForm() if err == nil { //we have multipart form return multipartForm.Value } - // if no multipart and post arguments ( means normal form) - if reqCtx.PostArgs().Len() == 0 && reqCtx.QueryArgs().Len() == 0 { - return // no found + postArgs := ctx.PostArgs() + queryArgs := ctx.QueryArgs() + + len := postArgs.Len() + queryArgs.Len() + if len == 0 { + return nil // nothing found } - reqCtx.PostArgs().VisitAll(func(k []byte, v []byte) { + valuesAll := make(map[string][]string, len) + + visitor := func(k []byte, v []byte) { key := string(k) value := string(v) // for slices @@ -306,21 +306,11 @@ func (ctx *Context) PostValuesAll() (valuesAll map[string][]string) { } else { valuesAll[key] = []string{value} } + } - }) - - reqCtx.QueryArgs().VisitAll(func(k []byte, v []byte) { - key := string(k) - value := string(v) - // for slices - if valuesAll[key] != nil { - valuesAll[key] = append(valuesAll[key], value) - } else { - valuesAll[key] = []string{value} - } - }) - - return + postArgs.VisitAll(visitor) + queryArgs.VisitAll(visitor) + return valuesAll } // PostValues returns the post data values as []string of a single key/name @@ -370,90 +360,61 @@ type BodyDecoder interface { Decode(data []byte) error } -// ReadJSON reads JSON from request's body -func (ctx *Context) ReadJSON(jsonObject interface{}) error { - rawData := ctx.Request.Body() - - // check if the jsonObject contains its own decode - // in this case the jsonObject should be a pointer also, - // but this is up to the user's custom Decode implementation* - // - // See 'BodyDecoder' for more - if decoder, isDecoder := jsonObject.(BodyDecoder); isDecoder { - return decoder.Decode(rawData) - } - - // check if jsonObject is already a pointer, if yes then pass as it's - if reflect.TypeOf(jsonObject).Kind() == reflect.Ptr { - return json.Unmarshal(rawData, jsonObject) - } - // finally, if the jsonObject doesn't contains a self-body decoder and it's not a pointer - return json.Unmarshal(rawData, &jsonObject) +// Unmarshaler is the interface implemented by types that can unmarshal any raw data +// TIP INFO: Any v object which implements the BodyDecoder can be override the unmarshaler +type Unmarshaler interface { + Unmarshal(data []byte, v interface{}) error } -// ReadXML reads XML from request's body -func (ctx *Context) ReadXML(xmlObject interface{}) error { +// UnmarshalerFunc a shortcut for the Unmarshaler interface +// +// See 'Unmarshaler' and 'BodyDecoder' for more +type UnmarshalerFunc func(data []byte, v interface{}) error + +// Unmarshal parses the X-encoded data and stores the result in the value pointed to by v. +// Unmarshal uses the inverse of the encodings that Marshal uses, allocating maps, +// slices, and pointers as necessary. +func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error { + return u(data, v) +} + +// UnmarshalBody reads the request's body and binds it to a value or pointer of any type +// Examples of usage: context.ReadJSON, context.ReadXML +func (ctx *Context) UnmarshalBody(v interface{}, unmarshaler Unmarshaler) error { rawData := ctx.Request.Body() - // check if the xmlObject contains its own decode - // in this case the jsonObject should be a pointer also, + // check if the v contains its own decode + // in this case the v should be a pointer also, // but this is up to the user's custom Decode implementation* // // See 'BodyDecoder' for more - if decoder, isDecoder := xmlObject.(BodyDecoder); isDecoder { + if decoder, isDecoder := v.(BodyDecoder); isDecoder { return decoder.Decode(rawData) } - // check if xmlObject is already a pointer, if yes then pass as it's - if reflect.TypeOf(xmlObject).Kind() == reflect.Ptr { - return xml.Unmarshal(rawData, xmlObject) + // check if v is already a pointer, if yes then pass as it's + if reflect.TypeOf(v).Kind() == reflect.Ptr { + return unmarshaler.Unmarshal(rawData, v) } - // finally, if the xmlObject doesn't contains a self-body decoder and it's not a pointer - return xml.Unmarshal(rawData, &xmlObject) + // finally, if the v doesn't contains a self-body decoder and it's not a pointer + // use the custom unmarshaler to bind the body + return unmarshaler.Unmarshal(rawData, &v) +} + +// ReadJSON reads JSON from request's body and binds it to a value of any json-valid type +func (ctx *Context) ReadJSON(jsonObject interface{}) error { + return ctx.UnmarshalBody(jsonObject, UnmarshalerFunc(json.Unmarshal)) +} + +// ReadXML reads XML from request's body and binds it to a value of any xml-valid type +func (ctx *Context) ReadXML(xmlObject interface{}) error { + return ctx.UnmarshalBody(xmlObject, UnmarshalerFunc(xml.Unmarshal)) } // ReadForm binds the formObject with the form data // it supports any kind of struct func (ctx *Context) ReadForm(formObject interface{}) error { - reqCtx := ctx.RequestCtx - // first check if we have multipart form - multipartForm, err := reqCtx.MultipartForm() - if err == nil { - //we have multipart form - return errReadBody.With(formBinder.Decode(multipartForm.Value, formObject)) - } - // if no multipart and post arguments ( means normal form) - - if reqCtx.PostArgs().Len() == 0 && reqCtx.QueryArgs().Len() == 0 { - return errReadBody.With(errNoForm) - } - - form := make(map[string][]string, reqCtx.PostArgs().Len()+reqCtx.QueryArgs().Len()) - - reqCtx.PostArgs().VisitAll(func(k []byte, v []byte) { - key := string(k) - value := string(v) - // for slices - if form[key] != nil { - form[key] = append(form[key], value) - } else { - form[key] = []string{value} - } - - }) - - reqCtx.QueryArgs().VisitAll(func(k []byte, v []byte) { - key := string(k) - value := string(v) - // for slices - if form[key] != nil { - form[key] = append(form[key], value) - } else { - form[key] = []string{value} - } - }) - - return errReadBody.With(formBinder.Decode(form, formObject)) + return errReadBody.With(formBinder.Decode(ctx.PostValuesAll(), formObject)) } /* Response */ @@ -699,6 +660,34 @@ func (ctx *Context) Markdown(status int, markdown string) { ctx.HTML(status, ctx.MarkdownString(markdown)) } +// staticCachePassed checks the IfModifiedSince header and +// returns true if (client-side) duration has expired +func (ctx *Context) staticCachePassed(modtime time.Time) bool { + if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(StaticCacheDuration)) { + ctx.Response.Header.Del(contentType) + ctx.Response.Header.Del(contentLength) + ctx.SetStatusCode(StatusNotModified) + return true + } + return false +} + +// SetClientCachedBody like SetBody but it sends with an expiration datetime +// which is managed by the client-side (all major browsers supports this feature) +func (ctx *Context) SetClientCachedBody(status int, bodyContent []byte, cType string, modtime time.Time) { + if ctx.staticCachePassed(modtime) { + return + } + + modtimeFormatted := modtime.UTC().Format(ctx.framework.Config.TimeFormat) + + ctx.Response.Header.Set(contentType, cType) + ctx.Response.Header.Set(lastModified, modtimeFormatted) + ctx.SetStatusCode(status) + + ctx.Response.SetBody(bodyContent) +} + // ServeContent serves content, headers are autoset // receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string) // diff --git a/http.go b/http.go index 3cebf64b..a2d68e22 100644 --- a/http.go +++ b/http.go @@ -313,8 +313,7 @@ const ( // matchEverythingByte is just a byte of '*" rune/char matchEverythingByte = byte('*') - isStatic entryCase = iota - isRoot + isRoot entryCase = iota hasParams matchEverything ) diff --git a/iris.go b/iris.go index a87762c0..c8f968de 100644 --- a/iris.go +++ b/iris.go @@ -80,7 +80,7 @@ const ( // IsLongTermSupport flag is true when the below version number is a long-term-support version IsLongTermSupport = false // Version is the current version number of the Iris web framework - Version = "5.0.3" + Version = "5.0.4" banner = ` _____ _ |_ _| (_) @@ -900,6 +900,28 @@ func Path(routeName string, args ...interface{}) string { return Default.Path(routeName, args...) } +func joinPathArguments(args ...interface{}) []interface{} { + arguments := args[0:] + for i, v := range arguments { + if arr, ok := v.([]string); ok { + if len(arr) > 0 { + interfaceArr := make([]interface{}, len(arr)) + for j, sv := range arr { + interfaceArr[j] = sv + } + // replace the current slice + // with the first string element (always as interface{}) + arguments[i] = interfaceArr[0] + // append the rest of them to the slice itself + // the range is not affected by these things in go, + // so we are safe to do it. + arguments = append(args, interfaceArr[1:]...) + } + } + } + return arguments +} + // Path used to check arguments with the route's named parameters and return the correct url // if parse failed returns empty string func (s *Framework) Path(routeName string, args ...interface{}) string { @@ -950,22 +972,7 @@ func (s *Framework) Path(routeName string, args ...interface{}) string { return "" } - arguments := args[0:] - - // check for arrays - for i, v := range arguments { - if arr, ok := v.([]string); ok { - if len(arr) > 0 { - interfaceArr := make([]interface{}, len(arr)) - for j, sv := range arr { - interfaceArr[j] = sv - } - arguments[i] = interfaceArr[0] - arguments = append(arguments, interfaceArr[1:]...) - } - - } - } + arguments := joinPathArguments(args...) return fmt.Sprintf(r.formattedPath, arguments...) } @@ -1019,22 +1026,7 @@ func (s *Framework) URL(routeName string, args ...interface{}) (url string) { scheme := s.Config.VScheme // if s.Config.VScheme was setted, that will be used instead of the real, in order to make easy to run behind nginx host := s.Config.VHost // if s.Config.VHost was setted, that will be used instead of the real, in order to make easy to run behind nginx - arguments := args[0:] - - // join arrays as arguments - for i, v := range arguments { - if arr, ok := v.([]string); ok { - if len(arr) > 0 { - interfaceArr := make([]interface{}, len(arr)) - for j, sv := range arr { - interfaceArr[j] = sv - } - arguments[i] = interfaceArr[0] - arguments = append(arguments, interfaceArr[1:]...) - } - - } - } + arguments := joinPathArguments(args...) // if it's dynamic subdomain then the first argument is the subdomain part if r.subdomain == dynamicSubdomainIndicator { @@ -1741,6 +1733,21 @@ func Static(reqPath string, systemPath string, stripSlashes int) RouteNameFunc { return Default.Static(reqPath, systemPath, stripSlashes) } +// if / then returns /*wildcard or /something then /something/*wildcard +// if empty then returns /*wildcard too +func validateWildcard(reqPath string, paramName string) string { + if reqPath[len(reqPath)-1] != slashByte { + reqPath += slash + } + reqPath += "*" + paramName + return reqPath +} + +func (api *muxAPI) registerResourceRoute(reqPath string, h HandlerFunc) RouteNameFunc { + api.Head(reqPath, h) + return api.Get(reqPath, h) +} + // Static registers a route which serves a system directory // this doesn't generates an index page which list all files // no compression is used also, for these features look at StaticFS func @@ -1752,14 +1759,9 @@ func Static(reqPath string, systemPath string, stripSlashes int) RouteNameFunc { // * stripSlashes = 1, original path: "/foo/bar", result: "/bar" // * stripSlashes = 2, original path: "/foo/bar", result: "" func (api *muxAPI) Static(reqPath string, systemPath string, stripSlashes int) RouteNameFunc { - if reqPath[len(reqPath)-1] != slashByte { // if / then /*filepath, if /something then /something/*filepath - reqPath += slash - } - h := api.StaticHandler(systemPath, stripSlashes, false, false, nil) - - api.Head(reqPath+"*filepath", h) - return api.Get(reqPath+"*filepath", h) + reqPath = validateWildcard(reqPath, "filepath") + return api.registerResourceRoute(reqPath, h) } // StaticFS registers a route which serves a system directory @@ -1791,13 +1793,9 @@ func StaticFS(reqPath string, systemPath string, stripSlashes int) RouteNameFunc // * stripSlashes = 1, original path: "/foo/bar", result: "/bar" // * stripSlashes = 2, original path: "/foo/bar", result: "" func (api *muxAPI) StaticFS(reqPath string, systemPath string, stripSlashes int) RouteNameFunc { - if reqPath[len(reqPath)-1] != slashByte { - reqPath += slash - } - h := api.StaticHandler(systemPath, stripSlashes, true, true, nil) - api.Head(reqPath+"*filepath", h) - return api.Get(reqPath+"*filepath", h) + reqPath = validateWildcard(reqPath, "filepath") + return api.registerResourceRoute(reqPath, h) } // StaticWeb same as Static but if index.html exists and request uri is '/' then display the index.html's contents @@ -1824,18 +1822,13 @@ func StaticWeb(reqPath string, systemPath string, stripSlashes int) RouteNameFun // * if you don't know what to put on stripSlashes just 1 // example: https://github.com/iris-contrib/examples/tree/master/static_web func (api *muxAPI) StaticWeb(reqPath string, systemPath string, stripSlashes int) RouteNameFunc { - if reqPath[len(reqPath)-1] != slashByte { // if / then /*filepath, if /something then /something/*filepath - reqPath += slash - } - //todo: fs.go hasIndex := utils.Exists(systemPath + utils.PathSeparator + "index.html") var indexNames []string if hasIndex { indexNames = []string{"index.html"} } serveHandler := api.StaticHandler(systemPath, stripSlashes, false, !hasIndex, indexNames) // if not index.html exists then generate index.html which shows the list of files - api.Head(reqPath+"*filepath", serveHandler) - return api.Get(reqPath+"*filepath", serveHandler) + return api.registerResourceRoute(reqPath+"*filepath", serveHandler) } // StaticServe serves a directory as web resource @@ -1890,26 +1883,11 @@ func StaticContent(reqPath string, contentType string, content []byte) RouteName // a good example of this is how the websocket server uses that to auto-register the /iris-ws.js func (api *muxAPI) StaticContent(reqPath string, cType string, content []byte) RouteNameFunc { // func(string) because we use that on websockets modtime := time.Now() - modtimeStr := "" h := func(ctx *Context) { - if modtimeStr == "" { - modtimeStr = modtime.UTC().Format(ctx.framework.Config.TimeFormat) - } - - if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(StaticCacheDuration)) { - ctx.Response.Header.Del(contentType) - ctx.Response.Header.Del(contentLength) - ctx.SetStatusCode(StatusNotModified) - return - } - - ctx.Response.Header.Set(contentType, cType) - ctx.Response.Header.Set(lastModified, modtimeStr) - ctx.SetStatusCode(StatusOK) - ctx.Response.SetBody(content) + ctx.SetClientCachedBody(StatusOK, content, cType, modtime) } - api.Head(reqPath, h) - return api.Get(reqPath, h) + + return api.registerResourceRoute(reqPath, h) } // StaticEmbedded used when files are distrubuted inside the app executable, using go-bindata mostly @@ -1983,7 +1961,6 @@ func (api *muxAPI) StaticEmbedded(requestPath string, vdir string, assetFn func( } modtime := time.Now() - modtimeStr := "" h := func(ctx *Context) { reqPath := ctx.Param(paramName) @@ -1994,17 +1971,6 @@ func (api *muxAPI) StaticEmbedded(requestPath string, vdir string, assetFn func( continue } - if modtimeStr == "" { - modtimeStr = modtime.UTC().Format(ctx.framework.Config.TimeFormat) - } - - if t, err := time.Parse(ctx.framework.Config.TimeFormat, ctx.RequestHeader(ifModifiedSince)); err == nil && modtime.Before(t.Add(StaticCacheDuration)) { - ctx.Response.Header.Del(contentType) - ctx.Response.Header.Del(contentLength) - ctx.SetStatusCode(StatusNotModified) - return - } - cType := fs.TypeByExtension(path) fullpath := vdir + path @@ -2014,24 +1980,16 @@ func (api *muxAPI) StaticEmbedded(requestPath string, vdir string, assetFn func( continue } - ctx.Response.Header.Set(contentType, cType) - ctx.Response.Header.Set(lastModified, modtimeStr) - - ctx.SetStatusCode(StatusOK) - ctx.SetContentType(cType) - - ctx.Response.SetBody(buf) + ctx.SetClientCachedBody(StatusOK, buf, cType, modtime) return } - // not found + // not found or error ctx.EmitError(StatusNotFound) } - api.Head(requestPath, h) - - return api.Get(requestPath, h) + return api.registerResourceRoute(requestPath, h) } // Favicon serves static favicon @@ -2104,8 +2062,8 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc if len(requestPath) > 0 { reqPath = requestPath[0] } - api.Head(reqPath, h) - return api.Get(reqPath, h) + + return api.registerResourceRoute(reqPath, h) } // Layout oerrides the parent template layout with a more specific layout for this Party diff --git a/iris/fs.go b/iris/fs.go index 00ff8d19..91ea0cf7 100644 --- a/iris/fs.go +++ b/iris/fs.go @@ -7,21 +7,6 @@ import ( "strings" ) -var workingDir string - -func getWorkingDir() string { - if workingDir == "" { - errUnableToGetWD := errors.New(Name + ": Unable to get working directory, %s") - // set the current working dir - d, err := os.Getwd() - if err != nil { - panic(errUnableToGetWD.Format(err)) - } - workingDir = d - } - return workingDir -} - var goPath string // returns the (last) gopath+"/src/" diff --git a/iris/main.go b/iris/main.go index 6a70ad70..3f3c74a6 100644 --- a/iris/main.go +++ b/iris/main.go @@ -7,9 +7,8 @@ import ( var ( // Name the name of the cmd tool - Name = "Iris Command Line Tool" - app *cli.App - defaultInstallDir string + Name = "Iris Command Line Tool" + app *cli.App ) func init() { diff --git a/plugin_test.go b/plugin_test.go index a8f68066..96ef92a2 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -127,27 +127,28 @@ func (t testPluginActivationType) Activate(p iris.PluginContainer) error { return nil } -func TestPluginActivate(t *testing.T) { - iris.ResetDefault() - var plugins = iris.Default.Plugins - myplugin := testPluginActivationType{shouldError: false} - plugins.Add(myplugin) - - if plugins.Len() != 2 { // 2 because it registeres a second plugin also - t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, plugins.Len()) +// a plugin may contain children plugins too, but, +// if an error happened then all of them are not activated/added to the plugin container +func AddPluginTo(t *testing.T, plugins iris.PluginContainer, plugin iris.Plugin, expectingCount int) { + plugins.Add(plugin) + if plugins.Len() != expectingCount { // 2 because it registeres a second plugin also + t.Fatalf("Expected activated plugins to be: %d but we got: %d", expectingCount, plugins.Len()) } } -// if any error returned from the Activate plugin's method, then this plugin and the plugins it registers should not be registered at all -func TestPluginActivationError(t *testing.T) { +// if any error returned from the Activate plugin's method, +// then this plugin and the plugins it registers should not be registered at all +func TestPluginActivate(t *testing.T) { iris.ResetDefault() - var plugins = iris.Default.Plugins - myplugin := testPluginActivationType{shouldError: true} - plugins.Add(myplugin) + plugins := iris.Plugins - if plugins.Len() > 0 { - t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, plugins.Len()) - } + myValidPluginWithChild := testPluginActivationType{shouldError: false} + AddPluginTo(t, plugins, myValidPluginWithChild, 2) // 2 because its children registered also (its parent is not throwing an error) + + myInvalidPlugin := testPluginActivationType{shouldError: true} + // should stay 2, even if we tried to add a new one, + // because it cancels the registration of its children too (shouldError = true) + AddPluginTo(t, plugins, myInvalidPlugin, 2) } func TestPluginEvents(t *testing.T) { diff --git a/template.go b/template.go index 2b5cf373..1f3a3ab7 100644 --- a/template.go +++ b/template.go @@ -6,10 +6,6 @@ import ( "io" ) -var ( - builtinFuncs = [...]string{"url", "urlpath"} -) - const ( // NoLayout to disable layout for a particular template file NoLayout = template.NoLayout