mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Helpers for third-party adaptors and middleware authors to generate route paths without even know the router that has being selected by user
Former-commit-id: b21147f2bc306d5c41539a1be0c83456c3d62651
This commit is contained in:
parent
b214df742b
commit
064282036c
|
@ -5,7 +5,7 @@
|
|||
|
||||
## 6.1.4 -> 6.2.0 (√Νεxτ)
|
||||
|
||||
_Last update: 22 Feb 2017_
|
||||
_Last update: 28 Feb 2017_
|
||||
|
||||
> Note: I want you to know that I spent more than 200 hours (16 days of ~10-15 hours per-day, do the math) for this release, two days to write these changes, please read the sections before think that you have an issue and post a new question, thanks!
|
||||
|
||||
|
@ -770,6 +770,7 @@ We have 8 policies, so far, and some of them have 'subpolicies' (the RouterRever
|
|||
- RouterReversionPolicy
|
||||
- StaticPath
|
||||
- WildcardPath
|
||||
- Param
|
||||
- URLPath
|
||||
- RouterBuilderPolicy
|
||||
- RouterWrapperPolicy
|
||||
|
|
|
@ -74,6 +74,9 @@ func New() iris.Policies {
|
|||
// finally return the path given + the wildcard path part
|
||||
return path + wildcardPart
|
||||
},
|
||||
Param: func(paramName string) string {
|
||||
return "{" + paramName + "}"
|
||||
},
|
||||
// Note: on gorilla mux the {{ url }} and {{ path}} should give the key and the value, not only the values by order.
|
||||
// {{ url "nameOfTheRoute" "parameterName" "parameterValue"}}.
|
||||
//
|
||||
|
|
|
@ -545,6 +545,9 @@ func New() iris.Policies {
|
|||
// finally return the path given + the wildcard path part
|
||||
return path + wildcardPart
|
||||
},
|
||||
Param: func(paramName string) string {
|
||||
return string(parameterStartByte) + paramName
|
||||
},
|
||||
// path = "/api/users/:id", args = ["42"]
|
||||
// return "/api/users/42"
|
||||
//
|
||||
|
|
100
iris.go
100
iris.go
|
@ -19,6 +19,7 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -89,8 +90,8 @@ type Framework struct {
|
|||
// - RouterReversionPolicy
|
||||
// - StaticPath
|
||||
// - WildcardPath
|
||||
// - Param
|
||||
// - URLPath
|
||||
// - RouteContextLinker
|
||||
// - RouterBuilderPolicy
|
||||
// - RouterWrapperPolicy
|
||||
// - RenderPolicy
|
||||
|
@ -884,6 +885,103 @@ func (s *Framework) URL(routeName string, args ...interface{}) (url string) {
|
|||
return
|
||||
}
|
||||
|
||||
// Regex takes pairs with the named path (without symbols) following by its expression
|
||||
// and returns a middleware which will do a pure but effective validation using the regexp package.
|
||||
//
|
||||
// Note: '/adaptors/gorillamux' already supports regex path validation.
|
||||
// It's useful while the developer uses the '/adaptors/httprouter' instead.
|
||||
func (s *Framework) Regex(pairParamExpr ...string) HandlerFunc {
|
||||
srvErr := func(ctx *Context) {
|
||||
ctx.EmitError(StatusInternalServerError)
|
||||
}
|
||||
|
||||
wp := s.policies.RouterReversionPolicy.WildcardPath
|
||||
if wp == nil {
|
||||
s.Log(ProdMode, "regex cannot be used when a router policy is missing\n"+errRouterIsMissing.Format(s.Config.VHost).Error())
|
||||
return srvErr
|
||||
}
|
||||
|
||||
if len(pairParamExpr)%2 != 0 {
|
||||
s.Log(ProdMode,
|
||||
"regex pre-compile error: the correct format is paramName, expression"+
|
||||
"paramName2, expression2. The len should be %2==0")
|
||||
return srvErr
|
||||
}
|
||||
pairs := make(map[string]*regexp.Regexp, len(pairParamExpr)/2)
|
||||
|
||||
for i := 0; i < len(pairParamExpr)-1; i++ {
|
||||
expr := pairParamExpr[i+1]
|
||||
r, err := regexp.Compile(expr)
|
||||
if err != nil {
|
||||
s.Log(ProdMode, "regex '"+expr+"' failed. Trace: "+err.Error())
|
||||
return srvErr
|
||||
}
|
||||
|
||||
pairs[pairParamExpr[i]] = r
|
||||
i++
|
||||
}
|
||||
|
||||
// return the middleware
|
||||
return func(ctx *Context) {
|
||||
for k, v := range pairs {
|
||||
pathPart := ctx.Param(k)
|
||||
if pathPart == "" {
|
||||
// take care, the router already
|
||||
// does the param validations
|
||||
// so if it's empty here it means that
|
||||
// the router has label it as optional.
|
||||
// so we skip it, and continue to the next.
|
||||
continue
|
||||
}
|
||||
// the improtant thing:
|
||||
// if the path part didn't match with the relative exp, then fire status not found.
|
||||
if !v.MatchString(pathPart) {
|
||||
ctx.EmitError(StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
// otherwise continue to the next handler...
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RouteParam returns a named parameter as each router defines named path parameters.
|
||||
// For example, with the httprouter(: as named param symbol):
|
||||
// userid should return :userid.
|
||||
// with gorillamux, userid should return {userid}
|
||||
// or userid[1-9]+ should return {userid[1-9]+}.
|
||||
// so basically we just wrap the raw parameter name
|
||||
// with the start (and end) dynamic symbols of each router implementing the RouterReversionPolicy.
|
||||
// It's an optional functionality but it can be used to create adaptors without even know the router
|
||||
// that the user uses (which can be taken by app.Config.Other[iris.RouterNameConfigKey].
|
||||
//
|
||||
// Note: we don't need a function like ToWildcardParam because the developer
|
||||
// can use the RouterParam with a combination with RouteWildcardPath.
|
||||
//
|
||||
// Example: https://github.com/iris-contrib/adaptors/blob/master/oauth/oauth.go
|
||||
func (s *Framework) RouteParam(paramName string) string {
|
||||
if s.policies.RouterReversionPolicy.Param == nil {
|
||||
// all Iris' routers are implementing all features but third-parties may not, so make sure that the user
|
||||
// will get a useful message back.
|
||||
s.Log(DevMode, "cannot wrap a route named path parameter because the functionality was not implemented by the current router.")
|
||||
return ""
|
||||
}
|
||||
|
||||
return s.policies.RouterReversionPolicy.Param(paramName)
|
||||
}
|
||||
|
||||
// RouteWildcardPath returns a path converted to a 'dynamic' path
|
||||
// for example, with the httprouter(wildcard symbol: '*'):
|
||||
// ("/static", "path") should return /static/*path
|
||||
// ("/myfiles/assets", "anything") should return /myfiles/assets/*anything
|
||||
func (s *Framework) RouteWildcardPath(path string, paramName string) string {
|
||||
if s.policies.RouterReversionPolicy.WildcardPath == nil {
|
||||
s.Log(DevMode, "please use WildcardPath after .Adapt(router).\n"+errRouterIsMissing.Format(s.Config.VHost).Error())
|
||||
return ""
|
||||
}
|
||||
return s.policies.RouterReversionPolicy.WildcardPath(path, paramName)
|
||||
}
|
||||
|
||||
// DecodeQuery returns the uri parameter as url (string)
|
||||
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
|
||||
// use it only for special cases, when the default behavior doesn't suits you.
|
||||
|
|
23
policy.go
23
policy.go
|
@ -92,7 +92,7 @@ const (
|
|||
// ProdMode the production level logger write mode,
|
||||
// responsible to fatal errors, errors that happen which
|
||||
// your app can't continue running.
|
||||
ProdMode LogMode = 1 << iota
|
||||
ProdMode LogMode = iota
|
||||
// DevMode is the development level logger write mode,
|
||||
// responsible to the rest of the errors, for example
|
||||
// if you set a app.Favicon("myfav.ico"..) and that fav doesn't exists
|
||||
|
@ -258,17 +258,30 @@ type (
|
|||
// which custom routers should create and adapt to the Policies.
|
||||
RouterReversionPolicy struct {
|
||||
// StaticPath should return the static part of the route path
|
||||
// for example, with the default router (: and *):
|
||||
// for example, with the httprouter(: and *):
|
||||
// /api/user/:userid should return /api/user
|
||||
// /api/user/:userid/messages/:messageid should return /api/user
|
||||
// /dynamicpath/*path should return /dynamicpath
|
||||
// /my/path should return /my/path
|
||||
StaticPath func(path string) string
|
||||
// WildcardPath should return a path converted to a 'dynamic' path
|
||||
// for example, with the default router(wildcard symbol: '*'):
|
||||
// for example, with the httprouter(wildcard symbol: '*'):
|
||||
// ("/static", "path") should return /static/*path
|
||||
// ("/myfiles/assets", "anything") should return /myfiles/assets/*anything
|
||||
WildcardPath func(path string, paramName string) string
|
||||
// Param should return a named parameter as each router defines named path parameters.
|
||||
// For example, with the httprouter(: as named param symbol):
|
||||
// userid should return :userid.
|
||||
// with gorillamux, userid should return {userid}
|
||||
// or userid[1-9]+ should return {userid[1-9]+}.
|
||||
// so basically we just wrap the raw parameter name
|
||||
// with the start (and end) dynamic symbols of each router implementing the RouterReversionPolicy.
|
||||
// It's an optional functionality but it can be used to create adaptors without even know the router
|
||||
// that the user uses (which can be taken by app.Config.Other[iris.RouterNameConfigKey].
|
||||
//
|
||||
// Note: we don't need a function like WildcardParam because the developer
|
||||
// can use the Param with a combination with WildcardPath.
|
||||
Param func(paramName string) string
|
||||
// URLPath used for reverse routing on templates with {{ url }} and {{ path }} funcs.
|
||||
// Receives the route name and arguments and returns its http path
|
||||
URLPath func(r RouteInfo, args ...string) string
|
||||
|
@ -317,6 +330,10 @@ func (r RouterReversionPolicy) Adapt(frame *Policies) {
|
|||
}
|
||||
}
|
||||
|
||||
if r.Param != nil {
|
||||
frame.RouterReversionPolicy.Param = r.Param
|
||||
}
|
||||
|
||||
if r.URLPath != nil {
|
||||
frame.RouterReversionPolicy.URLPath = r.URLPath
|
||||
}
|
||||
|
|
|
@ -194,3 +194,20 @@ func TestGorillaMuxRouteURLPath(t *testing.T) {
|
|||
t.Fatalf("gorillamux' reverse routing 'URLPath' error: expected %s but got %s", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGorillaMuxRouteParamAndWildcardPath(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.Adapt(gorillamux.New())
|
||||
routePath := app.RouteWildcardPath("/profile/"+app.RouteParam("user_id")+"/"+app.RouteParam("ref")+"/", "anything")
|
||||
expectedRoutePath := "/profile/{user_id}/{ref}/{anything:.*}"
|
||||
if routePath != expectedRoutePath {
|
||||
t.Fatalf("Gorilla Mux Error on RouteParam and RouteWildcardPath, expecting '%s' but got '%s'", expectedRoutePath, routePath)
|
||||
}
|
||||
|
||||
app.Get(routePath, func(ctx *iris.Context) {
|
||||
ctx.Writef(ctx.Path())
|
||||
})
|
||||
e := httptest.New(app, t)
|
||||
|
||||
e.GET("/profile/42/areference/anythinghere").Expect().Status(iris.StatusOK).Body().Equal("/profile/42/areference/anythinghere")
|
||||
}
|
||||
|
|
|
@ -246,3 +246,19 @@ func TestHTTPRouterRegexMiddleware(t *testing.T) {
|
|||
e.GET("/profile/gerasimosmaropoulos").Expect().Status(iris.StatusOK)
|
||||
e.GET("/profile/anumberof42").Expect().Status(iris.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestHTTPRouterRouteParamAndWildcardPath(t *testing.T) {
|
||||
app := newHTTPRouterApp()
|
||||
routePath := app.RouteWildcardPath("/profile/"+app.RouteParam("user_id")+"/"+app.RouteParam("ref")+"/", "anything")
|
||||
expectedRoutePath := "/profile/:user_id/:ref/*anything"
|
||||
if routePath != expectedRoutePath {
|
||||
t.Fatalf("HTTPRouter Error on RouteParam and RouteWildcardPath, expecting '%s' but got '%s'", expectedRoutePath, routePath)
|
||||
}
|
||||
|
||||
app.Get(routePath, func(ctx *iris.Context) {
|
||||
ctx.Writef(ctx.Path())
|
||||
})
|
||||
e := httptest.New(app, t)
|
||||
|
||||
e.GET("/profile/42/areference/anythinghere").Expect().Status(iris.StatusOK).Body().Equal("/profile/42/areference/anythinghere")
|
||||
}
|
||||
|
|
69
router.go
69
router.go
|
@ -4,7 +4,6 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -131,71 +130,6 @@ type Router struct {
|
|||
relativePath string
|
||||
}
|
||||
|
||||
// Regex takes pairs with the named path (without symbols) following by its expression
|
||||
// and returns a middleware which will do a pure but effective validation using the regexp package.
|
||||
//
|
||||
// Note: '/adaptors/gorillamux' already supports regex path validation.
|
||||
// It's useful while the developer uses the '/adaptors/httprouter' instead.
|
||||
func (s *Framework) Regex(pairParamExpr ...string) HandlerFunc {
|
||||
srvErr := func(ctx *Context) {
|
||||
ctx.EmitError(StatusInternalServerError)
|
||||
}
|
||||
|
||||
wp := s.policies.RouterReversionPolicy.WildcardPath
|
||||
if wp == nil {
|
||||
s.Log(ProdMode, "expr cannot be used when a router policy is missing\n"+errRouterIsMissing.Format(s.Config.VHost).Error())
|
||||
return srvErr
|
||||
}
|
||||
|
||||
if len(pairParamExpr)%2 != 0 {
|
||||
s.Log(ProdMode,
|
||||
"regexp expr pre-compile error: the correct format is paramName, expression"+
|
||||
"paramName2, expression2. The len should be %2==0")
|
||||
return srvErr
|
||||
}
|
||||
pairs := make(map[string]*regexp.Regexp, len(pairParamExpr)/2)
|
||||
|
||||
for i := 0; i < len(pairParamExpr)-1; i++ {
|
||||
expr := pairParamExpr[i+1]
|
||||
r, err := regexp.Compile(expr)
|
||||
if err != nil {
|
||||
s.Log(ProdMode, "expr: regexp failed on: "+expr+". Trace:"+err.Error())
|
||||
return srvErr
|
||||
}
|
||||
|
||||
pairs[pairParamExpr[i]] = r
|
||||
i++
|
||||
}
|
||||
|
||||
// return the middleware
|
||||
return func(ctx *Context) {
|
||||
for k, v := range pairs {
|
||||
pathPart := ctx.Param(k)
|
||||
if pathPart == "" {
|
||||
// take care, the router already
|
||||
// does the param validations
|
||||
// so if it's empty here it means that
|
||||
// the router has label it as optional.
|
||||
// so we skip it, and continue to the next.
|
||||
continue
|
||||
}
|
||||
// the improtant thing:
|
||||
// if the path part didn't match with the relative exp, then fire status not found.
|
||||
if !v.MatchString(pathPart) {
|
||||
ctx.EmitError(StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
// otherwise continue to the next handler...
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// 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")
|
||||
)
|
||||
|
||||
func (router *Router) build(builder RouterBuilderPolicy) {
|
||||
router.repository.sort() // sort - priority to subdomains
|
||||
router.handler = builder(router.repository, router.Context)
|
||||
|
@ -561,6 +495,9 @@ func (router *Router) StaticEmbedded(requestPath string, vdir string, assetFn fu
|
|||
return router.registerResourceRoute(requestPath, h)
|
||||
}
|
||||
|
||||
// errDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace'
|
||||
var errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")
|
||||
|
||||
// Favicon serves static favicon
|
||||
// accepts 2 parameters, second is optional
|
||||
// favPath (string), declare the system directory path of the __.ico
|
||||
|
|
Loading…
Reference in New Issue
Block a user