New ':int64' and ':uint64' route path parameters - and - support the new uint64 for MVC (int64 was already supported there) - and - add ctx.Params().GetUint64 (GetInt64 was already there) - and - make the ':int or :number' to accept negative numbers with no digit limit (at low level) and rename the 'app.Macros().Int.RegisterFunc' to 'Number.RegisterFunc' because number can be any type of number not only standard go type limited - and - add alias for ':boolean' -> ':bool'. Finally, Update the examples but not the version yet, I have to provide a good README table to explain the end-developers how they can benefit by those changes and why the breaking change (which is to accept negative numbers via ':int') is for their own good and how they can make their own macro functions so they do not depend on the Iris builtn macro funcs only. More to come tomorrow, stay tuned

Former-commit-id: 3601abfc89478185afec3594375080778214283e
This commit is contained in:
Gerasimos (Makis) Maropoulos 2018-08-23 06:30:12 +03:00
parent 01b5f6089d
commit b019a281eb
28 changed files with 478 additions and 242 deletions

View File

@ -145,6 +145,14 @@ func main() {
ctx.Writef("Hello %s", name)
})
// This handler will match /users/42
// but will not match /users/-1 because uint should be bigger than zero
// neither /users or /users/.
app.Get("/users/{id:uint64}", func(ctx iris.Context) {
id, _ := ctx.Params().GetUint64("id")
ctx.Writef("User with ID: %d", id)
})
// However, this one will match /user/john/ and also /user/john/send.
app.Post("/user/{name:string}/{action:path}", func(ctx iris.Context) {
name := ctx.Params().Get("name")

View File

@ -104,7 +104,7 @@ Structuring depends on your own needs. We can't tell you how to design your own
### Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context
* `app.Get("{userid:int min(1)}", myHandler)`
* `app.Get("{userid:number min(1)}", myHandler)`
* `app.Post("{asset:path}", myHandler)`
* `app.Put("{custom:string regexp([a-z]+)}", myHandler)`
@ -128,10 +128,10 @@ app.Get("/profile/me", userHandler)
// Matches all GET requests prefixed with /users/
// and followed by a number which should be equal or bigger than 1
app.Get("/user/{userid:int min(1)}", getUserHandler)
app.Get("/user/{userid:number min(1)}", getUserHandler)
// Matches all requests DELETE prefixed with /users/
// and following by a number which should be equal or bigger than 1
app.Delete("/user/{userid:int min(1)}", deleteUserHandler)
app.Delete("/user/{userid:number min(1)}", deleteUserHandler)
// Matches all GET requests except "/", "/about", anything starts with "/assets/" etc...
// because it does not conflict with the rest of the routes.

View File

@ -63,7 +63,7 @@ Iris 是个底层框架, 对 MVC 模式有很好的支持,但不限制文件
### 路由、路由分组、路径动态参数、路由参数处理宏 、 自定义上下文
* `app.Get("{userid:int min(1)}", myHandler)`
* `app.Get("{userid:number min(1)}", myHandler)`
* `app.Post("{asset:path}", myHandler)`
* `app.Put("{custom:string regexp([a-z]+)}", myHandler)`
@ -87,10 +87,10 @@ app.Get("/profile/me", userHandler)
// 匹配所有前缀为 /users/ 的 GET 请求
// 参数为数字,且 >= 1
app.Get("/user/{userid:int min(1)}", getUserHandler)
app.Get("/user/{userid:number min(1)}", getUserHandler)
// 匹配所有前缀为 /users/ 的 DELETE 请求
// 参数为数字,且 >= 1
app.Delete("/user/{userid:int min(1)}", deleteUserHandler)
app.Delete("/user/{userid:number min(1)}", deleteUserHandler)
// 匹配所有 GET 请求,除了 "/", "/about", 或其他以 "/assets/" 开头
// 因为它不会与其他路线冲突。

View File

@ -68,7 +68,7 @@ func main() {
usersRoutes := app.Party("/users", logThisMiddleware)
{
// Method GET: http://localhost:8080/users/42
usersRoutes.Get("/{id:int min(1)}", getUserByID)
usersRoutes.Get("/{id:number min(1)}", getUserByID)
// Method POST: http://localhost:8080/users/create
usersRoutes.Post("/create", createUser)
}

View File

@ -110,9 +110,9 @@ app := iris.New()
users := app.Party("/users", myAuthMiddlewareHandler)
// http://localhost:8080/users/42/profile
users.Get("/{id:int}/profile", userProfileHandler)
users.Get("/{id:uint64}/profile", userProfileHandler)
// http://localhost:8080/users/messages/1
users.Get("/inbox/{id:int}", userMessageHandler)
users.Get("/inbox/{id:uint64}", userMessageHandler)
```
The same could be also written using a function which accepts the child router(the Party).
@ -124,13 +124,13 @@ app.PartyFunc("/users", func(users iris.Party) {
users.Use(myAuthMiddlewareHandler)
// http://localhost:8080/users/42/profile
users.Get("/{id:int}/profile", userProfileHandler)
users.Get("/{id:uint64}/profile", userProfileHandler)
// http://localhost:8080/users/messages/1
users.Get("/inbox/{id:int}", userMessageHandler)
users.Get("/inbox/{id:uint64}", userMessageHandler)
})
```
> `id:int` is a (typed) dynamic path parameter, learn more by scrolling down.
> `id:uint` is a (typed) dynamic path parameter, learn more by scrolling down.
# Dynamic Path Parameters
@ -154,21 +154,27 @@ Standard macro types for route path parameters
string type
anything
+------------------------+
| {param:int} |
+------------------------+
+-------------------------------+
| {param:number} or {param:int} |
+-------------------------------+
int type
only numbers (0-9)
both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch)
+------------------------+
| {param:long} |
+------------------------+
+-------------------------------+
| {param:long} or {param:int64} |
+-------------------------------+
int64 type
only numbers (0-9)
-9223372036854775808 to 9223372036854775807
+------------------------+
| {param:boolean} |
| {param:uint64} |
+------------------------+
uint64 type
0 to 18446744073709551615
+---------------------------------+
| {param:bool} or {param:boolean} |
+---------------------------------+
bool type
only "1" or "t" or "T" or "TRUE" or "true" or "True"
or "0" or "f" or "F" or "FALSE" or "false" or "False"
@ -209,7 +215,7 @@ you are able to register your own too!.
Register a named path parameter function
```go
app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool {
app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool {
// [...]
return true
// -> true means valid, false means invalid fire 404 or if "else 500" is appended to the macro syntax then internal server error.
@ -219,7 +225,10 @@ app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string)
At the `func(argument ...)` you can have any standard type, it will be validated before the server starts so don't care about any performance cost there, the only thing it runs at serve time is the returning `func(paramValue string) bool`.
```go
{param:string equal(iris)} , "iris" will be the argument here:
{param:string equal(iris)}
```
The "iris" will be the argument here:
```go
app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool {
return func(paramValue string){ return argument == paramValue }
})
@ -234,12 +243,12 @@ app.Get("/username/{name}", func(ctx iris.Context) {
ctx.Writef("Hello %s", ctx.Params().Get("name"))
}) // type is missing = {name:string}
// Let's register our first macro attached to int macro type.
// Let's register our first macro attached to number macro type.
// "min" = the function
// "minValue" = the argument of the function
// func(string) bool = the macro's path parameter evaluator, this executes in serve time when
// a user requests a path which contains the :int macro type with the min(...) macro parameter function.
app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool {
// a user requests a path which contains the :number macro type with the min(...) macro parameter function.
app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool {
// do anything before serve here [...]
// at this case we don't need to do anything
return func(paramValue string) bool {
@ -254,21 +263,21 @@ app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool {
// http://localhost:8080/profile/id>=1
// this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1
// macro parameter functions are optional of course.
app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) {
app.Get("/profile/{id:uint64 min(1)}", func(ctx iris.Context) {
// second parameter is the error but it will always nil because we use macros,
// the validaton already happened.
id, _ := ctx.Params().GetInt("id")
id, _ := ctx.Params().GetUint64("id")
ctx.Writef("Hello id: %d", id)
})
// to change the error code per route's macro evaluator:
app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id")
friendid, _ := ctx.Params().GetInt("friendid")
app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) {
id, _ := ctx.Params().GetUint64("id")
friendid, _ := ctx.Params().GetUint64("friendid")
ctx.Writef("Hello id: %d looking for friend id: ", id, friendid)
}) // this will throw e 504 error code instead of 404 if all route's macros not passed.
// http://localhost:8080/game/a-zA-Z/level/0-9
// http://localhost:8080/game/a-zA-Z/level/42
// remember, alphabetical is lowercase or uppercase letters only.
app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) {
ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level"))
@ -297,12 +306,10 @@ app.Run(iris.Addr(":8080"))
}
```
A **path parameter name should contain only alphabetical letters. Symbols like '_' and numbers are NOT allowed**.
A path parameter name should contain only alphabetical letters or digits. Symbols like '_' are NOT allowed.
Last, do not confuse `ctx.Params()` with `ctx.Values()`.
Path parameter's values goes to `ctx.Params()` and context's local storage
that can be used to communicate between handlers and middleware(s) goes to
`ctx.Values()`.
Path parameter's values can be retrieved from `ctx.Params()`,
context's local storage that can be used to communicate between handlers and middleware(s) can be stored to `ctx.Values()`.
# Routing and reverse lookups

View File

@ -29,12 +29,12 @@ func main() {
app.Get("/donate", donateHandler, donateFinishHandler)
// Pssst, don't forget dynamic-path example for more "magic"!
app.Get("/api/users/{userid:int min(1)}", func(ctx iris.Context) {
userID, err := ctx.Params().GetInt("userid")
app.Get("/api/users/{userid:uint64 min(1)}", func(ctx iris.Context) {
userID, err := ctx.Params().GetUint64("userid")
if err != nil {
ctx.Writef("error while trying to parse userid parameter," +
"this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.")
"this will never happen if :uint64 is being used because if it's not a valid uint64 it will fire Not Found automatically.")
ctx.StatusCode(iris.StatusBadRequest)
return
}
@ -103,7 +103,7 @@ func main() {
ctx.Writef("All users")
})
// http://v1.localhost:8080/api/users/42
usersAPI.Get("/{userid:int}", func(ctx iris.Context) {
usersAPI.Get("/{userid:number}", func(ctx iris.Context) {
ctx.Writef("user with id: %s", ctx.Params().Get("userid"))
})
}

View File

@ -14,15 +14,14 @@ func main() {
// we've seen static routes, group of routes, subdomains, wildcard subdomains, a small example of parameterized path
// with a single known paramete and custom http errors, now it's time to see wildcard parameters and macros.
// iris, like net/http std package registers route's handlers
// Iris, like net/http std package registers route's handlers
// by a Handler, the iris' type of handler is just a func(ctx iris.Context)
// where context comes from github.com/kataras/iris/context.
// Until go 1.9 you will have to import that package too, after go 1.9 this will be not be necessary.
//
// iris has the easiest and the most powerful routing process you have ever meet.
// Iris has the easiest and the most powerful routing process you have ever meet.
//
// At the same time,
// iris has its own interpeter(yes like a programming language)
// Iris has its own interpeter(yes like a programming language)
// for route's path syntax and their dynamic path parameters parsing and evaluation,
// We call them "macros" for shortcut.
// How? It calculates its needs and if not any special regexp needed then it just
@ -36,21 +35,27 @@ func main() {
// string type
// anything
//
// +------------------------+
// | {param:int} |
// +------------------------+
// +-------------------------------+
// | {param:int} or {param:number} |
// +-------------------------------+
// int type
// only numbers (0-9)
// both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch)
//
// +------------------------+
// | {param:long} |
// +------------------------+
// +-------------------------------+
// | {param:int64} or {param:long} |
// +-------------------------------+
// int64 type
// only numbers (0-9)
// -9223372036854775808 to 9223372036854775807
//
// +------------------------+
// | {param:boolean} |
// | {param:uint64} |
// +------------------------+
// uint64 type
// 0 to 18446744073709551615
//
// +---------------------------------+
// | {param:bool} or {param:boolean} |
// +---------------------------------+
// bool type
// only "1" or "t" or "T" or "TRUE" or "true" or "True"
// or "0" or "f" or "F" or "FALSE" or "false" or "False"
@ -89,7 +94,7 @@ func main() {
// you are able to register your own too!.
//
// Register a named path parameter function:
// app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool {
// app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool {
// [...]
// return true/false -> true means valid.
// })
@ -107,12 +112,12 @@ func main() {
ctx.Writef("Hello %s", ctx.Params().Get("name"))
}) // type is missing = {name:string}
// Let's register our first macro attached to int macro type.
// Let's register our first macro attached to number macro type.
// "min" = the function
// "minValue" = the argument of the function
// func(string) bool = the macro's path parameter evaluator, this executes in serve time when
// a user requests a path which contains the :int macro type with the min(...) macro parameter function.
app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool {
// a user requests a path which contains the :number macro type with the min(...) macro parameter function.
app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool {
// do anything before serve here [...]
// at this case we don't need to do anything
return func(paramValue string) bool {
@ -127,7 +132,7 @@ func main() {
// http://localhost:8080/profile/id>=1
// this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1
// macro parameter functions are optional of course.
app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) {
app.Get("/profile/{id:number min(1)}", func(ctx iris.Context) {
// second parameter is the error but it will always nil because we use macros,
// the validaton already happened.
id, _ := ctx.Params().GetInt("id")
@ -135,8 +140,8 @@ func main() {
})
// to change the error code per route's macro evaluator:
app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id")
app.Get("/profile/{id:number min(1)}/friends/{friendid:number min(1) else 504}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id") // or GetUint64.
friendid, _ := ctx.Params().GetInt("friendid")
ctx.Writef("Hello id: %d looking for friend id: ", id, friendid)
}) // this will throw e 504 error code instead of 404 if all route's macros not passed.
@ -159,9 +164,9 @@ func main() {
//
// http://localhost:8080/game/a-zA-Z/level/0-9
// http://localhost:8080/game/a-zA-Z/level/42
// remember, alphabetical is lowercase or uppercase letters only.
app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) {
app.Get("/game/{name:alphabetical}/level/{level:number}", func(ctx iris.Context) {
ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level"))
})
@ -197,10 +202,9 @@ func main() {
// if "/mypath/{myparam:path}" then the parameter has two names, one is the "*" and the other is the user-defined "myparam".
// WARNING:
// A path parameter name should contain only alphabetical letters. Symbols like '_' and numbers are NOT allowed.
// A path parameter name should contain only alphabetical letters or digits. Symbols like '_' are NOT allowed.
// Last, do not confuse `ctx.Params()` with `ctx.Values()`.
// Path parameter's values goes to `ctx.Params()` and context's local storage
// that can be used to communicate between handlers and middleware(s) goes to
// `ctx.Values()`.
// Path parameter's values can be retrieved from `ctx.Params()`,
// context's local storage that can be used to communicate between handlers and middleware(s) can be stored to `ctx.Values()`.
app.Run(iris.Addr(":8080"))
}

View File

@ -35,25 +35,25 @@ func registerGamesRoutes(app *iris.Application) {
{ // braces are optional of course, it's just a style of code
// "GET" method
games.Get("/{gameID:int}/clans", h)
games.Get("/{gameID:int}/clans/clan/{clanPublicID:int}", h)
games.Get("/{gameID:int}/clans/search", h)
games.Get("/{gameID:uint64}/clans", h)
games.Get("/{gameID:uint64}/clans/clan/{clanPublicID:uint64}", h)
games.Get("/{gameID:uint64}/clans/search", h)
// "PUT" method
games.Put("/{gameID:int}/players/{clanPublicID:int}", h)
games.Put("/{gameID:int}/clans/clan/{clanPublicID:int}", h)
games.Put("/{gameID:uint64}/players/{clanPublicID:uint64}", h)
games.Put("/{gameID:uint64}/clans/clan/{clanPublicID:uint64}", h)
// remember: "clanPublicID" should not be changed to other routes with the same prefix.
// "POST" method
games.Post("/{gameID:int}/clans", h)
games.Post("/{gameID:int}/players", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/leave", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/application", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/application/{action}", h) // {action} == {action:string}
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/invitation", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/invitation/{action}", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/delete", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/promote", h)
games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/demote", h)
games.Post("/{gameID:uint64}/clans", h)
games.Post("/{gameID:uint64}/players", h)
games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/leave", h)
games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/application", h)
games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/application/{action}", h) // {action} == {action:string}
games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/invitation", h)
games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/invitation/{action}", h)
games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/delete", h)
games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/promote", h)
games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/demote", h)
gamesCh := games.Party("/challenge")
{

View File

@ -68,8 +68,8 @@ func main() {
// GET: http://localhost:8080/users/42
// **/users/42 and /users/help works after iris version 7.0.5**
usersRoutes.Get("/{id:int}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id")
usersRoutes.Get("/{id:uint64}", func(ctx iris.Context) {
id, _ := ctx.Params().GetUint64("id")
ctx.Writef("get user by id: %d", id)
})
@ -80,15 +80,15 @@ func main() {
})
// PUT: http://localhost:8080/users
usersRoutes.Put("/{id:int}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id") // or .Get to get its string represatantion.
usersRoutes.Put("/{id:uint64}", func(ctx iris.Context) {
id, _ := ctx.Params().GetUint64("id") // or .Get to get its string represatantion.
username := ctx.PostValue("username")
ctx.Writef("update user for id= %d and new username= %s", id, username)
})
// DELETE: http://localhost:8080/users/42
usersRoutes.Delete("/{id:int}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id")
usersRoutes.Delete("/{id:uint64}", func(ctx iris.Context) {
id, _ := ctx.Params().GetUint64("id")
ctx.Writef("delete user by id: %d", id)
})

View File

@ -13,11 +13,11 @@ func newApp() *iris.Application {
app.PartyFunc("/api/users", func(r iris.Party) {
r.Get("/", info)
r.Get("/{id:int}", info)
r.Get("/{id:uint64}", info)
r.Post("/", info)
r.Put("/{id:int}", info)
r.Put("/{id:uint64}", info)
}) /* <- same as:
usersAPI := app.Party("/api/users")
{ // those brackets are just syntactic-sugar things.

View File

@ -25,7 +25,7 @@ type RouteReadOnly interface {
// StaticPath returns the static part of the original, registered route path.
// if /user/{id} it will return /user
// if /user/{id}/friend/{friendid:int} it will return /user too
// if /user/{id}/friend/{friendid:uint64} it will return /user too
// if /assets/{filepath:path} it will return /assets.
StaticPath() string

View File

@ -270,10 +270,10 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
// otherwise use `Party` which can handle many paths with different handlers and middlewares.
//
// Usage:
// app.HandleMany("GET", "/user /user/{id:int} /user/me", genericUserHandler)
// app.HandleMany("GET", "/user /user/{id:uint64} /user/me", genericUserHandler)
// At the other side, with `Handle` we've had to write:
// app.Handle("GET", "/user", userHandler)
// app.Handle("GET", "/user/{id:int}", userByIDHandler)
// app.Handle("GET", "/user/{id:uint64}", userByIDHandler)
// app.Handle("GET", "/user/me", userMeHandler)
//
// This method is used behind the scenes at the `Controller` function

View File

@ -56,7 +56,7 @@ func genPaths(routesLength, minCharLength, maxCharLength int) []string {
b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength))
b.WriteString("/{name:string}/") // sugar.
b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength))
b.WriteString("/{age:int}/end")
b.WriteString("/{age:number}/end")
paths[i] = b.String()
b.Reset()

View File

@ -35,8 +35,9 @@ func registerBuiltinsMacroFuncs(out *macro.Map) {
//
// these can be overridden by the user, later on.
registerStringMacroFuncs(out.String)
registerIntMacroFuncs(out.Int)
registerIntMacroFuncs(out.Long)
registerNumberMacroFuncs(out.Number)
registerInt64MacroFuncs(out.Int64)
registerUint64MacroFuncs(out.Uint64)
registerAlphabeticalMacroFuncs(out.Alphabetical)
registerFileMacroFuncs(out.File)
registerPathMacroFuncs(out.Path)
@ -87,9 +88,9 @@ func registerStringMacroFuncs(out *macro.Macro) {
})
}
// Int
// only numbers (0-9)
func registerIntMacroFuncs(out *macro.Macro) {
// Number
// positive and negative numbers, number of digits depends on the arch.
func registerNumberMacroFuncs(out *macro.Macro) {
// checks if the param value's int representation is
// bigger or equal than 'min'
out.RegisterFunc("min", func(min int) macro.EvaluatorFunc {
@ -131,6 +132,94 @@ func registerIntMacroFuncs(out *macro.Macro) {
})
}
// Int64
// -9223372036854775808 to 9223372036854775807.
func registerInt64MacroFuncs(out *macro.Macro) {
// checks if the param value's int64 representation is
// bigger or equal than 'min'
out.RegisterFunc("min", func(min int64) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.ParseInt(paramValue, 10, 64)
if err != nil {
return false
}
return n >= min
}
})
// checks if the param value's int64 representation is
// smaller or equal than 'max'
out.RegisterFunc("max", func(max int64) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.ParseInt(paramValue, 10, 64)
if err != nil {
return false
}
return n <= max
}
})
// checks if the param value's int64 representation is
// between min and max, including 'min' and 'max'
out.RegisterFunc("range", func(min, max int64) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.ParseInt(paramValue, 10, 64)
if err != nil {
return false
}
if n < min || n > max {
return false
}
return true
}
})
}
// Uint64
// 0 to 18446744073709551615.
func registerUint64MacroFuncs(out *macro.Macro) {
// checks if the param value's uint64 representation is
// bigger or equal than 'min'
out.RegisterFunc("min", func(min uint64) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.ParseUint(paramValue, 10, 64)
if err != nil {
return false
}
return n >= min
}
})
// checks if the param value's uint64 representation is
// smaller or equal than 'max'
out.RegisterFunc("max", func(max uint64) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.ParseUint(paramValue, 10, 64)
if err != nil {
return false
}
return n <= max
}
})
// checks if the param value's uint64 representation is
// between min and max, including 'min' and 'max'
out.RegisterFunc("range", func(min, max uint64) macro.EvaluatorFunc {
return func(paramValue string) bool {
n, err := strconv.ParseUint(paramValue, 10, 64)
if err != nil {
return false
}
if n < min || n > max {
return false
}
return true
}
})
}
// Alphabetical
// letters only (upper or lowercase)
func registerAlphabeticalMacroFuncs(out *macro.Macro) {

View File

@ -16,20 +16,27 @@ const (
// ParamTypeString is the string type.
// If parameter type is missing then it defaults to String type.
// Allows anything
// Declaration: /mypath/{myparam:string} or /mypath{myparam}
// Declaration: /mypath/{myparam:string} or {myparam}
ParamTypeString
// ParamTypeInt is the integer, a number type.
// Allows only positive numbers (0-9)
// Declaration: /mypath/{myparam:int}
ParamTypeInt
// ParamTypeLong is the integer, a number type.
// Allows only positive numbers (0-9)
// Declaration: /mypath/{myparam:long}
ParamTypeLong
// ParamTypeNumber is the integer, a number type.
// Allows both positive and negative numbers, any number of digits.
// Declaration: /mypath/{myparam:number} or {myparam:int} for backwards-compatibility
ParamTypeNumber
// ParamTypeInt64 is a number type.
// Allows only -9223372036854775808 to 9223372036854775807.
// Declaration: /mypath/{myparam:int64} or {myparam:long}
ParamTypeInt64
// ParamTypeUint64 a number type.
// Allows only 0 to 18446744073709551615.
// Declaration: /mypath/{myparam:uint64}
ParamTypeUint64
// ParamTypeBoolean is the bool type.
// Allows only "1" or "t" or "T" or "TRUE" or "true" or "True"
// or "0" or "f" or "F" or "FALSE" or "false" or "False".
// Declaration: /mypath/{myparam:boolean}
// Declaration: /mypath/{myparam:bool} or {myparam:boolean}
ParamTypeBoolean
// ParamTypeAlphabetical is the alphabetical/letter type type.
// Allows letters only (upper or lowercase)
@ -79,10 +86,12 @@ func (pt ParamType) Kind() reflect.Kind {
fallthrough
case ParamTypeString:
return reflect.String
case ParamTypeInt:
case ParamTypeNumber:
return reflect.Int
case ParamTypeLong:
case ParamTypeInt64:
return reflect.Int64
case ParamTypeUint64:
return reflect.Uint64
case ParamTypeBoolean:
return reflect.Bool
}
@ -99,6 +108,8 @@ func ValidKind(k reflect.Kind) bool {
fallthrough
case reflect.Int64:
fallthrough
case reflect.Uint64:
fallthrough
case reflect.Bool:
return true
default:
@ -113,10 +124,17 @@ func (pt ParamType) Assignable(k reflect.Kind) bool {
}
var paramTypes = map[string]ParamType{
"string": ParamTypeString,
"int": ParamTypeInt,
"long": ParamTypeLong,
"boolean": ParamTypeBoolean,
"string": ParamTypeString,
"number": ParamTypeNumber,
"int": ParamTypeNumber, // same as number.
"long": ParamTypeInt64,
"int64": ParamTypeInt64, // same as long.
"uint64": ParamTypeUint64,
"boolean": ParamTypeBoolean,
"bool": ParamTypeBoolean, // same as boolean.
"alphabetical": ParamTypeAlphabetical,
"file": ParamTypeFile,
"path": ParamTypePath,
@ -131,8 +149,10 @@ var paramTypes = map[string]ParamType{
// representation of a parameter type.
// Available:
// "string"
// "int"
// "long"
// "number" or "int"
// "long" or "int64"
// "uint64"
// "boolean" or "bool"
// "alphabetical"
// "file"
// "path"
@ -149,17 +169,20 @@ func LookupParamType(ident string) ParamType {
// make sure that caller resolves these types before this call.
//
// string matches to string
// int matches to int
// int64 matches to long
// bool matches to boolean
// int matches to int/number
// int64 matches to int64/long
// uint64 matches to uint64
// bool matches to bool/boolean
func LookupParamTypeFromStd(goType string) ParamType {
switch goType {
case "string":
return ParamTypeString
case "int":
return ParamTypeInt
return ParamTypeNumber
case "int64":
return ParamTypeLong
return ParamTypeInt64
case "uint64":
return ParamTypeUint64
case "bool":
return ParamTypeBoolean
default:

View File

@ -179,7 +179,7 @@ func (l *Lexer) skipWhitespace() {
func (l *Lexer) readIdentifier() string {
pos := l.pos
for isLetter(l.ch) {
for isLetter(l.ch) || isDigit(l.ch) {
l.readChar()
}
return l.input[pos:l.pos]

View File

@ -7,27 +7,27 @@ import (
)
func TestNextToken(t *testing.T) {
input := `{id:int min(1) max(5) else 404}`
input := `{id:number min(1) max(5) else 404}`
tests := []struct {
expectedType token.Type
expectedLiteral string
}{
{token.LBRACE, "{"}, // 0
{token.IDENT, "id"}, // 1
{token.COLON, ":"}, // 2
{token.IDENT, "int"}, // 3
{token.IDENT, "min"}, // 4
{token.LPAREN, "("}, // 5
{token.INT, "1"}, // 6
{token.RPAREN, ")"}, // 7
{token.IDENT, "max"}, // 8
{token.LPAREN, "("}, // 9
{token.INT, "5"}, // 10
{token.RPAREN, ")"}, // 11
{token.ELSE, "else"}, // 12
{token.INT, "404"}, // 13
{token.RBRACE, "}"}, // 14
{token.LBRACE, "{"}, // 0
{token.IDENT, "id"}, // 1
{token.COLON, ":"}, // 2
{token.IDENT, "number"}, // 3
{token.IDENT, "min"}, // 4
{token.LPAREN, "("}, // 5
{token.INT, "1"}, // 6
{token.RPAREN, ")"}, // 7
{token.IDENT, "max"}, // 8
{token.LPAREN, "("}, // 9
{token.INT, "5"}, // 10
{token.RPAREN, ")"}, // 11
{token.ELSE, "else"}, // 12
{token.INT, "404"}, // 13
{token.RBRACE, "}"}, // 14
}
l := New(input)

View File

@ -120,11 +120,11 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) {
switch t.Type {
case token.LBRACE:
// name, alphabetical and _, param names are not allowed to contain any number.
// can accept only letter or number only.
nextTok := l.NextToken()
stmt.Name = nextTok.Literal
case token.COLON:
// type
// type can accept both letters and numbers but not symbols ofc.
nextTok := l.NextToken()
paramType := ast.LookupParamType(nextTok.Literal)
if paramType == ast.ParamTypeUnExpected {

View File

@ -30,7 +30,7 @@ func TestParseParamError(t *testing.T) {
//
// success
input2 := "{id:int range(1,5) else 404}"
input2 := "{id:uint64 range(1,5) else 404}"
p.Reset(input2)
_, err = p.Parse()
@ -47,9 +47,9 @@ func TestParseParam(t *testing.T) {
}{
{true,
ast.ParamStatement{
Src: "{id:int min(1) max(5) else 404}",
Src: "{id:number min(1) max(5) else 404}",
Name: "id",
Type: ast.ParamTypeInt,
Type: ast.ParamTypeNumber,
Funcs: []ast.ParamFunc{
{
Name: "min",
@ -63,9 +63,9 @@ func TestParseParam(t *testing.T) {
{true,
ast.ParamStatement{
Src: "{id:int range(1,5)}",
Src: "{id:number range(1,5)}",
Name: "id",
Type: ast.ParamTypeInt,
Type: ast.ParamTypeNumber,
Funcs: []ast.ParamFunc{
{
Name: "range",
@ -106,18 +106,18 @@ func TestParseParam(t *testing.T) {
Type: ast.ParamTypeUnExpected,
ErrorCode: 404,
}}, // 5
{false, // false because it will give an error of unexpeced token type with value 2
{true,
ast.ParamStatement{
Src: "{myparam2}",
Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names.
Name: "myparam2", // we now allow integers to the parameter names.
Type: ast.ParamTypeString,
ErrorCode: 404,
}}, // 6
{true,
ast.ParamStatement{
Src: "{id:int even()}", // test param funcs without any arguments (LPAREN peek for RPAREN)
Src: "{id:number even()}", // test param funcs without any arguments (LPAREN peek for RPAREN)
Name: "id",
Type: ast.ParamTypeInt,
Type: ast.ParamTypeNumber,
Funcs: []ast.ParamFunc{
{
Name: "even"},
@ -126,18 +126,32 @@ func TestParseParam(t *testing.T) {
}}, // 7
{true,
ast.ParamStatement{
Src: "{id:long else 404}",
Src: "{id:int64 else 404}",
Name: "id",
Type: ast.ParamTypeLong,
Type: ast.ParamTypeInt64,
ErrorCode: 404,
}}, // 8
{true,
ast.ParamStatement{
Src: "{has:boolean else 404}",
Src: "{id:long else 404}", // backwards-compatible test.
Name: "id",
Type: ast.ParamTypeInt64,
ErrorCode: 404,
}}, // 9
{true,
ast.ParamStatement{
Src: "{has:bool else 404}",
Name: "has",
Type: ast.ParamTypeBoolean,
ErrorCode: 404,
}}, // 9
}}, // 10
{true,
ast.ParamStatement{
Src: "{has:boolean else 404}", // backwards-compatible test.
Name: "has",
Type: ast.ParamTypeBoolean,
ErrorCode: 404,
}}, // 11
}
@ -167,11 +181,11 @@ func TestParse(t *testing.T) {
valid bool
expectedStatements []ast.ParamStatement
}{
{"/api/users/{id:int min(1) max(5) else 404}", true,
{"/api/users/{id:number min(1) max(5) else 404}", true,
[]ast.ParamStatement{{
Src: "{id:int min(1) max(5) else 404}",
Src: "{id:number min(1) max(5) else 404}",
Name: "id",
Type: ast.ParamTypeInt,
Type: ast.ParamTypeNumber,
Funcs: []ast.ParamFunc{
{
Name: "min",
@ -183,11 +197,11 @@ func TestParse(t *testing.T) {
ErrorCode: 404,
},
}}, // 0
{"/admin/{id:int range(1,5)}", true,
{"/admin/{id:uint64 range(1,5)}", true,
[]ast.ParamStatement{{
Src: "{id:int range(1,5)}",
Src: "{id:uint64 range(1,5)}", // test alternative (backwards-compatibility) "int"
Name: "id",
Type: ast.ParamTypeInt,
Type: ast.ParamTypeUint64,
Funcs: []ast.ParamFunc{
{
Name: "range",
@ -233,10 +247,10 @@ func TestParse(t *testing.T) {
ErrorCode: 404,
},
}}, // 5
{"/p2/{myparam2}", false, // false because it will give an error of unexpeced token type with value 2
{"/p2/{myparam2}", true,
[]ast.ParamStatement{{
Src: "{myparam2}",
Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names.
Name: "myparam2", // we now allow integers to the parameter names.
Type: ast.ParamTypeString,
ErrorCode: 404,
},

View File

@ -13,8 +13,8 @@ type Token struct {
// /about/{fullname:alphabetical}
// /profile/{anySpecialName:string}
// {id:int range(1,5) else 404}
// /admin/{id:int eq(1) else 402}
// {id:uint64 range(1,5) else 404}
// /admin/{id:number eq(1) else 402}
// /file/{filepath:file else 405}
const (
EOF = iota // 0

View File

@ -214,14 +214,17 @@ type Map struct {
// string type
// anything
String *Macro
// uint type
// only positive numbers (+0-9)
// it could be uint/uint32 but we keep int for simplicity
Int *Macro
// long an int64 type
// only positive numbers (+0-9)
// it could be uint64 but we keep int64 for simplicity
Long *Macro
// int type
// both positive and negative numbers, any number of digits.
Number *Macro
// int64 as int64 type
// -9223372036854775808 to 9223372036854775807.
Int64 *Macro
// uint64 as uint64 type
// 0 to 18446744073709551615.
Uint64 *Macro
// boolean as bool type
// a string which is "1" or "t" or "T" or "TRUE" or "true" or "True"
// or "0" or "f" or "F" or "FALSE" or "false" or "False".
@ -247,11 +250,26 @@ type Map struct {
//
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
func NewMap() *Map {
simpleNumberEvalutator := MustNewEvaluatorFromRegexp("^-?[0-9]+$")
return &Map{
// it allows everything, so no need for a regexp here.
String: newMacro(func(string) bool { return true }),
Int: newMacro(MustNewEvaluatorFromRegexp("^[0-9]+$")),
Long: newMacro(MustNewEvaluatorFromRegexp("^[0-9]+$")),
Number: newMacro(simpleNumberEvalutator), //"^(-?0\\.[0-9]*[1-9]+[0-9]*$)|(^-?[1-9]+[0-9]*((\\.[0-9]*[1-9]+[0-9]*$)|(\\.[0-9]+)))|(^-?[1-9]+[0-9]*$)|(^0$){1}")), //("^-?[0-9]+$")),
Int64: newMacro(func(paramValue string) bool {
if !simpleNumberEvalutator(paramValue) {
return false
}
_, err := strconv.ParseInt(paramValue, 10, 64)
// if err == strconv.ErrRange...
return err == nil
}), //("^-[1-9]|-?[1-9][0-9]{1,14}|-?1000000000000000|-?10000000000000000|-?100000000000000000|-?[1-9]000000000000000000|-?9[0-2]00000000000000000|-?92[0-2]0000000000000000|-?922[0-3]000000000000000|-?9223[0-3]00000000000000|-?92233[0-7]0000000000000|-?922337[0-2]000000000000|-?92233720[0-3]0000000000|-?922337203[0-6]000000000|-?9223372036[0-8]00000000|-?92233720368[0-5]0000000|-?922337203685[0-4]000000|-?9223372036854[0-7]00000|-?92233720368547[0-7]0000|-?922337203685477[0-5]000|-?922337203685477[56]000|[0-9]$")),
Uint64: newMacro(func(paramValue string) bool {
if !simpleNumberEvalutator(paramValue) {
return false
}
_, err := strconv.ParseUint(paramValue, 10, 64)
return err == nil
}), //("^[0-9]|[1-9][0-9]{1,14}|1000000000000000|10000000000000000|100000000000000000|1000000000000000000|1[0-8]000000000000000000|18[0-4]00000000000000000|184[0-4]0000000000000000|1844[0-6]000000000000000|18446[0-7]00000000000000|184467[0-4]0000000000000|1844674[0-4]000000000000|184467440[0-7]0000000000|1844674407[0-3]000000000|18446744073[0-7]00000000|1844674407370000000[0-9]|18446744073709[0-5]00000|184467440737095[0-5]0000|1844674407370955[0-2]000$")),
Boolean: newMacro(func(paramValue string) bool {
// a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$
// in this case.
@ -270,14 +288,16 @@ func NewMap() *Map {
// Lookup returns the specific Macro from the map
// based on the parameter type.
// i.e if ast.ParamTypeInt then it will return the m.Int.
// i.e if ast.ParamTypeNumber then it will return the m.Number.
// Returns the m.String if not matched.
func (m *Map) Lookup(typ ast.ParamType) *Macro {
switch typ {
case ast.ParamTypeInt:
return m.Int
case ast.ParamTypeLong:
return m.Long
case ast.ParamTypeNumber:
return m.Number
case ast.ParamTypeInt64:
return m.Int64
case ast.ParamTypeUint64:
return m.Uint64
case ast.ParamTypeBoolean:
return m.Boolean
case ast.ParamTypeAlphabetical:

View File

@ -64,9 +64,9 @@ func TestGoodParamFuncName(t *testing.T) {
}
}
func testEvaluatorRaw(macroEvaluator *Macro, input string, pass bool, i int, t *testing.T) {
func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, pass bool, i int) {
if got := macroEvaluator.Evaluator(input); pass != got {
t.Fatalf("tests[%d] - expecting %v but got %v", i, pass, got)
t.Fatalf("%s - tests[%d] - expecting %v but got %v", t.Name(), i, pass, got)
}
}
@ -86,26 +86,86 @@ func TestStringEvaluatorRaw(t *testing.T) {
} // 0
for i, tt := range tests {
testEvaluatorRaw(f.String, tt.input, tt.pass, i, t)
testEvaluatorRaw(t, f.String, tt.input, tt.pass, i)
}
}
func TestIntEvaluatorRaw(t *testing.T) {
func TestNumberEvaluatorRaw(t *testing.T) {
f := NewMap()
tests := []struct {
pass bool
input string
}{
{false, "astring"}, // 0
{false, "astringwith_numb3rS_and_symbol$"}, // 1
{true, "32321"}, // 2
{false, "main.css"}, // 3
{false, "/assets/main.css"}, // 4
{false, "astring"}, // 0
{false, "astringwith_numb3rS_and_symbol$"}, // 1
{true, "32321"}, // 2
{true, "18446744073709551615"}, // 3
{true, "-18446744073709551615"}, // 4
{true, "-18446744073709553213213213213213121615"}, // 5
{false, "42 18446744073709551615"}, // 6
{false, "--42"}, // 7
{false, "+42"}, // 9
{false, "main.css"}, // 9
{false, "/assets/main.css"}, // 10
}
for i, tt := range tests {
testEvaluatorRaw(f.Int, tt.input, tt.pass, i, t)
testEvaluatorRaw(t, f.Number, tt.input, tt.pass, i)
}
}
func TestInt64EvaluatorRaw(t *testing.T) {
f := NewMap()
tests := []struct {
pass bool
input string
}{
{false, "astring"}, // 0
{false, "astringwith_numb3rS_and_symbol$"}, // 1
{false, "18446744073709551615"}, // 2
{false, "92233720368547758079223372036854775807"}, // 3
{false, "9223372036854775808 9223372036854775808"}, // 4
{false, "main.css"}, // 5
{false, "/assets/main.css"}, // 6
{true, "9223372036854775807"}, // 7
{true, "-9223372036854775808"}, // 8
{true, "-0"}, // 9
{true, "1"}, // 10
{true, "-042"}, // 11
{true, "142"}, // 12
}
for i, tt := range tests {
testEvaluatorRaw(t, f.Int64, tt.input, tt.pass, i)
}
}
func TestUint64EvaluatorRaw(t *testing.T) {
f := NewMap()
tests := []struct {
pass bool
input string
}{
{false, "astring"}, // 0
{false, "astringwith_numb3rS_and_symbol$"}, // 1
{false, "-9223372036854775808"}, // 2
{false, "main.css"}, // 3
{false, "/assets/main.css"}, // 4
{false, "92233720368547758079223372036854775807"}, // 5
{false, "9223372036854775808 9223372036854775808"}, // 6
{false, "-1"}, // 7
{false, "-0"}, // 8
{false, "+1"}, // 9
{true, "18446744073709551615"}, // 10
{true, "9223372036854775807"}, // 11
{true, "0"}, // 12
}
for i, tt := range tests {
testEvaluatorRaw(t, f.Uint64, tt.input, tt.pass, i)
}
}
@ -124,7 +184,7 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) {
}
for i, tt := range tests {
testEvaluatorRaw(f.Alphabetical, tt.input, tt.pass, i, t)
testEvaluatorRaw(t, f.Alphabetical, tt.input, tt.pass, i)
}
}
@ -143,7 +203,7 @@ func TestFileEvaluatorRaw(t *testing.T) {
}
for i, tt := range tests {
testEvaluatorRaw(f.File, tt.input, tt.pass, i, t)
testEvaluatorRaw(t, f.File, tt.input, tt.pass, i)
}
}
@ -163,7 +223,7 @@ func TestPathEvaluatorRaw(t *testing.T) {
}
for i, tt := range pathTests {
testEvaluatorRaw(f.Path, tt.input, tt.pass, i, t)
testEvaluatorRaw(t, f.Path, tt.input, tt.pass, i)
}
}
@ -182,5 +242,5 @@ func TestPathEvaluatorRaw(t *testing.T) {
// // p.Params = append(p.)
// testEvaluatorRaw(m.String, p.Src, false, 0, t)
// testEvaluatorRaw(t, m.String, p.Src, false, 0)
// }

View File

@ -110,10 +110,10 @@ type Party interface {
// otherwise use `Party` which can handle many paths with different handlers and middlewares.
//
// Usage:
// app.HandleMany(iris.MethodGet, "/user /user/{id:int} /user/me", userHandler)
// app.HandleMany(iris.MethodGet, "/user /user/{id:uint64} /user/me", userHandler)
// At the other side, with `Handle` we've had to write:
// app.Handle(iris.MethodGet, "/user", userHandler)
// app.Handle(iris.MethodGet, "/user/{id:int}", userHandler)
// app.Handle(iris.MethodGet, "/user/{id:uint64}", userHandler)
// app.Handle(iris.MethodGet, "/user/me", userHandler)
//
// This method is used behind the scenes at the `Controller` function

View File

@ -27,8 +27,8 @@ func TestCleanPath(t *testing.T) {
"/total/{year:string regexp(\\d{4})}/more/{s:string regexp(\\d{7})}"},
{"/single_no_params",
"/single_no_params"},
{"/single/{id:int}",
"/single/{id:int}"},
{"/single/{id:uint64}",
"/single/{id:uint64}"},
}
for i, tt := range tests {
@ -45,14 +45,16 @@ func TestSplitPath(t *testing.T) {
}{
{"/v2/stores/{id:string format(uuid)} /v3",
[]string{"/v2/stores/{id:string format(uuid)}", "/v3"}},
{"/user/{id:int} /admin/{id:int}",
[]string{"/user/{id:int}", "/admin/{id:int}"}},
{"/user/{id:uint64} /admin/{id:uint64}",
[]string{"/user/{id:uint64}", "/admin/{id:uint64}"}},
{"/users/{id:int} /admins/{id:int64}",
[]string{"/users/{id:int}", "/admins/{id:int64}"}},
{"/user /admin",
[]string{"/user", "/admin"}},
{"/single_no_params",
[]string{"/single_no_params"}},
{"/single/{id:int}",
[]string{"/single/{id:int}"}},
{"/single/{id:number}",
[]string{"/single/{id:number}"}},
}
equalSlice := func(s1 []string, s2 []string) bool {

View File

@ -16,7 +16,7 @@ type Route struct {
Method string `json:"method"` // "GET"
methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one.
Subdomain string `json:"subdomain"` // "admin."
tmpl *macro.Template // Tmpl().Src: "/api/user/{id:int}"
tmpl *macro.Template // Tmpl().Src: "/api/user/{id:uint64}"
// temp storage, they're appended to the Handlers on build.
// Execution happens before Handlers, can be empty.
beginHandlers context.Handlers
@ -198,7 +198,7 @@ func formatPath(path string) string {
// StaticPath returns the static part of the original, registered route path.
// if /user/{id} it will return /user
// if /user/{id}/friend/{friendid:int} it will return /user too
// if /user/{id}/friend/{friendid:uint64} it will return /user too
// if /assets/{filepath:path} it will return /assets.
func (r Route) StaticPath() string {
src := r.tmpl.Src

69
doc.go
View File

@ -119,7 +119,7 @@ Example code:
usersRoutes := app.Party("/users", logThisMiddleware)
{
// Method GET: http://localhost:8080/users/42
usersRoutes.Get("/{id:int min(1)}", getUserByID)
usersRoutes.Get("/{id:uint64 min(1)}", getUserByID)
// Method POST: http://localhost:8080/users/create
usersRoutes.Post("/create", createUser)
}
@ -146,7 +146,7 @@ Example code:
}
func getUserByID(ctx iris.Context) {
userID := ctx.Params().Get("id") // Or convert directly using: .Values().GetInt/GetInt64 etc...
userID := ctx.Params().Get("id") // Or convert directly using: .Values().GetInt/GetUint64/GetInt64 etc...
// your own db fetch here instead of user :=...
user := User{Username: "username" + userID}
@ -489,9 +489,9 @@ Example code:
users := app.Party("/users", myAuthMiddlewareHandler)
// http://myhost.com/users/42/profile
users.Get("/{id:int}/profile", userProfileHandler)
users.Get("/{id:uint64}/profile", userProfileHandler)
// http://myhost.com/users/messages/1
users.Get("/inbox/{id:int}", userMessageHandler)
users.Get("/inbox/{id:number}", userMessageHandler)
Custom HTTP Errors
@ -548,12 +548,12 @@ Example code:
app.Get("/donate", donateHandler, donateFinishHandler)
// Pssst, don't forget dynamic-path example for more "magic"!
app.Get("/api/users/{userid:int min(1)}", func(ctx iris.Context) {
userID, err := ctx.Params().GetInt("userid")
app.Get("/api/users/{userid:uint64 min(1)}", func(ctx iris.Context) {
userID, err := ctx.Params().GetUint64("userid")
if err != nil {
ctx.Writef("error while trying to parse userid parameter," +
"this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.")
"this will never happen if :number is being used because if it's not integer it will fire Not Found automatically.")
ctx.StatusCode(iris.StatusBadRequest)
return
}
@ -622,8 +622,8 @@ Example code:
ctx.Writef("All users")
})
// http://v1.localhost:8080/api/users/42
usersAPI.Get("/{userid:int}", func(ctx iris.Context) {
ctx.Writef("user with id: %s", ctx.Params().Get("userid"))
usersAPI.Get("/{userid:uint64}", func(ctx iris.Context) {
ctx.Writef("user with id: %s", ctx.Params().GetUint64("userid"))
})
}
}
@ -711,21 +711,27 @@ Standard macro types for parameters:
string type
anything
+------------------------+
| {param:int} |
+------------------------+
+-------------------------------+
| {param:number} or {param:int} |
+-------------------------------+
int type
only numbers (0-9)
both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch)
+------------------------+
| {param:long} |
+------------------------+
+-------------------------------+
| {param:long} or {param:int64} |
+-------------------------------+
int64 type
only numbers (0-9)
-9223372036854775808 to 9223372036854775807
+------------------------+
| {param:boolean} |
| {param:uint64} |
+------------------------+
uint64 type
0 to 18446744073709551615
+---------------------------------+
| {param:bool} or {param:boolean} |
+---------------------------------+
bool type
only "1" or "t" or "T" or "TRUE" or "true" or "True"
or "0" or "f" or "F" or "FALSE" or "false" or "False"
@ -761,7 +767,7 @@ If a function not found on that type then the "string"'s types functions are bei
i.e:
{param:int min(3)}
{param:number min(3)}
Besides the fact that iris provides the basic types and some default "macro funcs"
@ -770,7 +776,7 @@ you are able to register your own too!.
Register a named path parameter function:
app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool {
app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool {
[...]
return true/false -> true means valid.
})
@ -792,12 +798,12 @@ Example Code:
ctx.Writef("Hello %s", ctx.Params().Get("name"))
}) // type is missing = {name:string}
// Let's register our first macro attached to int macro type.
// Let's register our first macro attached to number macro type.
// "min" = the function
// "minValue" = the argument of the function
// func(string) bool = the macro's path parameter evaluator, this executes in serve time when
// a user requests a path which contains the :int macro type with the min(...) macro parameter function.
app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool {
// a user requests a path which contains the number macro type with the min(...) macro parameter function.
app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool {
// do anything before serve here [...]
// at this case we don't need to do anything
return func(paramValue string) bool {
@ -812,21 +818,21 @@ Example Code:
// http://localhost:8080/profile/id>=1
// this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1
// macro parameter functions are optional of course.
app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) {
app.Get("/profile/{id:uint64 min(1)}", func(ctx iris.Context) {
// second parameter is the error but it will always nil because we use macros,
// the validaton already happened.
id, _ := ctx.Params().GetInt("id")
id, _ := ctx.Params().GetUint64("id")
ctx.Writef("Hello id: %d", id)
})
// to change the error code per route's macro evaluator:
app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id")
friendid, _ := ctx.Params().GetInt("friendid")
app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) {
id, _ := ctx.Params().GetUint64("id")
friendid, _ := ctx.Params().GetUint64("friendid")
ctx.Writef("Hello id: %d looking for friend id: ", id, friendid)
}) // this will throw e 504 error code instead of 404 if all route's macros not passed.
// http://localhost:8080/game/a-zA-Z/level/0-9
// http://localhost:8080/game/a-zA-Z/level/42
// remember, alphabetical is lowercase or uppercase letters only.
app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) {
ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level"))
@ -855,11 +861,6 @@ Example Code:
}
A path parameter name should contain only alphabetical letters, symbols, containing '_' and numbers are NOT allowed.
If route failed to be registered, the app will panic without any warnings
if you didn't catch the second return value(error) on .Handle/.Get....
Last, do not confuse ctx.Values() with ctx.Params().
Path parameter's values goes to ctx.Params() and context's local storage
that can be used to communicate between handlers and middleware(s) goes to

View File

@ -365,7 +365,8 @@ func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) {
}
func (c *testControllerRelPathFromFunc) Get() {}
func (c *testControllerRelPathFromFunc) GetBy(int64) {}
func (c *testControllerRelPathFromFunc) GetBy(uint64) {}
func (c *testControllerRelPathFromFunc) GetRatioBy(int64) {}
func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {}
func (c *testControllerRelPathFromFunc) GetLogin() {}
@ -388,8 +389,10 @@ func TestControllerRelPathFromFunc(t *testing.T) {
e.GET("/").Expect().Status(iris.StatusOK).
Body().Equal("GET:/")
e.GET("/42").Expect().Status(iris.StatusOK).
Body().Equal("GET:/42")
e.GET("/18446744073709551615").Expect().Status(iris.StatusOK).
Body().Equal("GET:/18446744073709551615")
e.GET("/ratio/-42").Expect().Status(iris.StatusOK).
Body().Equal("GET:/ratio/-42")
e.GET("/something/true").Expect().Status(iris.StatusOK).
Body().Equal("GET:/something/true")
e.GET("/something/false").Expect().Status(iris.StatusOK).

View File

@ -37,16 +37,21 @@ func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Valu
var fn interface{}
switch paramType {
case ast.ParamTypeInt:
case ast.ParamTypeNumber:
fn = func(ctx context.Context) int {
v, _ := ctx.Params().GetInt(paramName)
return v
}
case ast.ParamTypeLong:
case ast.ParamTypeInt64:
fn = func(ctx context.Context) int64 {
v, _ := ctx.Params().GetInt64(paramName)
return v
}
case ast.ParamTypeUint64:
fn = func(ctx context.Context) uint64 {
v, _ := ctx.Params().GetUint64(paramName)
return v
}
case ast.ParamTypeBoolean:
fn = func(ctx context.Context) bool {
v, _ := ctx.Params().GetBool(paramName)