diff --git a/README.md b/README.md index bfddd5d7..a0f167cc 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Iris suggests you to use [this](https://github.com/gavv/httpexpect) new suite t Versioning ------------ -Current: **v3.0.0-beta** +Current: **v3.0.0-beta.1** > Iris is an active project @@ -134,7 +134,7 @@ Todo - [x] Create server & client side (js) library for .on('event', func action(...)) / .emit('event')... (like socket.io but supports only websocket). - [x] Find and provide support for the most stable template engine and be able to change it via the configuration, keep html/templates support. - [x] Extend, test and publish to the public the [Iris' cmd](https://github.com/kataras/iris/tree/master/iris). - +- [x] Route naming and html url func, requested [here](https://github.com/kataras/iris/issues/165). If you're willing to donate click [here](DONATIONS.md) diff --git a/config/render.go b/config/render.go index 6b45ed63..3e9d1651 100644 --- a/config/render.go +++ b/config/render.go @@ -124,8 +124,13 @@ type ( Left string // Right delimeter, default is }} Right string - // Funcs for HTMLTemplate html/template + // Funcs like html/template Funcs template.FuncMap + // Funcs like html/template + // the difference from Funcs is that these funcs + // can be used inside a layout and can override the predefined (yield,partial...) or add more custom funcs + // these can override the Funcs inside no-layout templates also, use it when you know what you're doing + LayoutFuncs template.FuncMap } // Pongo the configs for PongoEngine Pongo struct { @@ -202,7 +207,7 @@ func DefaultTemplate() Template { ContentType: "text/html", Charset: "UTF-8", Layout: "", // currently this is the only config which not working for pongo2 yet but I will find a way - HTMLTemplate: HTMLTemplate{Left: "{{", Right: "}}", Funcs: template.FuncMap{}}, + HTMLTemplate: HTMLTemplate{Left: "{{", Right: "}}", Funcs: template.FuncMap{}, LayoutFuncs: template.FuncMap{}}, Pongo: Pongo{Filters: make(map[string]pongo2.FilterFunction, 0), Globals: make(map[string]interface{}, 0)}, Markdown: Markdown{Sanitize: false}, Amber: Amber{Funcs: template.FuncMap{}}, diff --git a/errors.go b/errors.go index 25d5bccb..31c9779c 100644 --- a/errors.go +++ b/errors.go @@ -12,8 +12,11 @@ var ( ErrHandleAnnotated = errors.New("HandleAnnotated parse: %s") // ErrControllerContextNotFound returns an error with message: 'Context *iris.Context could not be found, the Controller won't be registed.' ErrControllerContextNotFound = errors.New("Context *iris.Context could not be found, the Controller won't be registed.") - // ErrDirectoryFileNotFound returns an errir with message: 'Directory or file %s couldn't found. Trace: +error trace' + // ErrDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace' ErrDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s") + // ErrRenderRouteNotFound returns an error with message 'Route with name +route_name not found', used inside 'url' template func + ErrRenderRouteNotFound = errors.New("Route with name %s not found") + // Plugin // ErrPluginAlreadyExists returns an error with message: 'Cannot activate the same plugin again, plugin '+plugin name[+plugin description]' is already exists' diff --git a/iris.go b/iris.go index 5ba599b8..2866d91e 100644 --- a/iris.go +++ b/iris.go @@ -1,4 +1,4 @@ -// Package iris v3.0.0-beta +// Package iris v3.0.0-beta.1 // // Note: When 'Station', we mean the Iris type. package iris @@ -31,15 +31,14 @@ import ( const ( // Version of the iris - Version = "v3.0.0-beta" + Version = "v3.0.0-beta.1" banner = ` _____ _ |_ _| (_) | | ____ _ ___ | | | __|| |/ __| _| |_| | | |\__ \ - |_____|_| |_||___/ - - ` + |_____|_| |_||___/ ` + Version + ` + ` ) /* for conversion */ @@ -118,7 +117,39 @@ func (s *Iris) newContextPool() sync.Pool { func (s *Iris) initTemplates() { if s.templates == nil { // because if .Templates() called before server's listen, s.templates != nil when PreListen // init the templates + + // set the custom iris-direct-integration functions, layout and no-layout if HTMLEngine is used + if s.config.Render.Template.Engine == config.HTMLEngine { + funcs := map[string]interface{}{ + "url": func(routeName string, args ...interface{}) (string, error) { + r := s.RouteByName(routeName) + // check if not found + if r.GetPath() == "" { + return "", ErrRenderRouteNotFound.Format(routeName) + } + + if result, ok := r.parse(args...); ok { + return result, nil + } + return "", nil + }, + } + for k, v := range funcs { + // we don't want to override the user's LayoutFuncs, user should be able to override anything. + if s.config.Render.Template.HTMLTemplate.LayoutFuncs[k] == nil { + s.config.Render.Template.HTMLTemplate.LayoutFuncs[k] = v + } + + if s.config.Render.Template.HTMLTemplate.Funcs[k] == nil { + s.config.Render.Template.HTMLTemplate.Funcs[k] = v + } + + } + + } + s.templates = template.New(s.config.Render.Template) + } } @@ -153,7 +184,6 @@ func (s *Iris) printBanner() { c.Add(color.FgGreen) stationsRunning++ - c.Println() if stationsRunning > 1 { c.Println("Server[" + strconv.Itoa(stationsRunning) + "]") @@ -163,7 +193,7 @@ func (s *Iris) printBanner() { } }) - printTicker.Start(time.Duration(2) * time.Millisecond) + printTicker.Start(time.Duration(1) * time.Millisecond) } diff --git a/iris_singleton.go b/iris_singleton.go index fac55cf1..09876078 100644 --- a/iris_singleton.go +++ b/iris_singleton.go @@ -72,31 +72,24 @@ func Party(path string, handlersFn ...HandlerFunc) IParty { // Handle registers a route to the server's router // if empty method is passed then registers handler(s) for all methods, same as .Any -func Handle(method string, registedPath string, handlers ...Handler) { - DefaultIris.Handle(method, registedPath, handlers...) +func Handle(method string, registedPath string, handlers ...Handler) IRoute { + return DefaultIris.Handle(method, registedPath, handlers...) } // HandleFunc registers a route with a method, path string, and a handler -func HandleFunc(method string, path string, handlersFn ...HandlerFunc) { - DefaultIris.HandleFunc(method, path, handlersFn...) -} - -// HandleAnnotated registers a route handler using a Struct implements iris.Handler (as anonymous property) -// which it's metadata has the form of -// `method:"path"` and returns the route and an error if any occurs -// handler is passed by func(urstruct MyStruct) Serve(ctx *Context) {} -// -// HandleAnnotated will be deprecated until the final v3 ! -func HandleAnnotated(irisHandler Handler) error { - return DefaultIris.HandleAnnotated(irisHandler) +func HandleFunc(method string, path string, handlersFn ...HandlerFunc) IRoute { + return DefaultIris.HandleFunc(method, path, handlersFn...) } // API converts & registers a custom struct to the router -// receives three parameters +// receives two parameters // first is the request path (string) -// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field +// second is the custom struct (interface{}) which can be anything that has a *iris.Context as field. // third are the common middlewares, is optional parameter // +// Note that API's routes have their default-name to the full registed path, +// no need to give a special name for it, because it's not supposed to be used inside your templates. +// // Recommend to use when you retrieve data from an external database, // and the router-performance is not the (only) thing which slows the server's overall performance. // @@ -173,53 +166,59 @@ func UseFunc(handlersFn ...HandlerFunc) { } // Get registers a route for the Get http method -func Get(path string, handlersFn ...HandlerFunc) { - DefaultIris.Get(path, handlersFn...) +func Get(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Get(path, handlersFn...) } // Post registers a route for the Post http method -func Post(path string, handlersFn ...HandlerFunc) { - DefaultIris.Post(path, handlersFn...) +func Post(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Post(path, handlersFn...) } // Put registers a route for the Put http method -func Put(path string, handlersFn ...HandlerFunc) { - DefaultIris.Put(path, handlersFn...) +func Put(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Put(path, handlersFn...) } // Delete registers a route for the Delete http method -func Delete(path string, handlersFn ...HandlerFunc) { - DefaultIris.Delete(path, handlersFn...) +func Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Delete(path, handlersFn...) } // Connect registers a route for the Connect http method -func Connect(path string, handlersFn ...HandlerFunc) { - DefaultIris.Connect(path, handlersFn...) +func Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Connect(path, handlersFn...) } // Head registers a route for the Head http method -func Head(path string, handlersFn ...HandlerFunc) { - DefaultIris.Head(path, handlersFn...) +func Head(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Head(path, handlersFn...) } // Options registers a route for the Options http method -func Options(path string, handlersFn ...HandlerFunc) { - DefaultIris.Options(path, handlersFn...) +func Options(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Options(path, handlersFn...) } // Patch registers a route for the Patch http method -func Patch(path string, handlersFn ...HandlerFunc) { - DefaultIris.Patch(path, handlersFn...) +func Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Patch(path, handlersFn...) } // Trace registers a route for the Trace http methodd -func Trace(path string, handlersFn ...HandlerFunc) { - DefaultIris.Trace(path, handlersFn...) +func Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return DefaultIris.Trace(path, handlersFn...) } // Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete) -func Any(path string, handlersFn ...HandlerFunc) { - DefaultIris.Any(path, handlersFn...) +func Any(path string, handlersFn ...HandlerFunc) []IRoute { + return DefaultIris.Any(path, handlersFn...) +} + +// RouteByName returns a route by its name,if not found then returns a route with empty path +// Note that the searching is case-sensitive +func RouteByName(lookUpName string) IRoute { + return DefaultIris.RouteByName(lookUpName) } // StaticHandlerFunc returns a HandlerFunc to serve static system directory diff --git a/party.go b/party.go index a52e53f5..1735a419 100644 --- a/party.go +++ b/party.go @@ -19,20 +19,19 @@ import ( type ( // IParty is the interface which implements the whole Party of routes IParty interface { - Handle(string, string, ...Handler) - HandleFunc(string, string, ...HandlerFunc) - HandleAnnotated(Handler) error + Handle(string, string, ...Handler) IRoute + HandleFunc(string, string, ...HandlerFunc) IRoute API(path string, controller HandlerAPI, middlewares ...HandlerFunc) error - Get(string, ...HandlerFunc) - Post(string, ...HandlerFunc) - Put(string, ...HandlerFunc) - Delete(string, ...HandlerFunc) - Connect(string, ...HandlerFunc) - Head(string, ...HandlerFunc) - Options(string, ...HandlerFunc) - Patch(string, ...HandlerFunc) - Trace(string, ...HandlerFunc) - Any(string, ...HandlerFunc) + Get(string, ...HandlerFunc) RouteNameFunc + Post(string, ...HandlerFunc) RouteNameFunc + Put(string, ...HandlerFunc) RouteNameFunc + Delete(string, ...HandlerFunc) RouteNameFunc + Connect(string, ...HandlerFunc) RouteNameFunc + Head(string, ...HandlerFunc) RouteNameFunc + Options(string, ...HandlerFunc) RouteNameFunc + Patch(string, ...HandlerFunc) RouteNameFunc + Trace(string, ...HandlerFunc) RouteNameFunc + Any(string, ...HandlerFunc) []IRoute Use(...Handler) UseFunc(...HandlerFunc) StaticHandlerFunc(systemPath string, stripSlashes int, compress bool, generateIndexPages bool, indexNames []string) HandlerFunc @@ -61,13 +60,13 @@ func (p *GardenParty) IsRoot() bool { } // Handle registers a route to the server's router -// if empty method is passed then registers handler(s) for all methods, same as .Any -func (p *GardenParty) Handle(method string, registedPath string, handlers ...Handler) { +// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result +func (p *GardenParty) Handle(method string, registedPath string, handlers ...Handler) IRoute { if method == "" { // then use like it was .Any for _, k := range AllMethods { p.Handle(k, registedPath, handlers...) } - return + return nil } path := fixPath(p.relativePath + registedPath) // keep the last "/" as default ex: "/xyz/" if !p.station.config.DisablePathCorrection { @@ -79,69 +78,14 @@ func (p *GardenParty) Handle(method string, registedPath string, handlers ...Han p.station.plugins.DoPreHandle(route) p.station.addRoute(route) p.station.plugins.DoPostHandle(route) + return route } // HandleFunc registers and returns a route with a method string, path string and a handler // registedPath is the relative url path // handler is the iris.Handler which you can pass anything you want via iris.ToHandlerFunc(func(res,req){})... or just use func(c *iris.Context) -func (p *GardenParty) HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) { - p.Handle(method, registedPath, ConvertToHandlers(handlersFn)...) -} - -// HandleAnnotated registers a route handler using a Struct implements iris.Handler (as anonymous property) -// which it's metadata has the form of -// `method:"path"` and returns the route and an error if any occurs -// handler is passed by func(urstruct MyStruct) Serve(ctx *Context) {} -func (p *GardenParty) HandleAnnotated(irisHandler Handler) error { - var method string - var path string - var errMessage = "" - val := reflect.ValueOf(irisHandler).Elem() - - for i := 0; i < val.NumField(); i++ { - typeField := val.Type().Field(i) - - if typeField.Anonymous && typeField.Name == "Handler" { - tags := strings.Split(strings.TrimSpace(string(typeField.Tag)), " ") - firstTag := tags[0] - - idx := strings.Index(string(firstTag), ":") - - tagName := strings.ToUpper(string(firstTag[:idx])) - tagValue, unqerr := strconv.Unquote(string(firstTag[idx+1:])) - - if unqerr != nil { - errMessage = errMessage + "\non getting path: " + unqerr.Error() - continue - } - - path = tagValue - avalaibleMethodsStr := strings.Join(AllMethods[0:], ",") - - if !strings.Contains(avalaibleMethodsStr, tagName) { - //wrong method passed - errMessage = errMessage + "\nWrong method passed to the anonymous property iris.Handler -> " + tagName - continue - } - - method = tagName - - } else { - errMessage = "\nStruct passed but it doesn't have an anonymous property of type iris.Hanndler, please refer to docs\n" - } - - } - - if errMessage == "" { - p.Handle(method, path, irisHandler) - } - - var err error - if errMessage != "" { - err = ErrHandleAnnotated.Format(errMessage) - } - - return err +func (p *GardenParty) HandleFunc(method string, registedPath string, handlersFn ...HandlerFunc) IRoute { + return p.Handle(method, registedPath, ConvertToHandlers(handlersFn)...) } // API converts & registers a custom struct to the router @@ -150,6 +94,9 @@ func (p *GardenParty) HandleAnnotated(irisHandler Handler) error { // second is the custom struct (interface{}) which can be anything that has a *iris.Context as field. // third are the common middlewares, is optional parameter // +// Note that API's routes have their default-name to the full registed path, +// no need to give a special name for it, because it's not supposed to be used inside your templates. +// // Recommend to use when you retrieve data from an external database, // and the router-performance is not the (only) thing which slows the server's overall performance. // @@ -300,56 +247,59 @@ func (p *GardenParty) API(path string, controller HandlerAPI, middlewares ...Han } // Get registers a route for the Get http method -func (p *GardenParty) Get(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodGet, path, handlersFn...) +func (p *GardenParty) Get(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodGet, path, handlersFn...).Name } // Post registers a route for the Post http method -func (p *GardenParty) Post(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodPost, path, handlersFn...) +func (p *GardenParty) Post(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodPost, path, handlersFn...).Name } // Put registers a route for the Put http method -func (p *GardenParty) Put(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodPut, path, handlersFn...) +func (p *GardenParty) Put(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodPut, path, handlersFn...).Name } // Delete registers a route for the Delete http method -func (p *GardenParty) Delete(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodDelete, path, handlersFn...) +func (p *GardenParty) Delete(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodDelete, path, handlersFn...).Name } // Connect registers a route for the Connect http method -func (p *GardenParty) Connect(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodConnect, path, handlersFn...) +func (p *GardenParty) Connect(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodConnect, path, handlersFn...).Name } // Head registers a route for the Head http method -func (p *GardenParty) Head(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodHead, path, handlersFn...) +func (p *GardenParty) Head(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodHead, path, handlersFn...).Name } // Options registers a route for the Options http method -func (p *GardenParty) Options(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodOptions, path, handlersFn...) +func (p *GardenParty) Options(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodOptions, path, handlersFn...).Name } // Patch registers a route for the Patch http method -func (p *GardenParty) Patch(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodPatch, path, handlersFn...) +func (p *GardenParty) Patch(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodPatch, path, handlersFn...).Name } // Trace registers a route for the Trace http method -func (p *GardenParty) Trace(path string, handlersFn ...HandlerFunc) { - p.HandleFunc(MethodTrace, path, handlersFn...) +func (p *GardenParty) Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc { + return p.HandleFunc(MethodTrace, path, handlersFn...).Name } // Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete) -func (p *GardenParty) Any(registedPath string, handlersFn ...HandlerFunc) { - for _, k := range AllMethods { - p.HandleFunc(k, registedPath, handlersFn...) +func (p *GardenParty) Any(registedPath string, handlersFn ...HandlerFunc) []IRoute { + theRoutes := make([]IRoute, len(AllMethods), len(AllMethods)) + for idx, k := range AllMethods { + r := p.HandleFunc(k, registedPath, handlersFn...) + theRoutes[idx] = r } + return theRoutes } // H_ is used to convert a context.IContext handler func to iris.HandlerFunc, is used only inside iris internal package to avoid import cycles diff --git a/render/template/engine/html/html.go b/render/template/engine/html/html.go index a875bfb9..880047db 100644 --- a/render/template/engine/html/html.go +++ b/render/template/engine/html/html.go @@ -17,6 +17,9 @@ type ( Engine struct { Config *config.Template Templates *template.Template + // emptyFuncs returns empty functions, contains empty result for custom LayoutFuncs + + emptyFuncs template.FuncMap // Middleware // Note: // I see that many template engines returns html/template as result @@ -26,27 +29,26 @@ type ( } ) -var emptyFuncs = template.FuncMap{ - "yield": func() (string, error) { - return "", fmt.Errorf("yield was called, yet no layout defined") - }, - "partial": func() (string, error) { - return "", fmt.Errorf("block was called, yet no layout defined") - }, - "current": func() (string, error) { - return "", nil - }, "render": func() (string, error) { - return "", nil - }, - // just for test with jade - /*"bold": func() (string, error) { - return "", nil - },*/ -} - // New creates and returns the HTMLTemplate template engine func New(c config.Template) *Engine { - return &Engine{Config: &c} + s := &Engine{Config: &c} + funcs := template.FuncMap{ + "yield": func() (string, error) { + return "", fmt.Errorf("yield was called, yet no layout defined") + }, + "partial": func() (string, error) { + return "", fmt.Errorf("block was called, yet no layout defined") + }, + "current": func() (string, error) { + return "", nil + }, "render": func() (string, error) { + return "", nil + }, + } + + s.emptyFuncs = funcs + + return s } // BuildTemplates builds the templates @@ -123,7 +125,7 @@ func (s *Engine) buildFromDir() error { tmpl.Funcs(s.Config.HTMLTemplate.Funcs) } - tmpl.Funcs(emptyFuncs).Parse(contents) + tmpl.Funcs(s.emptyFuncs).Parse(contents) break } } @@ -170,7 +172,7 @@ func (s *Engine) buildFromAsset() error { tmpl.Funcs(s.Config.HTMLTemplate.Funcs) } - tmpl.Funcs(emptyFuncs).Parse(string(buf)) + tmpl.Funcs(s.emptyFuncs).Parse(string(buf)) break } } @@ -207,12 +209,13 @@ func (s *Engine) layoutFuncsFor(name string, binding interface{}) { buf, err := s.executeTemplateBuf(fullPartialName, binding) // Return safe HTML here since we are rendering our own template. return template.HTML(buf.String()), err - }, - // just for test with jade - /*"bold": func(content string) (template.HTML, error) { - return template.HTML("" + content + ""), nil - },*/ + } + _userLayoutFuncs := s.Config.HTMLTemplate.LayoutFuncs + if _userLayoutFuncs != nil && len(_userLayoutFuncs) > 0 { + for k, v := range _userLayoutFuncs { + funcs[k] = v + } } if tpl := s.Templates.Lookup(name); tpl != nil { tpl.Funcs(funcs) diff --git a/route.go b/route.go index ce73c8ce..6ed79b61 100644 --- a/route.go +++ b/route.go @@ -1,6 +1,8 @@ package iris import ( + "fmt" + "strconv" "strings" ) @@ -11,17 +13,33 @@ type ( GetMethod() string GetDomain() string GetPath() string + GetName() string + // Name sets the name of the route + Name(string) IRoute GetMiddleware() Middleware HasCors() bool + // used internaly to check arguments with the route's named parameters + parse(...interface{}) (string, bool) } + // RouteNameFunc is returned to from route handle + RouteNameFunc func(string) IRoute + // Route contains basic and temporary info about the route in order to be stored to the tree - // It's struct because we pass it ( as IRoute) to the plugins Route struct { - method string - domain string - fullpath string + method string + domain string + fullpath string + // the name of the route, the default name is just the registed path. + name string middleware Middleware + + // this is used to convert /mypath/:aparam/:something to -> /mypath/%v/%v and /mypath/* -> mypath/%v + // we use %v to escape from the conversions between strings,booleans and integers. + // used inside custom html template func 'url' + formattedPath string + // formattedParts is just the formattedPath count, used to see if we have one path parameter then the url's function arguments will be passed as one string to the %v + formattedParts int } ) @@ -57,11 +75,43 @@ func NewRoute(method string, registedPath string, middleware Middleware) *Route } } - r := &Route{method: method, domain: domain, fullpath: registedPath, middleware: middleware} - + r := &Route{method: method, domain: domain, fullpath: registedPath, middleware: middleware, name: registedPath, formattedPath: registedPath} + r.formatPath() return r } +func (r *Route) formatPath() { + // we don't care about performance here, no runtime func. + + n1Len := strings.Count(r.fullpath, ":") + isMatchEverything := r.fullpath[len(r.fullpath)-1] == MatchEverythingByte + if n1Len == 0 && !isMatchEverything { + // its a static + return + } + if n1Len == 0 && isMatchEverything { + //if we have something like: /mypath/anything/* -> /mypatch/anything/%v + r.formattedPath = r.fullpath[0:len(r.fullpath)-2] + "%v" + r.formattedParts++ + return + } + + tempPath := r.fullpath + + splittedN1 := strings.Split(r.fullpath, "/") + + for _, v := range splittedN1 { + if len(v) > 0 { + if v[0] == ':' || v[0] == MatchEverythingByte { + r.formattedParts++ + tempPath = strings.Replace(tempPath, v, "%v", -1) // n1Len, but let it we don't care about performance here. + } + } + + } + r.formattedPath = tempPath +} + // GetMethod returns the http method func (r Route) GetMethod() string { return r.method @@ -77,6 +127,17 @@ func (r Route) GetPath() string { return r.fullpath } +// GetName returns the name of the route +func (r Route) GetName() string { + return r.name +} + +// Name sets the route's name +func (r *Route) Name(newName string) IRoute { + r.name = newName + return r +} + // GetMiddleware returns the chain of the []HandlerFunc registed to this Route func (r Route) GetMiddleware() Middleware { return r.middleware @@ -87,6 +148,48 @@ func (r *Route) HasCors() bool { return RouteConflicts(r, "httpmethod") } +// used internaly to check arguments with the route's named parameters (iris.initTemplates for funcs) +func (r *Route) parse(args ...interface{}) (string, bool) { + // check if arguments are not equal to the named parameters ( : = 1, * = all named parameters split to / ), if this happens then send not found err + ///TODO: I'm thinking of making an option to disable these checks and just return a result, because they have cost when rendering an html/template, not too big compared to the render action but... we will see + // can also do a check if this url can be realy served (_tree.rootBranch.GetBranch(path, ctx.Params)) and if not then return a 404 or a link to a ./templates/errors/404.html + // but we don't have access to the context itself(so we will have some memory allocations), although it's a good idea but let's keep things simple here. + argsLen := len(args) + // we have named parameters but arguments not given + if argsLen == 0 && r.formattedParts > 0 { + return "", false + } + + // we have arguments but they are much more than the named parameters + + // 1 check if we have /*, if yes then join all arguments to one as path and pass that as parameter + if argsLen > r.formattedParts { + if r.fullpath[len(r.fullpath)-1] == MatchEverythingByte { + // we have to convert each argument to a string in this case + + argsString := make([]string, argsLen, argsLen) + + for i, v := range args { + if s, ok := v.(string); ok { + argsString[i] = s + } else if num, ok := v.(int); ok { + argsString[i] = strconv.Itoa(num) + } else if b, ok := v.(bool); ok { + argsString[i] = strconv.FormatBool(b) + } + } + + parameter := strings.Join(argsString, Slash) + result := fmt.Sprintf(r.formattedPath, parameter) + return result, true + } + // 2 if !1 return false + return "", false + } + + return fmt.Sprintf(r.formattedPath, args...), true +} + // RouteConflicts checks for route's middleware conflicts func RouteConflicts(r *Route, with string) bool { for _, h := range r.middleware { diff --git a/router.go b/router.go index ee1f00f7..52929aa0 100644 --- a/router.go +++ b/router.go @@ -59,7 +59,10 @@ type router struct { garden *Garden methodMatch func(m1, m2 string) bool getRequestPath func(*fasthttp.RequestCtx) []byte - ServeRequest func(reqCtx *fasthttp.RequestCtx) + // routes useful information, this info can be used to make custom links inside templates + // the route's information (can be) changed after its registration + lookups []IRoute + ServeRequest func(reqCtx *fasthttp.RequestCtx) // errorPool is responsible to get the Context to handle not found errors errorPool sync.Pool //it's true when optimize already ran @@ -89,6 +92,7 @@ func newRouter(station *Iris) *router { garden: &Garden{}, methodMatch: methodMatchFunc, getRequestPath: getRequestPathDefault, + lookups: make([]IRoute, 0), HTTPErrorContainer: defaultHTTPErrors(), GardenParty: &GardenParty{relativePath: "/", station: station, root: true}, errorPool: station.newContextPool()} @@ -99,13 +103,27 @@ func newRouter(station *Iris) *router { } -// addRoute calls the Plant, is created to set the router's station +// addRoute is a middleware between router and garden +// it just calls the garden's Plant method +// is 'thread-safe' func (r *router) addRoute(route IRoute) { r.mu.Lock() defer r.mu.Unlock() + r.lookups = append(r.lookups, route) r.garden.Plant(r.station, route) } +// RouteByName returns a route by its name,if not found then returns a route with empty path +// Note that the searching is case-sensitive +func (r *router) RouteByName(lookUpName string) IRoute { + for _, route := range r.lookups { + if route.GetName() == lookUpName { + return route + } + } + return &Route{} +} + //check if any tree has cors setted to true, means that cors middleware is added func (r *router) cors() (has bool) { r.garden.visitAll(func(i int, tree *tree) {