From b019a281eb2748273df600446d3d8d7c9cdb2afb Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 23 Aug 2018 06:30:12 +0300 Subject: [PATCH 01/42] 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 --- README.md | 8 ++ _examples/README.md | 6 +- _examples/README_ZH.md | 6 +- _examples/overview/main.go | 2 +- _examples/routing/README.md | 67 +++++++------ _examples/routing/basic/main.go | 8 +- _examples/routing/dynamic-path/main.go | 56 ++++++----- _examples/routing/main.go | 30 +++--- _examples/routing/overview/main.go | 12 +-- _examples/subdomains/www/main.go | 4 +- context/route.go | 2 +- core/router/api_builder.go | 4 +- core/router/api_builder_benchmark_test.go | 2 +- core/router/macro.go | 99 ++++++++++++++++++- core/router/macro/interpreter/ast/ast.go | 69 ++++++++----- core/router/macro/interpreter/lexer/lexer.go | 2 +- .../macro/interpreter/lexer/lexer_test.go | 32 +++--- .../router/macro/interpreter/parser/parser.go | 4 +- .../macro/interpreter/parser/parser_test.go | 56 +++++++---- core/router/macro/interpreter/token/token.go | 4 +- core/router/macro/macro.go | 50 +++++++--- core/router/macro/macro_test.go | 88 ++++++++++++++--- core/router/party.go | 4 +- core/router/path_test.go | 14 +-- core/router/route.go | 4 +- doc.go | 69 ++++++------- mvc/controller_test.go | 9 +- mvc/param.go | 9 +- 28 files changed, 478 insertions(+), 242 deletions(-) diff --git a/README.md b/README.md index 1168a196..9e1c4f54 100644 --- a/README.md +++ b/README.md @@ -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") diff --git a/_examples/README.md b/_examples/README.md index 26530a19..4caf6d3d 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -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. diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md index 3958a5d4..a10ca6ad 100644 --- a/_examples/README_ZH.md +++ b/_examples/README_ZH.md @@ -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/" 开头 // 因为它不会与其他路线冲突。 diff --git a/_examples/overview/main.go b/_examples/overview/main.go index 29e52560..a834876e 100644 --- a/_examples/overview/main.go +++ b/_examples/overview/main.go @@ -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) } diff --git a/_examples/routing/README.md b/_examples/routing/README.md index 62cde33e..14c3f904 100644 --- a/_examples/routing/README.md +++ b/_examples/routing/README.md @@ -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 diff --git a/_examples/routing/basic/main.go b/_examples/routing/basic/main.go index d694b20c..9a62bdef 100644 --- a/_examples/routing/basic/main.go +++ b/_examples/routing/basic/main.go @@ -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")) }) } diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 5a3f0b57..de0f84d1 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -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")) } diff --git a/_examples/routing/main.go b/_examples/routing/main.go index d9af5f3d..9f450327 100644 --- a/_examples/routing/main.go +++ b/_examples/routing/main.go @@ -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") { diff --git a/_examples/routing/overview/main.go b/_examples/routing/overview/main.go index 65c15dc6..d2ff7178 100644 --- a/_examples/routing/overview/main.go +++ b/_examples/routing/overview/main.go @@ -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) }) diff --git a/_examples/subdomains/www/main.go b/_examples/subdomains/www/main.go index c41e343d..9ecfc969 100644 --- a/_examples/subdomains/www/main.go +++ b/_examples/subdomains/www/main.go @@ -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. diff --git a/context/route.go b/context/route.go index e632f11f..9cda8e96 100644 --- a/context/route.go +++ b/context/route.go @@ -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 diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 242fbadb..0022428a 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -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 diff --git a/core/router/api_builder_benchmark_test.go b/core/router/api_builder_benchmark_test.go index e16f798c..54469636 100644 --- a/core/router/api_builder_benchmark_test.go +++ b/core/router/api_builder_benchmark_test.go @@ -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() diff --git a/core/router/macro.go b/core/router/macro.go index ba6969ba..aa2d8e8a 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -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) { diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 23e17236..213d6b33 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -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: diff --git a/core/router/macro/interpreter/lexer/lexer.go b/core/router/macro/interpreter/lexer/lexer.go index 79f7111f..01646361 100644 --- a/core/router/macro/interpreter/lexer/lexer.go +++ b/core/router/macro/interpreter/lexer/lexer.go @@ -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] diff --git a/core/router/macro/interpreter/lexer/lexer_test.go b/core/router/macro/interpreter/lexer/lexer_test.go index dad919f3..e104e802 100644 --- a/core/router/macro/interpreter/lexer/lexer_test.go +++ b/core/router/macro/interpreter/lexer/lexer_test.go @@ -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) diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 8a352a73..84b15770 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -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 { diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index b1ce0ad8..2ccd5f2d 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -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, }, diff --git a/core/router/macro/interpreter/token/token.go b/core/router/macro/interpreter/token/token.go index 620ad641..f5cecbe9 100644 --- a/core/router/macro/interpreter/token/token.go +++ b/core/router/macro/interpreter/token/token.go @@ -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 diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index e4141c07..26af655f 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -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: diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index d412da29..834c21e4 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -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) // } diff --git a/core/router/party.go b/core/router/party.go index 8e8b56bd..745d038e 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -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 diff --git a/core/router/path_test.go b/core/router/path_test.go index 66ef283b..103e2af9 100644 --- a/core/router/path_test.go +++ b/core/router/path_test.go @@ -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 { diff --git a/core/router/route.go b/core/router/route.go index 6c4918f1..b581b659 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -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 diff --git a/doc.go b/doc.go index 00a781ae..3137bead 100644 --- a/doc.go +++ b/doc.go @@ -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 diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 475b79dc..70de2a72 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -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). diff --git a/mvc/param.go b/mvc/param.go index 8b680aee..e261ac05 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -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) From 18c23e7d1ed33db1edce6a84142bb66c51d8b79f Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 23 Aug 2018 06:33:54 +0300 Subject: [PATCH 02/42] hero support for the new uint64 Former-commit-id: 0cd72427302afd50a18c88443f871db393429bfa --- hero/param.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hero/param.go b/hero/param.go index 9a9f028f..278e507a 100644 --- a/hero/param.go +++ b/hero/param.go @@ -44,6 +44,13 @@ func resolveParam(currentParamIndex int, typ reflect.Type) (reflect.Value, bool) entry, _ := ctx.Params().GetEntryAt(currentParamIndex) v, _ := entry.Int64Default(0) + return v + } + case reflect.Uint64: + fn = func(ctx context.Context) uint64 { + entry, _ := ctx.Params().GetEntryAt(currentParamIndex) + v, _ := entry.Uint64Default(0) + return v } case reflect.Bool: From ef5f383227b45fc7c3a9ae1400ad5d242250b444 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 23 Aug 2018 17:29:39 +0300 Subject: [PATCH 03/42] support more than string and int at macro functions route path input arguments: int,uint8,uint16,uint32,int8,int32,int64,slice of strings and string Former-commit-id: d29c4fbe5926bac590151322a585f68b394ff72d --- _examples/routing/dynamic-path/main.go | 14 ++--- core/router/macro/interpreter/ast/ast.go | 24 +------- .../router/macro/interpreter/parser/parser.go | 29 ++++----- .../macro/interpreter/parser/parser_test.go | 16 ++--- core/router/macro/macro.go | 60 +++++++++++++++++-- core/router/macro/macro_test.go | 36 ++++++++++- 6 files changed, 119 insertions(+), 60 deletions(-) diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index de0f84d1..42ce2f0a 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -112,16 +112,16 @@ func main() { ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} - // Let's register our first macro attached to number macro type. + // Let's register our first macro attached to uint64 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 :number macro type with the min(...) macro parameter function. - app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { + // a user requests a path which contains the :uint64 macro parameter type with the min(...) macro parameter function. + app.Macros().Uint64.RegisterFunc("min", func(minValue uint64) func(string) bool { // do anything before serve here [...] // at this case we don't need to do anything return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) + n, err := strconv.ParseUint(paramValue, 10, 64) if err != nil { return false } @@ -129,10 +129,10 @@ func main() { } }) - // http://localhost:8080/profile/id>=1 + // http://localhost:8080/profile/id>=20 // 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:number min(1)}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(20)}", 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") @@ -140,7 +140,7 @@ func main() { }) // to change the error code per route's macro evaluator: - app.Get("/profile/{id:number min(1)}/friends/{friendid:number min(1) else 504}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 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) diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 213d6b33..4a4103a4 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -1,9 +1,7 @@ package ast import ( - "fmt" "reflect" - "strconv" ) // ParamType is a specific uint8 type @@ -206,24 +204,6 @@ type ParamStatement struct { ErrorCode int // 404 } -// ParamFuncArg represents a single parameter function's argument -type ParamFuncArg interface{} - -// ParamFuncArgToInt converts and returns -// any type of "a", to an integer. -func ParamFuncArgToInt(a ParamFuncArg) (int, error) { - switch a.(type) { - case int: - return a.(int), nil - case string: - return strconv.Atoi(a.(string)) - case int64: - return int(a.(int64)), nil - default: - return -1, fmt.Errorf("unexpected function argument type: %q", a) - } -} - // ParamFunc holds the name of a parameter's function // and its arguments (values) // A param func is declared with: @@ -233,6 +213,6 @@ func ParamFuncArgToInt(a ParamFuncArg) (int, error) { // the 1 and 5 are the two param function arguments // range(1,5) type ParamFunc struct { - Name string // range - Args []ParamFuncArg // [1,5] + Name string // range + Args []string // ["1","5"] } diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 84b15770..97920d5b 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -82,10 +82,16 @@ const ( DefaultParamType = ast.ParamTypeString ) -func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { - if t.Type == token.INT { - return ast.ParamFuncArgToInt(t.Literal) - } +// func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { +// if t.Type == token.INT { +// return ast.ParamFuncArgToInt(t.Literal) +// } +// // act all as strings here, because of int vs int64 vs uint64 and etc. +// return t.Literal, nil +// } + +func parseParamFuncArg(t token.Token) (a string, err error) { + // act all as strings here, because of int vs int64 vs uint64 and etc. return t.Literal, nil } @@ -143,25 +149,14 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) { argValTok := l.NextDynamicToken() // catch anything from "(" and forward, until ")", because we need to // be able to use regex expression as a macro type's func argument too. - argVal, err := parseParamFuncArg(argValTok) - if err != nil { - p.appendErr("[%d:%d] expected param func argument to be a string or number but got %s", t.Start, t.End, argValTok.Literal) - continue - } // fmt.Printf("argValTok: %#v\n", argValTok) // fmt.Printf("argVal: %#v\n", argVal) - lastParamFunc.Args = append(lastParamFunc.Args, argVal) + lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal) case token.COMMA: argValTok := l.NextToken() - argVal, err := parseParamFuncArg(argValTok) - if err != nil { - p.appendErr("[%d:%d] expected param func argument to be a string or number type but got %s", t.Start, t.End, argValTok.Literal) - continue - } - - lastParamFunc.Args = append(lastParamFunc.Args, argVal) + lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal) case token.RPAREN: stmt.Funcs = append(stmt.Funcs, lastParamFunc) lastParamFunc = ast.ParamFunc{} // reset diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index 2ccd5f2d..25fb3400 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -53,10 +53,10 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "min", - Args: []ast.ParamFuncArg{1}}, + Args: []string{"1"}}, { Name: "max", - Args: []ast.ParamFuncArg{5}}, + Args: []string{"5"}}, }, ErrorCode: 404, }}, // 0 @@ -69,7 +69,7 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "range", - Args: []ast.ParamFuncArg{1, 5}}, + Args: []string{"1", "5"}}, }, ErrorCode: 404, }}, // 1 @@ -81,7 +81,7 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "contains", - Args: []ast.ParamFuncArg{"."}}, + Args: []string{"."}}, }, ErrorCode: 404, }}, // 2 @@ -189,10 +189,10 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "min", - Args: []ast.ParamFuncArg{1}}, + Args: []string{"1"}}, { Name: "max", - Args: []ast.ParamFuncArg{5}}, + Args: []string{"5"}}, }, ErrorCode: 404, }, @@ -205,7 +205,7 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "range", - Args: []ast.ParamFuncArg{1, 5}}, + Args: []string{"1", "5"}}, }, ErrorCode: 404, }, @@ -218,7 +218,7 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "contains", - Args: []ast.ParamFuncArg{"."}}, + Args: []string{"."}}, }, ErrorCode: 404, }, diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 26af655f..23a45d14 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -5,6 +5,7 @@ import ( "reflect" "regexp" "strconv" + "strings" "unicode" "github.com/kataras/iris/core/router/macro/interpreter/ast" @@ -95,7 +96,7 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { numFields := typFn.NumIn() - return func(args []ast.ParamFuncArg) EvaluatorFunc { + return func(args []string) EvaluatorFunc { if len(args) != numFields { // no variadics support, for now. panic("args should be the same len as numFields") @@ -105,11 +106,60 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { field := typFn.In(i) arg := args[i] - if field.Kind() != reflect.TypeOf(arg).Kind() { - panic("fields should have the same type") + // try to convert the string literal as we get it from the parser. + var ( + v interface{} + err error + ) + + // try to get the value based on the expected type. + switch field.Kind() { + case reflect.Int: + v, err = strconv.Atoi(arg) + case reflect.Int8: + v, err = strconv.ParseInt(arg, 10, 8) + case reflect.Int16: + v, err = strconv.ParseInt(arg, 10, 16) + case reflect.Int32: + v, err = strconv.ParseInt(arg, 10, 32) + case reflect.Int64: + v, err = strconv.ParseInt(arg, 10, 64) + case reflect.Uint8: + v, err = strconv.ParseUint(arg, 10, 8) + case reflect.Uint16: + v, err = strconv.ParseUint(arg, 10, 16) + case reflect.Uint32: + v, err = strconv.ParseUint(arg, 10, 32) + case reflect.Uint64: + v, err = strconv.ParseUint(arg, 10, 64) + case reflect.Float32: + v, err = strconv.ParseFloat(arg, 32) + case reflect.Float64: + v, err = strconv.ParseFloat(arg, 64) + case reflect.Bool: + v, err = strconv.ParseBool(arg) + case reflect.Slice: + if len(arg) > 1 { + if arg[0] == '[' && arg[len(arg)-1] == ']' { + // it is a single argument but as slice. + v = strings.Split(arg[1:len(arg)-1], ",") // only string slices. + } + } + + default: + v = arg } - argValues = append(argValues, reflect.ValueOf(arg)) + if err != nil { + panic(fmt.Sprintf("on field index: %d: %v", i, err)) + } + + argValue := reflect.ValueOf(v) + if expected, got := field.Kind(), argValue.Kind(); expected != got { + panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) + } + + argValues = append(argValues, argValue) } evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface() @@ -149,7 +199,7 @@ type ( // and returns an EvaluatorFunc, its job // is to make the macros to be registered // by user at the most generic possible way. - ParamEvaluatorBuilder func([]ast.ParamFuncArg) EvaluatorFunc + ParamEvaluatorBuilder func([]string) EvaluatorFunc // ParamFunc represents the parsed // parameter function, it holds diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 834c21e4..beab57f1 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -16,7 +16,7 @@ func TestGoodParamFunc(t *testing.T) { } } - good2 := func(min int, max int) func(string) bool { + good2 := func(min uint64, max uint64) func(string) bool { return func(paramValue string) bool { return true } @@ -244,3 +244,37 @@ func TestPathEvaluatorRaw(t *testing.T) { // testEvaluatorRaw(t, m.String, p.Src, false, 0) // } + +func TestConvertBuilderFunc(t *testing.T) { + fn := func(min uint64, slice []string) func(string) bool { + return func(paramValue string) bool { + if expected, got := "ok", paramValue; expected != got { + t.Fatalf("paramValue is not the expected one: %s vs %s", expected, got) + } + + if expected, got := uint64(1), min; expected != got { + t.Fatalf("min argument is not the expected one: %d vs %d", expected, got) + } + + if expected, got := []string{"name1", "name2"}, slice; len(expected) == len(got) { + if expected, got := "name1", slice[0]; expected != got { + t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 0, expected, got) + } + + if expected, got := "name2", slice[1]; expected != got { + t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 1, expected, got) + } + } else { + t.Fatalf("slice argument is not the expected one, the length is difference: %d vs %d", len(expected), len(got)) + } + + return true + } + } + + evalFunc := convertBuilderFunc(fn) + + if !evalFunc([]string{"1", "[name1,name2]"})("ok") { + t.Fatalf("failed, it should fail already") + } +} From cbd8fe95ac03d552878219f052f15509e08fa80f Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 24 Aug 2018 00:56:54 +0300 Subject: [PATCH 04/42] add uint8 parameter type, and mvc and hero - this commit may be a point of tutorial on how to add a completely new type from scratch to the hero for future contributors Former-commit-id: dc7a7e6c97ef0c644a22e92072e4bdb98ae10582 --- _examples/routing/README.md | 6 +++ _examples/routing/dynamic-path/main.go | 13 ++++++ context/context.go | 9 +++- core/memstore/memstore.go | 53 ++++++++++++++++++++++++ core/router/macro.go | 46 ++++++++++++++++++++ core/router/macro/interpreter/ast/ast.go | 12 ++++++ core/router/macro/macro.go | 6 +++ core/router/macro/macro_test.go | 31 ++++++++++++++ doc.go | 6 +++ hero/param.go | 7 ++++ mvc/controller_test.go | 10 +++-- mvc/param.go | 5 +++ 12 files changed, 200 insertions(+), 4 deletions(-) diff --git a/_examples/routing/README.md b/_examples/routing/README.md index 14c3f904..40074682 100644 --- a/_examples/routing/README.md +++ b/_examples/routing/README.md @@ -166,6 +166,12 @@ both positive and negative numbers, any number of digits (ctx.Params().GetInt wi int64 type -9223372036854775808 to 9223372036854775807 ++------------------------+ +| {param:uint8} | ++------------------------+ +uint8 type +0 to 255 + +------------------------+ | {param:uint64} | +------------------------+ diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 42ce2f0a..462634c7 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -48,6 +48,13 @@ func main() { // -9223372036854775808 to 9223372036854775807 // // +------------------------+ + // | {param:uint8} | + // +------------------------+ + // uint8 type + // 0 to 255 + // + // + // +------------------------+ // | {param:uint64} | // +------------------------+ // uint64 type @@ -146,6 +153,12 @@ func main() { 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. + // :uint8 0 to 255. + app.Get("/ages/{age:uint8 else 400}", func(ctx iris.Context) { + age, _ := ctx.Params().GetUint8("age") + ctx.Writef("age selected: %d", age) + }) + // Another example using a custom regexp and any custom logic. latLonExpr := "^-?[0-9]{1,3}(?:\\.[0-9]{1,10})?$" latLonRegex, err := regexp.Compile(latLonExpr) diff --git a/context/context.go b/context/context.go index 46397a10..b558fe9a 100644 --- a/context/context.go +++ b/context/context.go @@ -162,7 +162,14 @@ func (r RequestParams) GetFloat64(key string) (float64, error) { return r.store.GetFloat64(key) } -// GetUint64 returns the path paramete's value as uint64, based on its key. +// GetUint8 returns the path parameter's value as uint8, based on its key. +// It checks for all available types of int, including int, string. +// It will return 0 and a non-nil error if parameter wasn't found. +func (r RequestParams) GetUint8(key string) (uint8, error) { + return r.store.GetUint8(key) +} + +// GetUint64 returns the path parameter's value as uint64, based on its key. // It checks for all available types of int, including int, uint64, int64, strings etc. // It will return 0 and a non-nil error if parameter wasn't found. func (r RequestParams) GetUint64(key string) (uint64, error) { diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 5b6bbdf2..48986514 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -197,6 +197,39 @@ func (e Entry) Float32Default(key string, def float32) (float32, error) { return def, errFindParse.Format("float32", e.Key) } +// Uint8Default returns the entry's value as uint8. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint8Default(def uint8) (uint8, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint8", e.Key) + } + + if vuint8, ok := v.(uint8); ok { + return vuint8, nil + } + + if vint, ok := v.(int); ok { + if vint < 0 || vint > 255 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vint), nil + } + + if vstring, sok := v.(string); sok { + vuint64, err := strconv.ParseUint(vstring, 10, 8) + if err != nil { + return def, err + } + if vuint64 > 255 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vuint64), nil + } + + return def, errFindParse.Format("uint8", e.Key) +} + // Uint64Default returns the entry's value as uint64. // If not found returns "def" and a non-nil error. func (e Entry) Uint64Default(def uint64) (uint64, error) { @@ -449,6 +482,26 @@ func (r *Store) GetIntDefault(key string, def int) int { return def } +// GetUint8 returns the entry's value as uint8, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint8(key string) (uint8, error) { + v := r.GetEntry(key) + if v == nil { + return 0, errFindParse.Format("uint8", key) + } + return v.Uint8Default(0) +} + +// GetUint8Default returns the entry's value as uint8, based on its key. +// If not found returns "def". +func (r *Store) GetUint8Default(key string, def uint8) uint8 { + if v, err := r.GetUint8(key); err == nil { + return v + } + + return def +} + // GetUint64 returns the entry's value as uint64, based on its key. // If not found returns 0 and a non-nil error. func (r *Store) GetUint64(key string) (uint64, error) { diff --git a/core/router/macro.go b/core/router/macro.go index aa2d8e8a..e46a07c7 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -37,6 +37,7 @@ func registerBuiltinsMacroFuncs(out *macro.Map) { registerStringMacroFuncs(out.String) registerNumberMacroFuncs(out.Number) registerInt64MacroFuncs(out.Int64) + registerUint8MacroFuncs(out.Uint8) registerUint64MacroFuncs(out.Uint64) registerAlphabeticalMacroFuncs(out.Alphabetical) registerFileMacroFuncs(out.File) @@ -176,6 +177,51 @@ func registerInt64MacroFuncs(out *macro.Macro) { }) } +// Uint8 +// 0 to 255. +func registerUint8MacroFuncs(out *macro.Macro) { + // checks if the param value's uint8 representation is + // bigger or equal than 'min' + out.RegisterFunc("min", func(min uint8) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + + return uint8(n) >= min + } + }) + + // checks if the param value's uint8 representation is + // smaller or equal than 'max' + out.RegisterFunc("max", func(max uint8) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + return uint8(n) <= max + } + }) + + // checks if the param value's uint8 representation is + // between min and max, including 'min' and 'max' + out.RegisterFunc("range", func(min, max uint8) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + + if v := uint8(n); v < min || v > max { + return false + } + return true + } + }) +} + // Uint64 // 0 to 18446744073709551615. func registerUint64MacroFuncs(out *macro.Macro) { diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 4a4103a4..42995690 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -26,6 +26,10 @@ const ( // Allows only -9223372036854775808 to 9223372036854775807. // Declaration: /mypath/{myparam:int64} or {myparam:long} ParamTypeInt64 + // ParamTypeUint8 a number type. + // Allows only 0 to 255. + // Declaration: /mypath/{myparam:uint8} + ParamTypeUint8 // ParamTypeUint64 a number type. // Allows only 0 to 18446744073709551615. // Declaration: /mypath/{myparam:uint64} @@ -88,6 +92,8 @@ func (pt ParamType) Kind() reflect.Kind { return reflect.Int case ParamTypeInt64: return reflect.Int64 + case ParamTypeUint8: + return reflect.Uint8 case ParamTypeUint64: return reflect.Uint64 case ParamTypeBoolean: @@ -106,6 +112,8 @@ func ValidKind(k reflect.Kind) bool { fallthrough case reflect.Int64: fallthrough + case reflect.Uint8: + fallthrough case reflect.Uint64: fallthrough case reflect.Bool: @@ -128,6 +136,7 @@ var paramTypes = map[string]ParamType{ "int": ParamTypeNumber, // same as number. "long": ParamTypeInt64, "int64": ParamTypeInt64, // same as long. + "uint8": ParamTypeUint8, "uint64": ParamTypeUint64, "boolean": ParamTypeBoolean, @@ -149,6 +158,7 @@ var paramTypes = map[string]ParamType{ // "string" // "number" or "int" // "long" or "int64" +// "uint8" // "uint64" // "boolean" or "bool" // "alphabetical" @@ -179,6 +189,8 @@ func LookupParamTypeFromStd(goType string) ParamType { return ParamTypeNumber case "int64": return ParamTypeInt64 + case "uint8": + return ParamTypeUint8 case "uint64": return ParamTypeUint64 case "bool": diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 23a45d14..7ea4630f 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -271,6 +271,9 @@ type Map struct { // int64 as int64 type // -9223372036854775808 to 9223372036854775807. Int64 *Macro + // uint8 as uint8 type + // 0 to 255. + Uint8 *Macro // uint64 as uint64 type // 0 to 18446744073709551615. Uint64 *Macro @@ -313,6 +316,7 @@ func NewMap() *Map { // 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]$")), + Uint8: newMacro(MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")), Uint64: newMacro(func(paramValue string) bool { if !simpleNumberEvalutator(paramValue) { return false @@ -346,6 +350,8 @@ func (m *Map) Lookup(typ ast.ParamType) *Macro { return m.Number case ast.ParamTypeInt64: return m.Int64 + case ast.ParamTypeUint8: + return m.Uint8 case ast.ParamTypeUint64: return m.Uint64 case ast.ParamTypeBoolean: diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index beab57f1..83092939 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -142,6 +142,37 @@ func TestInt64EvaluatorRaw(t *testing.T) { } } +func TestUint8EvaluatorRaw(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 + {false, "18446744073709551615"}, // 10 + {false, "9223372036854775807"}, // 11 + {false, "021"}, // 12 - no leading zeroes are allowed. + {false, "300"}, // 13 + {true, "0"}, // 14 + {true, "255"}, // 15 + {true, "21"}, // 16 + } + + for i, tt := range tests { + testEvaluatorRaw(t, f.Uint8, tt.input, tt.pass, i) + } +} + func TestUint64EvaluatorRaw(t *testing.T) { f := NewMap() diff --git a/doc.go b/doc.go index 3137bead..dc5f93b1 100644 --- a/doc.go +++ b/doc.go @@ -723,6 +723,12 @@ Standard macro types for parameters: int64 type -9223372036854775808 to 9223372036854775807 + +------------------------+ + | {param:uint8} | + +------------------------+ + uint8 type + 0 to 255 + +------------------------+ | {param:uint64} | +------------------------+ diff --git a/hero/param.go b/hero/param.go index 278e507a..4ea62fc2 100644 --- a/hero/param.go +++ b/hero/param.go @@ -44,6 +44,13 @@ func resolveParam(currentParamIndex int, typ reflect.Type) (reflect.Value, bool) entry, _ := ctx.Params().GetEntryAt(currentParamIndex) v, _ := entry.Int64Default(0) + return v + } + case reflect.Uint8: + fn = func(ctx context.Context) uint8 { + entry, _ := ctx.Params().GetEntryAt(currentParamIndex) + v, _ := entry.Uint8Default(0) + return v } case reflect.Uint64: diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 70de2a72..525c2103 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -366,7 +366,8 @@ func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) { func (c *testControllerRelPathFromFunc) Get() {} func (c *testControllerRelPathFromFunc) GetBy(uint64) {} -func (c *testControllerRelPathFromFunc) GetRatioBy(int64) {} +func (c *testControllerRelPathFromFunc) GetUint8RatioBy(uint8) {} +func (c *testControllerRelPathFromFunc) GetUint64RatioBy(int64) {} func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {} func (c *testControllerRelPathFromFunc) GetLogin() {} @@ -391,8 +392,11 @@ func TestControllerRelPathFromFunc(t *testing.T) { 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("/uint8/ratio/255").Expect().Status(iris.StatusOK). + Body().Equal("GET:/uint8/ratio/255") + e.GET("/uint8/ratio/256").Expect().Status(iris.StatusNotFound) + e.GET("/uint64/ratio/-42").Expect().Status(iris.StatusOK). + Body().Equal("GET:/uint64/ratio/-42") e.GET("/something/true").Expect().Status(iris.StatusOK). Body().Equal("GET:/something/true") e.GET("/something/false").Expect().Status(iris.StatusOK). diff --git a/mvc/param.go b/mvc/param.go index e261ac05..c2f22990 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -47,6 +47,11 @@ func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Valu v, _ := ctx.Params().GetInt64(paramName) return v } + case ast.ParamTypeUint8: + fn = func(ctx context.Context) uint8 { + v, _ := ctx.Params().GetUint8(paramName) + return v + } case ast.ParamTypeUint64: fn = func(ctx context.Context) uint64 { v, _ := ctx.Params().GetUint64(paramName) From b3bc30c5aff0053f2229fb37839756a8383f65b1 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 24 Aug 2018 14:41:53 +0300 Subject: [PATCH 05/42] add a table on the README for the param types, macro funcs and do it yourself section Former-commit-id: eca9418779371c014d3bf3bca88430055841da4f --- README.md | 101 +++++++++++++++++++++++++ _examples/routing/dynamic-path/main.go | 46 +++++++++-- mvc/controller_test.go | 6 +- 3 files changed, 145 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9e1c4f54..eb800cee 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,107 @@ func main() { ### Parameters in path +| Param Type | Go Type | Validation | Retrieve Helper | +| -----------------|------|-------------|------| +| `:string` | string | anything | `Params().Get` | +| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | `Params().GetInt/Int64`...| +| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | +| `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | +| `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | +| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | +| `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` | +| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | `Params().Get` | +| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | `Params().Get` | + +**Usage**: + +```go +app.Get("/users/{id:uint64}", func(ctx iris.Context){ + id, _ := ctx.Params().GetUint64("id") + // [...] +}) +``` + +| Built'n Func | Param Types | +| -----------|---------------| +| `regexp`(expr string) | :string | +| `prefix`(prefix string) | :string | +| `suffix`(suffix string) | :string | +| `contains`(s string) | :string | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 | + +**Usage**: + +```go +app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ + name := ctx.Params().Get("name") + // len(name) <=255 otherwise this route will fire 404 Not Found + // and this handler will not be executed at all. +}) +``` + +**Do It Yourself**: + +The `RegisterFunc` can accept any function that returns a `func(paramValue string) bool`. +Or just a `func(string) bool`. +If the validation fails then it will fire `404` or whatever status code the `else` keyword has. + +```go +latLonExpr := "^-?[0-9]{1,3}(?:\\.[0-9]{1,10})?$" +latLonRegex, _ := regexp.Compile(latLonExpr) + +// Register your custom argument-less macro function to the :string param type. +// MatchString is a type of func(string) bool, so we use it as it is. +app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) + +app.Get("/coordinates/{lat:string coordinate() else 400}/{lon:string coordinate() else 400}", func(ctx iris.Context) { + ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) +}) +``` + +Register your custom macro function which accepts two int arguments. + +```go + +app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { + return func(paramValue string) bool { + return len(paramValue) >= minLength && len(paramValue) <= maxLength + } +}) + +app.Get("/limitchar/{name:string range(1,200)}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be between 1 and 200 characters length + otherwise this handler will not be executed`, name) +}) +``` + +Register your custom macro function which accepts a slice of strings `[...,...]`. + +```go +app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { + return func(paramValue string) bool { + for _, validName := range validNames { + if validName == paramValue { + return true + } + } + + return false + } +}) + +app.Get("/static_validation/{name:string has([kataras,gerasimos,maropoulos]}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be "kataras" or "gerasimos" or "maropoulos" + otherwise this handler will not be executed`, name) +}) +``` + +**Example Code**: + ```go func main() { app := iris.Default() diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 462634c7..926b4af1 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -159,17 +159,17 @@ func main() { ctx.Writef("age selected: %d", age) }) - // Another example using a custom regexp and any custom logic. + // Another example using a custom regexp or any custom logic. + + // Register your custom argument-less macro function to the :string param type. latLonExpr := "^-?[0-9]{1,3}(?:\\.[0-9]{1,10})?$" latLonRegex, err := regexp.Compile(latLonExpr) if err != nil { panic(err) } - app.Macros().String.RegisterFunc("coordinate", func() func(paramName string) (ok bool) { - // MatchString is a type of func(string) bool, so we can return that as it's. - return latLonRegex.MatchString - }) + // MatchString is a type of func(string) bool, so we use it as it is. + app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -177,6 +177,42 @@ func main() { // + // Another one is by using a custom body. + app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { + return func(paramValue string) bool { + return len(paramValue) >= minLength && len(paramValue) <= maxLength + } + }) + + app.Get("/limitchar/{name:string range(1,200)}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be between 1 and 200 characters length + otherwise this handler will not be executed`, name) + }) + + // + + // Register your custom macro function which accepts a slice of strings `[...,...]`. + app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { + return func(paramValue string) bool { + for _, validName := range validNames { + if validName == paramValue { + return true + } + } + + return false + } + }) + + app.Get("/static_validation/{name:string has([kataras,gerasimos,maropoulos]}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be "kataras" or "gerasimos" or "maropoulos" + otherwise this handler will not be executed`, name) + }) + + // + // http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. app.Get("/game/{name:alphabetical}/level/{level:number}", func(ctx iris.Context) { diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 525c2103..f367d0ea 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -367,7 +367,7 @@ func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) { func (c *testControllerRelPathFromFunc) Get() {} func (c *testControllerRelPathFromFunc) GetBy(uint64) {} func (c *testControllerRelPathFromFunc) GetUint8RatioBy(uint8) {} -func (c *testControllerRelPathFromFunc) GetUint64RatioBy(int64) {} +func (c *testControllerRelPathFromFunc) GetInt64RatioBy(int64) {} func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {} func (c *testControllerRelPathFromFunc) GetLogin() {} @@ -395,8 +395,8 @@ func TestControllerRelPathFromFunc(t *testing.T) { e.GET("/uint8/ratio/255").Expect().Status(iris.StatusOK). Body().Equal("GET:/uint8/ratio/255") e.GET("/uint8/ratio/256").Expect().Status(iris.StatusNotFound) - e.GET("/uint64/ratio/-42").Expect().Status(iris.StatusOK). - Body().Equal("GET:/uint64/ratio/-42") + e.GET("/int64/ratio/-42").Expect().Status(iris.StatusOK). + Body().Equal("GET:/int64/ratio/-42") e.GET("/something/true").Expect().Status(iris.StatusOK). Body().Equal("GET:/something/true") e.GET("/something/false").Expect().Status(iris.StatusOK). From 7b94a4666ce1f704fcdf59e3452345a261ab673a Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 24 Aug 2018 14:43:19 +0300 Subject: [PATCH 06/42] format the prev Former-commit-id: 4d203295b1daa1091a45de8ce683af0e6b47365b --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb800cee..e865eb43 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ latLonRegex, _ := regexp.Compile(latLonExpr) // MatchString is a type of func(string) bool, so we use it as it is. app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) -app.Get("/coordinates/{lat:string coordinate() else 400}/{lon:string coordinate() else 400}", func(ctx iris.Context) { +app.Get("/coordinates/{lat:string coordinate()}/{lon:string coordinate()}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) }) ``` @@ -205,7 +205,7 @@ app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(st } }) -app.Get("/limitchar/{name:string range(1,200)}", func(ctx iris.Context) { +app.Get("/limitchar/{name:string range(1,200) else 400}", func(ctx iris.Context) { name := ctx.Params().Get("name") ctx.Writef(`Hello %s | the name should be between 1 and 200 characters length otherwise this handler will not be executed`, name) From 8d33c7a267951857a93eeaa614d3d98b39cede0f Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 24 Aug 2018 14:47:56 +0300 Subject: [PATCH 07/42] add the di section after the parameters in path section Former-commit-id: 423060f77629a7d955e80258b681d62a791b7411 --- README.md | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index e865eb43..b758c7c7 100644 --- a/README.md +++ b/README.md @@ -87,30 +87,6 @@ func main() { $ go run example.go ``` -## Dependency Injection - -The package [hero](hero) contains features for binding any object or functions that `handlers` can use, these are called dependencies. - -With Iris you get truly safe bindings thanks to the [hero](_examples/hero) [package](hero). It is blazing-fast, near to raw handlers performance because Iris calculates everything before even server goes online! - -Below you will see some screenshots I prepared for you in order to be easier to understand: - -### 1. Path Parameters - Built'n Dependencies - -![](https://github.com/kataras/explore/raw/master/iris/hero/hero-1-monokai.png) - -### 2. Services - Static Dependencies - -![](https://github.com/kataras/explore/raw/master/iris/hero/hero-2-monokai.png) - -### 3. Per-Request - Dynamic Dependencies - -![](https://github.com/kataras/explore/raw/master/iris/hero/hero-3-monokai.png) - -`hero funcs` are very easy to understand and when you start using them **you never go back**. - -> With Iris you also get real and [blazing-fast](_benchmarks) [MVC support](_examples/mvc) which uses "hero" under the hoods. - ## API Examples ### Using Get, Post, Put, Patch, Delete and Options @@ -270,6 +246,31 @@ func main() { > Learn more about path parameter's types by navigating [here](_examples/routing/dynamic-path/main.go#L31). + +### Dependency Injection + +The package [hero](hero) contains features for binding any object or functions that `handlers` can use, these are called dependencies. + +With Iris you get truly safe bindings thanks to the [hero](_examples/hero) [package](hero). It is blazing-fast, near to raw handlers performance because Iris calculates everything before even server goes online! + +Below you will see some screenshots I prepared for you in order to be easier to understand: + +#### 1. Path Parameters - Built'n Dependencies + +![](https://github.com/kataras/explore/raw/master/iris/hero/hero-1-monokai.png) + +#### 2. Services - Static Dependencies + +![](https://github.com/kataras/explore/raw/master/iris/hero/hero-2-monokai.png) + +#### 3. Per-Request - Dynamic Dependencies + +![](https://github.com/kataras/explore/raw/master/iris/hero/hero-3-monokai.png) + +`hero funcs` are very easy to understand and when you start using them **you never go back**. + +> With Iris you also get real and [blazing-fast](_benchmarks) [MVC support](_examples/mvc) which uses "hero" under the hoods. + ### Querystring parameters ```go From 6cf48df5c0e8293593709d9b6b8252b904637195 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 25 Aug 2018 19:23:52 +0300 Subject: [PATCH 08/42] add the StatusMisdirectedRequest (421) added in go 1.11 net/http package as well Former-commit-id: 75820b859905cab2a5b62aad1577f704a66082b2 --- iris.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/iris.go b/iris.go index f84accea..e9929385 100644 --- a/iris.go +++ b/iris.go @@ -58,13 +58,13 @@ const ( StatusAlreadyReported = 208 // RFC 5842, 7.1 StatusIMUsed = 226 // RFC 3229, 10.4.1 - StatusMultipleChoices = 300 // RFC 7231, 6.4.1 - StatusMovedPermanently = 301 // RFC 7231, 6.4.2 - StatusFound = 302 // RFC 7231, 6.4.3 - StatusSeeOther = 303 // RFC 7231, 6.4.4 - StatusNotModified = 304 // RFC 7232, 4.1 - StatusUseProxy = 305 // RFC 7231, 6.4.5 - _ = 306 // RFC 7231, 6.4.6 (Unused) + StatusMultipleChoices = 300 // RFC 7231, 6.4.1 + StatusMovedPermanently = 301 // RFC 7231, 6.4.2 + StatusFound = 302 // RFC 7231, 6.4.3 + StatusSeeOther = 303 // RFC 7231, 6.4.4 + StatusNotModified = 304 // RFC 7232, 4.1 + StatusUseProxy = 305 // RFC 7231, 6.4.5 + StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 StatusPermanentRedirect = 308 // RFC 7538, 3 @@ -87,6 +87,7 @@ const ( StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4 StatusExpectationFailed = 417 // RFC 7231, 6.5.14 StatusTeapot = 418 // RFC 7168, 2.3.3 + StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2 StatusUnprocessableEntity = 422 // RFC 4918, 11.2 StatusLocked = 423 // RFC 4918, 11.3 StatusFailedDependency = 424 // RFC 4918, 11.4 From f365be3c62c1f04d8362af2babb6aa8c22b3a74d Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 31 Aug 2018 02:09:48 +0300 Subject: [PATCH 09/42] remove 'WithoutVersionChecker', update and test the new versions of some of the dependencies, add a history entry with unknown release date Former-commit-id: 399db6aac44d3b336648d6d61842f4d7a0266842 --- Gopkg.lock | 18 +++++ Gopkg.toml | 14 +++- HISTORY.md | 65 ++++++++++++++++ README.md | 1 - _benchmarks/iris-mvc-templates/main.go | 2 +- _benchmarks/iris-mvc/main.go | 2 +- _benchmarks/iris-sessions/main.go | 4 +- _benchmarks/iris/main.go | 2 +- _examples/hero/overview/main.go | 2 - _examples/hero/sessions/main.go | 1 - .../http_request/extract-referer/main.go | 2 +- _examples/mvc/login/main.go | 2 - _examples/mvc/overview/main.go | 2 - _examples/mvc/session-controller/main.go | 2 +- _examples/overview/main.go | 2 +- _examples/tutorial/url-shortener/main.go | 2 +- _examples/tutorial/url-shortener/store.go | 20 ++--- _examples/tutorial/vuejs-todo-mvc/README.md | 2 +- .../tutorial/vuejs-todo-mvc/src/web/main.go | 2 +- .../{hello_go11beta3.go => hello_go111.go} | 4 +- _examples/webassembly/basic/main.go | 2 +- configuration.go | 18 ----- configuration_test.go | 13 +--- core/host/proxy_test.go | 2 +- core/maintenance/maintenance.go | 6 -- core/maintenance/version.go | 78 ------------------- core/maintenance/version/fetch.go | 59 -------------- core/maintenance/version/version.go | 64 --------------- core/router/macro/macro_test.go | 2 +- httptest/httptest.go | 3 +- iris.go | 7 +- sessions/sessiondb/boltdb/database.go | 38 ++++----- 32 files changed, 143 insertions(+), 300 deletions(-) rename _examples/webassembly/basic/client/{hello_go11beta3.go => hello_go111.go} (70%) delete mode 100644 core/maintenance/maintenance.go delete mode 100644 core/maintenance/version.go delete mode 100644 core/maintenance/version/fetch.go delete mode 100644 core/maintenance/version/version.go diff --git a/Gopkg.lock b/Gopkg.lock index 24bd3afc..8afa3485 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -277,6 +277,24 @@ packages = ["."] revision = "aad8439df3bf67adb025382ee2e5f46a72fc9456" +[[projects]] + branch = "master" + name = "github.com/dgraph-io/badger" + packages = ["."] + revision = "391b6d3b93e6014fe8c2971fcc0c1266e47dbbd9" + +[[projects]] + branch = "master" + name = "github.com/etcd-io/bbolt" + packages = ["."] + revision = "acbc2c426a444a65e0cbfdcbb3573857bf18b465" + +[[projects]] + branch = "master" + name = "github.com/gomodule/redigo" + packages = ["internal","redis"] + revision = "2cd21d9966bf7ff9ae091419744f0b3fb0fecace" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index a0baeae3..eb9c030d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -80,4 +80,16 @@ [[constraint]] branch = "master" - name = "github.com/Shopify/goreferrer" \ No newline at end of file + name = "github.com/Shopify/goreferrer" + +[[constraint]] + name = "github.com/dgraph-io/badger" + version = "1.5.3" + +[[constraint]] + name = "github.com/etcd-io/bbolt" + version = "1.3.1-etcd.7" + +[[constraint]] + branch = "master" + name = "github.com/gomodule/redigo" diff --git a/HISTORY.md b/HISTORY.md index b042fd7c..2a24d778 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,6 +17,71 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you. +# Whenever | v11.0.0 + +## Breaking changes + +- Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` +- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:number` if possible (although it will stay for backwards-compatibility) + +## Routing + +- `:number` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers and without digits limitation +- Add `:int64` parameter type and `ctx.Params().GetInt64` +- Add `:uint8` parameter type and `ctx.Params().GetUint8` +- Add `:uint64` parameter type and `ctx.Params().GetUint64` +- `:bool` parameter type as an alias for `:boolean` + +Here is the full list of the built'n parameter types that we support now, including their validations/path segment rules. + +| Param Type | Go Type | Validation | +| -----------|---------|------------| +| `:string` | string | anything | +| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | +| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | +| `:uint8` | uint8 | 0 to 255 | +| `:uint64` | uint64 | 0 to 18446744073709551615 | +| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | +| `:alphabetical` | string | lowercase or uppercase letters | +| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | +| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | + +**Usage**: + +```go +app.Get("/users/{id:uint64}", func(ctx iris.Context){ + id, _ := ctx.Params().GetUint64("id") + // [...] +}) +``` + +| Built'n Func | Param Types | +| -----------|---------------| +| `regexp`(expr string) | :string | +| `prefix`(prefix string) | :string | +| `suffix`(suffix string) | :string | +| `contains`(s string) | :string | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 | + +**Usage**: + +```go +app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ + name := ctx.Params().Get("name") + // len(name) <=255 otherwise this route will fire 404 Not Found + // and this handler will not be executed at all. +}) +``` + +## Vendoring + +- Rename the vendor `sessions/sessiondb/vendor/...bbolt` from `coreos/bbolt` to `etcd-io/bbolt` and update to v1.3.1, based on [that](https://github.com/etcd-io/bbolt/releases/tag/v1.3.1-etcd.7) +- Update the vendor `sessions/sessiondb/vendor/...badger` to v1.5.3 + +> More to come, maybe it will be removed eventually. + # Sat, 11 August 2018 | v10.7.0 I am overjoyed to announce stage 1 of the the Iris Web framework **10.7 stable release is now available**. diff --git a/README.md b/README.md index b758c7c7..b8b32f5e 100644 --- a/README.md +++ b/README.md @@ -588,7 +588,6 @@ func main() { app.Run( iris.Addr(":8080"), iris.WithoutBanner, - iris.WithoutVersionChecker, iris.WithoutServerError(iris.ErrServerClosed), ) } diff --git a/_benchmarks/iris-mvc-templates/main.go b/_benchmarks/iris-mvc-templates/main.go index 5601ad14..cfd46558 100644 --- a/_benchmarks/iris-mvc-templates/main.go +++ b/_benchmarks/iris-mvc-templates/main.go @@ -24,7 +24,7 @@ func main() { mvc.New(app).Handle(new(controllers.HomeController)) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } type err struct { diff --git a/_benchmarks/iris-mvc/main.go b/_benchmarks/iris-mvc/main.go index 6ea755de..4b0e0a18 100644 --- a/_benchmarks/iris-mvc/main.go +++ b/_benchmarks/iris-mvc/main.go @@ -16,7 +16,7 @@ func main() { mvc.New(app.Party("/api/values/{id}")). Handle(new(controllers.ValuesController)) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } // +2MB/s faster than the previous implementation, 0.4MB/s difference from the raw handlers. diff --git a/_benchmarks/iris-sessions/main.go b/_benchmarks/iris-sessions/main.go index 956cad0d..d5a34062 100644 --- a/_benchmarks/iris-sessions/main.go +++ b/_benchmarks/iris-sessions/main.go @@ -24,9 +24,7 @@ func main() { app.Delete("/del", delHandler) */ - // 24 August 2017: Iris has a built'n version updater but we don't need it - // when benchmarking... - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } // Set and Get diff --git a/_benchmarks/iris/main.go b/_benchmarks/iris/main.go index 7c1ee6e2..d9885e88 100644 --- a/_benchmarks/iris/main.go +++ b/_benchmarks/iris/main.go @@ -8,5 +8,5 @@ func main() { ctx.WriteString("value") }) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } diff --git a/_examples/hero/overview/main.go b/_examples/hero/overview/main.go index ac722107..efb18884 100644 --- a/_examples/hero/overview/main.go +++ b/_examples/hero/overview/main.go @@ -50,8 +50,6 @@ func main() { app.Run( // Start the web server at localhost:8080 iris.Addr("localhost:8080"), - // disables updates: - iris.WithoutVersionChecker, // skip err server closed when CTRL/CMD+C pressed: iris.WithoutServerError(iris.ErrServerClosed), // enables faster json serialization and more: diff --git a/_examples/hero/sessions/main.go b/_examples/hero/sessions/main.go index aa1f67b3..76034532 100644 --- a/_examples/hero/sessions/main.go +++ b/_examples/hero/sessions/main.go @@ -34,7 +34,6 @@ func main() { app.Run( iris.Addr(":8080"), - iris.WithoutVersionChecker, iris.WithoutServerError(iris.ErrServerClosed), ) } diff --git a/_examples/http_request/extract-referer/main.go b/_examples/http_request/extract-referer/main.go index 8c14e54e..3e2436c5 100644 --- a/_examples/http_request/extract-referer/main.go +++ b/_examples/http_request/extract-referer/main.go @@ -25,5 +25,5 @@ func main() { // http://localhost:8080?referer=https://twitter.com/Xinterio/status/1023566830974251008 // http://localhost:8080?referer=https://www.google.com/search?q=Top+6+golang+web+frameworks&oq=Top+6+golang+web+frameworks - app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) } diff --git a/_examples/mvc/login/main.go b/_examples/mvc/login/main.go index 83bd350f..3d77e08a 100644 --- a/_examples/mvc/login/main.go +++ b/_examples/mvc/login/main.go @@ -79,8 +79,6 @@ func main() { app.Run( // Starts the web server at localhost:8080 iris.Addr("localhost:8080"), - // Disables the updater. - iris.WithoutVersionChecker, // Ignores err server closed log when CTRL/CMD+C pressed. iris.WithoutServerError(iris.ErrServerClosed), // Enables faster json serialization and more. diff --git a/_examples/mvc/overview/main.go b/_examples/mvc/overview/main.go index 9d7d9cf6..22013325 100644 --- a/_examples/mvc/overview/main.go +++ b/_examples/mvc/overview/main.go @@ -33,8 +33,6 @@ func main() { app.Run( // Start the web server at localhost:8080 iris.Addr("localhost:8080"), - // disables updates: - iris.WithoutVersionChecker, // skip err server closed when CTRL/CMD+C pressed: iris.WithoutServerError(iris.ErrServerClosed), // enables faster json serialization and more: diff --git a/_examples/mvc/session-controller/main.go b/_examples/mvc/session-controller/main.go index 8774d448..aa3ea051 100644 --- a/_examples/mvc/session-controller/main.go +++ b/_examples/mvc/session-controller/main.go @@ -68,5 +68,5 @@ func main() { // 3. refresh the page some times // 4. close the browser // 5. re-open the browser and re-play 2. - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } diff --git a/_examples/overview/main.go b/_examples/overview/main.go index a834876e..30714a09 100644 --- a/_examples/overview/main.go +++ b/_examples/overview/main.go @@ -74,7 +74,7 @@ func main() { } // Listen for incoming HTTP/1.x & HTTP/2 clients on localhost port 8080. - app.Run(iris.Addr(":8080"), iris.WithCharset("UTF-8"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080"), iris.WithCharset("UTF-8")) } func logThisMiddleware(ctx iris.Context) { diff --git a/_examples/tutorial/url-shortener/main.go b/_examples/tutorial/url-shortener/main.go index 1516627c..7fa91123 100644 --- a/_examples/tutorial/url-shortener/main.go +++ b/_examples/tutorial/url-shortener/main.go @@ -2,7 +2,7 @@ // // Article: https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7 // -// $ go get github.com/coreos/bbolt +// $ go get github.com/etcd-io/bbolt // $ go get github.com/satori/go.uuid // $ cd $GOPATH/src/github.com/kataras/iris/_examples/tutorial/url-shortener // $ go build diff --git a/_examples/tutorial/url-shortener/store.go b/_examples/tutorial/url-shortener/store.go index 32238b09..d76413df 100644 --- a/_examples/tutorial/url-shortener/store.go +++ b/_examples/tutorial/url-shortener/store.go @@ -3,7 +3,7 @@ package main import ( "bytes" - "github.com/coreos/bbolt" + "github.com/etcd-io/bbolt" ) // Panic panics, change it if you don't want to panic on critical INITIALIZE-ONLY-ERRORS @@ -28,17 +28,17 @@ var ( // Only one table/bucket which contains the urls, so it's not a fully Database, // it works only with single bucket because that all we need. type DB struct { - db *bolt.DB + db *bbolt.DB } var _ Store = &DB{} // openDatabase open a new database connection // and returns its instance. -func openDatabase(stumb string) *bolt.DB { +func openDatabase(stumb string) *bbolt.DB { // Open the data(base) file in the current working directory. // It will be created if it doesn't exist. - db, err := bolt.Open(stumb, 0600, nil) + db, err := bbolt.Open(stumb, 0600, nil) if err != nil { Panic(err) } @@ -48,7 +48,7 @@ func openDatabase(stumb string) *bolt.DB { tableURLs, } - db.Update(func(tx *bolt.Tx) (err error) { + db.Update(func(tx *bbolt.Tx) (err error) { for _, table := range tables { _, err = tx.CreateBucketIfNotExists(table) if err != nil { @@ -73,7 +73,7 @@ func NewDB(stumb string) *DB { // Set sets a shorten url and its key // Note: Caller is responsible to generate a key. func (d *DB) Set(key string, value string) error { - return d.db.Update(func(tx *bolt.Tx) error { + return d.db.Update(func(tx *bbolt.Tx) error { b, err := tx.CreateBucketIfNotExists(tableURLs) // Generate ID for the url // Note: we could use that instead of a random string key @@ -106,7 +106,7 @@ func (d *DB) Set(key string, value string) error { // Clear clears all the database entries for the table urls. func (d *DB) Clear() error { - return d.db.Update(func(tx *bolt.Tx) error { + return d.db.Update(func(tx *bbolt.Tx) error { return tx.DeleteBucket(tableURLs) }) } @@ -116,7 +116,7 @@ func (d *DB) Clear() error { // Returns an empty string if not found. func (d *DB) Get(key string) (value string) { keyB := []byte(key) - d.db.Update(func(tx *bolt.Tx) error { + d.db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(tableURLs) if b == nil { return nil @@ -138,7 +138,7 @@ func (d *DB) Get(key string) (value string) { // GetByValue returns all keys for a specific (original) url value. func (d *DB) GetByValue(value string) (keys []string) { valueB := []byte(value) - d.db.Update(func(tx *bolt.Tx) error { + d.db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(tableURLs) if b == nil { return nil @@ -159,7 +159,7 @@ func (d *DB) GetByValue(value string) (keys []string) { // Len returns all the "shorted" urls length func (d *DB) Len() (num int) { - d.db.View(func(tx *bolt.Tx) error { + d.db.View(func(tx *bbolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket(tableURLs) diff --git a/_examples/tutorial/vuejs-todo-mvc/README.md b/_examples/tutorial/vuejs-todo-mvc/README.md index 00c912fa..666f862d 100644 --- a/_examples/tutorial/vuejs-todo-mvc/README.md +++ b/_examples/tutorial/vuejs-todo-mvc/README.md @@ -530,7 +530,7 @@ func main() { todosApp.Handle(new(controllers.TodoController)) // start the web server at http://localhost:8080 - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } ``` diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go index c571e426..34e5ed88 100644 --- a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go +++ b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go @@ -51,5 +51,5 @@ func main() { todosApp.Handle(new(controllers.TodoController)) // start the web server at http://localhost:8080 - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } diff --git a/_examples/webassembly/basic/client/hello_go11beta3.go b/_examples/webassembly/basic/client/hello_go111.go similarity index 70% rename from _examples/webassembly/basic/client/hello_go11beta3.go rename to _examples/webassembly/basic/client/hello_go111.go index 9cbf5060..1822fc79 100644 --- a/_examples/webassembly/basic/client/hello_go11beta3.go +++ b/_examples/webassembly/basic/client/hello_go111.go @@ -1,4 +1,4 @@ -// +build go1.11beta3 +// +build go1.11 package main @@ -9,7 +9,7 @@ import ( ) func main() { - // GOARCH=wasm GOOS=js /home/$yourusername/go1.11beta1/bin/go build -o hello.wasm hello_go11beta2.go + // GOARCH=wasm GOOS=js /home/$yourusername/go1.11/bin/go build -o hello.wasm hello_go111.go js.Global().Get("console").Call("log", "Hello WebAssemply!") message := fmt.Sprintf("Hello, the current time is: %s", time.Now().String()) js.Global().Get("document").Call("getElementById", "hello").Set("innerText", message) diff --git a/_examples/webassembly/basic/main.go b/_examples/webassembly/basic/main.go index 7dfe0ad2..4099da53 100644 --- a/_examples/webassembly/basic/main.go +++ b/_examples/webassembly/basic/main.go @@ -6,7 +6,7 @@ import ( /* You need to build the hello.wasm first, download the go1.11 and execute the below command: -$ cd client && GOARCH=wasm GOOS=js /home/$yourname/go1.11beta3/bin/go build -o hello.wasm hello_go11beta3.go +$ cd client && GOARCH=wasm GOOS=js /home/$yourname/go1.11/bin/go build -o hello.wasm hello_go111.go */ func main() { diff --git a/configuration.go b/configuration.go index 0d08fc3e..3dedfce4 100644 --- a/configuration.go +++ b/configuration.go @@ -222,14 +222,6 @@ var WithoutInterruptHandler = func(app *Application) { app.config.DisableInterruptHandler = true } -// WithoutVersionChecker will disable the version checker and updater. -// The Iris server will be not -// receive automatic updates if you pass this -// to the `Run` function. Use it only while you're ready for Production environment. -var WithoutVersionChecker = func(app *Application) { - app.config.DisableVersionChecker = true -} - // WithoutPathCorrection disables the PathCorrection setting. // // See `Configuration`. @@ -388,11 +380,6 @@ type Configuration struct { // Defaults to false. DisableInterruptHandler bool `json:"disableInterruptHandler,omitempty" yaml:"DisableInterruptHandler" toml:"DisableInterruptHandler"` - // DisableVersionChecker if true then process will be not be notified for any available updates. - // - // Defaults to false. - DisableVersionChecker bool `json:"disableVersionChecker,omitempty" yaml:"DisableVersionChecker" toml:"DisableVersionChecker"` - // DisablePathCorrection corrects and redirects the requested path to the registered path // for example, if /home/ path is requested but no handler for this Route found, // then the Router checks if /home handler exists, if yes, @@ -677,10 +664,6 @@ func WithConfiguration(c Configuration) Configurator { main.DisableInterruptHandler = v } - if v := c.DisableVersionChecker; v { - main.DisableVersionChecker = v - } - if v := c.DisablePathCorrection; v { main.DisablePathCorrection = v } @@ -758,7 +741,6 @@ func DefaultConfiguration() Configuration { return Configuration{ DisableStartupLog: false, DisableInterruptHandler: false, - DisableVersionChecker: false, DisablePathCorrection: false, EnablePathEscape: false, FireMethodNotAllowed: false, diff --git a/configuration_test.go b/configuration_test.go index ab770fbe..913ebe84 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -77,13 +77,12 @@ func TestConfigurationOptions(t *testing.T) { func TestConfigurationOptionsDeep(t *testing.T) { charset := "MYCHARSET" - app := New().Configure(WithCharset(charset), WithoutBodyConsumptionOnUnmarshal, WithoutBanner, WithoutVersionChecker) + app := New().Configure(WithCharset(charset), WithoutBodyConsumptionOnUnmarshal, WithoutBanner) expected := DefaultConfiguration() expected.Charset = charset expected.DisableBodyConsumptionOnUnmarshal = true expected.DisableStartupLog = true - expected.DisableVersionChecker = true has := *app.config @@ -142,7 +141,6 @@ func TestConfigurationYAML(t *testing.T) { }() yamlConfigurationContents := ` -DisableVersionChecker: true DisablePathCorrection: false EnablePathEscape: false FireMethodNotAllowed: true @@ -165,10 +163,6 @@ Other: c := app.config - if expected := true; c.DisableVersionChecker != expected { - t.Fatalf("error on TestConfigurationYAML: Expected DisableVersionChecker %v but got %v", expected, c.DisableVersionChecker) - } - if expected := false; c.DisablePathCorrection != expected { t.Fatalf("error on TestConfigurationYAML: Expected DisablePathCorrection %v but got %v", expected, c.DisablePathCorrection) } @@ -241,7 +235,6 @@ func TestConfigurationTOML(t *testing.T) { }() tomlConfigurationContents := ` -DisableVersionChecker = true EnablePathEscape = false FireMethodNotAllowed = true EnableOptimizations = true @@ -265,10 +258,6 @@ Charset = "UTF-8" c := app.config - if expected := true; c.DisableVersionChecker != expected { - t.Fatalf("error on TestConfigurationTOML: Expected DisableVersionChecker %v but got %v", expected, c.DisableVersionChecker) - } - if expected := false; c.DisablePathCorrection != expected { t.Fatalf("error on TestConfigurationTOML: Expected DisablePathCorrection %v but got %v", expected, c.DisablePathCorrection) } diff --git a/core/host/proxy_test.go b/core/host/proxy_test.go index b7ad878e..76561b34 100644 --- a/core/host/proxy_test.go +++ b/core/host/proxy_test.go @@ -60,7 +60,7 @@ func TestProxy(t *testing.T) { t.Fatalf("%v while creating tcp4 listener for new tls local test listener", err) } // main server - go app.Run(iris.Listener(httptest.NewLocalTLSListener(l)), iris.WithoutVersionChecker, iris.WithoutStartupLog) + go app.Run(iris.Listener(httptest.NewLocalTLSListener(l)), iris.WithoutStartupLog) e := httptest.NewInsecure(t, httptest.URL("http://"+listener.Addr().String())) e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedIndex) diff --git a/core/maintenance/maintenance.go b/core/maintenance/maintenance.go deleted file mode 100644 index 613b67fd..00000000 --- a/core/maintenance/maintenance.go +++ /dev/null @@ -1,6 +0,0 @@ -package maintenance - -// Start starts the maintenance process. -func Start() { - CheckForUpdates() -} diff --git a/core/maintenance/version.go b/core/maintenance/version.go deleted file mode 100644 index f3d3113f..00000000 --- a/core/maintenance/version.go +++ /dev/null @@ -1,78 +0,0 @@ -package maintenance - -import ( - "fmt" - "os" - "os/exec" - - "github.com/kataras/iris/core/maintenance/version" - - "github.com/kataras/golog" - "github.com/kataras/survey" -) - -const ( - // Version is the string representation of the current local Iris Web Framework version. - Version = "10.7.0" -) - -// CheckForUpdates checks for any available updates -// and asks for the user if want to update now or not. -func CheckForUpdates() { - v := version.Acquire() - updateAvailale := v.Compare(Version) == version.Smaller - - if updateAvailale { - if confirmUpdate(v) { - installVersion() - return - } - } -} - -func confirmUpdate(v version.Version) bool { - // on help? when asking for installing the new update. - ignoreUpdatesMsg := "Would you like to ignore future updates? Disable the version checker via:\napp.Run(..., iris.WithoutVersionChecker)" - - // if update available ask for update action. - shouldUpdateNowMsg := - fmt.Sprintf("A new version is available online[%s > %s]. Type '?' for help.\nRelease notes: %s.\nUpdate now?", - v.String(), Version, v.ChangelogURL) - - var confirmUpdate bool - survey.AskOne(&survey.Confirm{ - Message: shouldUpdateNowMsg, - Help: ignoreUpdatesMsg, - }, &confirmUpdate, nil) - return confirmUpdate // it's true only when update was available and user typed "yes". -} - -func installVersion() { - golog.Infof("Downloading...\n") - repo := "github.com/kataras/iris/..." - cmd := exec.Command("go", "get", "-u", "-v", repo) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - - if err := cmd.Run(); err != nil { - golog.Warnf("unexpected message while trying to go get,\nif you edited the original source code then you've to remove the whole $GOPATH/src/github.com/kataras folder and execute `go get -u github.com/kataras/iris/...` manually\n%v", err) - return - } - - golog.Infof("Update process finished.\nManual rebuild and restart is required to apply the changes...\n") - return -} - -/* Author's note: -We could use github webhooks to automatic notify for updates -when a new update is pushed to the repository -even when server is already started and running but this would expose -a route which dev may don't know about, so let it for now but if -they ask it then I should add an optional configuration field -to "live/realtime update" and implement the idea (which is already implemented in the iris-go server). -*/ - -/* Author's note: -The old remote endpoint for version checker is still available on the server for backwards -compatibility with older clients, it will stay there for a long period of time. -*/ diff --git a/core/maintenance/version/fetch.go b/core/maintenance/version/fetch.go deleted file mode 100644 index 728c91a4..00000000 --- a/core/maintenance/version/fetch.go +++ /dev/null @@ -1,59 +0,0 @@ -package version - -import ( - "io/ioutil" - "strings" - "time" - - "github.com/hashicorp/go-version" - - "github.com/kataras/golog" - "github.com/kataras/iris/core/netutil" -) - -const ( - versionURL = "https://raw.githubusercontent.com/kataras/iris/master/VERSION" -) - -func fetch() (*version.Version, string) { - client := netutil.Client(time.Duration(30 * time.Second)) - - r, err := client.Get(versionURL) - if err != nil { - golog.Debugf("err: %v\n", err) - return nil, "" - } - defer r.Body.Close() - - if r.StatusCode >= 400 { - golog.Debugf("Internet connection is missing, updater is unable to fetch the latest Iris version\n", err) - return nil, "" - } - - b, err := ioutil.ReadAll(r.Body) - - if len(b) == 0 || err != nil { - golog.Debugf("err: %v\n", err) - return nil, "" - } - - var ( - fetchedVersion = string(b) - changelogURL string - ) - // Example output: - // Version(8.5.5) - // 8.5.5:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-02-november-2017--v855 - if idx := strings.IndexByte(fetchedVersion, ':'); idx > 0 { - changelogURL = fetchedVersion[idx+1:] - fetchedVersion = fetchedVersion[0:idx] - } - - latestVersion, err := version.NewVersion(fetchedVersion) - if err != nil { - golog.Debugf("while fetching and parsing latest version from github: %v\n", err) - return nil, "" - } - - return latestVersion, changelogURL -} diff --git a/core/maintenance/version/version.go b/core/maintenance/version/version.go deleted file mode 100644 index 4a338952..00000000 --- a/core/maintenance/version/version.go +++ /dev/null @@ -1,64 +0,0 @@ -package version - -import ( - "time" - - "github.com/hashicorp/go-version" -) - -// Version is a version wrapper which -// contains some additional customized properties. -type Version struct { - version.Version - WrittenAt time.Time - ChangelogURL string -} - -// Result is the compare result type. -// Available types are Invalid, Smaller, Equal or Larger. -type Result int32 - -const ( - // Smaller when the compared version is smaller than the latest one. - Smaller Result = -1 - // Equal when the compared version is equal with the latest one. - Equal Result = 0 - // Larger when the compared version is larger than the latest one. - Larger Result = 1 - // Invalid means that an error occurred when comparing the versions. - Invalid Result = -2 -) - -// Compare compares the "versionStr" with the latest Iris version, -// opossite to the version package -// it returns the result of the "versionStr" not the "v" itself. -func (v *Version) Compare(versionStr string) Result { - if len(v.Version.String()) == 0 { - // if version not refreshed, by an internet connection lose, - // then return Invalid. - return Invalid - } - - other, err := version.NewVersion(versionStr) - if err != nil { - return Invalid - } - return Result(other.Compare(&v.Version)) -} - -// Acquire returns the latest version info wrapper. -// It calls the fetch. -func Acquire() (v Version) { - newVersion, changelogURL := fetch() - if newVersion == nil { // if github was down then don't panic, just set it as the smallest version. - newVersion, _ = version.NewVersion("0.0.1") - } - - v = Version{ - Version: *newVersion, - WrittenAt: time.Now(), - ChangelogURL: changelogURL, - } - - return -} diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 83092939..33f37fd0 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -105,7 +105,7 @@ func TestNumberEvaluatorRaw(t *testing.T) { {true, "-18446744073709553213213213213213121615"}, // 5 {false, "42 18446744073709551615"}, // 6 {false, "--42"}, // 7 - {false, "+42"}, // 9 + {false, "+42"}, // 8 {false, "main.css"}, // 9 {false, "/assets/main.css"}, // 10 } diff --git a/httptest/httptest.go b/httptest/httptest.go index b37f9750..50253c53 100644 --- a/httptest/httptest.go +++ b/httptest/httptest.go @@ -84,8 +84,7 @@ func New(t *testing.T, app *iris.Application, setters ...OptionSetter) *httpexpe setter.Set(conf) } - // set the logger or disable it (default) and disable the updater (for any case). - app.Configure(iris.WithoutVersionChecker) + // set the logger or disable it (default). app.Logger().SetLevel(conf.LogLevel) if err := app.Build(); err != nil { diff --git a/iris.go b/iris.go index e9929385..8cab4999 100644 --- a/iris.go +++ b/iris.go @@ -17,7 +17,6 @@ import ( // core packages, needed to build the application "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/host" - "github.com/kataras/iris/core/maintenance" "github.com/kataras/iris/core/netutil" "github.com/kataras/iris/core/router" // handlerconv conversions @@ -34,7 +33,7 @@ import ( var ( // Version is the current version number of the Iris Web Framework. - Version = maintenance.Version + Version = "11.0.0" ) // HTTP status codes as registered with IANA. @@ -811,10 +810,6 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error { app.Configure(withOrWithout...) app.logger.Debugf("Application: running using %d host(s)", len(app.Hosts)+1) - if !app.config.DisableVersionChecker { - go maintenance.Start() - } - // this will block until an error(unless supervisor's DeferFlow called from a Task). err := serve(app) if err != nil { diff --git a/sessions/sessiondb/boltdb/database.go b/sessions/sessiondb/boltdb/database.go index fe1c90fe..db0ef827 100644 --- a/sessions/sessiondb/boltdb/database.go +++ b/sessions/sessiondb/boltdb/database.go @@ -6,7 +6,7 @@ import ( "runtime" "time" - "github.com/coreos/bbolt" + "github.com/etcd-io/bbolt" "github.com/kataras/golog" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" @@ -25,7 +25,7 @@ type Database struct { // Service is the underline BoltDB database connection, // it's initialized at `New` or `NewFromDB`. // Can be used to get stats. - Service *bolt.DB + Service *bbolt.DB } var errPathMissing = errors.New("path is required") @@ -51,8 +51,8 @@ func New(path string, fileMode os.FileMode) (*Database, error) { return nil, err } - service, err := bolt.Open(path, fileMode, - &bolt.Options{Timeout: 20 * time.Second}, + service, err := bbolt.Open(path, fileMode, + &bbolt.Options{Timeout: 20 * time.Second}, ) if err != nil { @@ -64,10 +64,10 @@ func New(path string, fileMode os.FileMode) (*Database, error) { } // NewFromDB same as `New` but accepts an already-created custom boltdb connection instead. -func NewFromDB(service *bolt.DB, bucketName string) (*Database, error) { +func NewFromDB(service *bbolt.DB, bucketName string) (*Database, error) { bucket := []byte(bucketName) - service.Update(func(tx *bolt.Tx) (err error) { + service.Update(func(tx *bbolt.Tx) (err error) { _, err = tx.CreateBucketIfNotExists(bucket) return }) @@ -78,15 +78,15 @@ func NewFromDB(service *bolt.DB, bucketName string) (*Database, error) { return db, db.cleanup() } -func (db *Database) getBucket(tx *bolt.Tx) *bolt.Bucket { +func (db *Database) getBucket(tx *bbolt.Tx) *bbolt.Bucket { return tx.Bucket(db.table) } -func (db *Database) getBucketForSession(tx *bolt.Tx, sid string) *bolt.Bucket { +func (db *Database) getBucketForSession(tx *bbolt.Tx, sid string) *bbolt.Bucket { b := db.getBucket(tx).Bucket([]byte(sid)) if b == nil { // session does not exist, it shouldn't happen, session bucket creation happens once at `Acquire`, - // no need to accept the `bolt.bucket.CreateBucketIfNotExists`'s performance cost. + // no need to accept the `bbolt.bucket.CreateBucketIfNotExists`'s performance cost. golog.Debugf("unreachable session access for '%s'", sid) } @@ -105,7 +105,7 @@ func getExpirationBucketName(bsid []byte) []byte { // Cleanup removes any invalid(have expired) session entries on initialization. func (db *Database) cleanup() error { - return db.Service.Update(func(tx *bolt.Tx) error { + return db.Service.Update(func(tx *bbolt.Tx) error { b := db.getBucket(tx) c := b.Cursor() // loop through all buckets, find one with expiration. @@ -151,7 +151,7 @@ var expirationKey = []byte("exp") // it can be random. // if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration. func (db *Database) Acquire(sid string, expires time.Duration) (lifetime sessions.LifeTime) { bsid := []byte(sid) - err := db.Service.Update(func(tx *bolt.Tx) (err error) { + err := db.Service.Update(func(tx *bbolt.Tx) (err error) { root := db.getBucket(tx) if expires > 0 { // should check or create the expiration bucket. @@ -218,7 +218,7 @@ func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) err return err } - err = db.Service.Update(func(tx *bolt.Tx) error { + err = db.Service.Update(func(tx *bbolt.Tx) error { expirationName := getExpirationBucketName([]byte(sid)) root := db.getBucket(tx) b := root.Bucket(expirationName) @@ -250,7 +250,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu return } - err = db.Service.Update(func(tx *bolt.Tx) error { + err = db.Service.Update(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -270,7 +270,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu // Get retrieves a session value based on the key. func (db *Database) Get(sid string, key string) (value interface{}) { - err := db.Service.View(func(tx *bolt.Tx) error { + err := db.Service.View(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -293,7 +293,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) { // Visit loops through all session keys and values. func (db *Database) Visit(sid string, cb func(key string, value interface{})) { - db.Service.View(func(tx *bolt.Tx) error { + db.Service.View(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -314,7 +314,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { // Len returns the length of the session's entries (keys). func (db *Database) Len(sid string) (n int) { - db.Service.View(func(tx *bolt.Tx) error { + db.Service.View(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -329,7 +329,7 @@ func (db *Database) Len(sid string) (n int) { // Delete removes a session key value based on its key. func (db *Database) Delete(sid string, key string) (deleted bool) { - err := db.Service.Update(func(tx *bolt.Tx) error { + err := db.Service.Update(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return sessions.ErrNotFound @@ -343,7 +343,7 @@ func (db *Database) Delete(sid string, key string) (deleted bool) { // Clear removes all session key values but it keeps the session entry. func (db *Database) Clear(sid string) { - db.Service.Update(func(tx *bolt.Tx) error { + db.Service.Update(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -358,7 +358,7 @@ func (db *Database) Clear(sid string) { // Release destroys the session, it clears and removes the session entry, // session manager will create a new session ID on the next request after this call. func (db *Database) Release(sid string) { - db.Service.Update(func(tx *bolt.Tx) error { + db.Service.Update(func(tx *bbolt.Tx) error { // delete the session bucket. b := db.getBucket(tx) bsid := []byte(sid) From e6bd3c1f0c9cda203df275e8e9a35e5a29fcb11f Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 31 Aug 2018 02:16:23 +0300 Subject: [PATCH 10/42] remove unnecessary deps Former-commit-id: 634283a9b227064b703c3c45ce893dfa08345bb7 --- Gopkg.lock | 6 ------ Gopkg.toml | 4 ---- 2 files changed, 10 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 8afa3485..d392261b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -115,12 +115,6 @@ packages = [".","terminal"] revision = "825e39f34365e7db2c9fbc3692c16220e3bd7418" -[[projects]] - branch = "v2" - name = "github.com/kataras/survey" - packages = [".","terminal","core"] - revision = "00934ae069eda15df26fa427ac393e67e239380c" - [[projects]] name = "github.com/klauspost/compress" packages = ["flate","gzip"] diff --git a/Gopkg.toml b/Gopkg.toml index eb9c030d..b0efe846 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -42,10 +42,6 @@ branch = "master" name = "github.com/kataras/golog" -[[constraint]] - branch = "v2" - name = "github.com/kataras/survey" - [[constraint]] name = "github.com/klauspost/compress" version = "1.2.1" From 4fd9886a10db5bfb73cc072c17bd2058f535944b Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 1 Sep 2018 02:06:56 +0300 Subject: [PATCH 11/42] go modules and vendoring section explain why not yet, in my opinion let's stick with the current system until gophers get acquainted with the new go modules and how it works - I putted a link to the TOC in order to help them Former-commit-id: 572527152de5808c834546ca1373de716046770e --- .travis.yml | 11 +++--- HISTORY.md | 3 +- _examples/tutorial/url-shortener/store.go | 20 +++++------ sessions/sessiondb/boltdb/database.go | 41 ++++++++++++----------- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e6ada55..a1912a50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,14 @@ os: - linux - osx go: - - "go1.9" - - "go1.10" + - 1.9.x + - 1.10.x + - 1.11.x go_import_path: github.com/kataras/iris # we disable test caching via GOCACHE=off -env: - global: - - GOCACHE=off +# env: +# global: +# - GOCACHE=off install: - go get ./... # for iris-contrib/httpexpect, kataras/golog script: diff --git a/HISTORY.md b/HISTORY.md index 2a24d778..d30424b1 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -80,7 +80,8 @@ app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ - Rename the vendor `sessions/sessiondb/vendor/...bbolt` from `coreos/bbolt` to `etcd-io/bbolt` and update to v1.3.1, based on [that](https://github.com/etcd-io/bbolt/releases/tag/v1.3.1-etcd.7) - Update the vendor `sessions/sessiondb/vendor/...badger` to v1.5.3 -> More to come, maybe it will be removed eventually. +I believe it is soon to adapt the new [go modules](https://github.com/golang/go/wiki/Modules#table-of-contents) inside Iris, the new `go mod` command may change until go 1.12, it is still an experimental feature. +The [vendor](https://github.com/kataras/iris/tree/v11/vendor) folder will be kept until most the majority of Go developers get acquainted with the new `go modules`. The `go.mod` and `go.sum` files will come at `iris v12` (or `go 1.12`), we could do that on this version as well but I don't want to have half-things, versioning should be passed on import path as well and that is a large breaking change to go with it right now, so it will probably have a new path such as `github.com/kataras/iris/v12` based on a `git tag` like every Iris release (we are lucky here because we used semantic versioning from day zero). No folder re-structure inside the root git repository to split versions will ever happen, so backwards-compatibility will be not enabled by-default although it's easy for anyone to grab any version from older [releases](https://github.com/kataras/iris/releases) or branch and target that. # Sat, 11 August 2018 | v10.7.0 diff --git a/_examples/tutorial/url-shortener/store.go b/_examples/tutorial/url-shortener/store.go index d76413df..f2b78891 100644 --- a/_examples/tutorial/url-shortener/store.go +++ b/_examples/tutorial/url-shortener/store.go @@ -3,7 +3,7 @@ package main import ( "bytes" - "github.com/etcd-io/bbolt" + bolt "github.com/etcd-io/bbolt" ) // Panic panics, change it if you don't want to panic on critical INITIALIZE-ONLY-ERRORS @@ -28,17 +28,17 @@ var ( // Only one table/bucket which contains the urls, so it's not a fully Database, // it works only with single bucket because that all we need. type DB struct { - db *bbolt.DB + db *bolt.DB } var _ Store = &DB{} // openDatabase open a new database connection // and returns its instance. -func openDatabase(stumb string) *bbolt.DB { +func openDatabase(stumb string) *bolt.DB { // Open the data(base) file in the current working directory. // It will be created if it doesn't exist. - db, err := bbolt.Open(stumb, 0600, nil) + db, err := bolt.Open(stumb, 0600, nil) if err != nil { Panic(err) } @@ -48,7 +48,7 @@ func openDatabase(stumb string) *bbolt.DB { tableURLs, } - db.Update(func(tx *bbolt.Tx) (err error) { + db.Update(func(tx *bolt.Tx) (err error) { for _, table := range tables { _, err = tx.CreateBucketIfNotExists(table) if err != nil { @@ -73,7 +73,7 @@ func NewDB(stumb string) *DB { // Set sets a shorten url and its key // Note: Caller is responsible to generate a key. func (d *DB) Set(key string, value string) error { - return d.db.Update(func(tx *bbolt.Tx) error { + return d.db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists(tableURLs) // Generate ID for the url // Note: we could use that instead of a random string key @@ -106,7 +106,7 @@ func (d *DB) Set(key string, value string) error { // Clear clears all the database entries for the table urls. func (d *DB) Clear() error { - return d.db.Update(func(tx *bbolt.Tx) error { + return d.db.Update(func(tx *bolt.Tx) error { return tx.DeleteBucket(tableURLs) }) } @@ -116,7 +116,7 @@ func (d *DB) Clear() error { // Returns an empty string if not found. func (d *DB) Get(key string) (value string) { keyB := []byte(key) - d.db.Update(func(tx *bbolt.Tx) error { + d.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(tableURLs) if b == nil { return nil @@ -138,7 +138,7 @@ func (d *DB) Get(key string) (value string) { // GetByValue returns all keys for a specific (original) url value. func (d *DB) GetByValue(value string) (keys []string) { valueB := []byte(value) - d.db.Update(func(tx *bbolt.Tx) error { + d.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(tableURLs) if b == nil { return nil @@ -159,7 +159,7 @@ func (d *DB) GetByValue(value string) (keys []string) { // Len returns all the "shorted" urls length func (d *DB) Len() (num int) { - d.db.View(func(tx *bbolt.Tx) error { + d.db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket(tableURLs) diff --git a/sessions/sessiondb/boltdb/database.go b/sessions/sessiondb/boltdb/database.go index db0ef827..7a27d5cc 100644 --- a/sessions/sessiondb/boltdb/database.go +++ b/sessions/sessiondb/boltdb/database.go @@ -6,10 +6,11 @@ import ( "runtime" "time" - "github.com/etcd-io/bbolt" - "github.com/kataras/golog" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" + + bolt "github.com/etcd-io/bbolt" + "github.com/kataras/golog" ) // DefaultFileMode used as the default database's "fileMode" @@ -25,7 +26,7 @@ type Database struct { // Service is the underline BoltDB database connection, // it's initialized at `New` or `NewFromDB`. // Can be used to get stats. - Service *bbolt.DB + Service *bolt.DB } var errPathMissing = errors.New("path is required") @@ -51,8 +52,8 @@ func New(path string, fileMode os.FileMode) (*Database, error) { return nil, err } - service, err := bbolt.Open(path, fileMode, - &bbolt.Options{Timeout: 20 * time.Second}, + service, err := bolt.Open(path, fileMode, + &bolt.Options{Timeout: 20 * time.Second}, ) if err != nil { @@ -64,10 +65,10 @@ func New(path string, fileMode os.FileMode) (*Database, error) { } // NewFromDB same as `New` but accepts an already-created custom boltdb connection instead. -func NewFromDB(service *bbolt.DB, bucketName string) (*Database, error) { +func NewFromDB(service *bolt.DB, bucketName string) (*Database, error) { bucket := []byte(bucketName) - service.Update(func(tx *bbolt.Tx) (err error) { + service.Update(func(tx *bolt.Tx) (err error) { _, err = tx.CreateBucketIfNotExists(bucket) return }) @@ -78,15 +79,15 @@ func NewFromDB(service *bbolt.DB, bucketName string) (*Database, error) { return db, db.cleanup() } -func (db *Database) getBucket(tx *bbolt.Tx) *bbolt.Bucket { +func (db *Database) getBucket(tx *bolt.Tx) *bolt.Bucket { return tx.Bucket(db.table) } -func (db *Database) getBucketForSession(tx *bbolt.Tx, sid string) *bbolt.Bucket { +func (db *Database) getBucketForSession(tx *bolt.Tx, sid string) *bolt.Bucket { b := db.getBucket(tx).Bucket([]byte(sid)) if b == nil { // session does not exist, it shouldn't happen, session bucket creation happens once at `Acquire`, - // no need to accept the `bbolt.bucket.CreateBucketIfNotExists`'s performance cost. + // no need to accept the `bolt.bucket.CreateBucketIfNotExists`'s performance cost. golog.Debugf("unreachable session access for '%s'", sid) } @@ -105,7 +106,7 @@ func getExpirationBucketName(bsid []byte) []byte { // Cleanup removes any invalid(have expired) session entries on initialization. func (db *Database) cleanup() error { - return db.Service.Update(func(tx *bbolt.Tx) error { + return db.Service.Update(func(tx *bolt.Tx) error { b := db.getBucket(tx) c := b.Cursor() // loop through all buckets, find one with expiration. @@ -151,7 +152,7 @@ var expirationKey = []byte("exp") // it can be random. // if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration. func (db *Database) Acquire(sid string, expires time.Duration) (lifetime sessions.LifeTime) { bsid := []byte(sid) - err := db.Service.Update(func(tx *bbolt.Tx) (err error) { + err := db.Service.Update(func(tx *bolt.Tx) (err error) { root := db.getBucket(tx) if expires > 0 { // should check or create the expiration bucket. @@ -218,7 +219,7 @@ func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) err return err } - err = db.Service.Update(func(tx *bbolt.Tx) error { + err = db.Service.Update(func(tx *bolt.Tx) error { expirationName := getExpirationBucketName([]byte(sid)) root := db.getBucket(tx) b := root.Bucket(expirationName) @@ -250,7 +251,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu return } - err = db.Service.Update(func(tx *bbolt.Tx) error { + err = db.Service.Update(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -270,7 +271,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu // Get retrieves a session value based on the key. func (db *Database) Get(sid string, key string) (value interface{}) { - err := db.Service.View(func(tx *bbolt.Tx) error { + err := db.Service.View(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -293,7 +294,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) { // Visit loops through all session keys and values. func (db *Database) Visit(sid string, cb func(key string, value interface{})) { - db.Service.View(func(tx *bbolt.Tx) error { + db.Service.View(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -314,7 +315,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { // Len returns the length of the session's entries (keys). func (db *Database) Len(sid string) (n int) { - db.Service.View(func(tx *bbolt.Tx) error { + db.Service.View(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -329,7 +330,7 @@ func (db *Database) Len(sid string) (n int) { // Delete removes a session key value based on its key. func (db *Database) Delete(sid string, key string) (deleted bool) { - err := db.Service.Update(func(tx *bbolt.Tx) error { + err := db.Service.Update(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return sessions.ErrNotFound @@ -343,7 +344,7 @@ func (db *Database) Delete(sid string, key string) (deleted bool) { // Clear removes all session key values but it keeps the session entry. func (db *Database) Clear(sid string) { - db.Service.Update(func(tx *bbolt.Tx) error { + db.Service.Update(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -358,7 +359,7 @@ func (db *Database) Clear(sid string) { // Release destroys the session, it clears and removes the session entry, // session manager will create a new session ID on the next request after this call. func (db *Database) Release(sid string) { - db.Service.Update(func(tx *bbolt.Tx) error { + db.Service.Update(func(tx *bolt.Tx) error { // delete the session bucket. b := db.getBucket(tx) bsid := []byte(sid) From 91fe161e905f360a9814a5012406ca9fd896c1ca Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 1 Sep 2018 02:10:22 +0300 Subject: [PATCH 12/42] more details Signed-off-by: Gerasimos (Makis) Maropoulos Former-commit-id: 4ed59111e5e5fc05a9935a9290f9e385c6413c79 --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index d30424b1..5f774844 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -81,7 +81,7 @@ app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ - Update the vendor `sessions/sessiondb/vendor/...badger` to v1.5.3 I believe it is soon to adapt the new [go modules](https://github.com/golang/go/wiki/Modules#table-of-contents) inside Iris, the new `go mod` command may change until go 1.12, it is still an experimental feature. -The [vendor](https://github.com/kataras/iris/tree/v11/vendor) folder will be kept until most the majority of Go developers get acquainted with the new `go modules`. The `go.mod` and `go.sum` files will come at `iris v12` (or `go 1.12`), we could do that on this version as well but I don't want to have half-things, versioning should be passed on import path as well and that is a large breaking change to go with it right now, so it will probably have a new path such as `github.com/kataras/iris/v12` based on a `git tag` like every Iris release (we are lucky here because we used semantic versioning from day zero). No folder re-structure inside the root git repository to split versions will ever happen, so backwards-compatibility will be not enabled by-default although it's easy for anyone to grab any version from older [releases](https://github.com/kataras/iris/releases) or branch and target that. +The [vendor](https://github.com/kataras/iris/tree/v11/vendor) folder will be kept until the majority of Go developers get acquainted with the new `go modules`. The `go.mod` and `go.sum` files will come at `iris v12` (or `go 1.12`), we could do that on this version as well but I don't want to have half-things, versioning should be passed on import path as well and that is a large breaking change to go with it right now, so it will probably have a new path such as `github.com/kataras/iris/v12` based on a `git tag` like every Iris release (we are lucky here because we used semantic versioning from day zero). No folder re-structure inside the root git repository to split versions will ever happen, so backwards-compatibility for older go versions(before go 1.9.3) and iris versions will be not enabled by-default although it's easy for anyone to grab any version from older [releases](https://github.com/kataras/iris/releases) or branch and target that. # Sat, 11 August 2018 | v10.7.0 From efa17e889922d847297ba3af4c59fcc376bb5a3a Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 1 Sep 2018 18:53:42 +0300 Subject: [PATCH 13/42] dynamic param types part 1 Former-commit-id: 5829d53de848c0ea4491b53e4798f6c9cdf8d9a7 --- core/router/macro/interpreter/ast/ast.go | 208 +++++------------- .../router/macro/interpreter/parser/parser.go | 81 ++++++- .../macro/interpreter/parser/parser_test.go | 65 ++++-- deprecated.go | 1 - 4 files changed, 169 insertions(+), 186 deletions(-) delete mode 100644 deprecated.go diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 42995690..fa695226 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -2,154 +2,63 @@ package ast import ( "reflect" + "strings" ) -// ParamType is a specific uint8 type -// which holds the parameter types' type. -type ParamType uint8 +// ParamType holds the necessary information about a parameter type. +type ParamType struct { + Indent string // the name of the parameter type. + Aliases []string // any aliases, can be empty. -const ( - // ParamTypeUnExpected is an unexpected parameter type. - ParamTypeUnExpected ParamType = iota - // ParamTypeString is the string type. - // If parameter type is missing then it defaults to String type. - // Allows anything - // Declaration: /mypath/{myparam:string} or {myparam} - ParamTypeString + GoType reflect.Kind // the go type useful for "mvc" and "hero" bindings. - // 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 + Default bool // if true then empty type param will target this and its functions will be available to the rest of the param type's funcs. + End bool // if true then it should be declared at the end of a route path and can accept any trailing path segment as one parameter. - // ParamTypeInt64 is a number type. - // Allows only -9223372036854775808 to 9223372036854775807. - // Declaration: /mypath/{myparam:int64} or {myparam:long} - ParamTypeInt64 - // ParamTypeUint8 a number type. - // Allows only 0 to 255. - // Declaration: /mypath/{myparam:uint8} - ParamTypeUint8 - // ParamTypeUint64 a number type. - // Allows only 0 to 18446744073709551615. - // Declaration: /mypath/{myparam:uint64} - ParamTypeUint64 + invalid bool // only true if returned by the parser via `LookupParamType`. +} - // 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:bool} or {myparam:boolean} - ParamTypeBoolean - // ParamTypeAlphabetical is the alphabetical/letter type type. - // Allows letters only (upper or lowercase) - // Declaration: /mypath/{myparam:alphabetical} - ParamTypeAlphabetical - // ParamTypeFile is the file single path type. - // Allows: - // letters (upper or lowercase) - // numbers (0-9) - // underscore (_) - // dash (-) - // point (.) - // no spaces! or other character - // Declaration: /mypath/{myparam:file} - ParamTypeFile - // ParamTypePath is the multi path (or wildcard) type. - // Allows anything, should be the last part - // Declaration: /mypath/{myparam:path} - ParamTypePath -) +// ParamTypeUnExpected is the unexpected parameter type. +var ParamTypeUnExpected = ParamType{invalid: true} func (pt ParamType) String() string { - for k, v := range paramTypes { - if v == pt { - return k - } - } - - return "unexpected" -} - -// Not because for a single reason -// a string may be a -// ParamTypeString or a ParamTypeFile -// or a ParamTypePath or ParamTypeAlphabetical. -// -// func ParamTypeFromStd(k reflect.Kind) ParamType { - -// Kind returns the std kind of this param type. -func (pt ParamType) Kind() reflect.Kind { - switch pt { - case ParamTypeAlphabetical: - fallthrough - case ParamTypeFile: - fallthrough - case ParamTypePath: - fallthrough - case ParamTypeString: - return reflect.String - case ParamTypeNumber: - return reflect.Int - case ParamTypeInt64: - return reflect.Int64 - case ParamTypeUint8: - return reflect.Uint8 - case ParamTypeUint64: - return reflect.Uint64 - case ParamTypeBoolean: - return reflect.Bool - } - return reflect.Invalid // 0 -} - -// ValidKind will return true if at least one param type is supported -// for this std kind. -func ValidKind(k reflect.Kind) bool { - switch k { - case reflect.String: - fallthrough - case reflect.Int: - fallthrough - case reflect.Int64: - fallthrough - case reflect.Uint8: - fallthrough - case reflect.Uint64: - fallthrough - case reflect.Bool: - return true - default: - return false - } + return pt.Indent } // Assignable returns true if the "k" standard type // is assignabled to this ParamType. func (pt ParamType) Assignable(k reflect.Kind) bool { - return pt.Kind() == k + return pt.GoType == k } -var paramTypes = map[string]ParamType{ - "string": ParamTypeString, +// GetDefaultParamType accepts a list of ParamType and returns its default. +// If no `Default` specified: +// and len(paramTypes) > 0 then it will return the first one, +// otherwise it returns a "string" parameter type. +func GetDefaultParamType(paramTypes ...ParamType) ParamType { + for _, pt := range paramTypes { + if pt.Default == true { + return pt + } + } - "number": ParamTypeNumber, - "int": ParamTypeNumber, // same as number. - "long": ParamTypeInt64, - "int64": ParamTypeInt64, // same as long. - "uint8": ParamTypeUint8, - "uint64": ParamTypeUint64, + if len(paramTypes) > 0 { + return paramTypes[0] + } - "boolean": ParamTypeBoolean, - "bool": ParamTypeBoolean, // same as boolean. + return ParamType{Indent: "string", GoType: reflect.String, Default: true} +} - "alphabetical": ParamTypeAlphabetical, - "file": ParamTypeFile, - "path": ParamTypePath, - // could be named also: - // "tail": - // "wild" - // "wildcard" +// ValidKind will return true if at least one param type is supported +// for this std kind. +func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool { + for _, pt := range paramTypes { + if pt.GoType == k { + return true + } + } + return false } // LookupParamType accepts the string @@ -164,11 +73,20 @@ var paramTypes = map[string]ParamType{ // "alphabetical" // "file" // "path" -func LookupParamType(ident string) ParamType { - if typ, ok := paramTypes[ident]; ok { - return typ +func LookupParamType(indent string, paramTypes ...ParamType) (ParamType, bool) { + for _, pt := range paramTypes { + if pt.Indent == indent { + return pt, true + } + + for _, alias := range pt.Aliases { + if alias == indent { + return pt, true + } + } } - return ParamTypeUnExpected + + return ParamTypeUnExpected, false } // LookupParamTypeFromStd accepts the string representation of a standard go type. @@ -181,23 +99,15 @@ func LookupParamType(ident string) ParamType { // 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 ParamTypeNumber - case "int64": - return ParamTypeInt64 - case "uint8": - return ParamTypeUint8 - case "uint64": - return ParamTypeUint64 - case "bool": - return ParamTypeBoolean - default: - return ParamTypeUnExpected +func LookupParamTypeFromStd(goType string, paramTypes ...ParamType) (ParamType, bool) { + goType = strings.ToLower(goType) + for _, pt := range paramTypes { + if strings.ToLower(pt.GoType.String()) == goType { + return pt, true + } } + + return ParamTypeUnExpected, false } // ParamStatement is a struct diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 97920d5b..9ce125c9 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -2,6 +2,7 @@ package parser import ( "fmt" + "reflect" "strconv" "strings" @@ -10,10 +11,69 @@ import ( "github.com/kataras/iris/core/router/macro/interpreter/token" ) +var ( + // paramTypeString is the string type. + // If parameter type is missing then it defaults to String type. + // Allows anything + // Declaration: /mypath/{myparam:string} or {myparam} + paramTypeString = ast.ParamType{Indent: "string", GoType: reflect.String, Default: true} + // 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 = ast.ParamType{Indent: "number", Aliases: []string{"int"}, GoType: reflect.Int} + // ParamTypeInt64 is a number type. + // Allows only -9223372036854775808 to 9223372036854775807. + // Declaration: /mypath/{myparam:int64} or {myparam:long} + paramTypeInt64 = ast.ParamType{Indent: "int64", Aliases: []string{"long"}, GoType: reflect.Int64} + // ParamTypeUint8 a number type. + // Allows only 0 to 255. + // Declaration: /mypath/{myparam:uint8} + paramTypeUint8 = ast.ParamType{Indent: "uint8", GoType: reflect.Uint8} + // ParamTypeUint64 a number type. + // Allows only 0 to 18446744073709551615. + // Declaration: /mypath/{myparam:uint64} + paramTypeUint64 = ast.ParamType{Indent: "uint64", GoType: reflect.Uint64} + // ParamTypeBool 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:bool} or {myparam:boolean} + paramTypeBool = ast.ParamType{Indent: "bool", Aliases: []string{"boolean"}, GoType: reflect.Bool} + // ParamTypeAlphabetical is the alphabetical/letter type type. + // Allows letters only (upper or lowercase) + // Declaration: /mypath/{myparam:alphabetical} + paramTypeAlphabetical = ast.ParamType{Indent: "alphabetical", GoType: reflect.String} + // ParamTypeFile is the file single path type. + // Allows: + // letters (upper or lowercase) + // numbers (0-9) + // underscore (_) + // dash (-) + // point (.) + // no spaces! or other character + // Declaration: /mypath/{myparam:file} + paramTypeFile = ast.ParamType{Indent: "file", GoType: reflect.String} + // ParamTypePath is the multi path (or wildcard) type. + // Allows anything, should be the last part + // Declaration: /mypath/{myparam:path} + paramTypePath = ast.ParamType{Indent: "path", GoType: reflect.String, End: true} +) + +// DefaultParamTypes are the built'n parameter types. +var DefaultParamTypes = []ast.ParamType{ + paramTypeString, + paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64, + paramTypeBool, + paramTypeAlphabetical, paramTypeFile, paramTypePath, +} + // Parse takes a route "fullpath" // and returns its param statements // and an error on failure. -func Parse(fullpath string) ([]*ast.ParamStatement, error) { +func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement, error) { + if len(paramTypes) == 0 { + paramTypes = DefaultParamTypes + } + pathParts := strings.SplitN(fullpath, "/", -1) p := new(ParamParser) statements := make([]*ast.ParamStatement, 0) @@ -28,14 +88,14 @@ func Parse(fullpath string) ([]*ast.ParamStatement, error) { } p.Reset(s) - stmt, err := p.Parse() + stmt, err := p.Parse(paramTypes) if err != nil { // exit on first error return nil, err } // if we have param type path but it's not the last path part - if stmt.Type == ast.ParamTypePath && i < len(pathParts)-1 { - return nil, fmt.Errorf("param type 'path' should be lived only inside the last path segment, but was inside: %s", s) + if stmt.Type.End && i < len(pathParts)-1 { + return nil, fmt.Errorf("param type '%s' should be lived only inside the last path segment, but was inside: %s", stmt.Type, s) } statements = append(statements, stmt) @@ -77,9 +137,6 @@ const ( // per-parameter. An error code can be setted via // the "else" keyword inside a route's path. DefaultParamErrorCode = 404 - // DefaultParamType when parameter type is missing use this param type, defaults to string - // and it should be remains unless earth split in two. - DefaultParamType = ast.ParamTypeString ) // func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { @@ -102,14 +159,14 @@ func (p ParamParser) Error() error { return nil } -// Parse parses the p.src and returns its param statement +// Parse parses the p.src based on the given param types and returns its param statement // and an error on failure. -func (p *ParamParser) Parse() (*ast.ParamStatement, error) { +func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, error) { l := lexer.New(p.src) stmt := &ast.ParamStatement{ ErrorCode: DefaultParamErrorCode, - Type: DefaultParamType, + Type: ast.GetDefaultParamType(paramTypes...), Src: p.src, } @@ -132,8 +189,8 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) { case token.COLON: // type can accept both letters and numbers but not symbols ofc. nextTok := l.NextToken() - paramType := ast.LookupParamType(nextTok.Literal) - if paramType == ast.ParamTypeUnExpected { + paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...) + if !found { p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal) } stmt.Type = paramType diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index 25fb3400..ffdd5335 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -16,7 +16,7 @@ func TestParseParamError(t *testing.T) { input := "{id" + string(illegalChar) + "int range(1,5) else 404}" p := NewParamParser(input) - _, err := p.Parse() + _, err := p.Parse(DefaultParamTypes) if err == nil { t.Fatalf("expecting not empty error on input '%s'", input) @@ -32,7 +32,7 @@ func TestParseParamError(t *testing.T) { // success input2 := "{id:uint64 range(1,5) else 404}" p.Reset(input2) - _, err = p.Parse() + _, err = p.Parse(DefaultParamTypes) if err != nil { t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error()) @@ -40,6 +40,16 @@ func TestParseParamError(t *testing.T) { // } +// mustLookupParamType same as `ast.LookupParamType` but it panics if "indent" does not match with a valid Param Type. +func mustLookupParamType(indent string) ast.ParamType { + pt, found := ast.LookupParamType(indent, DefaultParamTypes...) + if !found { + panic("param type '" + indent + "' is not part of the provided param types") + } + + return pt +} + func TestParseParam(t *testing.T) { tests := []struct { valid bool @@ -49,7 +59,7 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:number min(1) max(5) else 404}", Name: "id", - Type: ast.ParamTypeNumber, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "min", @@ -65,7 +75,7 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:number range(1,5)}", Name: "id", - Type: ast.ParamTypeNumber, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "range", @@ -77,7 +87,7 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{file:path contains(.)}", Name: "file", - Type: ast.ParamTypePath, + Type: mustLookupParamType("path"), Funcs: []ast.ParamFunc{ { Name: "contains", @@ -89,14 +99,14 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{username:alphabetical}", Name: "username", - Type: ast.ParamTypeAlphabetical, + Type: mustLookupParamType("alphabetical"), ErrorCode: 404, }}, // 3 {true, ast.ParamStatement{ Src: "{myparam}", Name: "myparam", - Type: ast.ParamTypeString, + Type: mustLookupParamType("string"), ErrorCode: 404, }}, // 4 {false, @@ -110,14 +120,14 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{myparam2}", Name: "myparam2", // we now allow integers to the parameter names. - Type: ast.ParamTypeString, + Type: ast.GetDefaultParamType(DefaultParamTypes...), ErrorCode: 404, }}, // 6 {true, ast.ParamStatement{ Src: "{id:number even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) Name: "id", - Type: ast.ParamTypeNumber, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "even"}, @@ -128,37 +138,44 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:int64 else 404}", Name: "id", - Type: ast.ParamTypeInt64, + Type: mustLookupParamType("int64"), ErrorCode: 404, }}, // 8 {true, ast.ParamStatement{ Src: "{id:long else 404}", // backwards-compatible test. Name: "id", - Type: ast.ParamTypeInt64, + Type: mustLookupParamType("int64"), ErrorCode: 404, }}, // 9 {true, ast.ParamStatement{ - Src: "{has:bool else 404}", - Name: "has", - Type: ast.ParamTypeBoolean, + Src: "{id:long else 404}", + Name: "id", + Type: mustLookupParamType("long"), // backwards-compatible test of LookupParamType. ErrorCode: 404, }}, // 10 {true, ast.ParamStatement{ - Src: "{has:boolean else 404}", // backwards-compatible test. + Src: "{has:bool else 404}", Name: "has", - Type: ast.ParamTypeBoolean, + Type: mustLookupParamType("bool"), ErrorCode: 404, }}, // 11 + {true, + ast.ParamStatement{ + Src: "{has:boolean else 404}", // backwards-compatible test. + Name: "has", + Type: mustLookupParamType("bool"), + ErrorCode: 404, + }}, // 12 } p := new(ParamParser) for i, tt := range tests { p.Reset(tt.expectedStatement.Src) - resultStmt, err := p.Parse() + resultStmt, err := p.Parse(DefaultParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) @@ -185,7 +202,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{id:number min(1) max(5) else 404}", Name: "id", - Type: ast.ParamTypeNumber, + Type: paramTypeNumber, Funcs: []ast.ParamFunc{ { Name: "min", @@ -201,7 +218,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{id:uint64 range(1,5)}", // test alternative (backwards-compatibility) "int" Name: "id", - Type: ast.ParamTypeUint64, + Type: paramTypeUint64, Funcs: []ast.ParamFunc{ { Name: "range", @@ -214,7 +231,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{file:path contains(.)}", Name: "file", - Type: ast.ParamTypePath, + Type: paramTypePath, Funcs: []ast.ParamFunc{ { Name: "contains", @@ -227,7 +244,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{username:alphabetical}", Name: "username", - Type: ast.ParamTypeAlphabetical, + Type: paramTypeAlphabetical, ErrorCode: 404, }, }}, // 3 @@ -235,7 +252,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{myparam}", Name: "myparam", - Type: ast.ParamTypeString, + Type: paramTypeString, ErrorCode: 404, }, }}, // 4 @@ -251,7 +268,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{myparam2}", Name: "myparam2", // we now allow integers to the parameter names. - Type: ast.ParamTypeString, + Type: paramTypeString, ErrorCode: 404, }, }}, // 6 @@ -259,7 +276,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{file:path}", Name: "file", - Type: ast.ParamTypePath, + Type: paramTypePath, ErrorCode: 404, }, }}, // 7 diff --git a/deprecated.go b/deprecated.go deleted file mode 100644 index d44707b2..00000000 --- a/deprecated.go +++ /dev/null @@ -1 +0,0 @@ -package iris From 52a07df0f4a9534b9f085f4bcc39d988050778a2 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 13 Sep 2018 02:53:13 +0300 Subject: [PATCH 14/42] upstream fixes Former-commit-id: 574060e41ace86cd86588795eadb1ad4083ec630 --- Gopkg.lock | 4 ++-- Gopkg.toml | 2 +- README.md | 3 ++- configuration.go | 4 ++-- context/context.go | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index d392261b..1f3d8186 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -260,10 +260,10 @@ version = "v1.28.2" [[projects]] - branch = "v2" name = "gopkg.in/yaml.v2" packages = ["."] - revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" + revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" + version = "2.2.1" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index b0efe846..8274cd48 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -71,8 +71,8 @@ name = "golang.org/x/crypto" [[constraint]] - branch = "v2" name = "gopkg.in/yaml.v2" + version = "2.2.1" [[constraint]] branch = "master" diff --git a/README.md b/README.md index b8b32f5e..3ae3103a 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,8 @@ func main() { ctx.Writef("User with ID: %d", id) }) - // However, this one will match /user/john/ and also /user/john/send. + // However, this one will match /user/john/send and also /user/john/everything/else/here + // but will not match /user/john neither /user/john/. app.Post("/user/{name:string}/{action:path}", func(ctx iris.Context) { name := ctx.Params().Get("name") action := ctx.Params().Get("action") diff --git a/configuration.go b/configuration.go index 3dedfce4..6ee8071f 100644 --- a/configuration.go +++ b/configuration.go @@ -118,8 +118,8 @@ func YAML(filename string) Configuration { // see `WithGlobalConfiguration` for more information. // // Usage: -// app.Configure(iris.WithConfiguration(iris.YAML("myconfig.tml"))) or -// app.Run([iris.Runner], iris.WithConfiguration(iris.YAML("myconfig.tml"))). +// app.Configure(iris.WithConfiguration(iris.TOML("myconfig.tml"))) or +// app.Run([iris.Runner], iris.WithConfiguration(iris.TOML("myconfig.tml"))). func TOML(filename string) Configuration { c := DefaultConfiguration() diff --git a/context/context.go b/context/context.go index b558fe9a..1ca344d0 100644 --- a/context/context.go +++ b/context/context.go @@ -1724,7 +1724,7 @@ type ( Subdomain string `json:"subdomain" form:"referrer_subdomain" xml:"Subdomain" yaml:"Subdomain" toml:"Subdomain"` Domain string `json:"domain" form:"referrer_domain" xml:"Domain" yaml:"Domain" toml:"Domain"` Tld string `json:"tld" form:"referrer_tld" xml:"Tld" yaml:"Tld" toml:"Tld"` - Path string `jsonn:"path" form:"referrer_path" xml:"Path" yaml:"Path" toml:"Path"` + Path string `json:"path" form:"referrer_path" xml:"Path" yaml:"Path" toml:"Path"` Query string `json:"query" form:"referrer_query" xml:"Query" yaml:"Query" toml:"GoogleType"` GoogleType ReferrerGoogleSearchType `json:"googleType" form:"referrer_google_type" xml:"GoogleType" yaml:"GoogleType" toml:"GoogleType"` } From dc3c38b189e4e7720908d6e1dd8520e532f79443 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 26 Sep 2018 11:37:11 +0300 Subject: [PATCH 15/42] add the ability to add custom parameter types to the interpreter and mapped macros with any number of macro functions - example added - although it's working it is not ready yet - I have to do some cleanup, doc comments and a TODO Former-commit-id: 8ac751b649a3b8e59948fd4c89ad53d25f49d0d5 --- README.md | 6 +- _examples/routing/macros/main.go | 64 +++ context/context.go | 134 +------ context/request_params.go | 159 ++++++++ core/memstore/memstore.go | 208 +++++----- core/router/api_builder.go | 12 +- core/router/macro.go | 290 +------------- core/router/macro/interpreter/ast/ast.go | 128 +++--- .../router/macro/interpreter/parser/parser.go | 67 +--- .../macro/interpreter/parser/parser_test.go | 58 ++- core/router/macro/macro.go | 222 ++++------- core/router/macro/macro_test.go | 50 +-- core/router/macro/macros.go | 375 ++++++++++++++++++ core/router/macro/template.go | 24 +- core/router/party.go | 6 +- core/router/route.go | 2 +- hero/di.go | 11 + hero/di/func.go | 7 +- hero/di/object.go | 5 + hero/di/reflect.go | 12 +- hero/handler.go | 18 +- hero/param.go | 58 +-- mvc/controller.go | 10 +- mvc/controller_handle_test.go | 8 +- mvc/controller_method_parser.go | 86 ++-- mvc/param.go | 86 ++-- 26 files changed, 1070 insertions(+), 1036 deletions(-) create mode 100644 _examples/routing/macros/main.go create mode 100644 context/request_params.go create mode 100644 core/router/macro/macros.go diff --git a/README.md b/README.md index 3ae3103a..9981c0b2 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ latLonRegex, _ := regexp.Compile(latLonExpr) // Register your custom argument-less macro function to the :string param type. // MatchString is a type of func(string) bool, so we use it as it is. -app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) +app.Macros().Get("string").RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate()}/{lon:string coordinate()}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -175,7 +175,7 @@ Register your custom macro function which accepts two int arguments. ```go -app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { +app.Macros().Get("string").RegisterFunc("range", func(minLength, maxLength int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= minLength && len(paramValue) <= maxLength } @@ -191,7 +191,7 @@ app.Get("/limitchar/{name:string range(1,200) else 400}", func(ctx iris.Context) Register your custom macro function which accepts a slice of strings `[...,...]`. ```go -app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { +app.Macros().Get("string").RegisterFunc("has", func(validNames []string) func(string) bool { return func(paramValue string) bool { for _, validName := range validNames { if validName == paramValue { diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go new file mode 100644 index 00000000..f45d4031 --- /dev/null +++ b/_examples/routing/macros/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "reflect" + "strconv" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + // "github.com/kataras/iris/core/memstore" + "github.com/kataras/iris/hero" +) + +func main() { + app := iris.New() + app.Logger().SetLevel("debug") + + // Let's see how we can register a custom macro such as ":uint32" or ":small" for its alias (optionally) for Uint32 types. + app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { + _, err := strconv.ParseUint(paramValue, 10, 32) + return err == nil + }). + RegisterFunc("min", func(min uint32) func(string) bool { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 32) + if err != nil { + return false + } + + return uint32(n) >= min + } + }) + + /* TODO: + somehow define one-time how the parameter should be parsed to a particular type (go std or custom) + tip: we can change the original value from string to X using the entry's.ValueRaw + */ + + context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { + // return func(store memstore.Store) uint32 { + // param, _ := store.GetEntryAt(paramIndex) + // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) + // return uint32(paramValueAsUint32) + // } + return func(ctx context.Context) uint32 { + param := ctx.Params().GetEntryAt(paramIndex) + paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) + return uint32(paramValueAsUint32) + } + } + // + + app.Get("/test_uint32/{myparam:uint32 min(10)}", hero.Handler(func(paramValue uint32) string { + return fmt.Sprintf("Value of the parameter is: %d\n", paramValue) + })) + + app.Get("test_uint64/{myparam:uint64}", handler) + + app.Run(iris.Addr(":8080")) +} + +func handler(ctx context.Context) { + ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) +} diff --git a/context/context.go b/context/context.go index 1ca344d0..a213b3b7 100644 --- a/context/context.go +++ b/context/context.go @@ -76,138 +76,6 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error { return u(data, v) } -// RequestParams is a key string - value string storage which -// context's request dynamic path params are being kept. -// Empty if the route is static. -type RequestParams struct { - store memstore.Store -} - -// Set adds a key-value pair to the path parameters values -// it's being called internally so it shouldn't be used as a local storage by the user, use `ctx.Values()` instead. -func (r *RequestParams) Set(key, value string) { - r.store.Set(key, value) -} - -// Visit accepts a visitor which will be filled -// by the key-value params. -func (r *RequestParams) Visit(visitor func(key string, value string)) { - r.store.Visit(func(k string, v interface{}) { - visitor(k, v.(string)) // always string here. - }) -} - -var emptyEntry memstore.Entry - -// GetEntryAt returns the internal Entry of the memstore based on its index, -// the stored index by the router. -// If not found then it returns a zero Entry and false. -func (r RequestParams) GetEntryAt(index int) (memstore.Entry, bool) { - if len(r.store) > index { - return r.store[index], true - } - return emptyEntry, false -} - -// GetEntry returns the internal Entry of the memstore based on its "key". -// If not found then it returns a zero Entry and false. -func (r RequestParams) GetEntry(key string) (memstore.Entry, bool) { - // we don't return the pointer here, we don't want to give the end-developer - // the strength to change the entry that way. - if e := r.store.GetEntry(key); e != nil { - return *e, true - } - return emptyEntry, false -} - -// Get returns a path parameter's value based on its route's dynamic path key. -func (r RequestParams) Get(key string) string { - return r.store.GetString(key) -} - -// GetTrim returns a path parameter's value without trailing spaces based on its route's dynamic path key. -func (r RequestParams) GetTrim(key string) string { - return strings.TrimSpace(r.Get(key)) -} - -// GetEscape returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. -func (r RequestParams) GetEscape(key string) string { - return DecodeQuery(DecodeQuery(r.Get(key))) -} - -// GetDecoded returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. -// same as `GetEscape`. -func (r RequestParams) GetDecoded(key string) string { - return r.GetEscape(key) -} - -// GetInt returns the path parameter's value as int, based on its key. -// It checks for all available types of int, including int64, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetInt(key string) (int, error) { - return r.store.GetInt(key) -} - -// GetInt64 returns the path paramete's value as int64, based on its key. -// It checks for all available types of int, including int, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetInt64(key string) (int64, error) { - return r.store.GetInt64(key) -} - -// GetFloat64 returns a path parameter's value based as float64 on its route's dynamic path key. -// It checks for all available types of int, including float64, int, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetFloat64(key string) (float64, error) { - return r.store.GetFloat64(key) -} - -// GetUint8 returns the path parameter's value as uint8, based on its key. -// It checks for all available types of int, including int, string. -// It will return 0 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetUint8(key string) (uint8, error) { - return r.store.GetUint8(key) -} - -// GetUint64 returns the path parameter's value as uint64, based on its key. -// It checks for all available types of int, including int, uint64, int64, strings etc. -// It will return 0 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetUint64(key string) (uint64, error) { - return r.store.GetUint64(key) -} - -// GetBool returns the path parameter's value as bool, based on its key. -// 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". -// Any other value returns an error. -func (r RequestParams) GetBool(key string) (bool, error) { - return r.store.GetBool(key) -} - -// GetIntUnslashed same as Get but it removes the first slash if found. -// Usage: Get an id from a wildcard path. -// -// Returns -1 with an error if the parameter couldn't be found. -func (r RequestParams) GetIntUnslashed(key string) (int, error) { - v := r.Get(key) - if v != "" { - if len(v) > 1 { - if v[0] == '/' { - v = v[1:] - } - } - return strconv.Atoi(v) - - } - - return -1, fmt.Errorf("unable to find int for '%s'", key) -} - -// Len returns the full length of the parameters. -func (r RequestParams) Len() int { - return r.store.Len() -} - // Context is the midle-man server's "object" for the clients. // // A New context is being acquired from a sync.Pool on each connection. @@ -1123,7 +991,7 @@ func NewContext(app Application) Context { func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) { ctx.handlers = nil // will be filled by router.Serve/HTTP ctx.values = ctx.values[0:0] // >> >> by context.Values().Set - ctx.params.store = ctx.params.store[0:0] + ctx.params.Store = ctx.params.Store[0:0] ctx.request = r ctx.currentHandlerIndex = 0 ctx.writer = AcquireResponseWriter() diff --git a/context/request_params.go b/context/request_params.go new file mode 100644 index 00000000..ebb20afb --- /dev/null +++ b/context/request_params.go @@ -0,0 +1,159 @@ +package context + +import ( + "reflect" + "strconv" + "strings" + + "github.com/kataras/iris/core/memstore" +) + +// RequestParams is a key string - value string storage which +// context's request dynamic path params are being kept. +// Empty if the route is static. +type RequestParams struct { + memstore.Store +} + +// GetEntryAt will return the parameter's internal store's `Entry` based on the index. +// If not found it will return an emptry `Entry`. +func (r *RequestParams) GetEntryAt(index int) memstore.Entry { + entry, _ := r.Store.GetEntryAt(index) + return entry +} + +// GetEntry will return the parameter's internal store's `Entry` based on its name/key. +// If not found it will return an emptry `Entry`. +func (r *RequestParams) GetEntry(key string) memstore.Entry { + entry, _ := r.Store.GetEntry(key) + return entry +} + +// Visit accepts a visitor which will be filled +// by the key-value params. +func (r *RequestParams) Visit(visitor func(key string, value string)) { + r.Store.Visit(func(k string, v interface{}) { + visitor(k, v.(string)) // always string here. + }) +} + +// Get returns a path parameter's value based on its route's dynamic path key. +func (r RequestParams) Get(key string) string { + return r.GetString(key) +} + +// GetTrim returns a path parameter's value without trailing spaces based on its route's dynamic path key. +func (r RequestParams) GetTrim(key string) string { + return strings.TrimSpace(r.Get(key)) +} + +// GetEscape returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. +func (r RequestParams) GetEscape(key string) string { + return DecodeQuery(DecodeQuery(r.Get(key))) +} + +// GetDecoded returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. +// same as `GetEscape`. +func (r RequestParams) GetDecoded(key string) string { + return r.GetEscape(key) +} + +// GetIntUnslashed same as Get but it removes the first slash if found. +// Usage: Get an id from a wildcard path. +// +// Returns -1 and false if not path parameter with that "key" found. +func (r RequestParams) GetIntUnslashed(key string) (int, bool) { + v := r.Get(key) + if v != "" { + if len(v) > 1 { + if v[0] == '/' { + v = v[1:] + } + } + + vInt, err := strconv.Atoi(v) + if err != nil { + return -1, false + } + return vInt, true + } + + return -1, false +} + +var ( + ParamResolvers = map[reflect.Kind]func(paramIndex int) interface{}{ + reflect.String: func(paramIndex int) interface{} { + return func(ctx Context) string { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(string) + } + }, + reflect.Int: func(paramIndex int) interface{} { + return func(ctx Context) int { + v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) + return v + } + }, + reflect.Int64: func(paramIndex int) interface{} { + return func(ctx Context) int64 { + v, _ := ctx.Params().GetEntryAt(paramIndex).Int64Default(0) + return v + } + }, + reflect.Uint8: func(paramIndex int) interface{} { + return func(ctx Context) uint8 { + v, _ := ctx.Params().GetEntryAt(paramIndex).Uint8Default(0) + return v + } + }, + reflect.Uint64: func(paramIndex int) interface{} { + return func(ctx Context) uint64 { + v, _ := ctx.Params().GetEntryAt(paramIndex).Uint64Default(0) + return v + } + }, + reflect.Bool: func(paramIndex int) interface{} { + return func(ctx Context) bool { + v, _ := ctx.Params().GetEntryAt(paramIndex).BoolDefault(false) + return v + } + }, + } +) + +// ParamResolverByKindAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type +// and the parameter's index based on the registered path. +// Usage: nameResolver := ParamResolverByKindAndKey(reflect.String, 0) +// Inside a Handler: nameResolver.Call(ctx)[0] +// it will return the reflect.Value Of the exact type of the parameter(based on the path parameters and macros). +// It is only useful for dynamic binding of the parameter, it is used on "hero" package and it should be modified +// only when Macros are modified in such way that the default selections for the available go std types are not enough. +// +// Returns empty value and false if "k" does not match any valid parameter resolver. +func ParamResolverByKindAndIndex(k reflect.Kind, paramIndex int) (reflect.Value, bool) { + /* NO: + // This could work but its result is not exact type, so direct binding is not possible. + resolver := m.ParamResolver + fn := func(ctx context.Context) interface{} { + entry, _ := ctx.Params().GetEntry(paramName) + return resolver(entry) + } + // + + // This works but it is slower on serve-time. + paramNameValue := []reflect.Value{reflect.ValueOf(paramName)} + var fnSignature func(context.Context) string + return reflect.MakeFunc(reflect.ValueOf(&fnSignature).Elem().Type(), func(in []reflect.Value) []reflect.Value { + return in[0].MethodByName("Params").Call(emptyIn)[0].MethodByName("Get").Call(paramNameValue) + // return []reflect.Value{reflect.ValueOf(in[0].Interface().(context.Context).Params().Get(paramName))} + }) + // + */ + + r, ok := ParamResolvers[k] + if !ok || r == nil { + return reflect.Value{}, false + } + + return reflect.ValueOf(r(paramIndex)), true +} diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 48986514..52c3b340 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -94,15 +94,17 @@ func (e Entry) IntDefault(def int) (int, error) { if v == nil { return def, errFindParse.Format("int", e.Key) } - if vint, ok := v.(int); ok { - return vint, nil - } else if vstring, sok := v.(string); sok && vstring != "" { - vint, err := strconv.Atoi(vstring) + + switch vv := v.(type) { + case string: + val, err := strconv.Atoi(vv) if err != nil { return def, err } - return vint, nil + return val, nil + case int: + return vv, nil } return def, errFindParse.Format("int", e.Key) @@ -116,16 +118,13 @@ func (e Entry) Int64Default(def int64) (int64, error) { return def, errFindParse.Format("int64", e.Key) } - if vint64, ok := v.(int64); ok { - return vint64, nil - } - - if vint, ok := v.(int); ok { - return int64(vint), nil - } - - if vstring, sok := v.(string); sok { - return strconv.ParseInt(vstring, 10, 64) + switch vv := v.(type) { + case string: + return strconv.ParseInt(vv, 10, 64) + case int64: + return vv, nil + case int: + return int64(vv), nil } return def, errFindParse.Format("int64", e.Key) @@ -135,30 +134,23 @@ func (e Entry) Int64Default(def int64) (int64, error) { // If not found returns "def" and a non-nil error. func (e Entry) Float64Default(def float64) (float64, error) { v := e.ValueRaw - if v == nil { return def, errFindParse.Format("float64", e.Key) } - if vfloat32, ok := v.(float32); ok { - return float64(vfloat32), nil - } - - if vfloat64, ok := v.(float64); ok { - return vfloat64, nil - } - - if vint, ok := v.(int); ok { - return float64(vint), nil - } - - if vstring, sok := v.(string); sok { - vfloat64, err := strconv.ParseFloat(vstring, 64) + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 64) if err != nil { return def, err } - - return vfloat64, nil + return val, nil + case float32: + return float64(vv), nil + case float64: + return vv, nil + case int: + return float64(vv), nil } return def, errFindParse.Format("float64", e.Key) @@ -168,30 +160,24 @@ func (e Entry) Float64Default(def float64) (float64, error) { // If not found returns "def" and a non-nil error. func (e Entry) Float32Default(key string, def float32) (float32, error) { v := e.ValueRaw - if v == nil { return def, errFindParse.Format("float32", e.Key) } - if vfloat32, ok := v.(float32); ok { - return vfloat32, nil - } - - if vfloat64, ok := v.(float64); ok { - return float32(vfloat64), nil - } - - if vint, ok := v.(int); ok { - return float32(vint), nil - } - - if vstring, sok := v.(string); sok { - vfloat32, err := strconv.ParseFloat(vstring, 32) + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 32) if err != nil { return def, err } - return float32(vfloat32), nil + return float32(val), nil + case float32: + return vv, nil + case float64: + return float32(vv), nil + case int: + return float32(vv), nil } return def, errFindParse.Format("float32", e.Key) @@ -205,26 +191,23 @@ func (e Entry) Uint8Default(def uint8) (uint8, error) { return def, errFindParse.Format("uint8", e.Key) } - if vuint8, ok := v.(uint8); ok { - return vuint8, nil - } - - if vint, ok := v.(int); ok { - if vint < 0 || vint > 255 { - return def, errFindParse.Format("uint8", e.Key) - } - return uint8(vint), nil - } - - if vstring, sok := v.(string); sok { - vuint64, err := strconv.ParseUint(vstring, 10, 8) + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 8) if err != nil { return def, err } - if vuint64 > 255 { + if val > 255 { return def, errFindParse.Format("uint8", e.Key) } - return uint8(vuint64), nil + return uint8(val), nil + case uint8: + return vv, nil + case int: + if vv < 0 || vv > 255 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil } return def, errFindParse.Format("uint8", e.Key) @@ -238,20 +221,15 @@ func (e Entry) Uint64Default(def uint64) (uint64, error) { return def, errFindParse.Format("uint64", e.Key) } - if vuint64, ok := v.(uint64); ok { - return vuint64, nil - } - - if vint64, ok := v.(int64); ok { - return uint64(vint64), nil - } - - if vint, ok := v.(int); ok { - return uint64(vint), nil - } - - if vstring, sok := v.(string); sok { - return strconv.ParseUint(vstring, 10, 64) + switch vv := v.(type) { + case string: + return strconv.ParseUint(vv, 10, 64) + case uint64: + return vv, nil + case int64: + return uint64(vv), nil + case int: + return uint64(vv), nil } return def, errFindParse.Format("uint64", e.Key) @@ -269,20 +247,17 @@ func (e Entry) BoolDefault(def bool) (bool, error) { return def, errFindParse.Format("bool", e.Key) } - if vBoolean, ok := v.(bool); ok { - return vBoolean, nil - } - - if vString, ok := v.(string); ok { - b, err := strconv.ParseBool(vString) + switch vv := v.(type) { + case string: + val, err := strconv.ParseBool(vv) if err != nil { return def, err } - return b, nil - } - - if vInt, ok := v.(int); ok { - if vInt == 1 { + return val, nil + case bool: + return vv, nil + case int: + if vv == 1 { return true, nil } return false, nil @@ -394,28 +369,39 @@ func (r *Store) SetImmutable(key string, value interface{}) (Entry, bool) { return r.Save(key, value, true) } +var emptyEntry Entry + // GetEntry returns a pointer to the "Entry" found with the given "key" -// if nothing found then it returns nil, so be careful with that, -// it's not supposed to be used by end-developers. -func (r *Store) GetEntry(key string) *Entry { +// if nothing found then it returns an empty Entry and false. +func (r *Store) GetEntry(key string) (Entry, bool) { args := *r n := len(args) for i := 0; i < n; i++ { - kv := &args[i] - if kv.Key == key { - return kv + if kv := args[i]; kv.Key == key { + return kv, true } } - return nil + return emptyEntry, false +} + +// GetEntryAt returns the internal Entry of the memstore based on its index, +// the stored index by the router. +// If not found then it returns a zero Entry and false. +func (r *Store) GetEntryAt(index int) (Entry, bool) { + args := *r + if len(args) > index { + return args[index], true + } + return emptyEntry, false } // GetDefault returns the entry's value based on its key. // If not found returns "def". // This function checks for immutability as well, the rest don't. func (r *Store) GetDefault(key string, def interface{}) interface{} { - v := r.GetEntry(key) - if v == nil || v.ValueRaw == nil { + v, ok := r.GetEntry(key) + if !ok || v.ValueRaw == nil { return def } vv := v.Value() @@ -444,8 +430,8 @@ func (r *Store) Visit(visitor func(key string, value interface{})) { // GetStringDefault returns the entry's value as string, based on its key. // If not found returns "def". func (r *Store) GetStringDefault(key string, def string) string { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return def } @@ -465,8 +451,8 @@ func (r *Store) GetStringTrim(name string) string { // GetInt returns the entry's value as int, based on its key. // If not found returns -1 and a non-nil error. func (r *Store) GetInt(key string) (int, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return 0, errFindParse.Format("int", key) } return v.IntDefault(-1) @@ -485,8 +471,8 @@ func (r *Store) GetIntDefault(key string, def int) int { // GetUint8 returns the entry's value as uint8, based on its key. // If not found returns 0 and a non-nil error. func (r *Store) GetUint8(key string) (uint8, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return 0, errFindParse.Format("uint8", key) } return v.Uint8Default(0) @@ -505,8 +491,8 @@ func (r *Store) GetUint8Default(key string, def uint8) uint8 { // GetUint64 returns the entry's value as uint64, based on its key. // If not found returns 0 and a non-nil error. func (r *Store) GetUint64(key string) (uint64, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return 0, errFindParse.Format("uint64", key) } return v.Uint64Default(0) @@ -525,8 +511,8 @@ func (r *Store) GetUint64Default(key string, def uint64) uint64 { // GetInt64 returns the entry's value as int64, based on its key. // If not found returns -1 and a non-nil error. func (r *Store) GetInt64(key string) (int64, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return -1, errFindParse.Format("int64", key) } return v.Int64Default(-1) @@ -545,8 +531,8 @@ func (r *Store) GetInt64Default(key string, def int64) int64 { // GetFloat64 returns the entry's value as float64, based on its key. // If not found returns -1 and a non nil error. func (r *Store) GetFloat64(key string) (float64, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return -1, errFindParse.Format("float64", key) } return v.Float64Default(-1) @@ -569,8 +555,8 @@ func (r *Store) GetFloat64Default(key string, def float64) float64 { // // If not found returns false and a non-nil error. func (r *Store) GetBool(key string) (bool, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return false, errFindParse.Format("bool", key) } diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 0022428a..614bddac 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -68,7 +68,7 @@ func (r *repository) getAll() []*Route { // and child routers. type APIBuilder struct { // the api builder global macros registry - macros *macro.Map + macros *macro.Macros // the api builder global handlers per status code registry (used for custom http errors) errorCodeHandlers *ErrorCodeHandlers // the api builder global routes repository @@ -116,7 +116,7 @@ var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handl // which is responsible to build the API and the router handler. func NewAPIBuilder() *APIBuilder { api := &APIBuilder{ - macros: defaultMacros(), + macros: macro.Defaults, errorCodeHandlers: defaultErrorCodeHandlers(), reporter: errors.NewReporter(), relativePath: "/", @@ -246,7 +246,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co ) for _, m := range methods { - route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, api.macros) + route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros) if err != nil { // template path parser errors: api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path) return nil // fail on first error. @@ -411,11 +411,11 @@ func (api *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party { return api.Subdomain(SubdomainWildcardIndicator, middleware...) } -// Macros returns the macro map which is responsible -// to register custom macro functions for all routes. +// Macros returns the macro collection that is responsible +// to register custom macros with their own parameter types and their macro functions for all routes. // // Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path -func (api *APIBuilder) Macros() *macro.Map { +func (api *APIBuilder) Macros() *macro.Macros { return api.macros } diff --git a/core/router/macro.go b/core/router/macro.go index e46a07c7..02c0ea95 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -1,295 +1,15 @@ package router import ( + "fmt" "net/http" - "strconv" "strings" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/core/router/macro/interpreter/ast" ) -// defaultMacros returns a new macro map which -// contains the default router's named param types functions. -func defaultMacros() *macro.Map { - macros := macro.NewMap() - // registers the String and Int default macro funcs - // user can add or override of his own funcs later on - // i.e: - // app.Macro.String.RegisterFunc("equal", func(eqWith string) func(string) bool { - // return func(paramValue string) bool { - // return eqWith == paramValue - // }}) - registerBuiltinsMacroFuncs(macros) - - return macros -} - -func registerBuiltinsMacroFuncs(out *macro.Map) { - // register the String which is the default type if not - // parameter type is specified or - // if a given parameter into path given but the func doesn't exist on the - // parameter type's function list. - // - // these can be overridden by the user, later on. - registerStringMacroFuncs(out.String) - registerNumberMacroFuncs(out.Number) - registerInt64MacroFuncs(out.Int64) - registerUint8MacroFuncs(out.Uint8) - registerUint64MacroFuncs(out.Uint64) - registerAlphabeticalMacroFuncs(out.Alphabetical) - registerFileMacroFuncs(out.File) - registerPathMacroFuncs(out.Path) -} - -// String -// anything one part -func registerStringMacroFuncs(out *macro.Macro) { - // this can be used everywhere, it's to help users to define custom regexp expressions - // on all macros - out.RegisterFunc("regexp", func(expr string) macro.EvaluatorFunc { - regexpEvaluator := macro.MustNewEvaluatorFromRegexp(expr) - return regexpEvaluator - }) - - // checks if param value starts with the 'prefix' arg - out.RegisterFunc("prefix", func(prefix string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.HasPrefix(paramValue, prefix) - } - }) - - // checks if param value ends with the 'suffix' arg - out.RegisterFunc("suffix", func(suffix string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.HasSuffix(paramValue, suffix) - } - }) - - // checks if param value contains the 's' arg - out.RegisterFunc("contains", func(s string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.Contains(paramValue, s) - } - }) - - // checks if param value's length is at least 'min' - out.RegisterFunc("min", func(min int) macro.EvaluatorFunc { - return func(paramValue string) bool { - return len(paramValue) >= min - } - }) - // checks if param value's length is not bigger than 'max' - out.RegisterFunc("max", func(max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - return max >= len(paramValue) - } - }) -} - -// 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 { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= min - } - }) - - // checks if the param value's int representation is - // smaller or equal than 'max' - out.RegisterFunc("max", func(max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n <= max - } - }) - - // checks if the param value's int representation is - // between min and max, including 'min' and 'max' - out.RegisterFunc("range", func(min, max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true - } - }) -} - -// 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 - } - }) -} - -// Uint8 -// 0 to 255. -func registerUint8MacroFuncs(out *macro.Macro) { - // checks if the param value's uint8 representation is - // bigger or equal than 'min' - out.RegisterFunc("min", func(min uint8) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - return uint8(n) >= min - } - }) - - // checks if the param value's uint8 representation is - // smaller or equal than 'max' - out.RegisterFunc("max", func(max uint8) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - return uint8(n) <= max - } - }) - - // checks if the param value's uint8 representation is - // between min and max, including 'min' and 'max' - out.RegisterFunc("range", func(min, max uint8) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - if v := uint8(n); v < min || v > 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) { - -} - -// File -// letters (upper or lowercase) -// numbers (0-9) -// underscore (_) -// dash (-) -// point (.) -// no spaces! or other character -func registerFileMacroFuncs(out *macro.Macro) { - -} - -// Path -// File+slashes(anywhere) -// should be the latest param, it's the wildcard -func registerPathMacroFuncs(out *macro.Macro) { - -} - // compileRoutePathAndHandlers receives a route info and returns its parsed/"compiled" path // and the new handlers (prepend all the macro's handler, if any). // @@ -325,9 +45,9 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) { // then the tmpl.Params will be filled, // so no any further check needed for i, p := range tmpl.Params { - if p.Type == ast.ParamTypePath { + if ast.IsTrailing(p.Type) { if i != len(tmpl.Params)-1 { - return "", errors.New("parameter type \"ParamTypePath\" should be putted to the very last of a path") + return "", fmt.Errorf("parameter type \"%s\" should be putted to the very last of a path", p.Type.Indent()) } routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1) } else { @@ -338,7 +58,7 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) { return routePath, nil } -// note: returns nil if not needed, the caller(router) should be check for that before adding that on route's Middleware +// Note: returns nil if not needed, the caller(router) should check for that before adding that on route's Middleware. func convertTmplToHandler(tmpl *macro.Template) context.Handler { needMacroHandler := false @@ -347,7 +67,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) // 2. if we don't have any named params then we don't need a handler too. for _, p := range tmpl.Params { - if len(p.Funcs) == 0 && (p.Type == ast.ParamTypeUnExpected || p.Type == ast.ParamTypeString || p.Type == ast.ParamTypePath) && p.ErrCode == http.StatusNotFound { + if len(p.Funcs) == 0 && (ast.IsMaster(p.Type) || ast.IsTrailing(p.Type)) && p.ErrCode == http.StatusNotFound { } else { // println("we need handler for: " + tmpl.Src) needMacroHandler = true diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index fa695226..a3508373 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -1,43 +1,68 @@ package ast -import ( - "reflect" - "strings" +type ( + // ParamType holds the necessary information about a parameter type for the parser to lookup for. + ParamType interface { + // The name of the parameter type. + // Indent should contain the characters for the parser. + Indent() string + } + + // MasterParamType if implemented and its `Master()` returns true then empty type param will be translated to this param type. + // Also its functions will be available to the rest of the macro param type's funcs. + // + // Only one Master is allowed. + MasterParamType interface { + ParamType + Master() bool + } + + // TrailingParamType if implemented and its `Trailing()` returns true + // then it should be declared at the end of a route path and can accept any trailing path segment as one parameter. + TrailingParamType interface { + ParamType + Trailing() bool + } + + // AliasParamType if implemeneted nad its `Alias()` returns a non-empty string + // then the param type can be written with that string literal too. + AliasParamType interface { + ParamType + Alias() string + } ) -// ParamType holds the necessary information about a parameter type. -type ParamType struct { - Indent string // the name of the parameter type. - Aliases []string // any aliases, can be empty. - - GoType reflect.Kind // the go type useful for "mvc" and "hero" bindings. - - Default bool // if true then empty type param will target this and its functions will be available to the rest of the param type's funcs. - End bool // if true then it should be declared at the end of a route path and can accept any trailing path segment as one parameter. - - invalid bool // only true if returned by the parser via `LookupParamType`. +// IsMaster returns true if the "pt" param type is a master one. +func IsMaster(pt ParamType) bool { + p, ok := pt.(MasterParamType) + return ok && p.Master() } -// ParamTypeUnExpected is the unexpected parameter type. -var ParamTypeUnExpected = ParamType{invalid: true} - -func (pt ParamType) String() string { - return pt.Indent +// IsTrailing returns true if the "pt" param type is a marked as trailing, +// which should accept more than one path segment when in the end. +func IsTrailing(pt ParamType) bool { + p, ok := pt.(TrailingParamType) + return ok && p.Trailing() } -// Assignable returns true if the "k" standard type -// is assignabled to this ParamType. -func (pt ParamType) Assignable(k reflect.Kind) bool { - return pt.GoType == k +// HasAlias returns any alias of the "pt" param type. +// If alias is empty or not found then it returns false as its second output argument. +func HasAlias(pt ParamType) (string, bool) { + if p, ok := pt.(AliasParamType); ok { + alias := p.Alias() + return alias, len(alias) > 0 + } + + return "", false } -// GetDefaultParamType accepts a list of ParamType and returns its default. -// If no `Default` specified: +// GetMasterParamType accepts a list of ParamType and returns its master. +// If no `Master` specified: // and len(paramTypes) > 0 then it will return the first one, -// otherwise it returns a "string" parameter type. -func GetDefaultParamType(paramTypes ...ParamType) ParamType { +// otherwise it returns nil. +func GetMasterParamType(paramTypes ...ParamType) ParamType { for _, pt := range paramTypes { - if pt.Default == true { + if IsMaster(pt) { return pt } } @@ -46,24 +71,12 @@ func GetDefaultParamType(paramTypes ...ParamType) ParamType { return paramTypes[0] } - return ParamType{Indent: "string", GoType: reflect.String, Default: true} -} - -// ValidKind will return true if at least one param type is supported -// for this std kind. -func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool { - for _, pt := range paramTypes { - if pt.GoType == k { - return true - } - } - - return false + return nil } // LookupParamType accepts the string // representation of a parameter type. -// Available: +// Example: // "string" // "number" or "int" // "long" or "int64" @@ -73,41 +86,20 @@ func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool { // "alphabetical" // "file" // "path" -func LookupParamType(indent string, paramTypes ...ParamType) (ParamType, bool) { +func LookupParamType(indentOrAlias string, paramTypes ...ParamType) (ParamType, bool) { for _, pt := range paramTypes { - if pt.Indent == indent { + if pt.Indent() == indentOrAlias { return pt, true } - for _, alias := range pt.Aliases { - if alias == indent { + if alias, has := HasAlias(pt); has { + if alias == indentOrAlias { return pt, true } } } - return ParamTypeUnExpected, false -} - -// LookupParamTypeFromStd accepts the string representation of a standard go type. -// It returns a ParamType, but it may differs for example -// the alphabetical, file, path and string are all string go types, so -// make sure that caller resolves these types before this call. -// -// string matches to string -// int matches to int/number -// int64 matches to int64/long -// uint64 matches to uint64 -// bool matches to bool/boolean -func LookupParamTypeFromStd(goType string, paramTypes ...ParamType) (ParamType, bool) { - goType = strings.ToLower(goType) - for _, pt := range paramTypes { - if strings.ToLower(pt.GoType.String()) == goType { - return pt, true - } - } - - return ParamTypeUnExpected, false + return nil, false } // ParamStatement is a struct diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 9ce125c9..b4bb0293 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -2,7 +2,6 @@ package parser import ( "fmt" - "reflect" "strconv" "strings" @@ -11,67 +10,12 @@ import ( "github.com/kataras/iris/core/router/macro/interpreter/token" ) -var ( - // paramTypeString is the string type. - // If parameter type is missing then it defaults to String type. - // Allows anything - // Declaration: /mypath/{myparam:string} or {myparam} - paramTypeString = ast.ParamType{Indent: "string", GoType: reflect.String, Default: true} - // 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 = ast.ParamType{Indent: "number", Aliases: []string{"int"}, GoType: reflect.Int} - // ParamTypeInt64 is a number type. - // Allows only -9223372036854775808 to 9223372036854775807. - // Declaration: /mypath/{myparam:int64} or {myparam:long} - paramTypeInt64 = ast.ParamType{Indent: "int64", Aliases: []string{"long"}, GoType: reflect.Int64} - // ParamTypeUint8 a number type. - // Allows only 0 to 255. - // Declaration: /mypath/{myparam:uint8} - paramTypeUint8 = ast.ParamType{Indent: "uint8", GoType: reflect.Uint8} - // ParamTypeUint64 a number type. - // Allows only 0 to 18446744073709551615. - // Declaration: /mypath/{myparam:uint64} - paramTypeUint64 = ast.ParamType{Indent: "uint64", GoType: reflect.Uint64} - // ParamTypeBool 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:bool} or {myparam:boolean} - paramTypeBool = ast.ParamType{Indent: "bool", Aliases: []string{"boolean"}, GoType: reflect.Bool} - // ParamTypeAlphabetical is the alphabetical/letter type type. - // Allows letters only (upper or lowercase) - // Declaration: /mypath/{myparam:alphabetical} - paramTypeAlphabetical = ast.ParamType{Indent: "alphabetical", GoType: reflect.String} - // ParamTypeFile is the file single path type. - // Allows: - // letters (upper or lowercase) - // numbers (0-9) - // underscore (_) - // dash (-) - // point (.) - // no spaces! or other character - // Declaration: /mypath/{myparam:file} - paramTypeFile = ast.ParamType{Indent: "file", GoType: reflect.String} - // ParamTypePath is the multi path (or wildcard) type. - // Allows anything, should be the last part - // Declaration: /mypath/{myparam:path} - paramTypePath = ast.ParamType{Indent: "path", GoType: reflect.String, End: true} -) - -// DefaultParamTypes are the built'n parameter types. -var DefaultParamTypes = []ast.ParamType{ - paramTypeString, - paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64, - paramTypeBool, - paramTypeAlphabetical, paramTypeFile, paramTypePath, -} - // Parse takes a route "fullpath" // and returns its param statements -// and an error on failure. -func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement, error) { +// or an error if failed. +func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, error) { if len(paramTypes) == 0 { - paramTypes = DefaultParamTypes + return nil, fmt.Errorf("empty parameter types") } pathParts := strings.SplitN(fullpath, "/", -1) @@ -94,7 +38,7 @@ func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement, return nil, err } // if we have param type path but it's not the last path part - if stmt.Type.End && i < len(pathParts)-1 { + if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 { return nil, fmt.Errorf("param type '%s' should be lived only inside the last path segment, but was inside: %s", stmt.Type, s) } @@ -166,7 +110,7 @@ func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, er stmt := &ast.ParamStatement{ ErrorCode: DefaultParamErrorCode, - Type: ast.GetDefaultParamType(paramTypes...), + Type: ast.GetMasterParamType(paramTypes...), Src: p.src, } @@ -190,6 +134,7 @@ func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, er // type can accept both letters and numbers but not symbols ofc. nextTok := l.NextToken() paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...) + if !found { p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal) } diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index ffdd5335..b492b361 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -9,6 +9,44 @@ import ( "github.com/kataras/iris/core/router/macro/interpreter/ast" ) +type simpleParamType string + +func (pt simpleParamType) Indent() string { return string(pt) } + +type masterParamType simpleParamType + +func (pt masterParamType) Indent() string { return string(pt) } +func (pt masterParamType) Master() bool { return true } + +type wildcardParamType string + +func (pt wildcardParamType) Indent() string { return string(pt) } +func (pt wildcardParamType) Trailing() bool { return true } + +type aliasedParamType []string + +func (pt aliasedParamType) Indent() string { return string(pt[0]) } +func (pt aliasedParamType) Alias() string { return pt[1] } + +var ( + paramTypeString = masterParamType("string") + paramTypeNumber = aliasedParamType{"number", "int"} + paramTypeInt64 = aliasedParamType{"int64", "long"} + paramTypeUint8 = simpleParamType("uint8") + paramTypeUint64 = simpleParamType("uint64") + paramTypeBool = aliasedParamType{"bool", "boolean"} + paramTypeAlphabetical = simpleParamType("alphabetical") + paramTypeFile = simpleParamType("file") + paramTypePath = wildcardParamType("path") +) + +var testParamTypes = []ast.ParamType{ + paramTypeString, + paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64, + paramTypeBool, + paramTypeAlphabetical, paramTypeFile, paramTypePath, +} + func TestParseParamError(t *testing.T) { // fail illegalChar := '$' @@ -16,7 +54,7 @@ func TestParseParamError(t *testing.T) { input := "{id" + string(illegalChar) + "int range(1,5) else 404}" p := NewParamParser(input) - _, err := p.Parse(DefaultParamTypes) + _, err := p.Parse(testParamTypes) if err == nil { t.Fatalf("expecting not empty error on input '%s'", input) @@ -32,7 +70,7 @@ func TestParseParamError(t *testing.T) { // success input2 := "{id:uint64 range(1,5) else 404}" p.Reset(input2) - _, err = p.Parse(DefaultParamTypes) + _, err = p.Parse(testParamTypes) if err != nil { t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error()) @@ -42,7 +80,7 @@ func TestParseParamError(t *testing.T) { // mustLookupParamType same as `ast.LookupParamType` but it panics if "indent" does not match with a valid Param Type. func mustLookupParamType(indent string) ast.ParamType { - pt, found := ast.LookupParamType(indent, DefaultParamTypes...) + pt, found := ast.LookupParamType(indent, testParamTypes...) if !found { panic("param type '" + indent + "' is not part of the provided param types") } @@ -113,14 +151,14 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{myparam_:thisianunexpected}", Name: "myparam_", - Type: ast.ParamTypeUnExpected, + Type: nil, ErrorCode: 404, }}, // 5 {true, ast.ParamStatement{ Src: "{myparam2}", Name: "myparam2", // we now allow integers to the parameter names. - Type: ast.GetDefaultParamType(DefaultParamTypes...), + Type: ast.GetMasterParamType(testParamTypes...), ErrorCode: 404, }}, // 6 {true, @@ -152,7 +190,7 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:long else 404}", Name: "id", - Type: mustLookupParamType("long"), // backwards-compatible test of LookupParamType. + Type: mustLookupParamType("int64"), // backwards-compatible test of LookupParamType. ErrorCode: 404, }}, // 10 {true, @@ -175,7 +213,7 @@ func TestParseParam(t *testing.T) { p := new(ParamParser) for i, tt := range tests { p.Reset(tt.expectedStatement.Src) - resultStmt, err := p.Parse(DefaultParamTypes) + resultStmt, err := p.Parse(testParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) @@ -216,7 +254,7 @@ func TestParse(t *testing.T) { }}, // 0 {"/admin/{id:uint64 range(1,5)}", true, []ast.ParamStatement{{ - Src: "{id:uint64 range(1,5)}", // test alternative (backwards-compatibility) "int" + Src: "{id:uint64 range(1,5)}", Name: "id", Type: paramTypeUint64, Funcs: []ast.ParamFunc{ @@ -260,7 +298,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{myparam_:thisianunexpected}", Name: "myparam_", - Type: ast.ParamTypeUnExpected, + Type: nil, ErrorCode: 404, }, }}, // 5 @@ -282,7 +320,7 @@ func TestParse(t *testing.T) { }}, // 7 } for i, tt := range tests { - statements, err := Parse(tt.path) + statements, err := Parse(tt.path, testParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 7ea4630f..5cb304e0 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -7,8 +7,6 @@ import ( "strconv" "strings" "unicode" - - "github.com/kataras/iris/core/router/macro/interpreter/ast" ) // EvaluatorFunc is the signature for both param types and param funcs. @@ -108,53 +106,78 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { // try to convert the string literal as we get it from the parser. var ( - v interface{} - err error + val interface{} + + panicIfErr = func(err error) { + if err != nil { + panic(fmt.Sprintf("on field index: %d: %v", i, err)) + } + } ) // try to get the value based on the expected type. switch field.Kind() { case reflect.Int: - v, err = strconv.Atoi(arg) + v, err := strconv.Atoi(arg) + panicIfErr(err) + val = v case reflect.Int8: - v, err = strconv.ParseInt(arg, 10, 8) + v, err := strconv.ParseInt(arg, 10, 8) + panicIfErr(err) + val = int8(v) case reflect.Int16: - v, err = strconv.ParseInt(arg, 10, 16) + v, err := strconv.ParseInt(arg, 10, 16) + panicIfErr(err) + val = int16(v) case reflect.Int32: - v, err = strconv.ParseInt(arg, 10, 32) + v, err := strconv.ParseInt(arg, 10, 32) + panicIfErr(err) + val = int32(v) case reflect.Int64: - v, err = strconv.ParseInt(arg, 10, 64) + v, err := strconv.ParseInt(arg, 10, 64) + panicIfErr(err) + val = v case reflect.Uint8: - v, err = strconv.ParseUint(arg, 10, 8) + v, err := strconv.ParseUint(arg, 10, 8) + panicIfErr(err) + val = uint8(v) case reflect.Uint16: - v, err = strconv.ParseUint(arg, 10, 16) + v, err := strconv.ParseUint(arg, 10, 16) + panicIfErr(err) + val = uint16(v) case reflect.Uint32: - v, err = strconv.ParseUint(arg, 10, 32) + v, err := strconv.ParseUint(arg, 10, 32) + panicIfErr(err) + val = uint32(v) case reflect.Uint64: - v, err = strconv.ParseUint(arg, 10, 64) + v, err := strconv.ParseUint(arg, 10, 64) + panicIfErr(err) + val = v case reflect.Float32: - v, err = strconv.ParseFloat(arg, 32) + v, err := strconv.ParseFloat(arg, 32) + panicIfErr(err) + val = float32(v) case reflect.Float64: - v, err = strconv.ParseFloat(arg, 64) + v, err := strconv.ParseFloat(arg, 64) + panicIfErr(err) + val = v case reflect.Bool: - v, err = strconv.ParseBool(arg) + v, err := strconv.ParseBool(arg) + panicIfErr(err) + val = v case reflect.Slice: if len(arg) > 1 { if arg[0] == '[' && arg[len(arg)-1] == ']' { // it is a single argument but as slice. - v = strings.Split(arg[1:len(arg)-1], ",") // only string slices. + val = strings.Split(arg[1:len(arg)-1], ",") // only string slices. } } default: - v = arg + val = arg } - if err != nil { - panic(fmt.Sprintf("on field index: %d: %v", i, err)) - } - - argValue := reflect.ValueOf(v) + argValue := reflect.ValueOf(val) if expected, got := field.Kind(), argValue.Kind(); expected != got { panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) } @@ -190,6 +213,11 @@ type ( // and it can register param functions // to that macro which maps to a parameter type. Macro struct { + indent string + alias string + master bool + trailing bool + Evaluator EvaluatorFunc funcs []ParamFunc } @@ -212,19 +240,51 @@ type ( } ) -func newMacro(evaluator EvaluatorFunc) *Macro { - return &Macro{Evaluator: evaluator} +// NewMacro creates and returns a Macro that can be used as a registry for +// a new customized parameter type and its functions. +func NewMacro(indent, alias string, master, trailing bool, evaluator EvaluatorFunc) *Macro { + return &Macro{ + indent: indent, + alias: alias, + master: master, + trailing: trailing, + + Evaluator: evaluator, + } } +func (m *Macro) Indent() string { + return m.indent +} + +func (m *Macro) Alias() string { + return m.alias +} + +func (m *Macro) Master() bool { + return m.master +} + +func (m *Macro) Trailing() bool { + return m.trailing +} + +// func (m *Macro) SetParamResolver(fn func(memstore.Entry) interface{}) *Macro { +// m.ParamResolver = fn +// return m +// } + // RegisterFunc registers a parameter function // to that macro. // Accepts the func name ("range") // and the function body, which should return an EvaluatorFunc // a bool (it will be converted to EvaluatorFunc later on), // i.e RegisterFunc("min", func(minValue int) func(paramValue string) bool){}) -func (m *Macro) RegisterFunc(funcName string, fn interface{}) { +func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro { fullFn := convertBuilderFunc(fn) m.registerFunc(funcName, fullFn) + + return m } func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { @@ -256,113 +316,3 @@ func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder { } return nil } - -// Map contains the default macros mapped to their types. -// This is the manager which is used by the caller to register custom -// parameter functions per param-type (String, Int, Long, Boolean, Alphabetical, File, Path). -type Map struct { - // string type - // anything - String *Macro - - // int type - // both positive and negative numbers, any number of digits. - Number *Macro - // int64 as int64 type - // -9223372036854775808 to 9223372036854775807. - Int64 *Macro - // uint8 as uint8 type - // 0 to 255. - Uint8 *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". - Boolean *Macro - // alphabetical/letter type - // letters only (upper or lowercase) - Alphabetical *Macro - // file type - // letters (upper or lowercase) - // numbers (0-9) - // underscore (_) - // dash (-) - // point (.) - // no spaces! or other character - File *Macro - // path type - // anything, should be the last part - Path *Macro -} - -// NewMap returns a new macro Map with default -// type evaluators. -// -// 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 }), - 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]$")), - Uint8: newMacro(MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")), - 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. - _, err := strconv.ParseBool(paramValue) - return err == nil - }), - Alphabetical: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")), - File: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")), - // it allows everything, we have String and Path as different - // types because I want to give the opportunity to the user - // to organise the macro functions based on wildcard or single dynamic named path parameter. - // Should be the last. - Path: newMacro(func(string) bool { return true }), - } -} - -// Lookup returns the specific Macro from the map -// based on the parameter type. -// 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.ParamTypeNumber: - return m.Number - case ast.ParamTypeInt64: - return m.Int64 - case ast.ParamTypeUint8: - return m.Uint8 - case ast.ParamTypeUint64: - return m.Uint64 - case ast.ParamTypeBoolean: - return m.Boolean - case ast.ParamTypeAlphabetical: - return m.Alphabetical - case ast.ParamTypeFile: - return m.File - case ast.ParamTypePath: - return m.Path - default: - return m.String - } -} diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 33f37fd0..20a29732 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -71,8 +71,6 @@ func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, pass bo } func TestStringEvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -86,13 +84,11 @@ func TestStringEvaluatorRaw(t *testing.T) { } // 0 for i, tt := range tests { - testEvaluatorRaw(t, f.String, tt.input, tt.pass, i) + testEvaluatorRaw(t, String, tt.input, tt.pass, i) } } func TestNumberEvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -111,13 +107,11 @@ func TestNumberEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Number, tt.input, tt.pass, i) + testEvaluatorRaw(t, Number, tt.input, tt.pass, i) } } func TestInt64EvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -138,13 +132,11 @@ func TestInt64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Int64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Int64, tt.input, tt.pass, i) } } func TestUint8EvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -169,13 +161,11 @@ func TestUint8EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Uint8, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint8, tt.input, tt.pass, i) } } func TestUint64EvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -196,13 +186,11 @@ func TestUint64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Uint64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint64, tt.input, tt.pass, i) } } func TestAlphabeticalEvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -215,13 +203,11 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Alphabetical, tt.input, tt.pass, i) + testEvaluatorRaw(t, Alphabetical, tt.input, tt.pass, i) } } func TestFileEvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -234,13 +220,11 @@ func TestFileEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.File, tt.input, tt.pass, i) + testEvaluatorRaw(t, File, tt.input, tt.pass, i) } } func TestPathEvaluatorRaw(t *testing.T) { - f := NewMap() - pathTests := []struct { pass bool input string @@ -254,28 +238,10 @@ func TestPathEvaluatorRaw(t *testing.T) { } for i, tt := range pathTests { - testEvaluatorRaw(t, f.Path, tt.input, tt.pass, i) + testEvaluatorRaw(t, Path, tt.input, tt.pass, i) } } -// func TestMapRegisterFunc(t *testing.T) { -// m := NewMap() -// m.String.RegisterFunc("prefix", func(prefix string) EvaluatorFunc { -// return func(paramValue string) bool { -// return strings.HasPrefix(paramValue, prefix) -// } -// }) - -// p, err := Parse("/user/@iris") -// if err != nil { -// t.Fatalf(err) -// } - -// // p.Params = append(p.) - -// testEvaluatorRaw(t, m.String, p.Src, false, 0) -// } - func TestConvertBuilderFunc(t *testing.T) { fn := func(min uint64, slice []string) func(string) bool { return func(paramValue string) bool { diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go new file mode 100644 index 00000000..1a520af2 --- /dev/null +++ b/core/router/macro/macros.go @@ -0,0 +1,375 @@ +package macro + +import ( + "strconv" + "strings" + + "github.com/kataras/iris/core/router/macro/interpreter/ast" +) + +var ( + // String type + // Allows anything (single path segment, as everything except the `Path`). + String = NewMacro("string", "", true, false, func(string) bool { return true }). + RegisterFunc("regexp", func(expr string) EvaluatorFunc { + return MustNewEvaluatorFromRegexp(expr) + }). + // checks if param value starts with the 'prefix' arg + RegisterFunc("prefix", func(prefix string) EvaluatorFunc { + return func(paramValue string) bool { + return strings.HasPrefix(paramValue, prefix) + } + }). + // checks if param value ends with the 'suffix' arg + RegisterFunc("suffix", func(suffix string) EvaluatorFunc { + return func(paramValue string) bool { + return strings.HasSuffix(paramValue, suffix) + } + }). + // checks if param value contains the 's' arg + RegisterFunc("contains", func(s string) EvaluatorFunc { + return func(paramValue string) bool { + return strings.Contains(paramValue, s) + } + }). + // checks if param value's length is at least 'min' + RegisterFunc("min", func(min int) EvaluatorFunc { + return func(paramValue string) bool { + return len(paramValue) >= min + } + }). + // checks if param value's length is not bigger than 'max' + RegisterFunc("max", func(max int) EvaluatorFunc { + return func(paramValue string) bool { + return max >= len(paramValue) + } + }) + + simpleNumberEvalutator = MustNewEvaluatorFromRegexp("^-?[0-9]+$") + // Number or int type + // both positive and negative numbers, any number of digits. + Number = NewMacro("number", "int", false, false, simpleNumberEvalutator). + // checks if the param value's int representation is + // bigger or equal than 'min' + RegisterFunc("min", func(min int) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.Atoi(paramValue) + if err != nil { + return false + } + return n >= min + } + }). + // checks if the param value's int representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max int) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.Atoi(paramValue) + if err != nil { + return false + } + return n <= max + } + }). + // checks if the param value's int representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max int) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.Atoi(paramValue) + if err != nil { + return false + } + + if n < min || n > max { + return false + } + return true + } + }) + + // Int64 as int64 type + // -9223372036854775808 to 9223372036854775807. + Int64 = NewMacro("int64", "long", false, false, func(paramValue string) bool { + if !simpleNumberEvalutator(paramValue) { + return false + } + _, err := strconv.ParseInt(paramValue, 10, 64) + // if err == strconv.ErrRange... + return err == nil + }). + // checks if the param value's int64 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min int64) 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'. + RegisterFunc("max", func(max int64) 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'. + RegisterFunc("range", func(min, max int64) 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 + } + }) + + // Uint8 as uint8 type + // 0 to 255. + Uint8 = NewMacro("uint8", "", false, false, MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")). + // checks if the param value's uint8 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min uint8) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + + return uint8(n) >= min + } + }). + // checks if the param value's uint8 representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max uint8) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + return uint8(n) <= max + } + }). + // checks if the param value's uint8 representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max uint8) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + + if v := uint8(n); v < min || v > max { + return false + } + return true + } + }) + + // Uint64 as uint64 type + // 0 to 18446744073709551615. + Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) bool { + if !simpleNumberEvalutator(paramValue) { + return false + } + _, err := strconv.ParseUint(paramValue, 10, 64) + return err == nil + }). + // checks if the param value's uint64 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min uint64) 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'. + RegisterFunc("max", func(max uint64) 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'. + RegisterFunc("range", func(min, max uint64) 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 + } + }) + + // Bool or 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". + Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) bool { + // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ + // in this case. + _, err := strconv.ParseBool(paramValue) + return err == nil + }) + + // Alphabetical letter type + // letters only (upper or lowercase) + Alphabetical = NewMacro("alphabetical", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")) + // File type + // letters (upper or lowercase) + // numbers (0-9) + // underscore (_) + // dash (-) + // point (.) + // no spaces! or other character + File = NewMacro("file", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")) + // Path type + // anything, should be the last part + // + // It allows everything, we have String and Path as different + // types because I want to give the opportunity to the user + // to organise the macro functions based on wildcard or single dynamic named path parameter. + // Should be living in the latest path segment of a route path. + Path = NewMacro("path", "", false, true, func(string) bool { return true }) + + Defaults = &Macros{ + String, + Number, + Int64, + Uint8, + Uint64, + Bool, + Alphabetical, + Path, + } +) + +type Macros []*Macro + +func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator EvaluatorFunc) *Macro { + macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) + if ms.register(macro) { + return macro + } + return nil +} + +func (ms *Macros) register(macro *Macro) bool { + if macro.Indent() == "" || macro.Evaluator == nil { + return false + } + + cp := *ms + + for _, m := range cp { + + // can't add more than one with the same ast characteristics. + if macro.Indent() == m.Indent() { + return false + } + + if macro.Alias() == m.Alias() || macro.Alias() == m.Indent() { + return false + } + + if macro.Master() && m.Master() { + return false + } + } + + cp = append(cp, macro) + + *ms = cp + return true +} + +func (ms *Macros) Unregister(indent string) bool { + cp := *ms + + for i, m := range cp { + if m.Indent() == indent { + copy(cp[i:], cp[i+1:]) + cp[len(cp)-1] = nil + cp = cp[:len(cp)-1] + + *ms = cp + return true + } + } + + return false +} + +func (ms *Macros) Lookup(pt ast.ParamType) *Macro { + if m := ms.Get(pt.Indent()); m != nil { + return m + } + + if alias, has := ast.HasAlias(pt); has { + if m := ms.Get(alias); m != nil { + return m + } + } + + return nil +} + +func (ms *Macros) Get(indentOrAlias string) *Macro { + if indentOrAlias == "" { + return nil + } + + for _, m := range *ms { + if m.Indent() == indentOrAlias { + return m + } + + if m.Alias() == indentOrAlias { + return m + } + } + + return nil +} + +func (ms *Macros) GetMaster() *Macro { + for _, m := range *ms { + if m.Master() { + return m + } + } + + return nil +} + +func (ms *Macros) GetTrailings() (macros []*Macro) { + for _, m := range *ms { + if m.Trailing() { + macros = append(macros, m) + } + } + + return +} diff --git a/core/router/macro/template.go b/core/router/macro/template.go index f26c1d06..3b30d2f7 100644 --- a/core/router/macro/template.go +++ b/core/router/macro/template.go @@ -25,6 +25,7 @@ type TemplateParam struct { // it's useful on host to decide how to convert the path template to specific router's syntax Type ast.ParamType `json:"type"` Name string `json:"name"` + Index int `json:"index"` ErrCode int `json:"errCode"` TypeEvaluator EvaluatorFunc `json:"-"` Funcs []EvaluatorFunc `json:"-"` @@ -34,15 +35,20 @@ type TemplateParam struct { // and returns a new Template. // It builds all the parameter functions for that template // and their evaluators, it's the api call that makes use the interpeter's parser -> lexer. -func Parse(src string, macros *Map) (*Template, error) { - params, err := parser.Parse(src) +func Parse(src string, macros Macros) (*Template, error) { + types := make([]ast.ParamType, len(macros)) + for i, m := range macros { + types[i] = m + } + + params, err := parser.Parse(src, types) if err != nil { return nil, err } t := new(Template) t.Src = src - for _, p := range params { + for idx, p := range params { funcMap := macros.Lookup(p.Type) typEval := funcMap.Evaluator @@ -50,17 +56,23 @@ func Parse(src string, macros *Map) (*Template, error) { Src: p.Src, Type: p.Type, Name: p.Name, + Index: idx, ErrCode: p.ErrorCode, TypeEvaluator: typEval, } + for _, paramfn := range p.Funcs { tmplFn := funcMap.getFunc(paramfn.Name) - if tmplFn == nil { // if not find on this type, check for String's which is for global funcs too - tmplFn = macros.String.getFunc(paramfn.Name) - if tmplFn == nil { // if not found then just skip this param + if tmplFn == nil { // if not find on this type, check for Master's which is for global funcs too. + if m := macros.GetMaster(); m != nil { + tmplFn = m.getFunc(paramfn.Name) + } + + if tmplFn == nil { // if not found then just skip this param. continue } } + evalFn := tmplFn(paramfn.Args) if evalFn == nil { continue diff --git a/core/router/party.go b/core/router/party.go index 745d038e..99e482e1 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -18,11 +18,11 @@ type Party interface { GetRelPath() string // GetReporter returns the reporter for adding errors GetReporter() *errors.Reporter - // Macros returns the macro map which is responsible - // to register custom macro functions for all routes. + // Macros returns the macro collection that is responsible + // to register custom macros with their own parameter types and their macro functions for all routes. // // Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path - Macros() *macro.Map + Macros() *macro.Macros // Party groups routes which may have the same prefix and share same handlers, // returns that new rich subrouter. diff --git a/core/router/route.go b/core/router/route.go index b581b659..11a69d6e 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -39,7 +39,7 @@ type Route struct { // It parses the path based on the "macros", // handlers are being changed to validate the macros at serve time, if needed. func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, - handlers context.Handlers, macros *macro.Map) (*Route, error) { + handlers context.Handlers, macros macro.Macros) (*Route, error) { tmpl, err := macro.Parse(unparsedPath, macros) if err != nil { diff --git a/hero/di.go b/hero/di.go index 4244cfa0..d5066514 100644 --- a/hero/di.go +++ b/hero/di.go @@ -8,6 +8,17 @@ import ( func init() { di.DefaultHijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) { + // if IsExpectingStore(fieldOrFuncInput) { + // return &di.BindObject{ + // Type: memstoreTyp, + // BindType: di.Dynamic, + // ReturnValue: func(ctxValue []reflect.Value) reflect.Value { + // // return ctxValue[0].MethodByName("Params").Call(di.EmptyIn)[0] + // return ctxValue[0].MethodByName("Params").Call(di.EmptyIn)[0].Field(0) // the Params' memstore.Store. + // }, + // }, true + // } + if !IsContext(fieldOrFuncInput) { return nil, false } diff --git a/hero/di/func.go b/hero/di/func.go index da81d6f3..3c417d8f 100644 --- a/hero/di/func.go +++ b/hero/di/func.go @@ -132,9 +132,8 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool { } if b.IsAssignable(inTyp) { - // println(inTyp.String() + " is assignable to " + val.Type().String()) // fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n", - // i, b.Type.String(), value.String(), val.Pointer()) + // i, b.Type.String(), inTyp.String(), inTyp.Pointer()) s.inputs = append(s.inputs, &targetFuncInput{ InputIndex: inputIndex, Object: &b, @@ -194,8 +193,8 @@ func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) { args := *in for _, input := range s.inputs { input.Object.Assign(ctx, func(v reflect.Value) { - // fmt.Printf("assign input index: %d for value: %v\n", - // input.InputIndex, v.String()) + // fmt.Printf("assign input index: %d for value: %v of type: %s\n", + // input.InputIndex, v.String(), v.Type().Name()) args[input.InputIndex] = v }) diff --git a/hero/di/object.go b/hero/di/object.go index 392abcc5..385162bd 100644 --- a/hero/di/object.go +++ b/hero/di/object.go @@ -101,6 +101,11 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Val if !v.IsValid() { return zeroOutVal } + // if v.String() == "" { + // println("di/object.go: " + v.String()) + // // println("di/object.go: because it's interface{} it should be returned as: " + v.Elem().Type().String() + " and its value: " + v.Elem().Interface().(string)) + // return v.Elem() + // } return v } diff --git a/hero/di/reflect.go b/hero/di/reflect.go index 3b91ebf8..08fc7f2a 100644 --- a/hero/di/reflect.go +++ b/hero/di/reflect.go @@ -54,6 +54,7 @@ func IsZero(v reflect.Value) bool { // if can't interface, i.e return value from unexported field or method then return false return false } + zero := reflect.Zero(v.Type()) return v.Interface() == zero.Interface() } @@ -62,7 +63,10 @@ func IsZero(v reflect.Value) bool { // If "v" is a nil pointer, Indirect returns a zero Value. // If "v" is not a pointer, Indirect returns v. func IndirectValue(v reflect.Value) reflect.Value { - return reflect.Indirect(v) + if k := v.Kind(); k == reflect.Ptr { //|| k == reflect.Interface { + return v.Elem() + } + return v } // ValueOf returns the reflect.Value of "o". @@ -123,6 +127,11 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool { // fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String()) return got.Implements(expected) } + + // if got.String() == "interface {}" { + // return true + // } + return false } @@ -161,7 +170,6 @@ func lookupFields(elemTyp reflect.Type, skipUnexported bool, parentIndex []int) for i, n := 0, elemTyp.NumField(); i < n; i++ { f := elemTyp.Field(i) - if IndirectType(f.Type).Kind() == reflect.Struct && !structFieldIgnored(f) { fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...) diff --git a/hero/handler.go b/hero/handler.go index bb427d77..6644dc83 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -5,19 +5,31 @@ import ( "reflect" "runtime" + "github.com/kataras/iris/context" + "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/hero/di" "github.com/kataras/golog" - "github.com/kataras/iris/context" ) -var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() +var ( + contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() + memstoreTyp = reflect.TypeOf(memstore.Store{}) +) // IsContext returns true if the "inTyp" is a type of Context. func IsContext(inTyp reflect.Type) bool { return inTyp.Implements(contextTyp) } +// IsExpectingStore returns true if the "inTyp" is a type of memstore.Store. +func IsExpectingStore(inTyp reflect.Type) bool { + print("di/handler.go: " + inTyp.String() + " vs " + memstoreTyp.String() + " : ") + println(inTyp == memstoreTyp) + + return inTyp == memstoreTyp +} + // checks if "handler" is context.Handler: func(context.Context). func isContextHandler(handler interface{}) (context.Handler, bool) { h, is := handler.(context.Handler) @@ -70,7 +82,7 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler, // is invalid when input len and values are not match // or their types are not match, we will take look at the // second statement, here we will re-try it - // using binders for path parameters: string, int, int64, bool. + // using binders for path parameters: string, int, int64, uint8, uint64, bool and so on. // We don't have access to the path, so neither to the macros here, // but in mvc. So we have to do it here. if valid = funcInjector.Retry(new(params).resolve); !valid { diff --git a/hero/param.go b/hero/param.go index 4ea62fc2..dc34b10e 100644 --- a/hero/param.go +++ b/hero/param.go @@ -19,64 +19,8 @@ type params struct { func (p *params) resolve(index int, typ reflect.Type) (reflect.Value, bool) { currentParamIndex := p.next - v, ok := resolveParam(currentParamIndex, typ) + v, ok := context.ParamResolverByKindAndIndex(typ.Kind(), currentParamIndex) p.next = p.next + 1 return v, ok } - -func resolveParam(currentParamIndex int, typ reflect.Type) (reflect.Value, bool) { - var fn interface{} - - switch typ.Kind() { - case reflect.Int: - fn = func(ctx context.Context) int { - // the second "ok/found" check is not necessary, - // because even if the entry didn't found on that "index" - // it will return an empty entry which will return the - // default value passed from the xDefault(def) because its `ValueRaw` is nil. - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.IntDefault(0) - return v - } - case reflect.Int64: - fn = func(ctx context.Context) int64 { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.Int64Default(0) - - return v - } - case reflect.Uint8: - fn = func(ctx context.Context) uint8 { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.Uint8Default(0) - - return v - } - case reflect.Uint64: - fn = func(ctx context.Context) uint64 { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.Uint64Default(0) - - return v - } - case reflect.Bool: - fn = func(ctx context.Context) bool { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.BoolDefault(false) - return v - } - case reflect.String: - fn = func(ctx context.Context) string { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - // print(entry.Key + " with index of: ") - // print(currentParamIndex) - // println(" and value: " + entry.String()) - return entry.String() - } - default: - return reflect.Value{}, false - } - - return reflect.ValueOf(fn), true -} diff --git a/mvc/controller.go b/mvc/controller.go index 41f37ac2..85675ec2 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -247,7 +247,7 @@ func (c *ControllerActivator) parseMethods() { } func (c *ControllerActivator) parseMethod(m reflect.Method) { - httpMethod, httpPath, err := parseMethod(m, c.isReservedMethod) + httpMethod, httpPath, err := parseMethod(*c.router.Macros(), m, c.isReservedMethod) if err != nil { if err != errSkip { c.addErr(fmt.Errorf("MVC: fail to parse the route path and HTTP method for '%s.%s': %v", c.fullName, m.Name, err)) @@ -283,7 +283,7 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . } // parse a route template which contains the parameters organised. - tmpl, err := macro.Parse(path, c.router.Macros()) + tmpl, err := macro.Parse(path, *c.router.Macros()) if err != nil { c.addErr(fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.fullName, funcName, err)) return nil @@ -338,6 +338,7 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref } // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) + funcInjector := di.Func(m.Func, funcDependencies...) // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) if funcInjector.Has { @@ -396,6 +397,11 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref in := make([]reflect.Value, n, n) in[0] = ctrl funcInjector.Inject(&in, ctxValue) + + // for idxx, inn := range in { + // println("controller.go: execution: in.Value = "+inn.String()+" and in.Type = "+inn.Type().Kind().String()+" of index: ", idxx) + // } + hero.DispatchFuncResult(ctx, call(in)) return } diff --git a/mvc/controller_handle_test.go b/mvc/controller_handle_test.go index 55e200d4..9864b3ad 100644 --- a/mvc/controller_handle_test.go +++ b/mvc/controller_handle_test.go @@ -37,6 +37,7 @@ type testControllerHandle struct { func (c *testControllerHandle) BeforeActivation(b BeforeActivation) { b.Handle("GET", "/histatic", "HiStatic") b.Handle("GET", "/hiservice", "HiService") + b.Handle("GET", "/hiservice/{ps:string}", "HiServiceBy") b.Handle("GET", "/hiparam/{ps:string}", "HiParamBy") b.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy") } @@ -84,6 +85,10 @@ func (c *testControllerHandle) HiService() string { return c.Service.Say("hi") } +func (c *testControllerHandle) HiServiceBy(v string) string { + return c.Service.Say("hi with param: " + v) +} + func (c *testControllerHandle) HiParamBy(v string) string { return v } @@ -116,7 +121,8 @@ func TestControllerHandle(t *testing.T) { // and can be used in a user-defined, dynamic "mvc handler". e.GET("/hiservice").Expect().Status(httptest.StatusOK). Body().Equal("service: hi") - + e.GET("/hiservice/value").Expect().Status(httptest.StatusOK). + Body().Equal("service: hi with param: value") // this worked with a temporary variadic on the resolvemethodfunc which is not // correct design, I should split the path and params with the rest of implementation // in order a simple template.Src can be given. diff --git a/mvc/controller_method_parser.go b/mvc/controller_method_parser.go index 97c1282e..e5477e9e 100644 --- a/mvc/controller_method_parser.go +++ b/mvc/controller_method_parser.go @@ -4,11 +4,12 @@ import ( "errors" "fmt" "reflect" + "strconv" "strings" "unicode" "github.com/kataras/iris/core/router" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/core/router/macro" ) const ( @@ -95,47 +96,25 @@ func (l *methodLexer) peekPrev() (w string) { return w } -var posWords = map[int]string{ - 0: "", - 1: "first", - 2: "second", - 3: "third", - 4: "forth", - 5: "five", - 6: "sixth", - 7: "seventh", - 8: "eighth", - 9: "ninth", - 10: "tenth", - 11: "eleventh", - 12: "twelfth", - 13: "thirteenth", - 14: "fourteenth", - 15: "fifteenth", - 16: "sixteenth", - 17: "seventeenth", - 18: "eighteenth", - 19: "nineteenth", - 20: "twentieth", -} - func genParamKey(argIdx int) string { - return "arg" + posWords[argIdx] // argfirst, argsecond... + return "param" + strconv.Itoa(argIdx) // param0, param1, param2... } type methodParser struct { - lexer *methodLexer - fn reflect.Method + lexer *methodLexer + fn reflect.Method + macros macro.Macros } -func parseMethod(fn reflect.Method, skipper func(string) bool) (method, path string, err error) { +func parseMethod(macros macro.Macros, fn reflect.Method, skipper func(string) bool) (method, path string, err error) { if skipper(fn.Name) { return "", "", errSkip } p := &methodParser{ - fn: fn, - lexer: newMethodLexer(fn.Name), + fn: fn, + lexer: newMethodLexer(fn.Name), + macros: macros, } return p.parse() } @@ -211,34 +190,45 @@ func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (st var ( paramKey = genParamKey(funcArgPos) // argfirst, argsecond... - paramType = ast.ParamTypeString // default string + m = p.macros.GetMaster() // default (String by-default) + trailings = p.macros.GetTrailings() ) // string, int... - goType := typ.In(funcArgPos).Name() + goType := typ.In(funcArgPos).Kind() nextWord := p.lexer.peekNext() if nextWord == tokenWildcard { p.lexer.skip() // skip the Wildcard word. - paramType = ast.ParamTypePath - } else if pType := ast.LookupParamTypeFromStd(goType); pType != ast.ParamTypeUnExpected { - // it's not wildcard, so check base on our available macro types. - paramType = pType - } else { - if typ.NumIn() > funcArgPos { - // has more input arguments but we are not in the correct - // index now, maybe the first argument was an `iris/context.Context` - // so retry with the "funcArgPos" incremented. - // - // the "funcArgPos" will be updated to the caller as well - // because we return it among the path and the error. - return p.parsePathParam(path, w, funcArgPos+1) + if len(trailings) == 0 { + return "", 0, errors.New("no trailing path parameter found") + } + m = trailings[0] + } else { + // validMacros := p.macros.LookupForGoType(goType) + + // instead of mapping with a reflect.Kind which has its limitation, + // we map the param types with a go type as a string, + // so custom structs such as "user" can be mapped to a macro with indent || alias == "user". + m = p.macros.Get(strings.ToLower(goType.String())) + + if m == nil { + if typ.NumIn() > funcArgPos { + // has more input arguments but we are not in the correct + // index now, maybe the first argument was an `iris/context.Context` + // so retry with the "funcArgPos" incremented. + // + // the "funcArgPos" will be updated to the caller as well + // because we return it among the path and the error. + return p.parsePathParam(path, w, funcArgPos+1) + } + + return "", 0, fmt.Errorf("invalid syntax: the standard go type: %s found in controller's function: %s at position: %d does not match any valid macro", goType, p.fn.Name, funcArgPos) } - return "", 0, errors.New("invalid syntax for " + p.fn.Name) } // /{argfirst:path}, /{argfirst:long}... - path += fmt.Sprintf("/{%s:%s}", paramKey, paramType.String()) + path += fmt.Sprintf("/{%s:%s}", paramKey, m.Indent()) if nextWord == "" && typ.NumIn() > funcArgPos+1 { // By is the latest word but func is expected diff --git a/mvc/param.go b/mvc/param.go index c2f22990..a81d191c 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -5,7 +5,6 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/router/macro" - "github.com/kataras/iris/core/router/macro/interpreter/ast" ) func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) { @@ -13,61 +12,40 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) return } - consumedParams := make(map[int]bool, 0) - for _, in := range funcIn { - for j, p := range params { - if _, consumed := consumedParams[j]; consumed { - continue - } - paramType := p.Type - paramName := p.Name - // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) - if paramType.Assignable(in.Kind()) { - consumedParams[j] = true - // fmt.Printf("param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) - values = append(values, makeFuncParamGetter(paramType, paramName)) - } + // consumedParams := make(map[int]bool, 0) + // for _, in := range funcIn { + // for j, p := range params { + // if _, consumed := consumedParams[j]; consumed { + // continue + // } + + // // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) + // if m := macros.Lookup(p.Type); m != nil && m.GoType == in.Kind() { + // consumedParams[j] = true + // // fmt.Printf("param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) + // funcDep, ok := context.ParamResolverByKindAndIndex(m.GoType, p.Index) + // // funcDep, ok := context.ParamResolverByKindAndKey(in.Kind(), paramName) + // if !ok { + // // here we can add a logger about invalid parameter type although it should never happen here + // // unless the end-developer modified the macro/macros with a special type but not the context/ParamResolvers. + // continue + // } + // values = append(values, funcDep) + // } + // } + // } + + for i, param := range params { + if len(funcIn) <= i { + return } + funcDep, ok := context.ParamResolverByKindAndIndex(funcIn[i].Kind(), param.Index) + if !ok { + continue + } + + values = append(values, funcDep) } return } - -func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Value { - var fn interface{} - - switch paramType { - case ast.ParamTypeNumber: - fn = func(ctx context.Context) int { - v, _ := ctx.Params().GetInt(paramName) - return v - } - case ast.ParamTypeInt64: - fn = func(ctx context.Context) int64 { - v, _ := ctx.Params().GetInt64(paramName) - return v - } - case ast.ParamTypeUint8: - fn = func(ctx context.Context) uint8 { - v, _ := ctx.Params().GetUint8(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) - return v - } - default: - // string, path... - fn = func(ctx context.Context) string { - return ctx.Params().Get(paramName) - } - } - - return reflect.ValueOf(fn) -} From d6d27b26050ad5dba0f6ec25c09fdfdd0709fa07 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 03:17:45 +0300 Subject: [PATCH 16/42] Conversion once at macros and their functions, internal changes required Former-commit-id: 7b778cccfb7c0e30ca5e8106017ada065993aba5 --- README.md | 6 +- _examples/routing/dynamic-path/main.go | 28 ++- _examples/routing/macros/main.go | 66 ++++--- context/request_params.go | 3 +- core/memstore/memstore.go | 33 ++++ core/router/macro.go | 32 +++- core/router/macro/macro.go | 118 +++++++----- core/router/macro/macro_test.go | 68 ++++--- core/router/macro/macros.go | 243 +++++++++++-------------- core/router/macro/template.go | 8 +- hero/handler.go | 12 +- 11 files changed, 338 insertions(+), 279 deletions(-) diff --git a/README.md b/README.md index 9981c0b2..49125deb 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ func main() { | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| | `:string` | string | anything | `Params().Get` | -| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | `Params().GetInt/Int64`...| +| `:int` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, depends on the arch | `Params().GetInt`...| | `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | | `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | | `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | @@ -127,7 +127,7 @@ func main() { ```go app.Get("/users/{id:uint64}", func(ctx iris.Context){ - id, _ := ctx.Params().GetUint64("id") + id := ctx.Params().GetUint64Default("id", 0) // [...] }) ``` @@ -226,7 +226,7 @@ func main() { // 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") + id := ctx.Params().GetUint64Default("id", 0) ctx.Writef("User with ID: %d", id) }) diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 926b4af1..6d11a878 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -2,7 +2,6 @@ package main import ( "regexp" - "strconv" "github.com/kataras/iris" ) @@ -122,17 +121,12 @@ func main() { // Let's register our first macro attached to uint64 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 + // func(uint64) bool = our func's evaluator, this executes in serve time when // a user requests a path which contains the :uint64 macro parameter type with the min(...) macro parameter function. - app.Macros().Uint64.RegisterFunc("min", func(minValue uint64) func(string) bool { - // do anything before serve here [...] - // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n >= minValue + app.Macros().Get("uint64").RegisterFunc("min", func(minValue uint64) func(uint64) bool { + // type of "paramValue" should match the type of the internal macro's evaluator function, which in this case is "uint64". + return func(paramValue uint64) bool { + return paramValue >= minValue } }) @@ -142,14 +136,14 @@ func main() { app.Get("/profile/{id:uint64 min(20)}", 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().GetUint64Default("id", 0) ctx.Writef("Hello id: %d", id) }) // to change the error code per route's macro evaluator: app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") // or GetUint64. - friendid, _ := ctx.Params().GetInt("friendid") + id := ctx.Params().GetUint64Default("id", 0) + friendid := ctx.Params().GetUint64Default("friendid", 0) 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. @@ -169,7 +163,7 @@ func main() { } // MatchString is a type of func(string) bool, so we use it as it is. - app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) + app.Macros().Get("string").RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -178,7 +172,7 @@ func main() { // // Another one is by using a custom body. - app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { + app.Macros().Get("string").RegisterFunc("range", func(minLength, maxLength int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= minLength && len(paramValue) <= maxLength } @@ -193,7 +187,7 @@ func main() { // // Register your custom macro function which accepts a slice of strings `[...,...]`. - app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { + app.Macros().Get("string").RegisterFunc("has", func(validNames []string) func(string) bool { return func(paramValue string) bool { for _, validName := range validNames { if validName == paramValue { diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index f45d4031..8548da88 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -16,37 +16,49 @@ func main() { app.Logger().SetLevel("debug") // Let's see how we can register a custom macro such as ":uint32" or ":small" for its alias (optionally) for Uint32 types. - app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { - _, err := strconv.ParseUint(paramValue, 10, 32) - return err == nil - }). - RegisterFunc("min", func(min uint32) func(string) bool { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 32) - if err != nil { - return false - } + // app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { + // _, err := strconv.ParseUint(paramValue, 10, 32) + // return err == nil + // }). + // RegisterFunc("min", func(min uint32) func(string) bool { + // return func(paramValue string) bool { + // n, err := strconv.ParseUint(paramValue, 10, 32) + // if err != nil { + // return false + // } - return uint32(n) >= min + // return uint32(n) >= min + // } + // }) + + /* TODO: + somehow define one-time how the parameter should be parsed to a particular type (go std or custom) + tip: we can change the original value from string to X using the entry's.ValueRaw + ^ Done 27 sep 2018. + */ + + app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 32) + return uint32(v), err == nil + }). + RegisterFunc("min", func(min uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue >= min } }) - /* TODO: - somehow define one-time how the parameter should be parsed to a particular type (go std or custom) - tip: we can change the original value from string to X using the entry's.ValueRaw - */ - + // optionally, only when mvc or hero features are used for this custom macro/parameter type. context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { - // return func(store memstore.Store) uint32 { - // param, _ := store.GetEntryAt(paramIndex) + /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ + // return func(ctx context.Context) uint32 { + // param := ctx.Params().GetEntryAt(paramIndex) // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) // return uint32(paramValueAsUint32) // } return func(ctx context.Context) uint32 { - param := ctx.Params().GetEntryAt(paramIndex) - paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) - return uint32(paramValueAsUint32) - } + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) + } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, + we must return a value i.e 0 for int for its interface{} */ } // @@ -54,11 +66,11 @@ func main() { return fmt.Sprintf("Value of the parameter is: %d\n", paramValue) })) - app.Get("test_uint64/{myparam:uint64}", handler) + app.Get("test_uint64/{myparam:uint64 min(5)}", func(ctx context.Context) { + // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) + // but better and faster because the macro converts the string to uint64 automatically: + ctx.Writef("Value of the parameter is: %d\n", ctx.Params().GetUint64Default("myparam", 0)) + }) app.Run(iris.Addr(":8080")) } - -func handler(ctx context.Context) { - ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) -} diff --git a/context/request_params.go b/context/request_params.go index ebb20afb..b8426856 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -1,6 +1,7 @@ package context import ( + "fmt" "reflect" "strconv" "strings" @@ -33,7 +34,7 @@ func (r *RequestParams) GetEntry(key string) memstore.Entry { // by the key-value params. func (r *RequestParams) Visit(visitor func(key string, value string)) { r.Store.Visit(func(k string, v interface{}) { - visitor(k, v.(string)) // always string here. + visitor(k, fmt.Sprintf("%v", v)) // always string here. }) } diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 52c3b340..4eda5113 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -6,6 +6,7 @@ package memstore import ( + "fmt" "reflect" "strconv" "strings" @@ -67,11 +68,19 @@ func (e Entry) GetByKindOrNil(k reflect.Kind) interface{} { // If not found returns "def". func (e Entry) StringDefault(def string) string { v := e.ValueRaw + if v == nil { + return def + } if vString, ok := v.(string); ok { return vString } + val := fmt.Sprintf("%v", v) + if val != "" { + return val + } + return def } @@ -105,6 +114,20 @@ func (e Entry) IntDefault(def int) (int, error) { return val, nil case int: return vv, nil + case int8: + return int(vv), nil + case int32: + return int(vv), nil + case int64: + return int(vv), nil + case uint: + return int(vv), nil + case uint8: + return int(vv), nil + case uint32: + return int(vv), nil + case uint64: + return int(vv), nil } return def, errFindParse.Format("int", e.Key) @@ -123,6 +146,10 @@ func (e Entry) Int64Default(def int64) (int64, error) { return strconv.ParseInt(vv, 10, 64) case int64: return vv, nil + case int32: + return int64(vv), nil + case int8: + return int64(vv), nil case int: return int64(vv), nil } @@ -151,6 +178,12 @@ func (e Entry) Float64Default(def float64) (float64, error) { return vv, nil case int: return float64(vv), nil + case int64: + return float64(vv), nil + case uint: + return float64(vv), nil + case uint64: + return float64(vv), nil } return def, errFindParse.Format("float64", e.Key) diff --git a/core/router/macro.go b/core/router/macro.go index 02c0ea95..2af58d34 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -3,6 +3,7 @@ package router import ( "fmt" "net/http" + "reflect" "strings" "github.com/kataras/iris/context" @@ -83,24 +84,37 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { return func(ctx context.Context) { for _, p := range tmpl.Params { paramValue := ctx.Params().Get(p.Name) - // first, check for type evaluator - if !p.TypeEvaluator(paramValue) { + if p.TypeEvaluator == nil { + // allow. + ctx.Next() + return + } + + // first, check for type evaluator. + newValue, passed := p.TypeEvaluator(paramValue) + if !passed { ctx.StatusCode(p.ErrCode) ctx.StopExecution() return } - // then check for all of its functions - for _, evalFunc := range p.Funcs { - if !evalFunc(paramValue) { - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return + if len(p.Funcs) > 0 { + paramIn := []reflect.Value{reflect.ValueOf(newValue)} + // then check for all of its functions + for _, evalFunc := range p.Funcs { + // or make it as func(interface{}) bool and pass directly the "newValue" + // but that would not be as easy for end-developer, so keep that "slower": + if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool + ctx.StatusCode(p.ErrCode) + ctx.StopExecution() + return + } } } + ctx.Params().Store.Set(p.Name, newValue) } - // if all passed, just continue + // if all passed, just continue. ctx.Next() } }(*tmpl) diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 5cb304e0..a589e60a 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -12,14 +12,51 @@ import ( // EvaluatorFunc is the signature for both param types and param funcs. // It should accepts the param's value as string // and return true if validated otherwise false. -type EvaluatorFunc func(paramValue string) bool +// type EvaluatorFunc func(paramValue string) bool +// type BinderFunc func(paramValue string) interface{} -// NewEvaluatorFromRegexp accepts a regexp "expr" expression -// and returns an EvaluatorFunc based on that regexp. -// the regexp is compiled before return. +type ( + ParamEvaluator func(paramValue string) (interface{}, bool) + // FuncEvaluator interface{} // i.e func(paramValue int) bool +) + +var goodEvaluatorFuncs = []reflect.Type{ + reflect.TypeOf(func(string) (interface{}, bool) { return nil, false }), + reflect.TypeOf(ParamEvaluator(func(string) (interface{}, bool) { return nil, false })), +} + +func goodParamFunc(typ reflect.Type) bool { + if typ.Kind() == reflect.Func { // it should be a func which returns a func (see below check). + if typ.NumOut() == 1 { + typOut := typ.Out(0) + if typOut.Kind() != reflect.Func { + return false + } + + if typOut.NumOut() == 2 { // if it's a type of EvaluatorFunc, used for param evaluator. + for _, fType := range goodEvaluatorFuncs { + if typOut == fType { + return true + } + } + return false + } + + if typOut.NumIn() == 1 && typOut.NumOut() == 1 { // if it's a type of func(paramValue [int,string...]) bool, used for param funcs. + return typOut.Out(0).Kind() == reflect.Bool + } + } + } + + return false +} + +// Regexp accepts a regexp "expr" expression +// and returns its MatchString. +// The regexp is compiled before return. // // Returns a not-nil error on regexp compile failure. -func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { +func Regexp(expr string) (func(string) bool, error) { if expr == "" { return nil, fmt.Errorf("empty regex expression") } @@ -37,36 +74,16 @@ func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { return r.MatchString, nil } -// MustNewEvaluatorFromRegexp same as NewEvaluatorFromRegexp +// MustRegexp same as Regexp // but it panics on the "expr" parse failure. -func MustNewEvaluatorFromRegexp(expr string) EvaluatorFunc { - r, err := NewEvaluatorFromRegexp(expr) +func MustRegexp(expr string) func(string) bool { + r, err := Regexp(expr) if err != nil { panic(err) } return r } -var ( - goodParamFuncReturnType = reflect.TypeOf(func(string) bool { return false }) - goodParamFuncReturnType2 = reflect.TypeOf(EvaluatorFunc(func(string) bool { return false })) -) - -func goodParamFunc(typ reflect.Type) bool { - // should be a func - // which returns a func(string) bool - if typ.Kind() == reflect.Func { - if typ.NumOut() == 1 { - typOut := typ.Out(0) - if typOut == goodParamFuncReturnType || typOut == goodParamFuncReturnType2 { - return true - } - } - } - - return false -} - // goodParamFuncName reports whether the function name is a valid identifier. func goodParamFuncName(name string) bool { if name == "" { @@ -85,7 +102,7 @@ func goodParamFuncName(name string) bool { // the convertBuilderFunc return value is generating at boot time. // convertFunc converts an interface to a valid full param function. -func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { +func convertBuilderFunc(fn interface{}) ParamFuncBuilder { typFn := reflect.TypeOf(fn) if !goodParamFunc(typFn) { @@ -94,7 +111,7 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { numFields := typFn.NumIn() - return func(args []string) EvaluatorFunc { + return func(args []string) reflect.Value { if len(args) != numFields { // no variadics support, for now. panic("args should be the same len as numFields") @@ -179,24 +196,25 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { argValue := reflect.ValueOf(val) if expected, got := field.Kind(), argValue.Kind(); expected != got { - panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) + panic(fmt.Sprintf("func's input arguments should have the same type: [%d] expected %s but got %s", i, expected, got)) } argValues = append(argValues, argValue) } - evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface() + evalFn := reflect.ValueOf(fn).Call(argValues)[0] - var evaluator EvaluatorFunc - // check for typed and not typed - if _v, ok := evalFn.(EvaluatorFunc); ok { - evaluator = _v - } else if _v, ok = evalFn.(func(string) bool); ok { - evaluator = _v - } - return func(paramValue string) bool { - return evaluator(paramValue) - } + // var evaluator EvaluatorFunc + // // check for typed and not typed + // if _v, ok := evalFn.(EvaluatorFunc); ok { + // evaluator = _v + // } else if _v, ok = evalFn.(func(string) bool); ok { + // evaluator = _v + // } + // return func(paramValue interface{}) bool { + // return evaluator(paramValue) + // } + return evalFn } } @@ -218,16 +236,16 @@ type ( master bool trailing bool - Evaluator EvaluatorFunc + Evaluator ParamEvaluator funcs []ParamFunc } - // ParamEvaluatorBuilder is a func + // ParamFuncBuilder is a func // which accepts a param function's arguments (values) - // and returns an EvaluatorFunc, its job + // and returns a function as value, its job // is to make the macros to be registered // by user at the most generic possible way. - ParamEvaluatorBuilder func([]string) EvaluatorFunc + ParamFuncBuilder func([]string) reflect.Value // the func // ParamFunc represents the parsed // parameter function, it holds @@ -236,13 +254,13 @@ type ( // the evaluator func. ParamFunc struct { Name string - Func ParamEvaluatorBuilder + Func ParamFuncBuilder } ) // NewMacro creates and returns a Macro that can be used as a registry for // a new customized parameter type and its functions. -func NewMacro(indent, alias string, master, trailing bool, evaluator EvaluatorFunc) *Macro { +func NewMacro(indent, alias string, master, trailing bool, evaluator ParamEvaluator) *Macro { return &Macro{ indent: indent, alias: alias, @@ -287,7 +305,7 @@ func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro { return m } -func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { +func (m *Macro) registerFunc(funcName string, fullFn ParamFuncBuilder) { if !goodParamFuncName(funcName) { return } @@ -305,7 +323,7 @@ func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { }) } -func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder { +func (m *Macro) getFunc(funcName string) ParamFuncBuilder { for _, fn := range m.funcs { if fn.Name == funcName { if fn.Func == nil { diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 20a29732..020f4d74 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -2,6 +2,7 @@ package macro import ( "reflect" + "strconv" "testing" ) @@ -64,9 +65,25 @@ func TestGoodParamFuncName(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("%s - tests[%d] - expecting %v but got %v", t.Name(), i, pass, got) +func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, expectedType reflect.Kind, pass bool, i int) { + if macroEvaluator.Evaluator == nil && pass { + return // if not evaluator defined then it should allow everything. + } + value, passed := macroEvaluator.Evaluator(input) + if pass != passed { + t.Fatalf("%s - tests[%d] - expecting[pass] %v but got %v", t.Name(), i, pass, passed) + } + + if !passed { + return + } + + if value == nil && expectedType != reflect.Invalid { + t.Fatalf("%s - tests[%d] - expecting[value] to not be nil", t.Name(), i) + } + + if v := reflect.ValueOf(value); v.Kind() != expectedType { + t.Fatalf("%s - tests[%d] - expecting[value.Kind] %v but got %v", t.Name(), i, expectedType, v.Kind()) } } @@ -84,30 +101,32 @@ func TestStringEvaluatorRaw(t *testing.T) { } // 0 for i, tt := range tests { - testEvaluatorRaw(t, String, tt.input, tt.pass, i) + testEvaluatorRaw(t, String, tt.input, reflect.String, tt.pass, i) } } -func TestNumberEvaluatorRaw(t *testing.T) { +func TestIntEvaluatorRaw(t *testing.T) { + x64 := strconv.IntSize == 64 + tests := []struct { pass bool input string }{ - {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"}, // 8 - {false, "main.css"}, // 9 - {false, "/assets/main.css"}, // 10 + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {x64, "9223372036854775807" /*max int64*/}, // 3 + {x64, "-9223372036854775808" /*min int64 */}, // 4 + {false, "-18446744073709553213213213213213121615"}, // 5 + {false, "42 18446744073709551615"}, // 6 + {false, "--42"}, // 7 + {false, "+42"}, // 8 + {false, "main.css"}, // 9 + {false, "/assets/main.css"}, // 10 } for i, tt := range tests { - testEvaluatorRaw(t, Number, tt.input, tt.pass, i) + testEvaluatorRaw(t, Int, tt.input, reflect.Int, tt.pass, i) } } @@ -132,7 +151,7 @@ func TestInt64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Int64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Int64, tt.input, reflect.Int64, tt.pass, i) } } @@ -161,7 +180,7 @@ func TestUint8EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Uint8, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint8, tt.input, reflect.Uint8, tt.pass, i) } } @@ -186,7 +205,7 @@ func TestUint64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Uint64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint64, tt.input, reflect.Uint64, tt.pass, i) } } @@ -203,7 +222,7 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Alphabetical, tt.input, tt.pass, i) + testEvaluatorRaw(t, Alphabetical, tt.input, reflect.String, tt.pass, i) } } @@ -220,7 +239,7 @@ func TestFileEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, File, tt.input, tt.pass, i) + testEvaluatorRaw(t, File, tt.input, reflect.String, tt.pass, i) } } @@ -238,7 +257,7 @@ func TestPathEvaluatorRaw(t *testing.T) { } for i, tt := range pathTests { - testEvaluatorRaw(t, Path, tt.input, tt.pass, i) + testEvaluatorRaw(t, Path, tt.input, reflect.String, tt.pass, i) } } @@ -270,8 +289,7 @@ func TestConvertBuilderFunc(t *testing.T) { } evalFunc := convertBuilderFunc(fn) - - if !evalFunc([]string{"1", "[name1,name2]"})("ok") { + if !evalFunc([]string{"1", "[name1,name2]"}).Call([]reflect.Value{reflect.ValueOf("ok")})[0].Interface().(bool) { t.Fatalf("failed, it should fail already") } } diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go index 1a520af2..756ef9dd 100644 --- a/core/router/macro/macros.go +++ b/core/router/macro/macros.go @@ -10,233 +10,205 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). - String = NewMacro("string", "", true, false, func(string) bool { return true }). - RegisterFunc("regexp", func(expr string) EvaluatorFunc { - return MustNewEvaluatorFromRegexp(expr) + String = NewMacro("string", "", true, false, nil). // if nil allows everything. + RegisterFunc("regexp", func(expr string) func(string) bool { + return MustRegexp(expr) }). // checks if param value starts with the 'prefix' arg - RegisterFunc("prefix", func(prefix string) EvaluatorFunc { + RegisterFunc("prefix", func(prefix string) func(string) bool { return func(paramValue string) bool { return strings.HasPrefix(paramValue, prefix) } }). // checks if param value ends with the 'suffix' arg - RegisterFunc("suffix", func(suffix string) EvaluatorFunc { + RegisterFunc("suffix", func(suffix string) func(string) bool { return func(paramValue string) bool { return strings.HasSuffix(paramValue, suffix) } }). // checks if param value contains the 's' arg - RegisterFunc("contains", func(s string) EvaluatorFunc { + RegisterFunc("contains", func(s string) func(string) bool { return func(paramValue string) bool { return strings.Contains(paramValue, s) } }). // checks if param value's length is at least 'min' - RegisterFunc("min", func(min int) EvaluatorFunc { + RegisterFunc("min", func(min int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= min } }). // checks if param value's length is not bigger than 'max' - RegisterFunc("max", func(max int) EvaluatorFunc { + RegisterFunc("max", func(max int) func(string) bool { return func(paramValue string) bool { return max >= len(paramValue) } }) - simpleNumberEvalutator = MustNewEvaluatorFromRegexp("^-?[0-9]+$") - // Number or int type - // both positive and negative numbers, any number of digits. - Number = NewMacro("number", "int", false, false, simpleNumberEvalutator). + simpleNumberEval = MustRegexp("^-?[0-9]+$") + // Int or int type + // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch. + Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + v, err := strconv.Atoi(paramValue) + if err != nil { + return nil, false + } + + return v, true + }). // checks if the param value's int representation is // bigger or equal than 'min' - RegisterFunc("min", func(min int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min int) func(int) bool { + return func(paramValue int) bool { + return paramValue >= min } }). // checks if the param value's int representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max int) func(int) bool { + return func(paramValue int) bool { + return paramValue <= max } }). // checks if the param value's int representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true + RegisterFunc("range", func(min, max int) func(int) bool { + return func(paramValue int) bool { + return !(paramValue < min || paramValue > max) } }) // Int64 as int64 type // -9223372036854775808 to 9223372036854775807. - Int64 = NewMacro("int64", "long", false, false, func(paramValue string) bool { - if !simpleNumberEvalutator(paramValue) { - return false + Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false } - _, err := strconv.ParseInt(paramValue, 10, 64) - // if err == strconv.ErrRange... - return err == nil + v, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { // if err == strconv.ErrRange... + return nil, false + } + return v, true }). // checks if the param value's int64 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min int64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min int64) func(int64) bool { + return func(paramValue int64) bool { + return paramValue >= min } }). // checks if the param value's int64 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max int64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max int64) func(int64) bool { + return func(paramValue int64) bool { + return paramValue <= max } }). // checks if the param value's int64 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max int64) 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 + RegisterFunc("range", func(min, max int64) func(int64) bool { + return func(paramValue int64) bool { + return !(paramValue < min || paramValue > max) } }) + uint8Eval = MustRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") // Uint8 as uint8 type // 0 to 255. - Uint8 = NewMacro("uint8", "", false, false, MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")). + Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) { + if !uint8Eval(paramValue) { + return nil, false + } + + v, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return nil, false + } + return uint8(v), true + }). // checks if the param value's uint8 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - return uint8(n) >= min + RegisterFunc("min", func(min uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return paramValue >= min } }). // checks if the param value's uint8 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - return uint8(n) <= max + RegisterFunc("max", func(max uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return paramValue <= max } }). // checks if the param value's uint8 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - if v := uint8(n); v < min || v > max { - return false - } - return true + RegisterFunc("range", func(min, max uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return !(paramValue < min || paramValue > max) } }) // Uint64 as uint64 type // 0 to 18446744073709551615. - Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) bool { - if !simpleNumberEvalutator(paramValue) { - return false + Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false } - _, err := strconv.ParseUint(paramValue, 10, 64) - return err == nil + v, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return nil, false + } + return v, true }). // checks if the param value's uint64 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min uint64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return paramValue >= min } }). // checks if the param value's uint64 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max uint64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return paramValue <= max } }). // checks if the param value's uint64 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max uint64) 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 + RegisterFunc("range", func(min, max uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return !(paramValue < min || paramValue > max) } }) // Bool or 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". - Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) bool { + Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) (interface{}, bool) { // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ // in this case. - _, err := strconv.ParseBool(paramValue) - return err == nil + v, err := strconv.ParseBool(paramValue) + if err != nil { + return nil, false + } + return v, true }) + alphabeticalEval = MustRegexp("^[a-zA-Z ]+$") // Alphabetical letter type // letters only (upper or lowercase) - Alphabetical = NewMacro("alphabetical", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")) + Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) { + if !alphabeticalEval(paramValue) { + return nil, false + } + return paramValue, true + }) + + fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$") // File type // letters (upper or lowercase) // numbers (0-9) @@ -244,7 +216,12 @@ var ( // dash (-) // point (.) // no spaces! or other character - File = NewMacro("file", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")) + File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) { + if !fileEval(paramValue) { + return nil, false + } + return paramValue, true + }) // Path type // anything, should be the last part // @@ -252,11 +229,11 @@ var ( // types because I want to give the opportunity to the user // to organise the macro functions based on wildcard or single dynamic named path parameter. // Should be living in the latest path segment of a route path. - Path = NewMacro("path", "", false, true, func(string) bool { return true }) + Path = NewMacro("path", "", false, true, nil) Defaults = &Macros{ String, - Number, + Int, Int64, Uint8, Uint64, @@ -268,7 +245,7 @@ var ( type Macros []*Macro -func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator EvaluatorFunc) *Macro { +func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro { macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) if ms.register(macro) { return macro diff --git a/core/router/macro/template.go b/core/router/macro/template.go index 3b30d2f7..9491b6df 100644 --- a/core/router/macro/template.go +++ b/core/router/macro/template.go @@ -1,6 +1,8 @@ package macro import ( + "reflect" + "github.com/kataras/iris/core/router/macro/interpreter/ast" "github.com/kataras/iris/core/router/macro/interpreter/parser" ) @@ -27,8 +29,8 @@ type TemplateParam struct { Name string `json:"name"` Index int `json:"index"` ErrCode int `json:"errCode"` - TypeEvaluator EvaluatorFunc `json:"-"` - Funcs []EvaluatorFunc `json:"-"` + TypeEvaluator ParamEvaluator `json:"-"` + Funcs []reflect.Value `json:"-"` } // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) @@ -74,7 +76,7 @@ func Parse(src string, macros Macros) (*Template, error) { } evalFn := tmplFn(paramfn.Args) - if evalFn == nil { + if evalFn.IsNil() || !evalFn.IsValid() || evalFn.Kind() != reflect.Func { continue } tmplParam.Funcs = append(tmplParam.Funcs, evalFn) diff --git a/hero/handler.go b/hero/handler.go index 6644dc83..f0b47044 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -6,15 +6,13 @@ import ( "runtime" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/hero/di" "github.com/kataras/golog" ) var ( - contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() - memstoreTyp = reflect.TypeOf(memstore.Store{}) + contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() ) // IsContext returns true if the "inTyp" is a type of Context. @@ -22,14 +20,6 @@ func IsContext(inTyp reflect.Type) bool { return inTyp.Implements(contextTyp) } -// IsExpectingStore returns true if the "inTyp" is a type of memstore.Store. -func IsExpectingStore(inTyp reflect.Type) bool { - print("di/handler.go: " + inTyp.String() + " vs " + memstoreTyp.String() + " : ") - println(inTyp == memstoreTyp) - - return inTyp == memstoreTyp -} - // checks if "handler" is context.Handler: func(context.Context). func isContextHandler(handler interface{}) (context.Handler, bool) { h, is := handler.(context.Handler) From f05ee872d0a40b71a3e045ba5b366305841ee8aa Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 06:20:03 +0300 Subject: [PATCH 17/42] add int8, int16, int32, uint, uint16 and uint32 default-builtn parameter types and macros - no doc update - no live tests yet - time for sleep Former-commit-id: 4a27265a9f1368c4bbecd852691155e56c875673 --- context/request_params.go | 47 +++++++-- core/router/macro.go | 3 +- core/router/macro/macro.go | 6 +- core/router/macro/macros.go | 183 +++++++++++++++++++++++++++++++++++- 4 files changed, 222 insertions(+), 17 deletions(-) diff --git a/context/request_params.go b/context/request_params.go index b8426856..9e45c363 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -91,32 +91,59 @@ var ( }, reflect.Int: func(paramIndex int) interface{} { return func(ctx Context) int { - v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) - return v + // v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) + // return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int) + } + }, + reflect.Int8: func(paramIndex int) interface{} { + return func(ctx Context) int8 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int8) + } + }, + reflect.Int16: func(paramIndex int) interface{} { + return func(ctx Context) int16 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int16) + } + }, + reflect.Int32: func(paramIndex int) interface{} { + return func(ctx Context) int32 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int32) } }, reflect.Int64: func(paramIndex int) interface{} { return func(ctx Context) int64 { - v, _ := ctx.Params().GetEntryAt(paramIndex).Int64Default(0) - return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int64) + } + }, + reflect.Uint: func(paramIndex int) interface{} { + return func(ctx Context) uint { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint) } }, reflect.Uint8: func(paramIndex int) interface{} { return func(ctx Context) uint8 { - v, _ := ctx.Params().GetEntryAt(paramIndex).Uint8Default(0) - return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint8) + } + }, + reflect.Uint16: func(paramIndex int) interface{} { + return func(ctx Context) uint16 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint16) + } + }, + reflect.Uint32: func(paramIndex int) interface{} { + return func(ctx Context) uint32 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) } }, reflect.Uint64: func(paramIndex int) interface{} { return func(ctx Context) uint64 { - v, _ := ctx.Params().GetEntryAt(paramIndex).Uint64Default(0) - return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint64) } }, reflect.Bool: func(paramIndex int) interface{} { return func(ctx Context) bool { - v, _ := ctx.Params().GetEntryAt(paramIndex).BoolDefault(false) - return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(bool) } }, } diff --git a/core/router/macro.go b/core/router/macro.go index 2af58d34..b17941a6 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -83,7 +83,6 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { return func(tmpl macro.Template) context.Handler { return func(ctx context.Context) { for _, p := range tmpl.Params { - paramValue := ctx.Params().Get(p.Name) if p.TypeEvaluator == nil { // allow. ctx.Next() @@ -91,7 +90,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { } // first, check for type evaluator. - newValue, passed := p.TypeEvaluator(paramValue) + newValue, passed := p.TypeEvaluator(ctx.Parms().Get(p.Name)) if !passed { ctx.StatusCode(p.ErrCode) ctx.StopExecution() diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index a589e60a..2e461ad2 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -154,6 +154,10 @@ func convertBuilderFunc(fn interface{}) ParamFuncBuilder { v, err := strconv.ParseInt(arg, 10, 64) panicIfErr(err) val = v + case reflect.Uint: + v, err := strconv.ParseUint(arg, 10, strconv.IntSize) + panicIfErr(err) + val = uint(v) case reflect.Uint8: v, err := strconv.ParseUint(arg, 10, 8) panicIfErr(err) @@ -245,7 +249,7 @@ type ( // and returns a function as value, its job // is to make the macros to be registered // by user at the most generic possible way. - ParamFuncBuilder func([]string) reflect.Value // the func + ParamFuncBuilder func([]string) reflect.Value // the func() bool // ParamFunc represents the parsed // parameter function, it holds diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go index 756ef9dd..a6297553 100644 --- a/core/router/macro/macros.go +++ b/core/router/macro/macros.go @@ -46,12 +46,13 @@ var ( }) simpleNumberEval = MustRegexp("^-?[0-9]+$") - // Int or int type + // Int or number type // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch. Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) { if !simpleNumberEval(paramValue) { return nil, false } + v, err := strconv.Atoi(paramValue) if err != nil { return nil, false @@ -81,12 +82,100 @@ var ( } }) + // Int8 type + // -128 to 127. + Int8 = NewMacro("int8", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 8) + if err != nil { + return nil, false + } + return int8(v), true + }). + RegisterFunc("min", func(min int8) func(int8) bool { + return func(paramValue int8) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int8) func(int8) bool { + return func(paramValue int8) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int8) func(int8) bool { + return func(paramValue int8) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Int16 type + // -32768 to 32767. + Int16 = NewMacro("int16", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 16) + if err != nil { + return nil, false + } + return int16(v), true + }). + RegisterFunc("min", func(min int16) func(int16) bool { + return func(paramValue int16) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int16) func(int16) bool { + return func(paramValue int16) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int16) func(int16) bool { + return func(paramValue int16) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Int32 type + // -2147483648 to 2147483647. + Int32 = NewMacro("int32", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 32) + if err != nil { + return nil, false + } + return int32(v), true + }). + RegisterFunc("min", func(min int32) func(int32) bool { + return func(paramValue int32) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int32) func(int32) bool { + return func(paramValue int32) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int32) func(int32) bool { + return func(paramValue int32) bool { + return !(paramValue < min || paramValue > max) + } + }) + // Int64 as int64 type // -9223372036854775808 to 9223372036854775807. Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) { if !simpleNumberEval(paramValue) { return nil, false } + v, err := strconv.ParseInt(paramValue, 10, 64) if err != nil { // if err == strconv.ErrRange... return nil, false @@ -115,6 +204,39 @@ var ( } }) + // Uint as uint type + // actual value can be min-max uint64 or min-max uint32 depends on the arch. + // if x64: 0 to 18446744073709551615 + // if x32: 0 to 4294967295 and etc. + Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64... + if err != nil { + return nil, false + } + return uint(v), true + }). + // checks if the param value's int representation is + // bigger or equal than 'min' + RegisterFunc("min", func(min uint) func(uint) bool { + return func(paramValue uint) bool { + return paramValue >= min + } + }). + // checks if the param value's int representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max uint) func(uint) bool { + return func(paramValue uint) bool { + return paramValue <= max + } + }). + // checks if the param value's int representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max uint) func(uint) bool { + return func(paramValue uint) bool { + return !(paramValue < min || paramValue > max) + } + }) + uint8Eval = MustRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") // Uint8 as uint8 type // 0 to 255. @@ -151,12 +273,59 @@ var ( } }) + // Uint16 as uint16 type + // 0 to 65535. + Uint16 = NewMacro("uint16", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 16) + if err != nil { + return nil, false + } + return uint16(v), true + }). + RegisterFunc("min", func(min uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Uint32 as uint32 type + // 0 to 4294967295. + Uint32 = NewMacro("uint32", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 32) + if err != nil { + return nil, false + } + return uint32(v), true + }). + RegisterFunc("min", func(min uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return !(paramValue < min || paramValue > max) + } + }) + // Uint64 as uint64 type // 0 to 18446744073709551615. Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) { - if !simpleNumberEval(paramValue) { - return nil, false - } v, err := strconv.ParseUint(paramValue, 10, 64) if err != nil { return nil, false @@ -234,8 +403,14 @@ var ( Defaults = &Macros{ String, Int, + Int8, + Int16, + Int32, Int64, + Uint, Uint8, + Uint16, + Uint32, Uint64, Bool, Alphabetical, From 39b180b14cbbd197569deb1f45496ed85f952843 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 06:28:47 +0300 Subject: [PATCH 18/42] see previous commit for more details Former-commit-id: e88246203ffc492ffa39e8d20ed19cac7abd52c2 --- core/router/macro.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/macro.go b/core/router/macro.go index b17941a6..0a84f67f 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -90,7 +90,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { } // first, check for type evaluator. - newValue, passed := p.TypeEvaluator(ctx.Parms().Get(p.Name)) + newValue, passed := p.TypeEvaluator(ctx.Params().Get(p.Name)) if !passed { ctx.StatusCode(p.ErrCode) ctx.StopExecution() From 972dff872924d3baa632856e04fc8920ae473b38 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 21:31:07 +0300 Subject: [PATCH 19/42] easy fix of macro handler caused tests to fail by before prev commit Former-commit-id: 32f67072f604935a8efecc90151715f27ba7c2c1 --- _examples/routing/macros/main.go | 56 ++++++++++++++++---------------- core/router/macro.go | 3 +- mvc/controller_test.go | 6 ++-- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index 8548da88..1afaf005 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "reflect" - "strconv" "github.com/kataras/iris" "github.com/kataras/iris/context" @@ -37,39 +36,40 @@ func main() { ^ Done 27 sep 2018. */ - app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { - v, err := strconv.ParseUint(paramValue, 10, 32) - return uint32(v), err == nil - }). - RegisterFunc("min", func(min uint32) func(uint32) bool { - return func(paramValue uint32) bool { - return paramValue >= min - } - }) + // app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { + // v, err := strconv.ParseUint(paramValue, 10, 32) + // return uint32(v), err == nil + // }). + // RegisterFunc("min", func(min uint32) func(uint32) bool { + // return func(paramValue uint32) bool { + // return paramValue >= min + // } + // }) - // optionally, only when mvc or hero features are used for this custom macro/parameter type. - context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { - /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ - // return func(ctx context.Context) uint32 { - // param := ctx.Params().GetEntryAt(paramIndex) - // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) - // return uint32(paramValueAsUint32) - // } - return func(ctx context.Context) uint32 { - return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) - } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, - we must return a value i.e 0 for int for its interface{} */ - } - // + // // optionally, only when mvc or hero features are used for this custom macro/parameter type. + // context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { + // /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ + // // return func(ctx context.Context) uint32 { + // // param := ctx.Params().GetEntryAt(paramIndex) + // // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) + // // return uint32(paramValueAsUint32) + // // } + // return func(ctx context.Context) uint32 { + // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) + // } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, + // we must return a value i.e 0 for int for its interface{} */ + // } + // // - app.Get("/test_uint32/{myparam:uint32 min(10)}", hero.Handler(func(paramValue uint32) string { - return fmt.Sprintf("Value of the parameter is: %d\n", paramValue) + app.Get("/test_uint32/{myparam1:string}/{myparam2:uint32 min(10)}", hero.Handler(func(myparam1 string, myparam2 uint32) string { + return fmt.Sprintf("Value of the parameters are: %s:%d\n", myparam1, myparam2) })) - app.Get("test_uint64/{myparam:uint64 min(5)}", func(ctx context.Context) { + app.Get("test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) // but better and faster because the macro converts the string to uint64 automatically: - ctx.Writef("Value of the parameter is: %d\n", ctx.Params().GetUint64Default("myparam", 0)) + println("type of myparam2 (should be uint64) is: " + reflect.ValueOf(ctx.Params().GetEntry("myparam2").ValueRaw).Kind().String()) + ctx.Writef("Value of the parameters are: %s:%d\n", ctx.Params().Get("myparam1"), ctx.Params().GetUint64Default("myparam2", 0)) }) app.Run(iris.Addr(":8080")) diff --git a/core/router/macro.go b/core/router/macro.go index 0a84f67f..dfbf39be 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -85,8 +85,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { for _, p := range tmpl.Params { if p.TypeEvaluator == nil { // allow. - ctx.Next() - return + continue } // first, check for type evaluator. diff --git a/mvc/controller_test.go b/mvc/controller_test.go index f367d0ea..7103c9e9 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -377,8 +377,10 @@ func (c *testControllerRelPathFromFunc) GetAdminLogin() {} func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {} -func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {} -func (c *testControllerRelPathFromFunc) GetSomethingByBy(string, int) {} +func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {} + +func (c *testControllerRelPathFromFunc) GetSomethingByBy(string, int) {} + func (c *testControllerRelPathFromFunc) GetSomethingNewBy(string, int) {} // two input arguments, one By which is the latest word. func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} // two input arguments From bf880033cd57af5fcefac7acd32761a4018f7855 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 28 Sep 2018 05:34:35 +0300 Subject: [PATCH 20/42] make macros even faster and smart catch common :string and do not execute anything at all if not really needed, more clean code as well Former-commit-id: 589c23d1f92cf36b7677dfe78b60d51252c979fb --- _examples/routing/macros/main.go | 18 ++++++++++ core/router/macro.go | 44 ++++++------------------ core/router/macro/macros.go | 2 +- core/router/macro/template.go | 58 +++++++++++++++++++++++++++++++- 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index 1afaf005..a1dd0d77 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -65,6 +65,24 @@ func main() { return fmt.Sprintf("Value of the parameters are: %s:%d\n", myparam1, myparam2) })) + app.Get("/test_string/{myparam1}/{myparam2 prefix(a)}", func(ctx context.Context) { + var ( + myparam1 = ctx.Params().Get("myparam1") + myparam2 = ctx.Params().Get("myparam2") + ) + + ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) + }) + + app.Get("/test_string2/{myparam1}/{myparam2}", func(ctx context.Context) { + var ( + myparam1 = ctx.Params().Get("myparam1") + myparam2 = ctx.Params().Get("myparam2") + ) + + ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) + }) + app.Get("test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) // but better and faster because the macro converts the string to uint64 automatically: diff --git a/core/router/macro.go b/core/router/macro.go index dfbf39be..07549640 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -2,8 +2,6 @@ package router import ( "fmt" - "net/http" - "reflect" "strings" "github.com/kataras/iris/context" @@ -61,21 +59,20 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) { // Note: returns nil if not needed, the caller(router) should check for that before adding that on route's Middleware. func convertTmplToHandler(tmpl *macro.Template) context.Handler { - - needMacroHandler := false - // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) // 2. if we don't have any named params then we don't need a handler too. + + needsMacroHandler := false for _, p := range tmpl.Params { - if len(p.Funcs) == 0 && (ast.IsMaster(p.Type) || ast.IsTrailing(p.Type)) && p.ErrCode == http.StatusNotFound { - } else { - // println("we need handler for: " + tmpl.Src) - needMacroHandler = true + if p.CanEval() { + // if at least one needs it, then create the handler. + needsMacroHandler = true + break } } - if !needMacroHandler { + if !needsMacroHandler { // println("we don't need handler for: " + tmpl.Src) return nil } @@ -83,38 +80,19 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { return func(tmpl macro.Template) context.Handler { return func(ctx context.Context) { for _, p := range tmpl.Params { - if p.TypeEvaluator == nil { - // allow. - continue + if !p.CanEval() { + // println(p.Src + " no need to evaluate anything") + continue // allow. } - // first, check for type evaluator. - newValue, passed := p.TypeEvaluator(ctx.Params().Get(p.Name)) - if !passed { + if !p.Eval(ctx.Params().Get(p.Name), ctx.Params().Set) { ctx.StatusCode(p.ErrCode) ctx.StopExecution() return } - - if len(p.Funcs) > 0 { - paramIn := []reflect.Value{reflect.ValueOf(newValue)} - // then check for all of its functions - for _, evalFunc := range p.Funcs { - // or make it as func(interface{}) bool and pass directly the "newValue" - // but that would not be as easy for end-developer, so keep that "slower": - if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return - } - } - } - - ctx.Params().Store.Set(p.Name, newValue) } // if all passed, just continue. ctx.Next() } }(*tmpl) - } diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go index a6297553..6832ee92 100644 --- a/core/router/macro/macros.go +++ b/core/router/macro/macros.go @@ -10,7 +10,7 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). - String = NewMacro("string", "", true, false, nil). // if nil allows everything. + String = NewMacro("string", "", true, false, nil). RegisterFunc("regexp", func(expr string) func(string) bool { return MustRegexp(expr) }). diff --git a/core/router/macro/template.go b/core/router/macro/template.go index 9491b6df..983427a8 100644 --- a/core/router/macro/template.go +++ b/core/router/macro/template.go @@ -1,6 +1,7 @@ package macro import ( + "github.com/kataras/iris/core/memstore" "reflect" "github.com/kataras/iris/core/router/macro/interpreter/ast" @@ -31,6 +32,61 @@ type TemplateParam struct { ErrCode int `json:"errCode"` TypeEvaluator ParamEvaluator `json:"-"` Funcs []reflect.Value `json:"-"` + + stringInFuncs []func(string) bool + canEval bool +} + +func (p TemplateParam) preComputed() TemplateParam { + for _, pfn := range p.Funcs { + if fn, ok := pfn.Interface().(func(string) bool); ok { + p.stringInFuncs = append(p.stringInFuncs, fn) + } + } + + // if true then it should be execute the type parameter or its functions + // else it can be ignored, + // i.e {myparam} or {myparam:string} or {myparam:path} -> + // their type evaluator is nil because they don't do any checks and they don't change + // the default parameter value's type (string) so no need for any work). + p.canEval = p.TypeEvaluator != nil || len(p.Funcs) > 0 || p.ErrCode != parser.DefaultParamErrorCode + + return p +} + +func (p *TemplateParam) CanEval() bool { + return p.canEval +} + +// paramChanger is the same form of context's Params().Set +func (p *TemplateParam) Eval(paramValue string, paramChanger func(key string, newValue interface{}) (memstore.Entry, bool)) bool { + if p.TypeEvaluator == nil { + for _, fn := range p.stringInFuncs { + if !fn(paramValue) { + return false + } + } + return true + } + + newValue, passed := p.TypeEvaluator(paramValue) + if !passed { + return false + } + + if len(p.Funcs) > 0 { + paramIn := []reflect.Value{reflect.ValueOf(newValue)} + for _, evalFunc := range p.Funcs { + // or make it as func(interface{}) bool and pass directly the "newValue" + // but that would not be as easy for end-developer, so keep that "slower": + if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool + return false + } + } + } + + paramChanger(p.Name, newValue) + return true } // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) @@ -82,7 +138,7 @@ func Parse(src string, macros Macros) (*Template, error) { tmplParam.Funcs = append(tmplParam.Funcs, evalFn) } - t.Params = append(t.Params, tmplParam) + t.Params = append(t.Params, tmplParam.preComputed()) } return t, nil From 6d9a35ddbafd59f35fff96ed80a3148c3fd3cba6 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 29 Sep 2018 02:41:51 +0300 Subject: [PATCH 21/42] partial cleanup of the macro pkg and move it from /core/router to the root because it may be used by the end-developers now to ammend the available macros per application Former-commit-id: 951a5e7a401af25ecaa904ff6463b0def2c87afb --- HISTORY.md | 2 +- HISTORY_ID.md | 2 +- HISTORY_ZH.md | 2 +- _examples/routing/macros/main.go | 2 +- context/route.go | 2 +- core/memstore/memstore.go | 7 ++ core/router/api_builder.go | 2 +- core/router/macro.go | 98 ------------------- core/router/party.go | 2 +- core/router/path.go | 26 ++++- core/router/route.go | 11 ++- macro/handler/handler.go | 52 ++++++++++ .../macro => macro}/interpreter/ast/ast.go | 0 .../interpreter/lexer/lexer.go | 2 +- .../interpreter/lexer/lexer_test.go | 2 +- .../interpreter/parser/parser.go | 8 +- .../interpreter/parser/parser_test.go | 2 +- .../interpreter/token/token.go | 0 {core/router/macro => macro}/macro.go | 0 {core/router/macro => macro}/macro_test.go | 0 {core/router/macro => macro}/macros.go | 2 +- {core/router/macro => macro}/template.go | 18 ++-- mvc/controller.go | 2 +- mvc/controller_method_parser.go | 2 +- mvc/param.go | 2 +- 25 files changed, 119 insertions(+), 129 deletions(-) delete mode 100644 core/router/macro.go create mode 100644 macro/handler/handler.go rename {core/router/macro => macro}/interpreter/ast/ast.go (100%) rename {core/router/macro => macro}/interpreter/lexer/lexer.go (98%) rename {core/router/macro => macro}/interpreter/lexer/lexer_test.go (95%) rename {core/router/macro => macro}/interpreter/parser/parser.go (94%) rename {core/router/macro => macro}/interpreter/parser/parser_test.go (99%) rename {core/router/macro => macro}/interpreter/token/token.go (100%) rename {core/router/macro => macro}/macro.go (100%) rename {core/router/macro => macro}/macro_test.go (100%) rename {core/router/macro => macro}/macros.go (99%) rename {core/router/macro => macro}/template.go (88%) diff --git a/HISTORY.md b/HISTORY.md index 5f774844..f1c56f5d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -294,7 +294,7 @@ For example: at [_examples/mvc/basic/main.go line 100](_examples/mvc/basic/main. - fix `APIBuilder, Party#StaticWeb` and `APIBuilder, Party#StaticEmbedded` wrong strip prefix inside children parties - keep the `iris, core/router#StaticEmbeddedHandler` and remove the `core/router/APIBuilder#StaticEmbeddedHandler`, (note the `Handler` suffix) it's global and has nothing to do with the `Party` or the `APIBuilder` -- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](core/router/macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - sync the `golang.org/x/sys/unix` vendor ## The most important diff --git a/HISTORY_ID.md b/HISTORY_ID.md index 001bdab1..12a588bc 100644 --- a/HISTORY_ID.md +++ b/HISTORY_ID.md @@ -83,7 +83,7 @@ For example: at [_examples/mvc/basic/main.go line 100](_examples/mvc/basic/main. - fix `APIBuilder, Party#StaticWeb` and `APIBuilder, Party#StaticEmbedded` wrong strip prefix inside children parties - keep the `iris, core/router#StaticEmbeddedHandler` and remove the `core/router/APIBuilder#StaticEmbeddedHandler`, (note the `Handler` suffix) it's global and has nothing to do with the `Party` or the `APIBuilder` -- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](core/router/macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - sync the `golang.org/x/sys/unix` vendor ## The most important diff --git a/HISTORY_ZH.md b/HISTORY_ZH.md index 674ede1a..653623f6 100644 --- a/HISTORY_ZH.md +++ b/HISTORY_ZH.md @@ -79,7 +79,7 @@ This history entry is not translated yet to the Chinese language yet, please ref - 修正 `APIBuilder, Party#StaticWeb` 和 `APIBuilder, Party#StaticEmbedded` 子分组内的前缀错误 - 保留 `iris, core/router#StaticEmbeddedHandler` 并移除 `core/router/APIBuilder#StaticEmbeddedHandler`, (`Handler` 后缀) 这是全局性的,与 `Party` `APIBuilder` 无关。 -- 修正 路径 `{}` 中的路径清理 (我们已经在 [解释器](core/router/macro/interpreter) 级别转义了这些字符, 但是一些符号仍然被更高级别的API构建器删除) , 例如 `\\` 字符串的宏函数正则表达式内容 [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- 修正 路径 `{}` 中的路径清理 (我们已经在 [解释器](macro/interpreter) 级别转义了这些字符, 但是一些符号仍然被更高级别的API构建器删除) , 例如 `\\` 字符串的宏函数正则表达式内容 [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - 同步 `golang.org/x/sys/unix` ## 重要变更 diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index a1dd0d77..fe68c598 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -83,7 +83,7 @@ func main() { ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) }) - app.Get("test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { + app.Get("/test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) // but better and faster because the macro converts the string to uint64 automatically: println("type of myparam2 (should be uint64) is: " + reflect.ValueOf(ctx.Params().GetEntry("myparam2").ValueRaw).Kind().String()) diff --git a/context/route.go b/context/route.go index 9cda8e96..7c680e7d 100644 --- a/context/route.go +++ b/context/route.go @@ -1,6 +1,6 @@ package context -import "github.com/kataras/iris/core/router/macro" +import "github.com/kataras/iris/macro" // RouteReadOnly allows decoupled access to the current route // inside the context. diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 4eda5113..c36272d8 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -15,6 +15,11 @@ import ( ) type ( + // ValueSetter is the interface which can be accepted as a generic solution of RequestParams or memstore when Set is the only requirement, + // i.e internally on macro/template/TemplateParam#Eval:paramChanger. + ValueSetter interface { + Set(key string, newValue interface{}) (Entry, bool) + } // Entry is the entry of the context storage Store - .Values() Entry struct { Key string @@ -26,6 +31,8 @@ type ( Store []Entry ) +var _ ValueSetter = (*Store)(nil) + // GetByKindOrNil will try to get this entry's value of "k" kind, // if value is not that kind it will NOT try to convert it the "k", instead // it will return nil, except if boolean; then it will return false diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 614bddac..7f1f3635 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -9,7 +9,7 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) const ( diff --git a/core/router/macro.go b/core/router/macro.go deleted file mode 100644 index 07549640..00000000 --- a/core/router/macro.go +++ /dev/null @@ -1,98 +0,0 @@ -package router - -import ( - "fmt" - "strings" - - "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router/macro" - "github.com/kataras/iris/core/router/macro/interpreter/ast" -) - -// compileRoutePathAndHandlers receives a route info and returns its parsed/"compiled" path -// and the new handlers (prepend all the macro's handler, if any). -// -// It's not exported for direct use. -func compileRoutePathAndHandlers(handlers context.Handlers, tmpl *macro.Template) (string, context.Handlers, error) { - // parse the path to node's path, now. - path, err := convertTmplToNodePath(tmpl) - if err != nil { - return tmpl.Src, handlers, err - } - // prepend the macro handler to the route, now, - // right before the register to the tree, so routerbuilder.UseGlobal will work as expected. - if len(tmpl.Params) > 0 { - macroEvaluatorHandler := convertTmplToHandler(tmpl) - // may return nil if no really need a macro handler evaluator - if macroEvaluatorHandler != nil { - handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) - } - } - - return path, handlers, nil -} - -func convertTmplToNodePath(tmpl *macro.Template) (string, error) { - routePath := tmpl.Src - if len(tmpl.Params) > 0 { - if routePath[len(routePath)-1] == '/' { - routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's - } - } - - // if it has started with {} and it's valid - // then the tmpl.Params will be filled, - // so no any further check needed - for i, p := range tmpl.Params { - if ast.IsTrailing(p.Type) { - if i != len(tmpl.Params)-1 { - return "", fmt.Errorf("parameter type \"%s\" should be putted to the very last of a path", p.Type.Indent()) - } - routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1) - } else { - routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1) - } - } - - return routePath, nil -} - -// Note: returns nil if not needed, the caller(router) should check for that before adding that on route's Middleware. -func convertTmplToHandler(tmpl *macro.Template) context.Handler { - // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. - // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) - // 2. if we don't have any named params then we don't need a handler too. - - needsMacroHandler := false - for _, p := range tmpl.Params { - if p.CanEval() { - // if at least one needs it, then create the handler. - needsMacroHandler = true - break - } - } - - if !needsMacroHandler { - // println("we don't need handler for: " + tmpl.Src) - return nil - } - - return func(tmpl macro.Template) context.Handler { - return func(ctx context.Context) { - for _, p := range tmpl.Params { - if !p.CanEval() { - // println(p.Src + " no need to evaluate anything") - continue // allow. - } - - if !p.Eval(ctx.Params().Get(p.Name), ctx.Params().Set) { - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return - } - } - // if all passed, just continue. - ctx.Next() - } - }(*tmpl) -} diff --git a/core/router/party.go b/core/router/party.go index 99e482e1..5d462392 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -3,7 +3,7 @@ package router import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) // Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. diff --git a/core/router/path.go b/core/router/path.go index 3a471f90..9b9d12a9 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -7,7 +7,9 @@ import ( "strings" "github.com/kataras/iris/core/netutil" - "github.com/kataras/iris/core/router/macro/interpreter/lexer" + "github.com/kataras/iris/macro" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/lexer" ) const ( @@ -31,6 +33,28 @@ func WildcardParam(name string) string { return prefix(name, WildcardParamStart) } +func convertTmplToNodePath(tmpl *macro.Template) string { + routePath := tmpl.Src + if len(tmpl.Params) > 0 { + if routePath[len(routePath)-1] == '/' { + routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's. + } + } + + // if it has started with {} and it's valid + // then the tmpl.Params will be filled, + // so no any further check needed. + for _, p := range tmpl.Params { + if ast.IsTrailing(p.Type) { + routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1) + } else { + routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1) + } + } + + return routePath +} + func prefix(s string, prefix string) string { if !strings.HasPrefix(s, prefix) { return prefix + s diff --git a/core/router/route.go b/core/router/route.go index 11a69d6e..c5c273e1 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -5,7 +5,8 @@ import ( "strings" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" + "github.com/kataras/iris/macro/handler" ) // Route contains the information about a registered Route. @@ -46,9 +47,11 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, return nil, err } - path, handlers, err := compileRoutePathAndHandlers(handlers, tmpl) - if err != nil { - return nil, err + path := convertTmplToNodePath(tmpl) + // prepend the macro handler to the route, now, + // right before the register to the tree, so APIBuilder#UseGlobal will work as expected. + if macroEvaluatorHandler, ok := handler.MakeHandler(tmpl); ok { + handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) } path = cleanPath(path) // maybe unnecessary here but who cares in this moment diff --git a/macro/handler/handler.go b/macro/handler/handler.go new file mode 100644 index 00000000..24b6367a --- /dev/null +++ b/macro/handler/handler.go @@ -0,0 +1,52 @@ +// Package handler is the highest level module of the macro package which makes use the rest of the macro package, +// it is mainly used, internally, by the router package. +package handler + +import ( + "github.com/kataras/iris/context" + "github.com/kataras/iris/macro" +) + +// MakeHandler creates and returns a handler from a macro template, the handler evaluates each of the parameters if necessary at all. +// If the template does not contain any dynamic attributes and a special handler is NOT required +// then it returns a nil handler and false as its second output value, +// the caller should check those two values before any further action. +func MakeHandler(tmpl *macro.Template) (context.Handler, bool) { + needsMacroHandler := len(tmpl.Params) > 0 + if !needsMacroHandler { + return nil, false + } + + // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. + // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) + // 2. if we don't have any named params then we don't need a handler too. + for _, p := range tmpl.Params { + if p.CanEval() { + // if at least one needs it, then create the handler. + needsMacroHandler = true + break + } + } + + if !needsMacroHandler { + return nil, false + } + + handler := func(ctx context.Context) { + for _, p := range tmpl.Params { + if !p.CanEval() { + continue // allow. + } + + if !p.Eval(ctx.Params().Get(p.Name), ctx.Params()) { + ctx.StatusCode(p.ErrCode) + ctx.StopExecution() + return + } + } + // if all passed, just continue. + ctx.Next() + } + + return handler, true +} diff --git a/core/router/macro/interpreter/ast/ast.go b/macro/interpreter/ast/ast.go similarity index 100% rename from core/router/macro/interpreter/ast/ast.go rename to macro/interpreter/ast/ast.go diff --git a/core/router/macro/interpreter/lexer/lexer.go b/macro/interpreter/lexer/lexer.go similarity index 98% rename from core/router/macro/interpreter/lexer/lexer.go rename to macro/interpreter/lexer/lexer.go index 01646361..6a772602 100644 --- a/core/router/macro/interpreter/lexer/lexer.go +++ b/macro/interpreter/lexer/lexer.go @@ -1,7 +1,7 @@ package lexer import ( - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/token" ) // Lexer helps us to read/scan characters of a source and resolve their token types. diff --git a/core/router/macro/interpreter/lexer/lexer_test.go b/macro/interpreter/lexer/lexer_test.go similarity index 95% rename from core/router/macro/interpreter/lexer/lexer_test.go rename to macro/interpreter/lexer/lexer_test.go index e104e802..4ed056a2 100644 --- a/core/router/macro/interpreter/lexer/lexer_test.go +++ b/macro/interpreter/lexer/lexer_test.go @@ -3,7 +3,7 @@ package lexer import ( "testing" - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/token" ) func TestNextToken(t *testing.T) { diff --git a/core/router/macro/interpreter/parser/parser.go b/macro/interpreter/parser/parser.go similarity index 94% rename from core/router/macro/interpreter/parser/parser.go rename to macro/interpreter/parser/parser.go index b4bb0293..1a0b8608 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/macro/interpreter/parser/parser.go @@ -5,9 +5,9 @@ import ( "strconv" "strings" - "github.com/kataras/iris/core/router/macro/interpreter/ast" - "github.com/kataras/iris/core/router/macro/interpreter/lexer" - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/lexer" + "github.com/kataras/iris/macro/interpreter/token" ) // Parse takes a route "fullpath" @@ -39,7 +39,7 @@ func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, } // if we have param type path but it's not the last path part if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 { - return nil, fmt.Errorf("param type '%s' should be lived only inside the last path segment, but was inside: %s", stmt.Type, s) + return nil, fmt.Errorf("%s: parameter type \"%s\" should be registered to the very last of a path", s, stmt.Type.Indent()) } statements = append(statements, stmt) diff --git a/core/router/macro/interpreter/parser/parser_test.go b/macro/interpreter/parser/parser_test.go similarity index 99% rename from core/router/macro/interpreter/parser/parser_test.go rename to macro/interpreter/parser/parser_test.go index b492b361..1695e6f4 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/macro/interpreter/parser/parser_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/ast" ) type simpleParamType string diff --git a/core/router/macro/interpreter/token/token.go b/macro/interpreter/token/token.go similarity index 100% rename from core/router/macro/interpreter/token/token.go rename to macro/interpreter/token/token.go diff --git a/core/router/macro/macro.go b/macro/macro.go similarity index 100% rename from core/router/macro/macro.go rename to macro/macro.go diff --git a/core/router/macro/macro_test.go b/macro/macro_test.go similarity index 100% rename from core/router/macro/macro_test.go rename to macro/macro_test.go diff --git a/core/router/macro/macros.go b/macro/macros.go similarity index 99% rename from core/router/macro/macros.go rename to macro/macros.go index 6832ee92..b1d9a96b 100644 --- a/core/router/macro/macros.go +++ b/macro/macros.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/ast" ) var ( diff --git a/core/router/macro/template.go b/macro/template.go similarity index 88% rename from core/router/macro/template.go rename to macro/template.go index 983427a8..33b14734 100644 --- a/core/router/macro/template.go +++ b/macro/template.go @@ -1,11 +1,11 @@ package macro import ( - "github.com/kataras/iris/core/memstore" "reflect" - "github.com/kataras/iris/core/router/macro/interpreter/ast" - "github.com/kataras/iris/core/router/macro/interpreter/parser" + "github.com/kataras/iris/core/memstore" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/parser" ) // Template contains a route's path full parsed template. @@ -59,7 +59,9 @@ func (p *TemplateParam) CanEval() bool { } // paramChanger is the same form of context's Params().Set -func (p *TemplateParam) Eval(paramValue string, paramChanger func(key string, newValue interface{}) (memstore.Entry, bool)) bool { +// we could accept a memstore.Store or even context.RequestParams +// but this form has been chosed in order to test easier and fully decoupled from a request when necessary. +func (p *TemplateParam) Eval(paramValue string, paramChanger memstore.ValueSetter) bool { if p.TypeEvaluator == nil { for _, fn := range p.stringInFuncs { if !fn(paramValue) { @@ -85,7 +87,7 @@ func (p *TemplateParam) Eval(paramValue string, paramChanger func(key string, ne } } - paramChanger(p.Name, newValue) + paramChanger.Set(p.Name, newValue) return true } @@ -107,8 +109,8 @@ func Parse(src string, macros Macros) (*Template, error) { t.Src = src for idx, p := range params { - funcMap := macros.Lookup(p.Type) - typEval := funcMap.Evaluator + m := macros.Lookup(p.Type) + typEval := m.Evaluator tmplParam := TemplateParam{ Src: p.Src, @@ -120,7 +122,7 @@ func Parse(src string, macros Macros) (*Template, error) { } for _, paramfn := range p.Funcs { - tmplFn := funcMap.getFunc(paramfn.Name) + tmplFn := m.getFunc(paramfn.Name) if tmplFn == nil { // if not find on this type, check for Master's which is for global funcs too. if m := macros.GetMaster(); m != nil { tmplFn = m.getFunc(paramfn.Name) diff --git a/mvc/controller.go b/mvc/controller.go index 85675ec2..f7c75eef 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -7,9 +7,9 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/router" - "github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/hero" "github.com/kataras/iris/hero/di" + "github.com/kataras/iris/macro" "github.com/kataras/golog" ) diff --git a/mvc/controller_method_parser.go b/mvc/controller_method_parser.go index e5477e9e..1deb40ef 100644 --- a/mvc/controller_method_parser.go +++ b/mvc/controller_method_parser.go @@ -9,7 +9,7 @@ import ( "unicode" "github.com/kataras/iris/core/router" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) const ( diff --git a/mvc/param.go b/mvc/param.go index a81d191c..72301787 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -4,7 +4,7 @@ import ( "reflect" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) { From 7568da3283aa19fbc6a269a9326f224f2fad2510 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 29 Sep 2018 04:35:09 +0300 Subject: [PATCH 22/42] add tests for the new types (int8, int16, int32, uint, uint8, uint16, uint32, uint64) Former-commit-id: 812b3fdcc47abdeac271473bfdbdd15f0afd0bc0 --- README.md | 14 ++-- core/router/path.go | 2 +- core/router/route.go | 2 +- macro/macro_test.go | 162 ++++++++++++++++++++++++++++++++++++++++++- macro/macros.go | 6 +- 5 files changed, 176 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 49125deb..23b860e9 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,15 @@ func main() { | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| | `:string` | string | anything | `Params().Get` | -| `:int` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, depends on the arch | `Params().GetInt`...| +| `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | +| `:int8` | int8 | -128 to 127 | `Params().GetInt8` | +| `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | +| `:int32` | int32 | -2147483648 to 2147483647 | `Params().GetInt32` | | `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | +| `:uint` | uint | 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32), depends on the host arch | `Params().GetUint` | | `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | +| `:uint16` | uint16 | 0 to 65535 | `Params().GetUint16` | +| `:uint32` | uint32 | 0 to 4294967295 | `Params().GetUint32` | | `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | | `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | | `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` | @@ -138,9 +144,9 @@ app.Get("/users/{id:uint64}", func(ctx iris.Context){ | `prefix`(prefix string) | :string | | `suffix`(suffix string) | :string | | `contains`(s string) | :string | -| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | -| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | -| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | **Usage**: diff --git a/core/router/path.go b/core/router/path.go index 9b9d12a9..05ba11c3 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -33,7 +33,7 @@ func WildcardParam(name string) string { return prefix(name, WildcardParamStart) } -func convertTmplToNodePath(tmpl *macro.Template) string { +func convertMacroTmplToNodePath(tmpl *macro.Template) string { routePath := tmpl.Src if len(tmpl.Params) > 0 { if routePath[len(routePath)-1] == '/' { diff --git a/core/router/route.go b/core/router/route.go index c5c273e1..5e0e6ef0 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -47,7 +47,7 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, return nil, err } - path := convertTmplToNodePath(tmpl) + path := convertMacroTmplToNodePath(tmpl) // prepend the macro handler to the route, now, // right before the register to the tree, so APIBuilder#UseGlobal will work as expected. if macroEvaluatorHandler, ok := handler.MakeHandler(tmpl); ok { diff --git a/macro/macro_test.go b/macro/macro_test.go index 020f4d74..ff25c817 100644 --- a/macro/macro_test.go +++ b/macro/macro_test.go @@ -115,8 +115,8 @@ func TestIntEvaluatorRaw(t *testing.T) { {false, "astring"}, // 0 {false, "astringwith_numb3rS_and_symbol$"}, // 1 {true, "32321"}, // 2 - {x64, "9223372036854775807" /*max int64*/}, // 3 - {x64, "-9223372036854775808" /*min int64 */}, // 4 + {x64, "9223372036854775807" /* max int64 */}, // 3 + {x64, "-9223372036854775808" /* min int64 */}, // 4 {false, "-18446744073709553213213213213213121615"}, // 5 {false, "42 18446744073709551615"}, // 6 {false, "--42"}, // 7 @@ -130,6 +130,83 @@ func TestIntEvaluatorRaw(t *testing.T) { } } +func TestInt8EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "32321"}, // 2 + {true, "127" /* max int8 */}, // 3 + {true, "-128" /* min int8 */}, // 4 + {false, "128"}, // 5 + {false, "-129"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int8, tt.input, reflect.Int8, tt.pass, i) + } +} + +func TestInt16EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "32767" /* max int16 */}, // 3 + {true, "-32768" /* min int16 */}, // 4 + {false, "-32769"}, // 5 + {false, "32768"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int16, tt.input, reflect.Int16, tt.pass, i) + } +} + +func TestInt32EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {true, "2147483647" /* max int32 */}, // 5 + {true, "-2147483648" /* min int32 */}, // 6 + {false, "-2147483649"}, // 7 + {false, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int32, tt.input, reflect.Int32, tt.pass, i) + } +} + func TestInt64EvaluatorRaw(t *testing.T) { tests := []struct { pass bool @@ -155,6 +232,35 @@ func TestInt64EvaluatorRaw(t *testing.T) { } } +func TestUintEvaluatorRaw(t *testing.T) { + x64 := strconv.IntSize == 64 + + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {x64, "18446744073709551615" /* max uint64 */}, // 5 + {true, "4294967295" /* max uint32 */}, // 6 + {false, "-2147483649"}, // 7 + {true, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint, tt.input, reflect.Uint, tt.pass, i) + } +} + func TestUint8EvaluatorRaw(t *testing.T) { tests := []struct { pass bool @@ -184,6 +290,58 @@ func TestUint8EvaluatorRaw(t *testing.T) { } } +func TestUint16EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "65535" /* max uint16 */}, // 3 + {true, "0" /* min uint16 */}, // 4 + {false, "-32769"}, // 5 + {true, "32768"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint16, tt.input, reflect.Uint16, tt.pass, i) + } +} + +func TestUint32EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {true, "4294967295" /* max uint32*/}, // 5 + {true, "0" /* min uint32 */}, // 6 + {false, "-2147483649"}, // 7 + {true, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint32, tt.input, reflect.Uint32, tt.pass, i) + } +} + func TestUint64EvaluatorRaw(t *testing.T) { tests := []struct { pass bool diff --git a/macro/macros.go b/macro/macros.go index b1d9a96b..c92c0202 100644 --- a/macro/macros.go +++ b/macro/macros.go @@ -48,6 +48,8 @@ var ( simpleNumberEval = MustRegexp("^-?[0-9]+$") // Int or number type // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch. + // If x64: -9223372036854775808 to 9223372036854775807. + // If x32: -2147483648 to 2147483647 and etc.. Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) { if !simpleNumberEval(paramValue) { return nil, false @@ -206,8 +208,8 @@ var ( // Uint as uint type // actual value can be min-max uint64 or min-max uint32 depends on the arch. - // if x64: 0 to 18446744073709551615 - // if x32: 0 to 4294967295 and etc. + // If x64: 0 to 18446744073709551615. + // If x32: 0 to 4294967295 and etc. Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) { v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64... if err != nil { From 4431a65a56660b5138bfc984f114e61312246cee Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 29 Sep 2018 19:32:32 +0300 Subject: [PATCH 23/42] add a test and fix a small issue on the macro/handler#MakeHandler Former-commit-id: 0bb24999fbd3f54e582fb9a86f55333facfbc338 --- macro/handler/handler.go | 13 +++++------ macro/handler/handler_test.go | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 macro/handler/handler_test.go diff --git a/macro/handler/handler.go b/macro/handler/handler.go index 24b6367a..e3f10859 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -11,10 +11,9 @@ import ( // If the template does not contain any dynamic attributes and a special handler is NOT required // then it returns a nil handler and false as its second output value, // the caller should check those two values before any further action. -func MakeHandler(tmpl *macro.Template) (context.Handler, bool) { - needsMacroHandler := len(tmpl.Params) > 0 - if !needsMacroHandler { - return nil, false +func MakeHandler(tmpl *macro.Template) (handler context.Handler, needsMacroHandler bool) { + if len(tmpl.Params) == 0 { + return } // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. @@ -29,10 +28,10 @@ func MakeHandler(tmpl *macro.Template) (context.Handler, bool) { } if !needsMacroHandler { - return nil, false + return } - handler := func(ctx context.Context) { + handler = func(ctx context.Context) { for _, p := range tmpl.Params { if !p.CanEval() { continue // allow. @@ -48,5 +47,5 @@ func MakeHandler(tmpl *macro.Template) (context.Handler, bool) { ctx.Next() } - return handler, true + return } diff --git a/macro/handler/handler_test.go b/macro/handler/handler_test.go new file mode 100644 index 00000000..c22ed663 --- /dev/null +++ b/macro/handler/handler_test.go @@ -0,0 +1,41 @@ +package handler + +import ( + "testing" + + "github.com/kataras/iris/macro" +) + +func TestMakeHandlerNeeds(t *testing.T) { + tests := []struct { + src string + needsHandler bool + }{ + {"/static/static", false}, + {"/{myparam}", false}, + {"/{myparam min(1)}", true}, + {"/{myparam else 500}", true}, + {"/{myparam else 404}", false}, + {"/{myparam:string}/static", false}, + {"/{myparam:int}", true}, + {"/static/{myparam:int}/static", true}, + {"/{myparam:path}", false}, + {"/{myparam:path min(1) else 404}", true}, + } + + availableMacros := *macro.Defaults + for i, tt := range tests { + tmpl, err := macro.Parse(tt.src, availableMacros) + if err != nil { + t.Fatalf("[%d] '%s' failed to be parsed: %v", i, tt.src, err) + } + if _, got := MakeHandler(tmpl); got != tt.needsHandler { + if tt.needsHandler { + t.Fatalf("[%d] '%s' expected to be able to generate an evaluator handler instead of a nil one", i, tt.src) + } else { + t.Fatalf("[%d] '%s' should not need an evaluator handler", i, tt.src) + } + + } + } +} From b08df3a7856e733b52c99e65678b1691dc83eeab Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 29 Sep 2018 19:59:39 +0300 Subject: [PATCH 24/42] make the macro#Parse to return a value of a Template instead of its ptr and debug logs for handlers length ignores the internal generated macro evaluator handler if it is there, so end-dev cannot be confused about the debug logs at that point Former-commit-id: c23a3d10b43f145de575f1ea11e3dbf9bbd33a6b --- _examples/routing/macros/main.go | 2 +- core/router/path.go | 2 +- core/router/route.go | 29 +++++++++++++++++++++-------- macro/handler/handler.go | 23 ++++++++++++++--------- macro/handler/handler_test.go | 6 +++--- macro/template.go | 11 +++++------ 6 files changed, 45 insertions(+), 28 deletions(-) diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index fe68c598..9361ff5f 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -72,7 +72,7 @@ func main() { ) ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) - }) + }, func(ctx context.Context) {}) app.Get("/test_string2/{myparam1}/{myparam2}", func(ctx context.Context) { var ( diff --git a/core/router/path.go b/core/router/path.go index 05ba11c3..11d4ffd0 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -33,7 +33,7 @@ func WildcardParam(name string) string { return prefix(name, WildcardParamStart) } -func convertMacroTmplToNodePath(tmpl *macro.Template) string { +func convertMacroTmplToNodePath(tmpl macro.Template) string { routePath := tmpl.Src if len(tmpl.Params) > 0 { if routePath[len(routePath)-1] == '/' { diff --git a/core/router/route.go b/core/router/route.go index 5e0e6ef0..132eb7c7 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -13,11 +13,11 @@ import ( // If any of the following fields are changed then the // caller should Refresh the router. type Route struct { - Name string `json:"name"` // "userRoute" - 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:uint64}" + Name string `json:"name"` // "userRoute" + 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:uint64}" // temp storage, they're appended to the Handlers on build. // Execution happens before Handlers, can be empty. beginHandlers context.Handlers @@ -50,7 +50,8 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, path := convertMacroTmplToNodePath(tmpl) // prepend the macro handler to the route, now, // right before the register to the tree, so APIBuilder#UseGlobal will work as expected. - if macroEvaluatorHandler, ok := handler.MakeHandler(tmpl); ok { + if handler.CanMakeHandler(tmpl) { + macroEvaluatorHandler := handler.MakeHandler(tmpl) handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) } @@ -155,7 +156,18 @@ func (r Route) String() string { // via Tmpl().Src, Route.Path is the path // converted to match the underline router's specs. func (r Route) Tmpl() macro.Template { - return *r.tmpl + return r.tmpl +} + +// RegisteredHandlersLen returns the end-developer's registered handlers, all except the macro evaluator handler +// if was required by the build process. +func (r Route) RegisteredHandlersLen() int { + n := len(r.Handlers) + if handler.CanMakeHandler(r.tmpl) { + n-- + } + + return n } // IsOnline returns true if the route is marked as "online" (state). @@ -245,7 +257,8 @@ func (r Route) Trace() string { printfmt += fmt.Sprintf(" %s", r.Subdomain) } printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src) - if l := len(r.Handlers); l > 1 { + + if l := r.RegisteredHandlersLen(); l > 1 { printfmt += fmt.Sprintf("-> %s() and %d more", r.MainHandlerName, l-1) } else { printfmt += fmt.Sprintf("-> %s()", r.MainHandlerName) diff --git a/macro/handler/handler.go b/macro/handler/handler.go index e3f10859..16f466c1 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -7,11 +7,11 @@ import ( "github.com/kataras/iris/macro" ) -// MakeHandler creates and returns a handler from a macro template, the handler evaluates each of the parameters if necessary at all. +// CanMakeHandler reports whether a macro template needs a special macro's evaluator handler to be validated +// before procceed to the next handler(s). // If the template does not contain any dynamic attributes and a special handler is NOT required -// then it returns a nil handler and false as its second output value, -// the caller should check those two values before any further action. -func MakeHandler(tmpl *macro.Template) (handler context.Handler, needsMacroHandler bool) { +// then it returns false. +func CanMakeHandler(tmpl macro.Template) (needsMacroHandler bool) { if len(tmpl.Params) == 0 { return } @@ -27,11 +27,18 @@ func MakeHandler(tmpl *macro.Template) (handler context.Handler, needsMacroHandl } } - if !needsMacroHandler { - return + return +} + +// MakeHandler creates and returns a handler from a macro template, the handler evaluates each of the parameters if necessary at all. +// If the template does not contain any dynamic attributes and a special handler is NOT required +// then it returns a nil handler. +func MakeHandler(tmpl macro.Template) context.Handler { + if !CanMakeHandler(tmpl) { + return nil } - handler = func(ctx context.Context) { + return func(ctx context.Context) { for _, p := range tmpl.Params { if !p.CanEval() { continue // allow. @@ -46,6 +53,4 @@ func MakeHandler(tmpl *macro.Template) (handler context.Handler, needsMacroHandl // if all passed, just continue. ctx.Next() } - - return } diff --git a/macro/handler/handler_test.go b/macro/handler/handler_test.go index c22ed663..0acc1e83 100644 --- a/macro/handler/handler_test.go +++ b/macro/handler/handler_test.go @@ -6,7 +6,7 @@ import ( "github.com/kataras/iris/macro" ) -func TestMakeHandlerNeeds(t *testing.T) { +func TestCanMakeHandler(t *testing.T) { tests := []struct { src string needsHandler bool @@ -29,13 +29,13 @@ func TestMakeHandlerNeeds(t *testing.T) { if err != nil { t.Fatalf("[%d] '%s' failed to be parsed: %v", i, tt.src, err) } - if _, got := MakeHandler(tmpl); got != tt.needsHandler { + + if got := CanMakeHandler(tmpl); got != tt.needsHandler { if tt.needsHandler { t.Fatalf("[%d] '%s' expected to be able to generate an evaluator handler instead of a nil one", i, tt.src) } else { t.Fatalf("[%d] '%s' should not need an evaluator handler", i, tt.src) } - } } } diff --git a/macro/template.go b/macro/template.go index 33b14734..cd83d9c7 100644 --- a/macro/template.go +++ b/macro/template.go @@ -95,18 +95,17 @@ func (p *TemplateParam) Eval(paramValue string, paramChanger memstore.ValueSette // and returns a new Template. // It builds all the parameter functions for that template // and their evaluators, it's the api call that makes use the interpeter's parser -> lexer. -func Parse(src string, macros Macros) (*Template, error) { +func Parse(src string, macros Macros) (Template, error) { types := make([]ast.ParamType, len(macros)) for i, m := range macros { types[i] = m } + tmpl := Template{Src: src} params, err := parser.Parse(src, types) if err != nil { - return nil, err + return tmpl, err } - t := new(Template) - t.Src = src for idx, p := range params { m := macros.Lookup(p.Type) @@ -140,8 +139,8 @@ func Parse(src string, macros Macros) (*Template, error) { tmplParam.Funcs = append(tmplParam.Funcs, evalFn) } - t.Params = append(t.Params, tmplParam.preComputed()) + tmpl.Params = append(tmpl.Params, tmplParam.preComputed()) } - return t, nil + return tmpl, nil } From 21ab51bde7a32d179101254a61302333abc02c2d Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 30 Sep 2018 16:46:10 +0300 Subject: [PATCH 25/42] fix https://github.com/kataras/iris/issues/1087 Former-commit-id: 5f55201d9f494efd3f2f4d92231ad8f271ce94e6 --- websocket/connection.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/websocket/connection.go b/websocket/connection.go index 748f8067..4d1ec8ab 100644 --- a/websocket/connection.go +++ b/websocket/connection.go @@ -2,6 +2,7 @@ package websocket import ( "bytes" + "errors" "io" "net" "strconv" @@ -580,7 +581,14 @@ func (c *connection) Wait() { c.startReader() } +// ErrAlreadyDisconnected can be reported on the `Connection#Disconnect` function whenever the caller tries to close the +// connection when it is already closed by the client or the caller previously. +var ErrAlreadyDisconnected = errors.New("already disconnected") + func (c *connection) Disconnect() error { + if c == nil || c.disconnected { + return ErrAlreadyDisconnected + } return c.server.Disconnect(c.ID()) } From a675e8191a2ea31c67e6f3619365526370cd3ab1 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 30 Sep 2018 17:26:40 +0300 Subject: [PATCH 26/42] fix macro registration issue and match by kind for MVC and hero instead of its kind, so custom types like structs can be used without any issues. Add an example on how to register a custom macro it is just few lines and all in one place in this version. Former-commit-id: 93c439560fcfad820f9f3e39c1d9557c83cef0ee --- _examples/routing/macros/main.go | 124 +++++++++++++------------------ context/request_params.go | 34 ++++----- hero/param.go | 2 +- macro/AUTHORS | 4 + macro/LICENSE | 27 +++++++ macro/macros.go | 15 ++-- mvc/param.go | 2 +- 7 files changed, 111 insertions(+), 97 deletions(-) create mode 100644 macro/AUTHORS create mode 100644 macro/LICENSE diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index 9361ff5f..3de4e29a 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -1,12 +1,14 @@ +// Package main shows how you can register a custom parameter type and macro functions that belongs to it. package main import ( "fmt" "reflect" + "sort" + "strings" "github.com/kataras/iris" "github.com/kataras/iris/context" - // "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/hero" ) @@ -14,80 +16,60 @@ func main() { app := iris.New() app.Logger().SetLevel("debug") - // Let's see how we can register a custom macro such as ":uint32" or ":small" for its alias (optionally) for Uint32 types. - // app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { - // _, err := strconv.ParseUint(paramValue, 10, 32) - // return err == nil - // }). - // RegisterFunc("min", func(min uint32) func(string) bool { - // return func(paramValue string) bool { - // n, err := strconv.ParseUint(paramValue, 10, 32) - // if err != nil { - // return false - // } + app.Macros().Register("slice", "", false, true, func(paramValue string) (interface{}, bool) { + return strings.Split(paramValue, "/"), true + }).RegisterFunc("contains", func(expectedItems []string) func(paramValue []string) bool { + sort.Strings(expectedItems) + return func(paramValue []string) bool { + if len(paramValue) != len(expectedItems) { + return false + } - // return uint32(n) >= min - // } - // }) + sort.Strings(paramValue) + for i := 0; i < len(paramValue); i++ { + if paramValue[i] != expectedItems[i] { + return false + } + } - /* TODO: - somehow define one-time how the parameter should be parsed to a particular type (go std or custom) - tip: we can change the original value from string to X using the entry's.ValueRaw - ^ Done 27 sep 2018. - */ - - // app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { - // v, err := strconv.ParseUint(paramValue, 10, 32) - // return uint32(v), err == nil - // }). - // RegisterFunc("min", func(min uint32) func(uint32) bool { - // return func(paramValue uint32) bool { - // return paramValue >= min - // } - // }) - - // // optionally, only when mvc or hero features are used for this custom macro/parameter type. - // context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { - // /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ - // // return func(ctx context.Context) uint32 { - // // param := ctx.Params().GetEntryAt(paramIndex) - // // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) - // // return uint32(paramValueAsUint32) - // // } - // return func(ctx context.Context) uint32 { - // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) - // } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, - // we must return a value i.e 0 for int for its interface{} */ - // } - // // - - app.Get("/test_uint32/{myparam1:string}/{myparam2:uint32 min(10)}", hero.Handler(func(myparam1 string, myparam2 uint32) string { - return fmt.Sprintf("Value of the parameters are: %s:%d\n", myparam1, myparam2) - })) - - app.Get("/test_string/{myparam1}/{myparam2 prefix(a)}", func(ctx context.Context) { - var ( - myparam1 = ctx.Params().Get("myparam1") - myparam2 = ctx.Params().Get("myparam2") - ) - - ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) - }, func(ctx context.Context) {}) - - app.Get("/test_string2/{myparam1}/{myparam2}", func(ctx context.Context) { - var ( - myparam1 = ctx.Params().Get("myparam1") - myparam2 = ctx.Params().Get("myparam2") - ) - - ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) + return true + } }) - app.Get("/test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { - // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) - // but better and faster because the macro converts the string to uint64 automatically: - println("type of myparam2 (should be uint64) is: " + reflect.ValueOf(ctx.Params().GetEntry("myparam2").ValueRaw).Kind().String()) - ctx.Writef("Value of the parameters are: %s:%d\n", ctx.Params().Get("myparam1"), ctx.Params().GetUint64Default("myparam2", 0)) + // In order to use your new param type inside MVC controller's function input argument or a hero function input argument + // you have to tell the Iris what type it is, the `ValueRaw` of the parameter is the same type + // as you defined it above with the func(paramValue string) (interface{}, bool). + // The new value and its type(from string to your new custom type) it is stored only once now, + // you don't have to do any conversions for simple cases like this. + context.ParamResolvers[reflect.TypeOf([]string{})] = func(paramIndex int) interface{} { + return func(ctx context.Context) []string { + // When you want to retrieve a parameter with a value type that it is not supported by-default, such as ctx.Params().GetInt + // then you can use the `GetEntry` or `GetEntryAt` and cast its underline `ValueRaw` to the desired type. + // The type should be the same as the macro's evaluator function (last argument on the Macros#Register) return value. + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.([]string) + } + } + + /* + http://localhost:8080/test_slice_hero/myvaluei1/myavlue2 -> + myparam's value (a trailing path parameter type) is: []string{"myvaluei1", "myavlue2"} + */ + app.Get("/test_slice_hero/{myparam:slice}", hero.Handler(func(myparam []string) string { + return fmt.Sprintf("myparam's value (a trailing path parameter type) is: %#v\n", myparam) + })) + + /* + http://localhost:8080/test_slice_contains/notcontains1/value2 -> + (404) Not Found + + http://localhost:8080/test_slice_contains/value1/value2 -> + myparam's value (a trailing path parameter type) is: []string{"value1", "value2"} + */ + app.Get("/test_slice_contains/{myparam:slice contains([value1,value2])}", func(ctx context.Context) { + // When it is not a built'n function available to retrieve your value with the type you want, such as ctx.Params().GetInt + // then you can use the `GetEntry.ValueRaw` to get the real value, which is set-ed by your macro above. + myparam := ctx.Params().GetEntry("myparam").ValueRaw.([]string) + ctx.Writef("myparam's value (a trailing path parameter type) is: %#v\n", myparam) }) app.Run(iris.Addr(":8080")) diff --git a/context/request_params.go b/context/request_params.go index 9e45c363..860ccf90 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -83,65 +83,65 @@ func (r RequestParams) GetIntUnslashed(key string) (int, bool) { } var ( - ParamResolvers = map[reflect.Kind]func(paramIndex int) interface{}{ - reflect.String: func(paramIndex int) interface{} { + ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ + reflect.TypeOf(""): func(paramIndex int) interface{} { return func(ctx Context) string { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(string) } }, - reflect.Int: func(paramIndex int) interface{} { + reflect.TypeOf(int(1)): func(paramIndex int) interface{} { return func(ctx Context) int { // v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) // return v return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int) } }, - reflect.Int8: func(paramIndex int) interface{} { + reflect.TypeOf(int8(1)): func(paramIndex int) interface{} { return func(ctx Context) int8 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int8) } }, - reflect.Int16: func(paramIndex int) interface{} { + reflect.TypeOf(int16(1)): func(paramIndex int) interface{} { return func(ctx Context) int16 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int16) } }, - reflect.Int32: func(paramIndex int) interface{} { + reflect.TypeOf(int32(1)): func(paramIndex int) interface{} { return func(ctx Context) int32 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int32) } }, - reflect.Int64: func(paramIndex int) interface{} { + reflect.TypeOf(int64(1)): func(paramIndex int) interface{} { return func(ctx Context) int64 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int64) } }, - reflect.Uint: func(paramIndex int) interface{} { + reflect.TypeOf(uint(1)): func(paramIndex int) interface{} { return func(ctx Context) uint { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint) } }, - reflect.Uint8: func(paramIndex int) interface{} { + reflect.TypeOf(uint8(1)): func(paramIndex int) interface{} { return func(ctx Context) uint8 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint8) } }, - reflect.Uint16: func(paramIndex int) interface{} { + reflect.TypeOf(uint16(1)): func(paramIndex int) interface{} { return func(ctx Context) uint16 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint16) } }, - reflect.Uint32: func(paramIndex int) interface{} { + reflect.TypeOf(uint32(1)): func(paramIndex int) interface{} { return func(ctx Context) uint32 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) } }, - reflect.Uint64: func(paramIndex int) interface{} { + reflect.TypeOf(uint64(1)): func(paramIndex int) interface{} { return func(ctx Context) uint64 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint64) } }, - reflect.Bool: func(paramIndex int) interface{} { + reflect.TypeOf(true): func(paramIndex int) interface{} { return func(ctx Context) bool { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(bool) } @@ -149,16 +149,16 @@ var ( } ) -// ParamResolverByKindAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type +// ParamResolverByTypeAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type // and the parameter's index based on the registered path. -// Usage: nameResolver := ParamResolverByKindAndKey(reflect.String, 0) +// Usage: nameResolver := ParamResolverByKindAndKey(reflect.TypeOf(""), 0) // Inside a Handler: nameResolver.Call(ctx)[0] // it will return the reflect.Value Of the exact type of the parameter(based on the path parameters and macros). // It is only useful for dynamic binding of the parameter, it is used on "hero" package and it should be modified // only when Macros are modified in such way that the default selections for the available go std types are not enough. // // Returns empty value and false if "k" does not match any valid parameter resolver. -func ParamResolverByKindAndIndex(k reflect.Kind, paramIndex int) (reflect.Value, bool) { +func ParamResolverByTypeAndIndex(typ reflect.Type, paramIndex int) (reflect.Value, bool) { /* NO: // This could work but its result is not exact type, so direct binding is not possible. resolver := m.ParamResolver @@ -178,7 +178,7 @@ func ParamResolverByKindAndIndex(k reflect.Kind, paramIndex int) (reflect.Value, // */ - r, ok := ParamResolvers[k] + r, ok := ParamResolvers[typ] if !ok || r == nil { return reflect.Value{}, false } diff --git a/hero/param.go b/hero/param.go index dc34b10e..6941d554 100644 --- a/hero/param.go +++ b/hero/param.go @@ -19,7 +19,7 @@ type params struct { func (p *params) resolve(index int, typ reflect.Type) (reflect.Value, bool) { currentParamIndex := p.next - v, ok := context.ParamResolverByKindAndIndex(typ.Kind(), currentParamIndex) + v, ok := context.ParamResolverByTypeAndIndex(typ, currentParamIndex) p.next = p.next + 1 return v, ok diff --git a/macro/AUTHORS b/macro/AUTHORS new file mode 100644 index 00000000..04764750 --- /dev/null +++ b/macro/AUTHORS @@ -0,0 +1,4 @@ +# This is the official list of Iris Macro and Route path interpreter authors for copyright +# purposes. + +Gerasimos Maropoulos diff --git a/macro/LICENSE b/macro/LICENSE new file mode 100644 index 00000000..c73df4ce --- /dev/null +++ b/macro/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2017-2018 The Iris Macro and Route path interpreter. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Iris nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/macro/macros.go b/macro/macros.go index c92c0202..12a5cf21 100644 --- a/macro/macros.go +++ b/macro/macros.go @@ -10,10 +10,10 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). + // Its functions can be used by the rest of the macros and param types whenever not available function by name is used. + // Because of its "master" boolean value to true (third parameter). String = NewMacro("string", "", true, false, nil). - RegisterFunc("regexp", func(expr string) func(string) bool { - return MustRegexp(expr) - }). + RegisterFunc("regexp", MustRegexp). // checks if param value starts with the 'prefix' arg RegisterFunc("prefix", func(prefix string) func(string) bool { return func(paramValue string) bool { @@ -431,21 +431,22 @@ func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, eval } func (ms *Macros) register(macro *Macro) bool { - if macro.Indent() == "" || macro.Evaluator == nil { + if macro.Indent() == "" { return false } cp := *ms for _, m := range cp { - // can't add more than one with the same ast characteristics. if macro.Indent() == m.Indent() { return false } - if macro.Alias() == m.Alias() || macro.Alias() == m.Indent() { - return false + if alias := macro.Alias(); alias != "" { + if alias == m.Alias() || alias == m.Indent() { + return false + } } if macro.Master() && m.Master() { diff --git a/mvc/param.go b/mvc/param.go index 72301787..faa68396 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -39,7 +39,7 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) if len(funcIn) <= i { return } - funcDep, ok := context.ParamResolverByKindAndIndex(funcIn[i].Kind(), param.Index) + funcDep, ok := context.ParamResolverByTypeAndIndex(funcIn[i], param.Index) if !ok { continue } From 2a1f3d4e43b9eca9d744bf77244bdd0d3a086b8d Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 1 Oct 2018 15:27:45 +0300 Subject: [PATCH 27/42] add some more helpers for the parameters and the memstore for num types Former-commit-id: b96380fa8c8dc9abeaea248f87ea5d70f6bdf650 --- core/memstore/memstore.go | 587 +++++++++++++++++++++++++++++++------- 1 file changed, 491 insertions(+), 96 deletions(-) diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index c36272d8..001762e0 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -7,6 +7,7 @@ package memstore import ( "fmt" + "math" "reflect" "strconv" "strings" @@ -117,12 +118,13 @@ func (e Entry) IntDefault(def int) (int, error) { if err != nil { return def, err } - return val, nil case int: return vv, nil case int8: return int(vv), nil + case int16: + return int(vv), nil case int32: return int(vv), nil case int64: @@ -131,6 +133,8 @@ func (e Entry) IntDefault(def int) (int, error) { return int(vv), nil case uint8: return int(vv), nil + case uint16: + return int(vv), nil case uint32: return int(vv), nil case uint64: @@ -140,6 +144,96 @@ func (e Entry) IntDefault(def int) (int, error) { return def, errFindParse.Format("int", e.Key) } +// Int8Default returns the entry's value as int8. +// If not found returns "def" and a non-nil error. +func (e Entry) Int8Default(def int8) (int8, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("int8", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 8) + if err != nil { + return def, err + } + return int8(val), nil + case int: + return int8(vv), nil + case int8: + return vv, nil + case int16: + return int8(vv), nil + case int32: + return int8(vv), nil + case int64: + return int8(vv), nil + } + + return def, errFindParse.Format("int8", e.Key) +} + +// Int16Default returns the entry's value as int16. +// If not found returns "def" and a non-nil error. +func (e Entry) Int16Default(def int16) (int16, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("int16", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 16) + if err != nil { + return def, err + } + return int16(val), nil + case int: + return int16(vv), nil + case int8: + return int16(vv), nil + case int16: + return vv, nil + case int32: + return int16(vv), nil + case int64: + return int16(vv), nil + } + + return def, errFindParse.Format("int16", e.Key) +} + +// Int32Default returns the entry's value as int32. +// If not found returns "def" and a non-nil error. +func (e Entry) Int32Default(def int32) (int32, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("int32", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 32) + if err != nil { + return def, err + } + return int32(val), nil + case int: + return int32(vv), nil + case int8: + return int32(vv), nil + case int16: + return int32(vv), nil + case int32: + return vv, nil + case int64: + return int32(vv), nil + } + + return def, errFindParse.Format("int32", e.Key) +} + // Int64Default returns the entry's value as int64. // If not found returns "def" and a non-nil error. func (e Entry) Int64Default(def int64) (int64, error) { @@ -164,6 +258,266 @@ func (e Entry) Int64Default(def int64) (int64, error) { return def, errFindParse.Format("int64", e.Key) } +// UintDefault returns the entry's value as uint. +// If not found returns "def" and a non-nil error. +func (e Entry) UintDefault(def uint) (uint, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint", e.Key) + } + + x64 := strconv.IntSize == 64 + var maxValue uint = math.MaxUint32 + if x64 { + maxValue = math.MaxUint64 + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, strconv.IntSize) + if err != nil { + return def, err + } + if val > uint64(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(val), nil + case uint: + return vv, nil + case uint8: + return uint(vv), nil + case uint16: + return uint(vv), nil + case uint32: + return uint(vv), nil + case uint64: + if vv > uint64(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(vv), nil + case int: + if vv < 0 || vv > int(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(vv), nil + } + + return def, errFindParse.Format("uint", e.Key) +} + +// Uint8Default returns the entry's value as uint8. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint8Default(def uint8) (uint8, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint8", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 8) + if err != nil { + return def, err + } + if val > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(val), nil + case uint: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case uint8: + return vv, nil + case uint16: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case uint32: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case uint64: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case int: + if vv < 0 || vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + } + + return def, errFindParse.Format("uint8", e.Key) +} + +// Uint16Default returns the entry's value as uint16. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint16Default(def uint16) (uint16, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint16", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 16) + if err != nil { + return def, err + } + if val > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(val), nil + case uint: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + case uint8: + return uint16(vv), nil + case uint16: + return vv, nil + case uint32: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + case uint64: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + case int: + if vv < 0 || vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + } + + return def, errFindParse.Format("uint16", e.Key) +} + +// Uint32Default returns the entry's value as uint32. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint32Default(def uint32) (uint32, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint32", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 32) + if err != nil { + return def, err + } + if val > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(val), nil + case uint: + if vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(vv), nil + case uint8: + return uint32(vv), nil + case uint16: + return uint32(vv), nil + case uint32: + return vv, nil + case uint64: + if vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(vv), nil + case int: + if vv < 0 || vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(vv), nil + } + + return def, errFindParse.Format("uint32", e.Key) +} + +// Uint64Default returns the entry's value as uint64. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint64Default(def uint64) (uint64, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint64", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 64) + if err != nil { + return def, err + } + if val > math.MaxUint64 { + return def, errFindParse.Format("uint64", e.Key) + } + return uint64(val), nil + case uint: + if vv > math.MaxUint64 { + return def, errFindParse.Format("uint64", e.Key) + } + return uint64(vv), nil + case uint8: + return uint64(vv), nil + case uint16: + return uint64(vv), nil + case uint32: + return uint64(vv), nil + case uint64: + return vv, nil + case int64: + return uint64(vv), nil + case int: + return uint64(vv), nil + } + + return def, errFindParse.Format("uint64", e.Key) +} + +// Float32Default returns the entry's value as float32. +// If not found returns "def" and a non-nil error. +func (e Entry) Float32Default(key string, def float32) (float32, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("float32", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 32) + if err != nil { + return def, err + } + if val > math.MaxFloat32 { + return def, errFindParse.Format("float32", e.Key) + } + return float32(val), nil + case float32: + return vv, nil + case float64: + if vv > math.MaxFloat32 { + return def, errFindParse.Format("float32", e.Key) + } + return float32(vv), nil + case int: + return float32(vv), nil + } + + return def, errFindParse.Format("float32", e.Key) +} + // Float64Default returns the entry's value as float64. // If not found returns "def" and a non-nil error. func (e Entry) Float64Default(def float64) (float64, error) { @@ -196,85 +550,6 @@ func (e Entry) Float64Default(def float64) (float64, error) { return def, errFindParse.Format("float64", e.Key) } -// Float32Default returns the entry's value as float32. -// If not found returns "def" and a non-nil error. -func (e Entry) Float32Default(key string, def float32) (float32, error) { - v := e.ValueRaw - if v == nil { - return def, errFindParse.Format("float32", e.Key) - } - - switch vv := v.(type) { - case string: - val, err := strconv.ParseFloat(vv, 32) - if err != nil { - return def, err - } - - return float32(val), nil - case float32: - return vv, nil - case float64: - return float32(vv), nil - case int: - return float32(vv), nil - } - - return def, errFindParse.Format("float32", e.Key) -} - -// Uint8Default returns the entry's value as uint8. -// If not found returns "def" and a non-nil error. -func (e Entry) Uint8Default(def uint8) (uint8, error) { - v := e.ValueRaw - if v == nil { - return def, errFindParse.Format("uint8", e.Key) - } - - switch vv := v.(type) { - case string: - val, err := strconv.ParseUint(vv, 10, 8) - if err != nil { - return def, err - } - if val > 255 { - return def, errFindParse.Format("uint8", e.Key) - } - return uint8(val), nil - case uint8: - return vv, nil - case int: - if vv < 0 || vv > 255 { - return def, errFindParse.Format("uint8", e.Key) - } - return uint8(vv), nil - } - - return def, errFindParse.Format("uint8", e.Key) -} - -// Uint64Default returns the entry's value as uint64. -// If not found returns "def" and a non-nil error. -func (e Entry) Uint64Default(def uint64) (uint64, error) { - v := e.ValueRaw - if v == nil { - return def, errFindParse.Format("uint64", e.Key) - } - - switch vv := v.(type) { - case string: - return strconv.ParseUint(vv, 10, 64) - case uint64: - return vv, nil - case int64: - return uint64(vv), nil - case int: - return uint64(vv), nil - } - - return def, errFindParse.Format("uint64", e.Key) -} - // BoolDefault returns the user's value as bool. // 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". @@ -508,40 +783,60 @@ func (r *Store) GetIntDefault(key string, def int) int { return def } -// GetUint8 returns the entry's value as uint8, based on its key. -// If not found returns 0 and a non-nil error. -func (r *Store) GetUint8(key string) (uint8, error) { +// GetInt8 returns the entry's value as int8, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt8(key string) (int8, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("uint8", key) + return -1, errFindParse.Format("int8", key) } - return v.Uint8Default(0) + return v.Int8Default(-1) } -// GetUint8Default returns the entry's value as uint8, based on its key. +// GetInt8Default returns the entry's value as int8, based on its key. // If not found returns "def". -func (r *Store) GetUint8Default(key string, def uint8) uint8 { - if v, err := r.GetUint8(key); err == nil { +func (r *Store) GetInt8Default(key string, def int8) int8 { + if v, err := r.GetInt8(key); err == nil { return v } return def } -// GetUint64 returns the entry's value as uint64, based on its key. -// If not found returns 0 and a non-nil error. -func (r *Store) GetUint64(key string) (uint64, error) { +// GetInt16 returns the entry's value as int16, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt16(key string) (int16, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("uint64", key) + return -1, errFindParse.Format("int16", key) } - return v.Uint64Default(0) + return v.Int16Default(-1) } -// GetUint64Default returns the entry's value as uint64, based on its key. +// GetInt16Default returns the entry's value as int16, based on its key. // If not found returns "def". -func (r *Store) GetUint64Default(key string, def uint64) uint64 { - if v, err := r.GetUint64(key); err == nil { +func (r *Store) GetInt16Default(key string, def int16) int16 { + if v, err := r.GetInt16(key); err == nil { + return v + } + + return def +} + +// GetInt32 returns the entry's value as int32, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt32(key string) (int32, error) { + v, ok := r.GetEntry(key) + if !ok { + return -1, errFindParse.Format("int32", key) + } + return v.Int32Default(-1) +} + +// GetInt32Default returns the entry's value as int32, based on its key. +// If not found returns "def". +func (r *Store) GetInt32Default(key string, def int32) int32 { + if v, err := r.GetInt32(key); err == nil { return v } @@ -568,6 +863,106 @@ func (r *Store) GetInt64Default(key string, def int64) int64 { return def } +// GetUint returns the entry's value as uint, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint(key string) (uint, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint", key) + } + return v.UintDefault(0) +} + +// GetUintDefault returns the entry's value as uint, based on its key. +// If not found returns "def". +func (r *Store) GetUintDefault(key string, def uint) uint { + if v, err := r.GetUint(key); err == nil { + return v + } + + return def +} + +// GetUint8 returns the entry's value as uint8, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint8(key string) (uint8, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint8", key) + } + return v.Uint8Default(0) +} + +// GetUint8Default returns the entry's value as uint8, based on its key. +// If not found returns "def". +func (r *Store) GetUint8Default(key string, def uint8) uint8 { + if v, err := r.GetUint8(key); err == nil { + return v + } + + return def +} + +// GetUint16 returns the entry's value as uint16, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint16(key string) (uint16, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint16", key) + } + return v.Uint16Default(0) +} + +// GetUint16Default returns the entry's value as uint16, based on its key. +// If not found returns "def". +func (r *Store) GetUint16Default(key string, def uint16) uint16 { + if v, err := r.GetUint16(key); err == nil { + return v + } + + return def +} + +// GetUint32 returns the entry's value as uint32, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint32(key string) (uint32, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint32", key) + } + return v.Uint32Default(0) +} + +// GetUint32Default returns the entry's value as uint32, based on its key. +// If not found returns "def". +func (r *Store) GetUint32Default(key string, def uint32) uint32 { + if v, err := r.GetUint32(key); err == nil { + return v + } + + return def +} + +// GetUint64 returns the entry's value as uint64, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint64(key string) (uint64, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint64", key) + } + return v.Uint64Default(0) +} + +// GetUint64Default returns the entry's value as uint64, based on its key. +// If not found returns "def". +func (r *Store) GetUint64Default(key string, def uint64) uint64 { + if v, err := r.GetUint64(key); err == nil { + return v + } + + return def +} + // GetFloat64 returns the entry's value as float64, based on its key. // If not found returns -1 and a non nil error. func (r *Store) GetFloat64(key string) (float64, error) { From 97e96ed6ca6c4a64909731cd35ba11221fc7ff93 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 2 Oct 2018 04:05:39 +0300 Subject: [PATCH 28/42] init go modules but keep the dep files and vendor folder and update badger sessionsdb's dependency to 1.5.4 from 1.5.3 Former-commit-id: 4f58a9bd0e7eb5f9535953125c27b9c5316ffc0f --- Gopkg.lock | 2 +- Gopkg.toml | 2 +- .../README.md | 2 - .../counter/configurator.go | 30 ---- .../main.go | 22 ++- .../http-listening/notify-on-shutdown/main.go | 16 +- go.mod | 68 ++++++++ go.sum | 148 ++++++++++++++++++ sessions/sessiondb/badger/database.go | 24 ++- 9 files changed, 267 insertions(+), 47 deletions(-) delete mode 100644 _examples/http-listening/iris-configurator-and-host-configurator/README.md delete mode 100644 _examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/Gopkg.lock b/Gopkg.lock index 1f3d8186..64107208 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -275,7 +275,7 @@ branch = "master" name = "github.com/dgraph-io/badger" packages = ["."] - revision = "391b6d3b93e6014fe8c2971fcc0c1266e47dbbd9" + revision = "99233d725dbdd26d156c61b2f42ae1671b794656" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index 8274cd48..b03d8693 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -80,7 +80,7 @@ [[constraint]] name = "github.com/dgraph-io/badger" - version = "1.5.3" + version = "1.5.4" [[constraint]] name = "github.com/etcd-io/bbolt" diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/README.md b/_examples/http-listening/iris-configurator-and-host-configurator/README.md deleted file mode 100644 index 89ecb89c..00000000 --- a/_examples/http-listening/iris-configurator-and-host-configurator/README.md +++ /dev/null @@ -1,2 +0,0 @@ -A silly example for this issue: https://github.com/kataras/iris/issues/688#issuecomment-318828259. -However it seems useful and therefore is being included in the examples for everyone else. \ No newline at end of file diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go b/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go deleted file mode 100644 index 72988303..00000000 --- a/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go +++ /dev/null @@ -1,30 +0,0 @@ -package counter - -import ( - "time" - - "github.com/kataras/iris" - "github.com/kataras/iris/core/host" -) - -func Configurator(app *iris.Application) { - counterValue := 0 - - go func() { - ticker := time.NewTicker(time.Second) - - for range ticker.C { - counterValue++ - } - - app.ConfigureHost(func(h *host.Supervisor) { // <- HERE: IMPORTANT - h.RegisterOnShutdown(func() { - ticker.Stop() - }) - }) // or put the ticker outside of the gofunc and put the configurator before or after the app.Get, outside of this gofunc - }() - - app.Get("/counter", func(ctx iris.Context) { - ctx.Writef("Counter value = %d", counterValue) - }) -} diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/main.go b/_examples/http-listening/iris-configurator-and-host-configurator/main.go index a82d974f..f1d7ad85 100644 --- a/_examples/http-listening/iris-configurator-and-host-configurator/main.go +++ b/_examples/http-listening/iris-configurator-and-host-configurator/main.go @@ -1,14 +1,28 @@ package main import ( - "github.com/kataras/iris/_examples/http-listening/iris-configurator-and-host-configurator/counter" - "github.com/kataras/iris" ) func main() { app := iris.New() - app.Configure(counter.Configurator) - app.Run(iris.Addr(":8080")) + app.ConfigureHost(func(host *iris.Supervisor) { // <- HERE: IMPORTANT + // You can control the flow or defer something using some of the host's methods: + // host.RegisterOnError + // host.RegisterOnServe + host.RegisterOnShutdown(func() { + app.Logger().Infof("Application shutdown on signal") + }) + }) + + app.Get("/", func(ctx iris.Context) { + ctx.HTML("

Hello

\n") + }) + + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) + + /* There are more easy ways to notify for global shutdown using the `iris.RegisterOnInterrupt` for default signal interrupt events. + You can even go it even further by looking at the: "graceful-shutdown" example. + */ } diff --git a/_examples/http-listening/notify-on-shutdown/main.go b/_examples/http-listening/notify-on-shutdown/main.go index 67400aa8..9a6e3709 100644 --- a/_examples/http-listening/notify-on-shutdown/main.go +++ b/_examples/http-listening/notify-on-shutdown/main.go @@ -1,11 +1,10 @@ package main import ( - stdContext "context" + "context" "time" "github.com/kataras/iris" - "github.com/kataras/iris/core/host" ) func main() { @@ -15,20 +14,20 @@ func main() { ctx.HTML("

Hello, try to refresh the page after ~10 secs

") }) - // app.ConfigureHost(configureHost) -> or pass "configureHost" as `app.Addr` argument, same result. - app.Logger().Info("Wait 10 seconds and check your terminal again") // simulate a shutdown action here... go func() { <-time.After(10 * time.Second) timeout := 5 * time.Second - ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // close all hosts, this will notify the callback we had register // inside the `configureHost` func. app.Shutdown(ctx) }() + // app.ConfigureHost(configureHost) -> or pass "configureHost" as `app.Addr` argument, same result. + // start the server as usual, the only difference is that // we're adding a second (optional) function // to configure the just-created host supervisor. @@ -37,9 +36,14 @@ func main() { // wait 10 seconds and check your terminal. app.Run(iris.Addr(":8080", configureHost), iris.WithoutServerError(iris.ErrServerClosed)) + /* + Or for simple cases you can just use the: + iris.RegisterOnInterrupt for global catch of the CTRL/CMD+C and OS events. + Look at the "graceful-shutdown" example for more. + */ } -func configureHost(su *host.Supervisor) { +func configureHost(su *iris.Supervisor) { // here we have full access to the host that will be created // inside the `app.Run` function or `NewHost`. // diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..8711dc12 --- /dev/null +++ b/go.mod @@ -0,0 +1,68 @@ +module github.com/kataras/iris + +require ( + github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 // indirect + github.com/BurntSushi/toml v0.3.1 + github.com/Joker/jade v0.7.0 + github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc + github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f // indirect + github.com/aymerick/raymond v2.0.2+incompatible + github.com/boltdb/bolt v1.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/badger v1.5.4 + github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect + github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 + github.com/etcd-io/bbolt v1.3.0 + github.com/fatih/structs v1.0.0 + github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 + github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d // indirect + github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/go-querystring v1.0.0 // indirect + github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c // indirect + github.com/gorilla/websocket v1.4.0 + github.com/imkira/go-interpol v1.1.0 // indirect + github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f + github.com/iris-contrib/go.uuid v2.0.0+incompatible + github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce + github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0 + github.com/json-iterator/go v1.1.5 + github.com/jtolds/gls v4.2.1+incompatible // indirect + github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 // indirect + github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect + github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect + github.com/kataras/golog v0.0.0-20180321173939-03be10146386 + github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83 // indirect + github.com/klauspost/compress v1.4.0 + github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + github.com/microcosm-cc/bluemonday v1.0.1 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b // indirect + github.com/onsi/gomega v1.4.2 // indirect + github.com/pkg/errors v0.8.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ryanuber/columnize v2.1.0+incompatible + github.com/sergi/go-diff v1.0.0 // indirect + github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect + github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect + github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect + github.com/stretchr/testify v1.2.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yudai/pp v2.0.1+incompatible // indirect + golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 + golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 // indirect + golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect + gopkg.in/ini.v1 v1.38.3 // indirect + gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect + gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..40e48054 --- /dev/null +++ b/go.sum @@ -0,0 +1,148 @@ +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 h1:PqzgE6kAMi81xWQA2QIVxjWkFHptGgC547vchpUbtFo= +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Joker/jade v0.7.0 h1:9sEpF/GAfkn0D0n+x8/SVGpxvdjIStsyPaTvtXW6z/U= +github.com/Joker/jade v0.7.0/go.mod h1:R1kvvouJogE6SnKqO5Qw3j2rCE2T9HjIWaFeSET/qMQ= +github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc h1:zZYkIbeMNcH1lhztdVxy4+Ykk8NoMhqUfSigsrT/x7Y= +github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= +github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.5.4 h1:gVTrpUTbbr/T24uvoCaqY2KSHfNLVGm0w+hbee2HMeg= +github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 h1:afESQBXJEnj3fu+34X//E8Wg3nEbMJxJkwSc0tPePK0= +github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/etcd-io/bbolt v1.3.0 h1:ec0U3x11Mk69A8YwQyZEhNaUqHkQSv2gDR3Bioz5DfU= +github.com/etcd-io/bbolt v1.3.0/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 h1:ZHx2BEERvWkuwuE7qWN9TuRxucHDH2JrsvneZjVJfo0= +github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0/go.mod h1:rE0ErqqBaMcp9pzj8JxV1GcfDBpuypXYxlR1c37AUwg= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d h1:oYXrtNhqNKL1dVtKdv8XUq5zqdGVFNQ0/4tvccXZOLM= +github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f h1:WgD6cqCSncBgkftw34mSPlMKU5JgoruAomW/SJtRrGU= +github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f/go.mod h1:i8kTYUOEstd/S8TG0ChTXQdf4ermA/e8vJX0+QruD9w= +github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce h1:q8Ka/exfHNgK7izJE+aUOZd7KZXJ7oQbnJWiZakEiMo= +github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce/go.mod h1:VER17o2JZqquOx41avolD/wMGQSFEFBKWmhag9/RQRY= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0 h1:Kyp9KiXwsyZRTeoNjgVCrWks7D8ht9+kg6yCjh8K97o= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= +github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 h1:wnhMXidtb70kDZCeLt/EfsVtkXS5c8zLnE9y/6DIRAU= +github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 h1:WQM1NildKThwdP7qWrNAFGzp4ijNLw8RlgENkaI4MJs= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.0-20180321173939-03be10146386 h1:VT6AeCHO/mc+VedKBMhoqb5eAK8B1i9F6nZl7EGlHvA= +github.com/kataras/golog v0.0.0-20180321173939-03be10146386/go.mod h1:PcaEvfvhGsqwXZ6S3CgCbmjcp+4UDUh2MIfF2ZEul8M= +github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83 h1:NoJ+fI58ptwrPc1blX116i+5xWGAY/2TJww37AN8X54= +github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8gh8= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b h1:Pip12xNtMvEFUBF4f8/b5yRXj94LLrNdLWELfOr2KcY= +github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY= +github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.38.3 h1:ourkRZgR6qjJYoec9lYhX4+nuN1tEbV34dQEQ3IRk9U= +gopkg.in/ini.v1 v1.38.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible h1:l1Mna0cVh8WlpyB8uFtc2c+5cdvrI5CDyuwTgIChojI= +gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/sessions/sessiondb/badger/database.go b/sessions/sessiondb/badger/database.go index 223deafe..5c990e06 100644 --- a/sessions/sessiondb/badger/database.go +++ b/sessions/sessiondb/badger/database.go @@ -7,11 +7,11 @@ import ( "sync/atomic" "time" - "github.com/kataras/golog" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" "github.com/dgraph-io/badger" + "github.com/kataras/golog" ) // DefaultFileMode used as the default database's "fileMode" @@ -144,7 +144,13 @@ func (db *Database) Get(sid string, key string) (value interface{}) { if err != nil { return err } - // item.ValueCopy + + // return item.Value(func(valueBytes []byte) { + // if err := sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { + // golog.Error(err) + // } + // }) + valueBytes, err := item.Value() if err != nil { return err @@ -173,13 +179,25 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { for iter.Rewind(); iter.ValidForPrefix(prefix); iter.Next() { item := iter.Item() + var value interface{} + + // err := item.Value(func(valueBytes []byte) { + // if err := sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { + // golog.Error(err) + // } + // }) + + // if err != nil { + // golog.Error(err) + // continue + // } + valueBytes, err := item.Value() if err != nil { golog.Error(err) continue } - var value interface{} if err = sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { golog.Error(err) continue From 120b5fb63543054e09054c98c8c3e23d436b11b4 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 2 Oct 2018 06:36:51 +0300 Subject: [PATCH 29/42] example: write our own customized router using the high-level API which gives access to the correct context and routes Former-commit-id: d2369c20490619cad0dd6f7b6ed01cdbef51a853 --- Dockerfile | 5 - Dockerfile.build | 12 -- _examples/README.md | 5 +- _examples/README_ZH.md | 3 + .../routing/custom-high-level-router/main.go | 103 ++++++++++++++++++ .../routing/custom-low-level-router/main.go | 6 + core/router/handler.go | 5 +- core/router/router.go | 24 +++- iris.go | 2 +- 9 files changed, 138 insertions(+), 27 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Dockerfile.build create mode 100644 _examples/routing/custom-high-level-router/main.go create mode 100644 _examples/routing/custom-low-level-router/main.go diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 54e3725a..00000000 --- a/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM irisgo/cloud-native-go:latest - -ENV APPSOURCES /go/src/github.com/iris-contrib/cloud-native-go - -RUN ${APPSOURCES}/cloud-native-go \ No newline at end of file diff --git a/Dockerfile.build b/Dockerfile.build deleted file mode 100644 index 6c127b0e..00000000 --- a/Dockerfile.build +++ /dev/null @@ -1,12 +0,0 @@ -FROM golang:1.9.3-alpine - -RUN apk update && apk upgrade && apk add --no-cache bash git -RUN go get github.com/iris-contrib/cloud-native-go - -ENV SOURCES /go/src/github.com/iris-contrib/cloud-native-go -# COPY . ${SOURCES} - -RUN cd ${SOURCES} $$ CGO_ENABLED=0 go build - -ENTRYPOINT cloud-native-go -EXPOSE 8080 \ No newline at end of file diff --git a/_examples/README.md b/_examples/README.md index 4caf6d3d..e0b00d67 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -146,8 +146,11 @@ Navigate through examples for a better understanding. - [Custom HTTP Errors](routing/http-errors/main.go) - [Dynamic Path](routing/dynamic-path/main.go) * [root level wildcard path](routing/dynamic-path/root-wildcard/main.go) +- [Write your own custom parameter types](routing/macros/main.go) **NEW** - [Reverse routing](routing/reverse/main.go) -- [Custom wrapper](routing/custom-wrapper/main.go) +- [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** +- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW** +- [Custom Wrapper](routing/custom-wrapper/main.go) - Custom Context * [method overriding](routing/custom-context/method-overriding/main.go) * [new implementation](routing/custom-context/new-implementation/main.go) diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md index a10ca6ad..82b5648e 100644 --- a/_examples/README_ZH.md +++ b/_examples/README_ZH.md @@ -105,7 +105,10 @@ app.Get("{root:path}", rootWildcardHandler) - [自定义 HTTP 错误](routing/http-errors/main.go) - [动态路径](routing/dynamic-path/main.go) * [根级通配符路径](routing/dynamic-path/root-wildcard/main.go) +- [Write your own custom parameter types](routing/macros/main.go) **NEW** - [反向路由](routing/reverse/main.go) +- [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** +- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW** - [自定义包装](routing/custom-wrapper/main.go) - 自定义上下文    * [方法重写](routing/custom-context/method-overriding/main.go) diff --git a/_examples/routing/custom-high-level-router/main.go b/_examples/routing/custom-high-level-router/main.go new file mode 100644 index 00000000..1557c311 --- /dev/null +++ b/_examples/routing/custom-high-level-router/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "strings" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/core/router" +) + +/* A Router should contain all three of the following methods: + - HandleRequest should handle the request based on the Context. + HandleRequest(ctx context.Context) + - Build should builds the handler, it's being called on router's BuildRouter. + Build(provider router.RoutesProvider) error + - RouteExists reports whether a particular route exists. + RouteExists(ctx context.Context, method, path string) bool + +For a more detailed, complete and useful example +you can take a look at the iris' router itself which is located at: +https://github.com/kataras/iris/tree/master/core/router/handler.go +which completes this exact interface, the `router#RequestHandler`. +*/ +type customRouter struct { + // a copy of routes (safer because you will not be able to alter a route on serve-time without a `app.RefreshRouter` call): + // []router.Route + // or just expect the whole routes provider: + provider router.RoutesProvider +} + +// HandleRequest a silly example which finds routes based only on the first part of the requested path +// which must be a static one as well, the rest goes to fill the parameters. +func (r *customRouter) HandleRequest(ctx context.Context) { + path := ctx.Path() + ctx.Application().Logger().Infof("Requested resource path: %s", path) + + parts := strings.Split(path, "/")[1:] + staticPath := "/" + parts[0] + for _, route := range r.provider.GetRoutes() { + if strings.HasPrefix(route.Path, staticPath) { + paramParts := parts[1:] + for _, paramValue := range paramParts { + for _, p := range route.Tmpl().Params { + ctx.Params().Set(p.Name, paramValue) + } + } + + ctx.SetCurrentRouteName(route.Name) + ctx.Do(route.Handlers) + return + } + } + + // if nothing found... + ctx.StatusCode(iris.StatusNotFound) +} + +func (r *customRouter) Build(provider router.RoutesProvider) error { + for _, route := range provider.GetRoutes() { + // do any necessary validation or conversations based on your custom logic here + // but always run the "BuildHandlers" for each registered route. + route.BuildHandlers() + // [...] r.routes = append(r.routes, *route) + } + + r.provider = provider + return nil +} + +func (r *customRouter) RouteExists(ctx context.Context, method, path string) bool { + // [...] + return false +} + +func main() { + app := iris.New() + + // In case you are wondering, the parameter types and macros like "{param:string $func()}" still work inside + // your custom router if you fetch by the Route's Handler + // because they are middlewares under the hood, so you don't have to implement the logic of handling them manually, + // though you have to match what requested path is what route and fill the ctx.Params(), this is the work of your custom router. + app.Get("/hello/{name}", func(ctx context.Context) { + name := ctx.Params().Get("name") + ctx.Writef("Hello %s\n", name) + }) + + app.Get("/cs/{num:uint64 min(10) else 400}", func(ctx context.Context) { + num := ctx.Params().GetUint64Default("num", 0) + ctx.Writef("num is: %d\n", num) + }) + + // To replace the existing router with a customized one by using the iris/context.Context + // you have to use the `app.BuildRouter` method before `app.Run` and after the routes registered. + // You should pass your custom router's instance as the second input arg, which must completes the `router#RequestHandler` + // interface as shown above. + // + // To see how you can build something even more low-level without direct iris' context support (you can do that manually as well) + // navigate to the "custom-wrapper" example instead. + myCustomRouter := new(customRouter) + app.BuildRouter(app.ContextPool, myCustomRouter, app.APIBuilder, true) + + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) +} diff --git a/_examples/routing/custom-low-level-router/main.go b/_examples/routing/custom-low-level-router/main.go new file mode 100644 index 00000000..d1aac6ed --- /dev/null +++ b/_examples/routing/custom-low-level-router/main.go @@ -0,0 +1,6 @@ +/// TODO: showcase the `app.Downgrade` feature tomorrow if not already existing elsewhere. +package main + +func main() { + panic("TODO") +} diff --git a/core/router/handler.go b/core/router/handler.go index 24ecfc5d..f16e1df0 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -17,10 +17,9 @@ import ( // RequestHandler the middle man between acquiring a context and releasing it. // By-default is the router algorithm. type RequestHandler interface { - // HandleRequest is same as context.Handler but its usage is only about routing, - // separate the concept here. + // HandleRequest should handle the request based on the Context. HandleRequest(context.Context) - // Build should builds the handler, it's being called on router's BuildRouter. + // Build should builds the handler, it's being called on router's BuildRouter. Build(provider RoutesProvider) error // RouteExists reports whether a particular route exists. RouteExists(ctx context.Context, method, path string) bool diff --git a/core/router/router.go b/core/router/router.go index 50526395..f4e9840d 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -31,7 +31,7 @@ func NewRouter() *Router { return &Router{} } // RefreshRouter re-builds the router. Should be called when a route's state // changed (i.e Method changed at serve-time). func (router *Router) RefreshRouter() error { - return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider) + return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider, true) } // BuildRouter builds the router based on @@ -41,7 +41,7 @@ func (router *Router) RefreshRouter() error { // its wrapper. // // Use of RefreshRouter to re-build the router if needed. -func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider) error { +func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider, force bool) error { if requestHandler == nil { return errors.New("router: request handler is nil") @@ -60,9 +60,23 @@ func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHan defer router.mu.Unlock() // store these for RefreshRouter's needs. - router.cPool = cPool - router.requestHandler = requestHandler - router.routesProvider = routesProvider + if force { + router.cPool = cPool + router.requestHandler = requestHandler + router.routesProvider = routesProvider + } else { + if router.cPool == nil { + router.cPool = cPool + } + + if router.requestHandler == nil { + router.requestHandler = requestHandler + } + + if router.routesProvider == nil && routesProvider != nil { + router.routesProvider = routesProvider + } + } // the important router.mainHandler = func(w http.ResponseWriter, r *http.Request) { diff --git a/iris.go b/iris.go index 8cab4999..5660847b 100644 --- a/iris.go +++ b/iris.go @@ -761,7 +761,7 @@ func (app *Application) Build() error { // create the request handler, the default routing handler routerHandler := router.NewDefaultHandler() - rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder)) + rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder, false)) // re-build of the router from outside can be done with; // app.RefreshRouter() } From 0f2c5da7df519d240a6c0b2f569d0f2266e5322b Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 3 Oct 2018 20:49:49 +0300 Subject: [PATCH 30/42] context#ErrEmptyForm Former-commit-id: c623e7ac433f11c9d89376bee0cd100aa26f35c5 --- context/context.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/context/context.go b/context/context.go index a213b3b7..e39d29fd 100644 --- a/context/context.go +++ b/context/context.go @@ -2289,6 +2289,9 @@ var ( errReadBody = errors.New("while trying to read %s from the request body. Trace %s") ) +// ErrEmptyForm will be thrown from `context#ReadForm` when empty request data. +var ErrEmptyForm = errors.New("form data: empty") + // ReadForm binds the formObject with the form data // it supports any kind of struct. // @@ -2296,7 +2299,7 @@ var ( func (ctx *context) ReadForm(formObject interface{}) error { values := ctx.FormValues() if values == nil { - return errors.New("An empty form passed on ReadForm") + return ErrEmptyForm } // or dec := formbinder.NewDecoder(&formbinder.DecoderOptions{TagName: "form"}) From 29a4354e1de5d1c28acba0e0b262d87d291fb409 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 4 Oct 2018 12:05:55 +0300 Subject: [PATCH 31/42] `context#ReadForm`: do not return an error if request data are not there, instead return nil without touching the ptr value. Requested at: https://github.com/kataras/iris/issues/1095 Former-commit-id: 9662854b3242b91ccb74d6adca72cdcb61689bd8 --- context/context.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/context/context.go b/context/context.go index e39d29fd..a7ad80b1 100644 --- a/context/context.go +++ b/context/context.go @@ -560,7 +560,8 @@ type Context interface { // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go ReadXML(xmlObjectPtr interface{}) error // ReadForm binds the formObject with the form data - // it supports any kind of struct. + // it supports any kind of type, including custom structs. + // It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go ReadForm(formObjectPtr interface{}) error @@ -2289,17 +2290,15 @@ var ( errReadBody = errors.New("while trying to read %s from the request body. Trace %s") ) -// ErrEmptyForm will be thrown from `context#ReadForm` when empty request data. -var ErrEmptyForm = errors.New("form data: empty") - // ReadForm binds the formObject with the form data -// it supports any kind of struct. +// it supports any kind of type, including custom structs. +// It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go func (ctx *context) ReadForm(formObject interface{}) error { values := ctx.FormValues() - if values == nil { - return ErrEmptyForm + if len(values) == 0 { + return nil } // or dec := formbinder.NewDecoder(&formbinder.DecoderOptions{TagName: "form"}) From 30027360868ab0b87d57f74acff13b37eca3a1d8 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 15 Oct 2018 10:49:09 +0300 Subject: [PATCH 32/42] add my new trie data structure implementation written from scratch and specifically designed for HTTP (and Iris) - see https://github.com/kataras/muxie for the net/http version of it Former-commit-id: 4eed1585f29b57418b61f6de058f5d6db4bb98bf --- HISTORY.md | 46 +- README.md | 2 +- _examples/README.md | 7 +- _examples/README_ZH.md | 7 +- _examples/overview/main.go | 2 +- _examples/routing/README.md | 129 +++-- _examples/routing/basic/main.go | 2 +- .../routing/custom-high-level-router/main.go | 2 +- .../routing/custom-low-level-router/main.go | 6 - _examples/routing/dynamic-path/main.go | 4 +- .../webassembly/basic/client/hello_go111.go | 2 +- context/request_params.go | 7 + core/router/api_builder_benchmark_test.go | 2 +- core/router/handler.go | 130 +++-- core/router/node/node.go | 448 ------------------ core/router/path.go | 14 +- core/router/path_test.go | 4 +- core/router/router_wildcard_root_test.go | 13 +- core/router/trie.go | 267 +++++++++++ doc.go | 84 +++- macro/handler/handler.go | 2 +- macro/interpreter/lexer/lexer_test.go | 32 +- macro/interpreter/parser/parser_test.go | 9 +- macro/interpreter/token/token.go | 2 +- 24 files changed, 575 insertions(+), 648 deletions(-) delete mode 100644 _examples/routing/custom-low-level-router/main.go delete mode 100644 core/router/node/node.go create mode 100644 core/router/trie.go diff --git a/HISTORY.md b/HISTORY.md index f1c56f5d..34a6e675 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -22,29 +22,41 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene ## Breaking changes - Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` -- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:number` if possible (although it will stay for backwards-compatibility) +- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:int` if possible (although it will stay for backwards-compatibility) ## Routing -- `:number` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers and without digits limitation +- `:int` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers +- Add `:int8` parameter type and `ctx.Params().GetInt8` +- Add `:int16` parameter type and `ctx.Params().GetInt16` +- Add `:int32` parameter type and `ctx.Params().GetInt32` - Add `:int64` parameter type and `ctx.Params().GetInt64` +- Add `:uint` parameter type and `ctx.Params().GetUint` - Add `:uint8` parameter type and `ctx.Params().GetUint8` +- Add `:uint16` parameter type and `ctx.Params().GetUint16` +- Add `:uint32` parameter type and `ctx.Params().GetUint32` - Add `:uint64` parameter type and `ctx.Params().GetUint64` -- `:bool` parameter type as an alias for `:boolean` +- Add alias `:bool` for the `:boolean` parameter type Here is the full list of the built'n parameter types that we support now, including their validations/path segment rules. -| Param Type | Go Type | Validation | -| -----------|---------|------------| -| `:string` | string | anything | -| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | -| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | -| `:uint8` | uint8 | 0 to 255 | -| `:uint64` | uint64 | 0 to 18446744073709551615 | -| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | -| `:alphabetical` | string | lowercase or uppercase letters | -| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | -| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | +| Param Type | Go Type | Validation | Retrieve Helper | +| -----------------|------|-------------|------| +| `:string` | string | anything (single path segment) | `Params().Get` | +| `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | +| `:int8` | int8 | -128 to 127 | `Params().GetInt8` | +| `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | +| `:int32` | int32 | -2147483648 to 2147483647 | `Params().GetInt32` | +| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | +| `:uint` | uint | 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32), depends on the host arch | `Params().GetUint` | +| `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | +| `:uint16` | uint16 | 0 to 65535 | `Params().GetUint16` | +| `:uint32` | uint32 | 0 to 4294967295 | `Params().GetUint32` | +| `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | +| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | +| `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` | +| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | `Params().Get` | +| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | `Params().Get` | **Usage**: @@ -61,9 +73,9 @@ app.Get("/users/{id:uint64}", func(ctx iris.Context){ | `prefix`(prefix string) | :string | | `suffix`(suffix string) | :string | | `contains`(s string) | :string | -| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | -| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | -| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | **Usage**: diff --git a/README.md b/README.md index 23b860e9..1183f7e1 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ func main() { | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| -| `:string` | string | anything | `Params().Get` | +| `:string` | string | anything (single path segment) | `Params().Get` | | `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | | `:int8` | int8 | -128 to 127 | `Params().GetInt8` | | `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | diff --git a/_examples/README.md b/_examples/README.md index e0b00d67..fd0f4f7c 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -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:number min(1)}", myHandler)` +* `app.Get("{userid:int 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:number min(1)}", getUserHandler) +app.Get("/user/{userid:int 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:number min(1)}", deleteUserHandler) +app.Delete("/user/{userid:int 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. @@ -149,7 +149,6 @@ Navigate through examples for a better understanding. - [Write your own custom parameter types](routing/macros/main.go) **NEW** - [Reverse routing](routing/reverse/main.go) - [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** -- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW** - [Custom Wrapper](routing/custom-wrapper/main.go) - Custom Context * [method overriding](routing/custom-context/method-overriding/main.go) diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md index 82b5648e..1fd80a86 100644 --- a/_examples/README_ZH.md +++ b/_examples/README_ZH.md @@ -63,7 +63,7 @@ Iris 是个底层框架, 对 MVC 模式有很好的支持,但不限制文件 ### 路由、路由分组、路径动态参数、路由参数处理宏 、 自定义上下文 -* `app.Get("{userid:number min(1)}", myHandler)` +* `app.Get("{userid:int 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:number min(1)}", getUserHandler) +app.Get("/user/{userid:int min(1)}", getUserHandler) // 匹配所有前缀为 /users/ 的 DELETE 请求 // 参数为数字,且 >= 1 -app.Delete("/user/{userid:number min(1)}", deleteUserHandler) +app.Delete("/user/{userid:int min(1)}", deleteUserHandler) // 匹配所有 GET 请求,除了 "/", "/about", 或其他以 "/assets/" 开头 // 因为它不会与其他路线冲突。 @@ -108,7 +108,6 @@ app.Get("{root:path}", rootWildcardHandler) - [Write your own custom parameter types](routing/macros/main.go) **NEW** - [反向路由](routing/reverse/main.go) - [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** -- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW** - [自定义包装](routing/custom-wrapper/main.go) - 自定义上下文    * [方法重写](routing/custom-context/method-overriding/main.go) diff --git a/_examples/overview/main.go b/_examples/overview/main.go index 30714a09..d416f29e 100644 --- a/_examples/overview/main.go +++ b/_examples/overview/main.go @@ -68,7 +68,7 @@ func main() { usersRoutes := app.Party("/users", logThisMiddleware) { // Method GET: http://localhost:8080/users/42 - usersRoutes.Get("/{id:number min(1)}", getUserByID) + usersRoutes.Get("/{id:int min(1)}", getUserByID) // Method POST: http://localhost:8080/users/create usersRoutes.Post("/create", createUser) } diff --git a/_examples/routing/README.md b/_examples/routing/README.md index 40074682..88c40db9 100644 --- a/_examples/routing/README.md +++ b/_examples/routing/README.md @@ -152,26 +152,62 @@ Standard macro types for route path parameters | {param:string} | +------------------------+ string type -anything +anything (single path segmnent) +-------------------------------+ -| {param:number} or {param:int} | +| {param:int} | +-------------------------------+ int type -both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) +-9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch -+-------------------------------+ -| {param:long} or {param:int64} | -+-------------------------------+ ++------------------------+ +| {param:int8} | ++------------------------+ +int8 type +-128 to 127 + ++------------------------+ +| {param:int16} | ++------------------------+ +int16 type +-32768 to 32767 + ++------------------------+ +| {param:int32} | ++------------------------+ +int32 type +-2147483648 to 2147483647 + ++------------------------+ +| {param:int64} | ++------------------------+ int64 type -9223372036854775808 to 9223372036854775807 ++------------------------+ +| {param:uint} | ++------------------------+ +uint type +0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32) + +------------------------+ | {param:uint8} | +------------------------+ uint8 type 0 to 255 ++------------------------+ +| {param:uint16} | ++------------------------+ +uint16 type +0 to 65535 + ++------------------------+ +| {param:uint32} | ++------------------------+ +uint32 type +0 to 4294967295 + +------------------------+ | {param:uint64} | +------------------------+ @@ -206,8 +242,8 @@ no spaces ! or other character | {param:path} | +------------------------+ path type -anything, should be the last part, more than one path segment, -i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3" +anything, should be the last part, can be more than one path segment, +i.e: "/test/*param" and request: "/test/path1/path2/path3" , ctx.Params().Get("param") == "path1/path2/path3" ``` If type is missing then parameter's type is defaulted to string, so @@ -221,21 +257,24 @@ you are able to register your own too!. Register a named path parameter function ```go -app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool { +app.Macros().Get("int").RegisterFunc("min", func(argument int) func(paramValue int) 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. + return func(paramValue int) bool { + // -> true means valid, false means invalid fire 404 + // or if "else 500" is appended to the macro syntax then internal server error. + return true + } }) ``` -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`. +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 ) bool`. ```go {param:string equal(iris)} ``` The "iris" will be the argument here: ```go -app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool { +app.Macros().Get("string").RegisterFunc("equal", func(argument string) func(paramValue string) bool { return func(paramValue string){ return argument == paramValue } }) ``` @@ -249,20 +288,16 @@ 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 number macro type. +// Let's register our first macro attached to int 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 :number macro type with the min(...) macro parameter function. -app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { +// func() 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().Get("int").RegisterFunc("min", func(minValue int) func(int) bool { // do anything before serve here [...] // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= minValue + return func(paramValue int) bool { + return paramValue >= minValue } }) @@ -789,6 +824,32 @@ type Context interface { // IsStopped checks and returns true if the current position of the Context is 255, // means that the StopExecution() was called. IsStopped() bool + // OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev) + // when the underlying connection has gone away. + // + // This mechanism can be used to cancel long operations on the server + // if the client has disconnected before the response is ready. + // + // It depends on the `http#CloseNotify`. + // CloseNotify may wait to notify until Request.Body has been + // fully read. + // + // After the main Handler has returned, there is no guarantee + // that the channel receives a value. + // + // Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported). + // The "cb" will not fire for sure if the output value is false. + // + // Note that you can register only one callback for the entire request handler chain/per route. + // + // Look the `ResponseWriter#CloseNotifier` for more. + OnConnectionClose(fnGoroutine func()) bool + // OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose` + // and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`. + // Note that you can register only one callback for the entire request handler chain/per route. + // + // Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more. + OnClose(cb func()) // +------------------------------------------------------------+ // | Current "user/request" storage | @@ -868,8 +929,12 @@ type Context interface { // // Keep note that this checks the "User-Agent" request header. IsMobile() bool + // GetReferrer extracts and returns the information from the "Referer" header as specified + // in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + // or by the URL query parameter "referer". + GetReferrer() Referrer // +------------------------------------------------------------+ - // | Response Headers helpers | + // | Headers helpers | // +------------------------------------------------------------+ // Header adds a header to the response writer. @@ -880,16 +945,18 @@ type Context interface { // GetContentType returns the response writer's header value of "Content-Type" // which may, setted before with the 'ContentType'. GetContentType() string + // GetContentType returns the request's header value of "Content-Type". + GetContentTypeRequested() string // GetContentLength returns the request's header value of "Content-Length". // Returns 0 if header was unable to be found or its value was not a valid number. GetContentLength() int64 // StatusCode sets the status code header to the response. - // Look .GetStatusCode too. + // Look .`GetStatusCode` too. StatusCode(statusCode int) // GetStatusCode returns the current status code of the response. - // Look StatusCode too. + // Look `StatusCode` too. GetStatusCode() int // Redirect sends a redirect response to the client @@ -924,6 +991,9 @@ type Context interface { // URLParamIntDefault returns the url query parameter as int value from a request, // if not found or parse failed then "def" is returned. URLParamIntDefault(name string, def int) int + // URLParamInt32Default returns the url query parameter as int32 value from a request, + // if not found or parse failed then "def" is returned. + URLParamInt32Default(name string, def int32) int32 // URLParamInt64 returns the url query parameter as int64 value from a request, // returns -1 and an error if parse failed. URLParamInt64(name string) (int64, error) @@ -1071,6 +1141,10 @@ type Context interface { // Examples of usage: context.ReadJSON, context.ReadXML. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go + // + // UnmarshalBody does not check about gzipped data. + // Do not rely on compressed data incoming to your server. The main reason is: https://en.wikipedia.org/wiki/Zip_bomb + // However you are still free to read the `ctx.Request().Body io.Reader` manually. UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error // ReadJSON reads JSON from request's body and binds it to a pointer of a value of any json-valid type. // @@ -1081,7 +1155,8 @@ type Context interface { // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go ReadXML(xmlObjectPtr interface{}) error // ReadForm binds the formObject with the form data - // it supports any kind of struct. + // it supports any kind of type, including custom structs. + // It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go ReadForm(formObjectPtr interface{}) error diff --git a/_examples/routing/basic/main.go b/_examples/routing/basic/main.go index 9a62bdef..d03ea46c 100644 --- a/_examples/routing/basic/main.go +++ b/_examples/routing/basic/main.go @@ -103,7 +103,7 @@ func main() { ctx.Writef("All users") }) // http://v1.localhost:8080/api/users/42 - usersAPI.Get("/{userid:number}", func(ctx iris.Context) { + usersAPI.Get("/{userid:int}", func(ctx iris.Context) { ctx.Writef("user with id: %s", ctx.Params().Get("userid")) }) } diff --git a/_examples/routing/custom-high-level-router/main.go b/_examples/routing/custom-high-level-router/main.go index 1557c311..77830024 100644 --- a/_examples/routing/custom-high-level-router/main.go +++ b/_examples/routing/custom-high-level-router/main.go @@ -37,7 +37,7 @@ func (r *customRouter) HandleRequest(ctx context.Context) { parts := strings.Split(path, "/")[1:] staticPath := "/" + parts[0] for _, route := range r.provider.GetRoutes() { - if strings.HasPrefix(route.Path, staticPath) { + if strings.HasPrefix(route.Path, staticPath) && route.Method == ctx.Method() { paramParts := parts[1:] for _, paramValue := range paramParts { for _, p := range route.Tmpl().Params { diff --git a/_examples/routing/custom-low-level-router/main.go b/_examples/routing/custom-low-level-router/main.go deleted file mode 100644 index d1aac6ed..00000000 --- a/_examples/routing/custom-low-level-router/main.go +++ /dev/null @@ -1,6 +0,0 @@ -/// TODO: showcase the `app.Downgrade` feature tomorrow if not already existing elsewhere. -package main - -func main() { - panic("TODO") -} diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 6d11a878..d94b0c18 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -35,7 +35,7 @@ func main() { // anything // // +-------------------------------+ - // | {param:int} or {param:number} | + // | {param:int} or {param:int} | // +-------------------------------+ // int type // both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) @@ -209,7 +209,7 @@ func main() { // http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. - app.Get("/game/{name:alphabetical}/level/{level:number}", func(ctx iris.Context) { + 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")) }) diff --git a/_examples/webassembly/basic/client/hello_go111.go b/_examples/webassembly/basic/client/hello_go111.go index 1822fc79..97dda1f5 100644 --- a/_examples/webassembly/basic/client/hello_go111.go +++ b/_examples/webassembly/basic/client/hello_go111.go @@ -1,4 +1,4 @@ -// +build go1.11 +// +build js package main diff --git a/context/request_params.go b/context/request_params.go index 860ccf90..fc1b47c8 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -16,6 +16,13 @@ type RequestParams struct { memstore.Store } +// Set inserts a value to the key-value storage. +// +// See `SetImmutable` and `Get` too. +func (r *RequestParams) Set(key, value string) { + r.Store.Set(key, value) +} + // GetEntryAt will return the parameter's internal store's `Entry` based on the index. // If not found it will return an emptry `Entry`. func (r *RequestParams) GetEntryAt(index int) memstore.Entry { diff --git a/core/router/api_builder_benchmark_test.go b/core/router/api_builder_benchmark_test.go index 54469636..e16f798c 100644 --- a/core/router/api_builder_benchmark_test.go +++ b/core/router/api_builder_benchmark_test.go @@ -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:number}/end") + b.WriteString("/{age:int}/end") paths[i] = b.String() b.Reset() diff --git a/core/router/handler.go b/core/router/handler.go index f16e1df0..cc91819f 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -11,7 +11,6 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/netutil" - "github.com/kataras/iris/core/router/node" ) // RequestHandler the middle man between acquiring a context and releasing it. @@ -25,25 +24,17 @@ type RequestHandler interface { RouteExists(ctx context.Context, method, path string) bool } -type tree struct { - Method string - // subdomain is empty for default-hostname routes, - // ex: mysubdomain. - Subdomain string - Nodes *node.Nodes -} - type routerHandler struct { - trees []*tree + trees []*trie hosts bool // true if at least one route contains a Subdomain. } var _ RequestHandler = &routerHandler{} -func (h *routerHandler) getTree(method, subdomain string) *tree { +func (h *routerHandler) getTree(method, subdomain string) *trie { for i := range h.trees { t := h.trees[i] - if t.Method == method && t.Subdomain == subdomain { + if t.method == method && t.subdomain == subdomain { return t } } @@ -63,12 +54,14 @@ func (h *routerHandler) addRoute(r *Route) error { t := h.getTree(method, subdomain) if t == nil { - n := node.Nodes{} + n := newTrieNode() // first time we register a route to this method with this subdomain - t = &tree{Method: method, Subdomain: subdomain, Nodes: &n} + t = &trie{method: method, subdomain: subdomain, root: n} h.trees = append(h.trees, t) } - return t.Nodes.Add(routeName, path, handlers) + + t.insert(path, routeName, handlers) + return nil } // NewDefaultHandler returns the handler which is responsible @@ -188,11 +181,11 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { for i := range h.trees { t := h.trees[i] - if method != t.Method { + if method != t.method { continue } - if h.hosts && t.Subdomain != "" { + if h.hosts && t.subdomain != "" { requestHost := ctx.Host() if netutil.IsLoopbackSubdomain(requestHost) { // this fixes a bug when listening on @@ -201,7 +194,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { continue // it's not a subdomain, it's something like 127.0.0.1 probably } // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty - if t.Subdomain == SubdomainWildcardIndicator { + if t.subdomain == SubdomainWildcardIndicator { // mydomain.com -> invalid // localhost -> invalid // sub.mydomain.com -> valid @@ -219,14 +212,14 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { continue } // continue to that, any subdomain is valid. - } else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot. + } else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot. continue } } - routeName, handlers := t.Nodes.Find(path, ctx.Params()) - if len(handlers) > 0 { - ctx.SetCurrentRouteName(routeName) - ctx.Do(handlers) + n := t.search(path, ctx.Params()) + if n != nil { + ctx.SetCurrentRouteName(n.RouteName) + ctx.Do(n.Handlers) // found return } @@ -237,15 +230,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() { for i := range h.trees { t := h.trees[i] - // a bit slower than previous implementation but @kataras let me to apply this change - // because it's more reliable. - // // if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not // run, therefore performance kept as before. - if t.Nodes.Exists(path) { + if h.subdomainAndPathAndMethodExists(ctx, t, "", path) { // RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html // The response MUST include an Allow header containing a list of valid methods for the requested resource. - ctx.Header("Allow", t.Method) + ctx.Header("Allow", t.method) ctx.StatusCode(http.StatusMethodNotAllowed) return } @@ -255,55 +245,55 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { ctx.StatusCode(http.StatusNotFound) } +func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool { + if method != "" && method != t.method { + return false + } + + if h.hosts && t.subdomain != "" { + requestHost := ctx.Host() + if netutil.IsLoopbackSubdomain(requestHost) { + // this fixes a bug when listening on + // 127.0.0.1:8080 for example + // and have a wildcard subdomain and a route registered to root domain. + return false // it's not a subdomain, it's something like 127.0.0.1 probably + } + // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty + if t.subdomain == SubdomainWildcardIndicator { + // mydomain.com -> invalid + // localhost -> invalid + // sub.mydomain.com -> valid + // sub.localhost -> valid + serverHost := ctx.Application().ConfigurationReadOnly().GetVHost() + if serverHost == requestHost { + return false // it's not a subdomain, it's a full domain (with .com...) + } + + dotIdx := strings.IndexByte(requestHost, '.') + slashIdx := strings.IndexByte(requestHost, '/') + if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) { + // if "." was found anywhere but not at the first path segment (host). + } else { + return false + } + // continue to that, any subdomain is valid. + } else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot. + return false + } + } + + n := t.search(path, ctx.Params()) + return n != nil +} + // RouteExists reports whether a particular route exists // It will search from the current subdomain of context's host, if not inside the root domain. func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool { for i := range h.trees { t := h.trees[i] - if method != t.Method { - continue - } - - if h.hosts && t.Subdomain != "" { - requestHost := ctx.Host() - if netutil.IsLoopbackSubdomain(requestHost) { - // this fixes a bug when listening on - // 127.0.0.1:8080 for example - // and have a wildcard subdomain and a route registered to root domain. - continue // it's not a subdomain, it's something like 127.0.0.1 probably - } - // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty - if t.Subdomain == SubdomainWildcardIndicator { - // mydomain.com -> invalid - // localhost -> invalid - // sub.mydomain.com -> valid - // sub.localhost -> valid - serverHost := ctx.Application().ConfigurationReadOnly().GetVHost() - if serverHost == requestHost { - continue // it's not a subdomain, it's a full domain (with .com...) - } - - dotIdx := strings.IndexByte(requestHost, '.') - slashIdx := strings.IndexByte(requestHost, '/') - if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) { - // if "." was found anywhere but not at the first path segment (host). - } else { - continue - } - // continue to that, any subdomain is valid. - } else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot. - continue - } - } - - _, handlers := t.Nodes.Find(path, ctx.Params()) - if len(handlers) > 0 { - // found + if h.subdomainAndPathAndMethodExists(ctx, t, method, path) { return true } - - // not found or method not allowed. - break } return false diff --git a/core/router/node/node.go b/core/router/node/node.go deleted file mode 100644 index 4a4adb05..00000000 --- a/core/router/node/node.go +++ /dev/null @@ -1,448 +0,0 @@ -package node - -import ( - "sort" - "strings" - - "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" -) - -// Nodes a conversion type for []*node. -type Nodes []*node - -type node struct { - s string - routeName string - wildcardParamName string // name of the wildcard parameter, only one per whole Node is allowed - paramNames []string // only-names - childrenNodes Nodes - handlers context.Handlers - root bool - rootWildcard bool // if it's a wildcard {path} type on root, it should allow everything but it is not conflicts with - // any other static or dynamic or wildcard paths if exists on other nodes. -} - -// ErrDublicate returnned from `Add` when two or more routes have the same registered path. -var ErrDublicate = errors.New("two or more routes have the same registered path") - -/// TODO: clean up needed until v8.5 - -// Add adds a node to the tree, returns an ErrDublicate error on failure. -func (nodes *Nodes) Add(routeName string, path string, handlers context.Handlers) error { - // println("[Add] adding path: " + path) - // resolve params and if that node should be added as root - var params []string - var paramStart, paramEnd int - for { - paramStart = strings.IndexByte(path[paramEnd:], ':') - if paramStart == -1 { - break - } - paramStart += paramEnd - paramStart++ - paramEnd = strings.IndexByte(path[paramStart:], '/') - - if paramEnd == -1 { - params = append(params, path[paramStart:]) - path = path[:paramStart] - break - } - paramEnd += paramStart - params = append(params, path[paramStart:paramEnd]) - path = path[:paramStart] + path[paramEnd:] - paramEnd -= paramEnd - paramStart - } - - var p []int - for i := 0; i < len(path); i++ { - idx := strings.IndexByte(path[i:], ':') - if idx == -1 { - break - } - p = append(p, idx+i) - i = idx + i - } - - for _, idx := range p { - // print("-2 nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if err := nodes.add(routeName, path[:idx], nil, nil, true); err != nil { - return err - } - // print("-1 nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if nidx := idx + 1; len(path) > nidx { - if err := nodes.add(routeName, path[:nidx], nil, nil, true); err != nil { - return err - } - } - } - - // print("nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if err := nodes.add(routeName, path, params, handlers, true); err != nil { - return err - } - - // prioritize by static path remember, they were already sorted by subdomains too. - nodes.prioritize() - return nil -} - -func (nodes *Nodes) add(routeName, path string, paramNames []string, handlers context.Handlers, root bool) (err error) { - // println("[add] route name: " + routeName) - // println("[add] adding path: " + path) - - // wraia etsi doulevei ara - // na to kanw na exei to node to diko tou wildcard parameter name - // kai sto telos na pernei auto, me vasi to *paramname - // alla edw mesa 9a ginete register vasi tou last / - - // set the wildcard param name to the root and its children. - wildcardIdx := strings.IndexByte(path, '*') - wildcardParamName := "" - if wildcardIdx > 0 && len(paramNames) == 0 { // 27 Oct comment: && len(paramNames) == 0 { - wildcardParamName = path[wildcardIdx+1:] - path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash - - // if path[len(path)-1] == '/' { - // if root wildcard, then add it as it's and return - rootWildcard := path == "/" - if rootWildcard { - path += "/" // if root wildcard, then do it like "//" instead of simple "/" - } - - n := &node{ - rootWildcard: rootWildcard, - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - *nodes = append(*nodes, n) - // println("1. nodes.Add path: " + path) - return - - } - -loop: - for _, n := range *nodes { - if n.rootWildcard { - continue - } - - if len(n.paramNames) == 0 && n.wildcardParamName != "" { - continue - } - - minlen := len(n.s) - if len(path) < minlen { - minlen = len(path) - } - - for i := 0; i < minlen; i++ { - if n.s[i] == path[i] { - continue - } - if i == 0 { - continue loop - } - - *n = node{ - s: n.s[:i], - childrenNodes: Nodes{ - { - s: n.s[i:], - routeName: n.routeName, - wildcardParamName: n.wildcardParamName, // wildcardParamName - paramNames: n.paramNames, - childrenNodes: n.childrenNodes, - handlers: n.handlers, - }, - { - s: path[i:], - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - }, - }, - root: n.root, - } - - // println("2. change n and return " + n.s[:i] + " and " + path[i:]) - return - } - - if len(path) < len(n.s) { - // println("3. change n and return | n.s[:len(path)] = " + n.s[:len(path)-1] + " and child: " + n.s[len(path)-1:]) - - *n = node{ - s: n.s[:len(path)], - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - childrenNodes: Nodes{ - { - s: n.s[len(path):], - routeName: n.routeName, - wildcardParamName: n.wildcardParamName, // wildcardParamName - paramNames: n.paramNames, - childrenNodes: n.childrenNodes, - handlers: n.handlers, - }, - }, - handlers: handlers, - root: n.root, - } - - return - } - - if len(path) > len(n.s) { - if n.wildcardParamName != "" { - n := &node{ - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - // println("3.5. nodes.Add path: " + n.s) - *nodes = append(*nodes, n) - return - } - - pathToAdd := path[len(n.s):] - // println("4. nodes.Add route name: " + routeName) - // println("4. nodes.Add path: " + pathToAdd) - err = n.childrenNodes.add(routeName, pathToAdd, paramNames, handlers, false) - return err - } - - if len(handlers) == 0 { // missing handlers - return nil - } - - if len(n.handlers) > 0 { // n.handlers already setted - return ErrDublicate - } - n.paramNames = paramNames - n.handlers = handlers - n.routeName = routeName - return - } - - // START - // Author's note: - // 27 Oct 2017; fixes s|i|l+static+p - // without breaking the current tests. - if wildcardIdx > 0 { - wildcardParamName = path[wildcardIdx+1:] - path = path[0:wildcardIdx-1] + "/" - } - // END - - n := &node{ - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - *nodes = append(*nodes, n) - - // println("5. node add on path: " + path + " n.s: " + n.s + " wildcard param: " + n.wildcardParamName) - return -} - -// Find resolves the path, fills its params -// and returns the registered to the resolved node's handlers. -func (nodes Nodes) Find(path string, params *context.RequestParams) (string, context.Handlers) { - n, paramValues := nodes.findChild(path, nil) - if n != nil { - // map the params, - // n.params are the param names - if len(paramValues) > 0 { - // println("-----------") - // print("param values returned len: ") - // println(len(paramValues)) - // println("first value is: " + paramValues[0]) - // print("n.paramNames len: ") - // println(len(n.paramNames)) - for i, name := range n.paramNames { - // println("setting param name: " + name + " = " + paramValues[i]) - params.Set(name, paramValues[i]) - } - // last is the wildcard, - // if paramValues are exceed from the registered param names. - // Note that n.wildcardParamName can be not empty but that doesn't meaning - // that it contains a wildcard path, so the check is required. - if len(paramValues) > len(n.paramNames) { - // println("len(paramValues) > len(n.paramNames)") - lastWildcardVal := paramValues[len(paramValues)-1] - // println("setting wildcard param name: " + n.wildcardParamName + " = " + lastWildcardVal) - params.Set(n.wildcardParamName, lastWildcardVal) - } - } - - return n.routeName, n.handlers - } - - return "", nil -} - -// Exists returns true if a node with that "path" exists, -// otherise false. -// -// We don't care about parameters here. -func (nodes Nodes) Exists(path string) bool { - n, _ := nodes.findChild(path, nil) - return n != nil && len(n.handlers) > 0 -} - -func (nodes Nodes) findChild(path string, params []string) (*node, []string) { - - for _, n := range nodes { - if n.s == ":" { - paramEnd := strings.IndexByte(path, '/') - if paramEnd == -1 { - if len(n.handlers) == 0 { - return nil, nil - } - return n, append(params, path) - } - return n.childrenNodes.findChild(path[paramEnd:], append(params, path[:paramEnd])) - } - - // println("n.s: " + n.s) - // print("n.childrenNodes len: ") - // println(len(n.childrenNodes)) - // print("n.root: ") - // println(n.root) - - // by runtime check of:, - // if n.s == "//" && n.root && n.wildcardParamName != "" { - // but this will slow down, so we have a static field on the node itself: - if n.rootWildcard { - // println("return from n.rootWildcard") - // single root wildcard - if len(path) < 2 { - // do not remove that, it seems useless but it's not, - // we had an error while production, this fixes that. - path = "/" + path - } - return n, append(params, path[1:]) - } - - // second conditional may be unnecessary - // because of the n.rootWildcard before, but do it. - if n.wildcardParamName != "" && len(path) > 2 { - // println("n has wildcard n.s: " + n.s + " on path: " + path) - // n.s = static/, path = static - - // println(n.s + " vs path: " + path) - - // we could have /other/ as n.s so - // we must do this check, remember: - // now wildcards live on their own nodes - if len(path) == len(n.s)-1 { - // then it's like: - // path = /other2 - // ns = /other2/ - if path == n.s[0:len(n.s)-1] { - return n, params - } - } - - // othwerwise path = /other2/dsadas - // ns= /other2/ - if strings.HasPrefix(path, n.s) { - if len(path) > len(n.s)+1 { - return n, append(params, path[len(n.s):]) // without slash - } - } - - } - - if !strings.HasPrefix(path, n.s) { - // fmt.Printf("---here root: %v, n.s: "+n.s+" and path: "+path+" is dynamic: %v , wildcardParamName: %s, children len: %v \n", n.root, n.isDynamic(), n.wildcardParamName, len(n.childrenNodes)) - // println(path + " n.s: " + n.s + " continue...") - continue - } - - if len(path) == len(n.s) { - if len(n.handlers) == 0 { - return nil, nil - } - return n, params - } - - child, childParamNames := n.childrenNodes.findChild(path[len(n.s):], params) - - // print("childParamNames len: ") - // println(len(childParamNames)) - - // if len(childParamNames) > 0 { - // println("childParamsNames[0] = " + childParamNames[0]) - // } - - if child == nil || len(child.handlers) == 0 { - if n.s[len(n.s)-1] == '/' && !(n.root && (n.s == "/" || len(n.childrenNodes) > 0)) { - if len(n.handlers) == 0 { - return nil, nil - } - - // println("if child == nil.... | n.s = " + n.s) - // print("n.paramNames len: ") - // println(n.paramNames) - // print("n.wildcardParamName is: ") - // println(n.wildcardParamName) - // print("return n, append(params, path[len(n.s) | params: ") - // println(path[len(n.s):]) - return n, append(params, path[len(n.s):]) - } - - continue - } - - return child, childParamNames - } - return nil, nil -} - -// childLen returns all the children's and their children's length. -func (n *node) childLen() (i int) { - for _, n := range n.childrenNodes { - i++ - i += n.childLen() - } - return -} - -func (n *node) isDynamic() bool { - return n.s == ":" || n.wildcardParamName != "" || n.rootWildcard -} - -// prioritize sets the static paths first. -func (nodes Nodes) prioritize() { - sort.Slice(nodes, func(i, j int) bool { - if nodes[i].isDynamic() { - return false - } - if nodes[j].isDynamic() { - return true - } - - return nodes[i].childLen() > nodes[j].childLen() - }) - - for _, n := range nodes { - n.childrenNodes.prioritize() - } -} diff --git a/core/router/path.go b/core/router/path.go index 11d4ffd0..0a8b4014 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -12,14 +12,6 @@ import ( "github.com/kataras/iris/macro/interpreter/lexer" ) -const ( - // ParamStart the character in string representation where the underline router starts its dynamic named parameter. - ParamStart = ":" - // WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard - // path parameter. - WildcardParamStart = "*" -) - // Param receives a parameter name prefixed with the ParamStart symbol. func Param(name string) string { return prefix(name, ParamStart) @@ -35,10 +27,8 @@ func WildcardParam(name string) string { func convertMacroTmplToNodePath(tmpl macro.Template) string { routePath := tmpl.Src - if len(tmpl.Params) > 0 { - if routePath[len(routePath)-1] == '/' { - routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's. - } + if len(routePath) > 1 && routePath[len(routePath)-1] == '/' { + routePath = routePath[0 : len(routePath)-1] // remove any last "/" } // if it has started with {} and it's valid diff --git a/core/router/path_test.go b/core/router/path_test.go index 103e2af9..2c26c6da 100644 --- a/core/router/path_test.go +++ b/core/router/path_test.go @@ -53,8 +53,8 @@ func TestSplitPath(t *testing.T) { []string{"/user", "/admin"}}, {"/single_no_params", []string{"/single_no_params"}}, - {"/single/{id:number}", - []string{"/single/{id:number}"}}, + {"/single/{id:int}", + []string{"/single/{id:int}"}}, } equalSlice := func(s1 []string, s2 []string) bool { diff --git a/core/router/router_wildcard_root_test.go b/core/router/router_wildcard_root_test.go index 110192eb..c3190ce5 100644 --- a/core/router/router_wildcard_root_test.go +++ b/core/router/router_wildcard_root_test.go @@ -122,20 +122,25 @@ func TestRouterWildcardRootMany(t *testing.T) { func TestRouterWildcardRootManyAndRootStatic(t *testing.T) { var tt = []testRoute{ - // all routes will be handlded by "h" because we added wildcard to root, + // routes that may return 404 will be handled by the below route ("h" handler) because we added wildcard to root, // this feature is very important and can remove noumerous of previous hacks on our apps. + // + // Static paths and parameters have priority over wildcard, all three types can be registered in the same path prefix. + // + // Remember, all of those routes are registered don't be tricked by the visual appearance of the below test blocks. {"GET", "/{p:path}", h, []testRouteRequest{ {"GET", "", "/other2almost/some", iris.StatusOK, same_as_request_path}, }}, {"GET", "/static/{p:path}", h, []testRouteRequest{ - {"GET", "", "/static", iris.StatusOK, same_as_request_path}, + {"GET", "", "/static", iris.StatusOK, same_as_request_path}, // HERE<- IF NOT FOUND THEN BACKWARDS TO WILDCARD IF THERE IS ONE, HMM. {"GET", "", "/static/something/here", iris.StatusOK, same_as_request_path}, }}, {"GET", "/", h, []testRouteRequest{ {"GET", "", "/", iris.StatusOK, same_as_request_path}, }}, {"GET", "/other/{paramother:path}", h2, []testRouteRequest{ - {"GET", "", "/other", iris.StatusForbidden, same_as_request_path}, + // OK and not h2 because of the root wildcard. + {"GET", "", "/other", iris.StatusOK, same_as_request_path}, {"GET", "", "/other/wildcard", iris.StatusForbidden, same_as_request_path}, {"GET", "", "/other/wildcard/here", iris.StatusForbidden, same_as_request_path}, }}, @@ -145,6 +150,7 @@ func TestRouterWildcardRootManyAndRootStatic(t *testing.T) { }}, {"GET", "/other2/static", h3, []testRouteRequest{ {"GET", "", "/other2/static", iris.StatusOK, prefix_static_path_following_by_request_path}, + // h2(Forbiddenn) instead of h3 OK because it will be handled by the /other2/{paramothersecond:path}'s handler which gives 403. {"GET", "", "/other2/staticed", iris.StatusForbidden, same_as_request_path}, }}, } @@ -165,6 +171,7 @@ func testTheRoutes(t *testing.T, tests []testRoute, debug bool) { // run the tests for _, tt := range tests { for _, req := range tt.requests { + // t.Logf("req: %s:%s\n", tt.method, tt.path) method := req.method if method == "" { method = tt.method diff --git a/core/router/trie.go b/core/router/trie.go new file mode 100644 index 00000000..2d11e257 --- /dev/null +++ b/core/router/trie.go @@ -0,0 +1,267 @@ +package router + +import ( + "strings" + + "github.com/kataras/iris/context" +) + +const ( + // ParamStart the character in string representation where the underline router starts its dynamic named parameter. + ParamStart = ":" + // WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard + // path parameter. + WildcardParamStart = "*" +) + +// An iris-specific identical version of the https://github.com/kataras/muxie version 1.0.0 released at 15 Oct 2018 +type trieNode struct { + parent *trieNode + + children map[string]*trieNode + hasDynamicChild bool // does one of the children contains a parameter or wildcard? + childNamedParameter bool // is the child a named parameter (single segmnet) + childWildcardParameter bool // or it is a wildcard (can be more than one path segments) ? + paramKeys []string // the param keys without : or *. + end bool // it is a complete node, here we stop and we can say that the node is valid. + key string // if end == true then key is filled with the original value of the insertion's key. + // if key != "" && its parent has childWildcardParameter == true, + // we need it to track the static part for the closest-wildcard's parameter storage. + staticKey string + + // insert data. + Handlers context.Handlers + RouteName string +} + +func newTrieNode() *trieNode { + n := new(trieNode) + return n +} + +func (tn *trieNode) hasChild(s string) bool { + return tn.getChild(s) != nil +} + +func (tn *trieNode) getChild(s string) *trieNode { + if tn.children == nil { + tn.children = make(map[string]*trieNode) + } + + return tn.children[s] +} + +func (tn *trieNode) addChild(s string, n *trieNode) { + if tn.children == nil { + tn.children = make(map[string]*trieNode) + } + + if _, exists := tn.children[s]; exists { + return + } + + n.parent = tn + tn.children[s] = n +} + +func (tn *trieNode) findClosestParentWildcardNode() *trieNode { + tn = tn.parent + for tn != nil { + if tn.childWildcardParameter { + return tn.getChild(WildcardParamStart) + } + + tn = tn.parent + } + + return nil +} + +func (tn *trieNode) String() string { + return tn.key +} + +type trie struct { + root *trieNode + + // if true then it will handle any path if not other parent wildcard exists, + // so even 404 (on http services) is up to it, see trie#insert. + hasRootWildcard bool + + method string + // subdomain is empty for default-hostname routes, + // ex: mysubdomain. + subdomain string +} + +func newTrie() *trie { + return &trie{ + root: newTrieNode(), + } +} + +const ( + pathSep = "/" + pathSepB = '/' +) + +func slowPathSplit(path string) []string { + if path == "/" { + return []string{"/"} + } + + return strings.Split(path, pathSep)[1:] +} + +func (tr *trie) insert(path, routeName string, handlers context.Handlers) { + input := slowPathSplit(path) + + n := tr.root + var paramKeys []string + + for _, s := range input { + c := s[0] + + if isParam, isWildcard := c == ParamStart[0], c == WildcardParamStart[0]; isParam || isWildcard { + n.hasDynamicChild = true + paramKeys = append(paramKeys, s[1:]) // without : or *. + + // if node has already a wildcard, don't force a value, check for true only. + if isParam { + n.childNamedParameter = true + s = ParamStart + } + + if isWildcard { + n.childWildcardParameter = true + s = WildcardParamStart + if tr.root == n { + tr.hasRootWildcard = true + } + } + } + + if !n.hasChild(s) { + child := newTrieNode() + n.addChild(s, child) + } + + n = n.getChild(s) + } + + n.RouteName = routeName + n.Handlers = handlers + n.paramKeys = paramKeys + n.key = path + n.end = true + + i := strings.Index(path, ParamStart) + if i == -1 { + i = strings.Index(path, WildcardParamStart) + } + if i == -1 { + i = len(n.key) + } + + n.staticKey = path[:i] +} + +func (tr *trie) search(q string, params *context.RequestParams) *trieNode { + end := len(q) + n := tr.root + if end == 1 && q[0] == pathSepB { + return n.getChild(pathSep) + } + + start := 1 + i := 1 + var paramValues []string + + for { + if i == end || q[i] == pathSepB { + if child := n.getChild(q[start:i]); child != nil { + n = child + } else if n.childNamedParameter { + n = n.getChild(ParamStart) + if ln := len(paramValues); cap(paramValues) > ln { + paramValues = paramValues[:ln+1] + paramValues[ln] = q[start:i] + } else { + paramValues = append(paramValues, q[start:i]) + } + } else if n.childWildcardParameter { + n = n.getChild(WildcardParamStart) + if ln := len(paramValues); cap(paramValues) > ln { + paramValues = paramValues[:ln+1] + paramValues[ln] = q[start:] + } else { + paramValues = append(paramValues, q[start:]) + } + break + } else { + n = n.findClosestParentWildcardNode() + if n != nil { + // means that it has :param/static and *wildcard, we go trhough the :param + // but the next path segment is not the /static, so go back to *wildcard + // instead of not found. + // + // Fixes: + // /hello/*p + // /hello/:p1/static/:p2 + // req: http://localhost:8080/hello/dsadsa/static/dsadsa => found + // req: http://localhost:8080/hello/dsadsa => but not found! + // and + // /second/wild/*p + // /second/wild/static/otherstatic/ + // req: /second/wild/static/otherstatic/random => but not found! + params.Set(n.paramKeys[0], q[len(n.staticKey):]) + return n + } + + return nil + } + + if i == end { + break + } + + i++ + start = i + continue + } + + i++ + } + + if n == nil || !n.end { + if n != nil { // we need it on both places, on last segment (below) or on the first unnknown (above). + if n = n.findClosestParentWildcardNode(); n != nil { + params.Set(n.paramKeys[0], q[len(n.staticKey):]) + return n + } + } + + if tr.hasRootWildcard { + // that's the case for root wildcard, tests are passing + // even without it but stick with it for reference. + // Note ote that something like: + // Routes: /other2/*myparam and /other2/static + // Reqs: /other2/staticed will be handled + // the /other2/*myparam and not the root wildcard, which is what we want. + // + n = tr.root.getChild(WildcardParamStart) + params.Set(n.paramKeys[0], q[1:]) + return n + } + + return nil + } + + for i, paramValue := range paramValues { + if len(n.paramKeys) > i { + params.Set(n.paramKeys[i], paramValue) + } + } + + return n +} diff --git a/doc.go b/doc.go index dc5f93b1..88b67c40 100644 --- a/doc.go +++ b/doc.go @@ -491,7 +491,7 @@ Example code: // http://myhost.com/users/42/profile users.Get("/{id:uint64}/profile", userProfileHandler) // http://myhost.com/users/messages/1 - users.Get("/inbox/{id:number}", userMessageHandler) + users.Get("/inbox/{id:int}", userMessageHandler) Custom HTTP Errors @@ -553,7 +553,7 @@ Example code: if err != nil { ctx.Writef("error while trying to parse userid parameter," + - "this will never happen if :number is being used because if it's not integer it will fire Not Found automatically.") + "this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.") ctx.StatusCode(iris.StatusBadRequest) return } @@ -709,26 +709,62 @@ Standard macro types for parameters: | {param:string} | +------------------------+ string type - anything + anything (single path segmnent) +-------------------------------+ - | {param:number} or {param:int} | + | {param:int} | +-------------------------------+ int type - both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) + -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch - +-------------------------------+ - | {param:long} or {param:int64} | - +-------------------------------+ + +------------------------+ + | {param:int8} | + +------------------------+ + int8 type + -128 to 127 + + +------------------------+ + | {param:int16} | + +------------------------+ + int16 type + -32768 to 32767 + + +------------------------+ + | {param:int32} | + +------------------------+ + int32 type + -2147483648 to 2147483647 + + +------------------------+ + | {param:int64} | + +------------------------+ int64 type -9223372036854775808 to 9223372036854775807 + +------------------------+ + | {param:uint} | + +------------------------+ + uint type + 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32) + +------------------------+ | {param:uint8} | +------------------------+ uint8 type 0 to 255 + +------------------------+ + | {param:uint16} | + +------------------------+ + uint16 type + 0 to 65535 + + +------------------------+ + | {param:uint32} | + +------------------------+ + uint32 type + 0 to 4294967295 + +------------------------+ | {param:uint64} | +------------------------+ @@ -763,8 +799,8 @@ Standard macro types for parameters: | {param:path} | +------------------------+ path type - anything, should be the last part, more than one path segment, - i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3" + anything, should be the last part, can be more than one path segment, + i.e: "/test/*param" and request: "/test/path1/path2/path3" , ctx.Params().Get("param") == "path1/path2/path3" if type is missing then parameter's type is defaulted to string, so {param} == {param:string}. @@ -773,7 +809,7 @@ If a function not found on that type then the "string"'s types functions are bei i.e: - {param:number min(3)} + {param:int min(3)} Besides the fact that iris provides the basic types and some default "macro funcs" @@ -782,16 +818,18 @@ you are able to register your own too!. Register a named path parameter function: - app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool { - [...] - return true/false -> true means valid. + app.Macros().Get("int").RegisterFunc("min", func(argument int) func(paramValue int) bool { + return func(paramValue int) bool { + [...] + return true/false -> true means valid. + } }) at the func(argument ...) you can have any standard type, it will be validated before the server starts so don't care about performance here, the only thing it runs at serve time is the returning func(paramValue string) bool. {param:string equal(iris)} , "iris" will be the argument here: - app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool { + app.Macros().Get("string").RegisterFunc("equal", func(argument string) func(paramValue string) bool { return func(paramValue string){ return argument == paramValue } }) @@ -804,20 +842,16 @@ Example Code: ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} - // Let's register our first macro attached to number macro type. + // Let's register our first macro attached to int 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 number macro type with the min(...) macro parameter function. - app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { + // func() 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().Get("int").RegisterFunc("min", func(minValue int) func(int) bool { // do anything before serve here [...] // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= minValue + return func(paramValue int) bool { + return paramValue >= minValue } }) diff --git a/macro/handler/handler.go b/macro/handler/handler.go index 16f466c1..ea36c7d5 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -44,7 +44,7 @@ func MakeHandler(tmpl macro.Template) context.Handler { continue // allow. } - if !p.Eval(ctx.Params().Get(p.Name), ctx.Params()) { + if !p.Eval(ctx.Params().Get(p.Name), &ctx.Params().Store) { ctx.StatusCode(p.ErrCode) ctx.StopExecution() return diff --git a/macro/interpreter/lexer/lexer_test.go b/macro/interpreter/lexer/lexer_test.go index 4ed056a2..848731e0 100644 --- a/macro/interpreter/lexer/lexer_test.go +++ b/macro/interpreter/lexer/lexer_test.go @@ -7,27 +7,27 @@ import ( ) func TestNextToken(t *testing.T) { - input := `{id:number min(1) max(5) else 404}` + input := `{id:int 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, "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 + {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 } l := New(input) diff --git a/macro/interpreter/parser/parser_test.go b/macro/interpreter/parser/parser_test.go index 1695e6f4..5424f675 100644 --- a/macro/interpreter/parser/parser_test.go +++ b/macro/interpreter/parser/parser_test.go @@ -95,7 +95,7 @@ func TestParseParam(t *testing.T) { }{ {true, ast.ParamStatement{ - Src: "{id:number min(1) max(5) else 404}", + Src: "{id:int min(1) max(5) else 404}", Name: "id", Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ @@ -111,6 +111,7 @@ func TestParseParam(t *testing.T) { {true, ast.ParamStatement{ + // test alias of int. Src: "{id:number range(1,5)}", Name: "id", Type: mustLookupParamType("number"), @@ -163,7 +164,7 @@ func TestParseParam(t *testing.T) { }}, // 6 {true, ast.ParamStatement{ - Src: "{id:number even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) + Src: "{id:int even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) Name: "id", Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ @@ -236,9 +237,9 @@ func TestParse(t *testing.T) { valid bool expectedStatements []ast.ParamStatement }{ - {"/api/users/{id:number min(1) max(5) else 404}", true, + {"/api/users/{id:int min(1) max(5) else 404}", true, []ast.ParamStatement{{ - Src: "{id:number min(1) max(5) else 404}", + Src: "{id:int min(1) max(5) else 404}", Name: "id", Type: paramTypeNumber, Funcs: []ast.ParamFunc{ diff --git a/macro/interpreter/token/token.go b/macro/interpreter/token/token.go index f5cecbe9..b964db1d 100644 --- a/macro/interpreter/token/token.go +++ b/macro/interpreter/token/token.go @@ -14,7 +14,7 @@ type Token struct { // /about/{fullname:alphabetical} // /profile/{anySpecialName:string} // {id:uint64 range(1,5) else 404} -// /admin/{id:number eq(1) else 402} +// /admin/{id:int eq(1) else 402} // /file/{filepath:file else 405} const ( EOF = iota // 0 From 8f53f7c399f52422a983af4ba55f5ad7e3f44011 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 16 Oct 2018 01:39:27 +0300 Subject: [PATCH 33/42] add godoc comments for the updated macro package Former-commit-id: 5a90777926f6457b6639514e51ce48a4a57886c6 --- README.md | 2 +- .../read-json-struct-validation/main.go | 2 +- context/request_params.go | 12 +++++++++++ macro/macro.go | 18 ++++++++++------- macro/macros.go | 20 +++++++++++++++++++ macro/template.go | 15 +++++++++++--- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1183f7e1..a6f6972d 100644 --- a/README.md +++ b/README.md @@ -644,7 +644,7 @@ func main() { // Register validation for 'User' // NOTE: only have to register a non-pointer type for 'User', validator - // interanlly dereferences during it's type checks. + // internally dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) app := iris.New() diff --git a/_examples/http_request/read-json-struct-validation/main.go b/_examples/http_request/read-json-struct-validation/main.go index f7beedc0..16dab511 100644 --- a/_examples/http_request/read-json-struct-validation/main.go +++ b/_examples/http_request/read-json-struct-validation/main.go @@ -36,7 +36,7 @@ func main() { // Register validation for 'User' // NOTE: only have to register a non-pointer type for 'User', validator - // interanlly dereferences during it's type checks. + // internally dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) app := iris.New() diff --git a/context/request_params.go b/context/request_params.go index fc1b47c8..7757308b 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -90,6 +90,18 @@ func (r RequestParams) GetIntUnslashed(key string) (int, bool) { } var ( + // ParamResolvers is the global param resolution for a parameter type for a specific go std or custom type. + // + // Key is the specific type, which should be unique. + // The value is a function which accepts the parameter index + // and it should return the value as the parameter type evaluator expects it. + // i.e [reflect.TypeOf("string")] = func(paramIndex int) interface{} { + // return func(ctx Context) { + // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.() + // } + // } + // + // Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ reflect.TypeOf(""): func(paramIndex int) interface{} { return func(ctx Context) string { diff --git a/macro/macro.go b/macro/macro.go index 2e461ad2..8a656e2c 100644 --- a/macro/macro.go +++ b/macro/macro.go @@ -9,15 +9,12 @@ import ( "unicode" ) -// EvaluatorFunc is the signature for both param types and param funcs. -// It should accepts the param's value as string -// and return true if validated otherwise false. -// type EvaluatorFunc func(paramValue string) bool -// type BinderFunc func(paramValue string) interface{} - type ( + // ParamEvaluator is the signature for param type evaluator. + // It accepts the param's value as string and returns + // the value (which its type is used for the input argument of the parameter functions, if any) + // and a true value for passed, otherwise nil and false shoudl be returned. ParamEvaluator func(paramValue string) (interface{}, bool) - // FuncEvaluator interface{} // i.e func(paramValue int) bool ) var goodEvaluatorFuncs = []reflect.Type{ @@ -275,18 +272,25 @@ func NewMacro(indent, alias string, master, trailing bool, evaluator ParamEvalua } } +// Indent returns the name of the parameter type. func (m *Macro) Indent() string { return m.indent } +// Alias returns the alias of the parameter type, if any. func (m *Macro) Alias() string { return m.alias } +// Master returns true if that macro's parameter type is the +// default one if not :type is followed by a parameter type inside the route path. func (m *Macro) Master() bool { return m.master } +// Trailing returns true if that macro's parameter type +// is wildcard and can accept one or more path segments as one parameter value. +// A wildcard should be registered in the last path segment only. func (m *Macro) Trailing() bool { return m.trailing } diff --git a/macro/macros.go b/macro/macros.go index 12a5cf21..4c6f6416 100644 --- a/macro/macros.go +++ b/macro/macros.go @@ -402,6 +402,9 @@ var ( // Should be living in the latest path segment of a route path. Path = NewMacro("path", "", false, true, nil) + // Defaults contains the defaults macro and parameters types for the router. + // + // Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. Defaults = &Macros{ String, Int, @@ -420,8 +423,18 @@ var ( } ) +// Macros is just a type of a slice of *Macro +// which is responsible to register and search for macros based on the indent(parameter type). type Macros []*Macro +// Register registers a custom Macro. +// The "indent" should not be empty and should be unique, it is the parameter type's name, i.e "string". +// The "alias" is optionally and it should be unique, it is the alias of the parameter type. +// "isMaster" and "isTrailing" is for default parameter type and wildcard respectfully. +// The "evaluator" is the function that is converted to an Iris handler which is executed every time +// before the main chain of a route's handlers that contains this macro of the specific parameter type. +// +// Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro { macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) if ms.register(macro) { @@ -460,6 +473,7 @@ func (ms *Macros) register(macro *Macro) bool { return true } +// Unregister removes a macro and its parameter type from the list. func (ms *Macros) Unregister(indent string) bool { cp := *ms @@ -477,6 +491,7 @@ func (ms *Macros) Unregister(indent string) bool { return false } +// Lookup returns the responsible macro for a parameter type, it can return nil. func (ms *Macros) Lookup(pt ast.ParamType) *Macro { if m := ms.Get(pt.Indent()); m != nil { return m @@ -491,6 +506,7 @@ func (ms *Macros) Lookup(pt ast.ParamType) *Macro { return nil } +// Get returns the responsible macro for a parameter type, it can return nil. func (ms *Macros) Get(indentOrAlias string) *Macro { if indentOrAlias == "" { return nil @@ -509,6 +525,8 @@ func (ms *Macros) Get(indentOrAlias string) *Macro { return nil } +// GetMaster returns the default macro and its parameter type, +// by default it will return the `String` macro which is responsible for the "string" parameter type. func (ms *Macros) GetMaster() *Macro { for _, m := range *ms { if m.Master() { @@ -519,6 +537,8 @@ func (ms *Macros) GetMaster() *Macro { return nil } +// GetTrailings returns the macros that have support for wildcards parameter types. +// By default it will return the `Path` macro which is responsible for the "path" parameter type. func (ms *Macros) GetTrailings() (macros []*Macro) { for _, m := range *ms { if m.Trailing() { diff --git a/macro/template.go b/macro/template.go index cd83d9c7..8e766cb5 100644 --- a/macro/template.go +++ b/macro/template.go @@ -54,14 +54,23 @@ func (p TemplateParam) preComputed() TemplateParam { return p } +// CanEval returns true if this "p" TemplateParam should be evaluated in serve time. +// It is computed before server ran and it is used to determinate if a route needs to build a macro handler (middleware). func (p *TemplateParam) CanEval() bool { return p.canEval } -// paramChanger is the same form of context's Params().Set +// Eval is the most critical part of the TEmplateParam. +// It is responsible to return "passed:true" or "not passed:false" +// if the "paramValue" is the correct type of the registered parameter type +// and all functions, if any, are passed. +// "paramChanger" is the same form of context's Params().Set // we could accept a memstore.Store or even context.RequestParams // but this form has been chosed in order to test easier and fully decoupled from a request when necessary. -func (p *TemplateParam) Eval(paramValue string, paramChanger memstore.ValueSetter) bool { +// +// It is called from the converted macro handler (middleware) +// from the higher-level component of "kataras/iris/macro/handler#MakeHandler". +func (p *TemplateParam) Eval(paramValue string, paramSetter memstore.ValueSetter) bool { if p.TypeEvaluator == nil { for _, fn := range p.stringInFuncs { if !fn(paramValue) { @@ -87,7 +96,7 @@ func (p *TemplateParam) Eval(paramValue string, paramChanger memstore.ValueSette } } - paramChanger.Set(p.Name, newValue) + paramSetter.Set(p.Name, newValue) return true } From c0728deff97bb29d485cf9b03342ac5bdc0c1619 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 16 Oct 2018 01:40:17 +0300 Subject: [PATCH 34/42] typo fix Former-commit-id: 6ac3649296694e4c5462f97f645ace604b6bc4bf --- macro/macro.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macro/macro.go b/macro/macro.go index 8a656e2c..c4c55704 100644 --- a/macro/macro.go +++ b/macro/macro.go @@ -13,7 +13,7 @@ type ( // ParamEvaluator is the signature for param type evaluator. // It accepts the param's value as string and returns // the value (which its type is used for the input argument of the parameter functions, if any) - // and a true value for passed, otherwise nil and false shoudl be returned. + // and a true value for passed, otherwise nil and false should be returned. ParamEvaluator func(paramValue string) (interface{}, bool) ) From 192d71432fbea0b13760a6c4e801df487582f164 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 16 Oct 2018 01:46:00 +0300 Subject: [PATCH 35/42] version preparation, it should be ready before novemember. Former-commit-id: 7d0cc9b00fd5b9355928389bd47254c03e050f04 --- README.md | 2 +- VERSION | 2 +- doc.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a6f6972d..de91acb8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/v11/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris is a fast, simple yet fully featured and very efficient web framework for Go. diff --git a/VERSION b/VERSION index 729c3531..8274a13e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.7.0:https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070 \ No newline at end of file +11.0.0:https://github.com/kataras/iris/blob/master/HISTORY.md#day??-dayN??-october-2018--v1100 \ No newline at end of file diff --git a/doc.go b/doc.go index 88b67c40..6c8bb567 100644 --- a/doc.go +++ b/doc.go @@ -35,11 +35,11 @@ Source code and other details for the project are available at GitHub: Current Version -10.7.0 +11.0.0 Installation -The only requirement is the Go Programming Language, at least version 1.8 but 1.10 and above is highly recommended. +The only requirement is the Go Programming Language, at least version 1.8 but 1.11.1 and above is highly recommended. $ go get -u github.com/kataras/iris From e3876d793c17ab865c8d58bf8aae5c3f6884782f Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 16 Oct 2018 05:37:21 +0300 Subject: [PATCH 36/42] make the core/router.AllMethods a variable that can be changed by end-devs as requested at: https://github.com/kataras/iris/issues/1102 Former-commit-id: 47c997e0d3a90e4c7ccb8c4dfd1459065490d59e --- HISTORY.md | 8 +++++++- core/router/api_builder.go | 10 ++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 34a6e675..bd6bfee8 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -22,10 +22,16 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene ## Breaking changes - Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` -- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:int` if possible (although it will stay for backwards-compatibility) +- `:int` parameter type **can accept negative numbers now**. +- `app.Macros().String/Int/Uint64/Path...RegisterFunc` should be replaced to: `app.Macros().Get("string" or "int" or "uint64" or "path" when "path" is the ":path" parameter type).RegisterFunc`, because you can now add custom macros and parameter types as well, see [here](_examples/routing/macros). +- `RegisterFunc("min", func(paramValue string) bool {...})` should be replaced to `RegisterFunc("min", func(paramValue ) bool {...})`, the `paramValue` argument is now stored in the exact type the macro's type evaluator inits it, i.e `uint64` or `int` and so on, therefore you don't have to convert the parameter value each time (this should make your handlers with macro functions activated even faster now) ## Routing +The `github.com/kataras/iris/core/router.AllMethods` is now a variable that can be altered by end-developers, so things like `app.Any` can register to custom methods as well, as requested at: https://github.com/kataras/iris/issues/1102. For example, import that package and do `router.AllMethods = append(router.AllMethods, "LINK")` in your `main` or `init` function. + +The old `github.com/kataras/iris/core/router/macro` package was moved to `guthub.com/kataras/iris/macro` to allow end-developers to add custom parameter types and macros, it supports all go standard types by default as you will see below. + - `:int` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers - Add `:int8` parameter type and `ctx.Params().GetInt8` - Add `:int16` parameter type and `ctx.Params().GetInt16` diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 7f1f3635..9aa9a338 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -12,17 +12,15 @@ import ( "github.com/kataras/iris/macro" ) -const ( - // MethodNone is a Virtual method - // to store the "offline" routes. - MethodNone = "NONE" -) +// MethodNone is a Virtual method +// to store the "offline" routes. +const MethodNone = "NONE" var ( // AllMethods contains the valid http methods: // "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", // "PATCH", "OPTIONS", "TRACE". - AllMethods = [...]string{ + AllMethods = []string{ "GET", "POST", "PUT", From df85be52a498cbd22efccf04f0a0bd4009e95dc6 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 17 Oct 2018 07:13:34 +0300 Subject: [PATCH 37/42] sync with master fixes and add more details in the HISTORY.md for the upcoming release Former-commit-id: 13d5f3bd0dd844dbe2f709cfe8423a7417542744 --- HISTORY.md | 83 ++++++++++++++++++++++++++++++++++++++++++++- websocket/server.go | 13 ++++--- 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index bd6bfee8..b3d5ab53 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,6 +28,87 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene ## Routing +I wrote a [new router implementation](https://github.com/kataras/muxie#philosophy) for our Iris internal(low-level) routing mechanism, it is good to know that this was the second time we have updated the router internals without a single breaking change after the v6, thanks to the very well-writen and designed-first code we have for the high-level path syntax component called [macro interpreter](macro/interpreter). + +The new router supports things like **closest wildcard resolution**. + +> If the name doesn't sound good to you it is because I named that feature myself, I don't know any other framework or router that supports a thing like that so be gentle:) + +Previously you couldn't register routes like: `/{myparam:path}` and `/static` and `/{myparam:string}` and `/{myparam:string}/static` and `/static/{myparam:string}` all in one path prefix without a "decision handler". And generally if you had a wildcard it was possible to add (a single) static part and (a single) named parameter but not without performance cost and limits, why only one? (one is better than nothing: look the Iris' alternatives) We struggle to overcome our own selves, now you **can definitely do it without a bit of performance cost**, and surely we hand't imagine the wildcard to **catch all if nothing else found** without huge routing performance cost, the wildcard(`:path`) meant ONLY: "accept one or more path segments and put them into the declared parameter" so if you had register a dynamic single-path-segment named parameter like `:string, :int, :uint, :alphabetical...` in between those path segments it wouldn't work. The **closest wildcard resolution** offers you the opportunity to design your APIs even better via custom handlers and error handlers like `404 not found` to path prefixes for your API's groups, now you can do it without any custom code for path resolution inside a "decision handler" or a middleware. + +Code worths 1000 words, now it is possible to define your routes like this without any issues: + +```go +package main + +import ( + "github.com/kataras/iris" + "github.com/kataras/iris/context" +) + +func main() { + app := iris.New() + + // matches everyhing if nothing else found, + // so you can use it for custom 404 root-level/main pages! + app.Get("/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + // gives the path without the first "/". + ctx.Writef("Site Custom 404 Error Message\nPage of: '%s' not found", path) + }) + + app.Get("/", indexHandler) + + // request: http://localhost:8080/profile + // response: "Profile Index" + app.Get("/profile", func(ctx context.Context) { + ctx.Writef("Profile Index") + }) + + // request: http://localhost:8080/profile/kataras + // response: "Profile of username: 'kataras'" + app.Get("/profile/{username}", func(ctx context.Context) { + username := ctx.Params().Get("username") + ctx.Writef("Profile of username: '%s'", username) + }) + + // request: http://localhost:8080/profile/settings + // response: "Profile personal settings" + app.Get("/profile/settings", func(ctx context.Context) { + ctx.Writef("Profile personal settings") + }) + + // request: http://localhost:8080/profile/settings/security + // response: "Profile personal security settings" + app.Get("/profile/settings/security", func(ctx context.Context) { + ctx.Writef("Profile personal security settings") + }) + + // matches everyhing /profile/*somethng_here* + // if no other route matches the path semgnet after the + // /profile or /profile/ + // + // So, you can use it for custom 404 profile pages + // side-by-side to your root wildcard without issues! + // For example: + // request: http://localhost:8080/profile/kataras/what + // response: + // Profile Page Custom 404 Error Message + // Profile Page of: 'kataras/what' was unable to be found + app.Get("/profile/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + ctx.Writef("Profile Page Custom 404 Error Message\nProfile Page of: '%s' not found", path) + }) + + app.Run(iris.Addr(":8080")) +} + +func indexHandler(ctx context.Context) { + ctx.HTML("This is the index page") +} + +``` + The `github.com/kataras/iris/core/router.AllMethods` is now a variable that can be altered by end-developers, so things like `app.Any` can register to custom methods as well, as requested at: https://github.com/kataras/iris/issues/1102. For example, import that package and do `router.AllMethods = append(router.AllMethods, "LINK")` in your `main` or `init` function. The old `github.com/kataras/iris/core/router/macro` package was moved to `guthub.com/kataras/iris/macro` to allow end-developers to add custom parameter types and macros, it supports all go standard types by default as you will see below. @@ -48,7 +129,7 @@ Here is the full list of the built'n parameter types that we support now, includ | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| -| `:string` | string | anything (single path segment) | `Params().Get` | +| `:string` | string | the default if param type is missing, anything (single path segment) | `Params().Get` | | `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | | `:int8` | int8 | -128 to 127 | `Params().GetInt8` | | `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | diff --git a/websocket/server.go b/websocket/server.go index f704a05e..a88f1806 100644 --- a/websocket/server.go +++ b/websocket/server.go @@ -52,9 +52,9 @@ type ( func New(cfg Config) *Server { cfg = cfg.Validate() return &Server{ - config: cfg, - connections: sync.Map{}, // ready-to-use, this is not necessary. - rooms: make(map[string][]string), + config: cfg, + connections: sync.Map{}, // ready-to-use, this is not necessary. + rooms: make(map[string][]string), onConnectionListeners: make([]ConnectionFunc, 0), upgrader: websocket.Upgrader{ HandshakeTimeout: cfg.HandshakeTimeout, @@ -352,9 +352,12 @@ func (s *Server) GetConnectionsByRoom(roomName string) []Connection { // let's keep it unexported for the best. func (s *Server) emitMessage(from, to string, data []byte) { if to != All && to != Broadcast { - if s.rooms[to] != nil { + s.mu.RLock() + room := s.rooms[to] + s.mu.RUnlock() + if room != nil { // it suppose to send the message to a specific room/or a user inside its own room - for _, connectionIDInsideRoom := range s.rooms[to] { + for _, connectionIDInsideRoom := range room { if c, ok := s.getConnection(connectionIDInsideRoom); ok { c.writeDefault(data) //send the message to the client(s) } else { From 977b67dd470ff4ab457b1d5016b7b607b38371d9 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 18 Oct 2018 20:56:41 +0300 Subject: [PATCH 38/42] add a HISTORY note about the Context#ReadForm return error Former-commit-id: ae423978262575d21d098f1ca2986de05d0cda49 --- HISTORY.md | 99 +++++++++++++++++++++++----------------------- context/context.go | 6 +-- 2 files changed, 52 insertions(+), 53 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index b3d5ab53..67c65700 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -25,6 +25,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene - `:int` parameter type **can accept negative numbers now**. - `app.Macros().String/Int/Uint64/Path...RegisterFunc` should be replaced to: `app.Macros().Get("string" or "int" or "uint64" or "path" when "path" is the ":path" parameter type).RegisterFunc`, because you can now add custom macros and parameter types as well, see [here](_examples/routing/macros). - `RegisterFunc("min", func(paramValue string) bool {...})` should be replaced to `RegisterFunc("min", func(paramValue ) bool {...})`, the `paramValue` argument is now stored in the exact type the macro's type evaluator inits it, i.e `uint64` or `int` and so on, therefore you don't have to convert the parameter value each time (this should make your handlers with macro functions activated even faster now) +- The `Context#ReadForm` will no longer return an error if it has no value to read from the request, we let those checks to the caller and validators as requested at: https://github.com/kataras/iris/issues/1095 by [@haritsfahreza](https://github.com/haritsfahreza) ## Routing @@ -42,69 +43,69 @@ Code worths 1000 words, now it is possible to define your routes like this witho package main import ( - "github.com/kataras/iris" - "github.com/kataras/iris/context" + "github.com/kataras/iris" + "github.com/kataras/iris/context" ) func main() { - app := iris.New() + app := iris.New() - // matches everyhing if nothing else found, - // so you can use it for custom 404 root-level/main pages! - app.Get("/{p:path}", func(ctx context.Context) { - path := ctx.Params().Get("p") - // gives the path without the first "/". - ctx.Writef("Site Custom 404 Error Message\nPage of: '%s' not found", path) - }) + // matches everyhing if nothing else found, + // so you can use it for custom 404 root-level/main pages! + app.Get("/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + // gives the path without the first "/". + ctx.Writef("Site Custom 404 Error Message\nPage of: '%s' not found", path) + }) - app.Get("/", indexHandler) + app.Get("/", indexHandler) - // request: http://localhost:8080/profile - // response: "Profile Index" - app.Get("/profile", func(ctx context.Context) { - ctx.Writef("Profile Index") - }) + // request: http://localhost:8080/profile + // response: "Profile Index" + app.Get("/profile", func(ctx context.Context) { + ctx.Writef("Profile Index") + }) - // request: http://localhost:8080/profile/kataras - // response: "Profile of username: 'kataras'" - app.Get("/profile/{username}", func(ctx context.Context) { - username := ctx.Params().Get("username") - ctx.Writef("Profile of username: '%s'", username) - }) + // request: http://localhost:8080/profile/kataras + // response: "Profile of username: 'kataras'" + app.Get("/profile/{username}", func(ctx context.Context) { + username := ctx.Params().Get("username") + ctx.Writef("Profile of username: '%s'", username) + }) - // request: http://localhost:8080/profile/settings - // response: "Profile personal settings" - app.Get("/profile/settings", func(ctx context.Context) { - ctx.Writef("Profile personal settings") - }) + // request: http://localhost:8080/profile/settings + // response: "Profile personal settings" + app.Get("/profile/settings", func(ctx context.Context) { + ctx.Writef("Profile personal settings") + }) - // request: http://localhost:8080/profile/settings/security - // response: "Profile personal security settings" - app.Get("/profile/settings/security", func(ctx context.Context) { - ctx.Writef("Profile personal security settings") - }) + // request: http://localhost:8080/profile/settings/security + // response: "Profile personal security settings" + app.Get("/profile/settings/security", func(ctx context.Context) { + ctx.Writef("Profile personal security settings") + }) - // matches everyhing /profile/*somethng_here* - // if no other route matches the path semgnet after the - // /profile or /profile/ - // - // So, you can use it for custom 404 profile pages - // side-by-side to your root wildcard without issues! - // For example: - // request: http://localhost:8080/profile/kataras/what - // response: - // Profile Page Custom 404 Error Message - // Profile Page of: 'kataras/what' was unable to be found - app.Get("/profile/{p:path}", func(ctx context.Context) { - path := ctx.Params().Get("p") - ctx.Writef("Profile Page Custom 404 Error Message\nProfile Page of: '%s' not found", path) - }) + // matches everyhing /profile/*somethng_here* + // if no other route matches the path semgnet after the + // /profile or /profile/ + // + // So, you can use it for custom 404 profile pages + // side-by-side to your root wildcard without issues! + // For example: + // request: http://localhost:8080/profile/kataras/what + // response: + // Profile Page Custom 404 Error Message + // Profile Page of: 'kataras/what' was unable to be found + app.Get("/profile/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + ctx.Writef("Profile Page Custom 404 Error Message\nProfile Page of: '%s' not found", path) + }) - app.Run(iris.Addr(":8080")) + app.Run(iris.Addr(":8080")) } func indexHandler(ctx context.Context) { - ctx.HTML("This is the index page") + ctx.HTML("This is the index page") } ``` diff --git a/context/context.go b/context/context.go index a7ad80b1..7ea78229 100644 --- a/context/context.go +++ b/context/context.go @@ -2864,12 +2864,10 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { options = opts[0] } - optimize := ctx.shouldOptimize() - ctx.ContentType(ContentJSONHeaderValue) if options.StreamingJSON { - if optimize { + if ctx.shouldOptimize() { var jsoniterConfig = jsoniter.Config{ EscapeHTML: !options.UnescapeHTML, IndentionStep: 4, @@ -2890,7 +2888,7 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { return ctx.writer.Written(), err } - n, err = WriteJSON(ctx.writer, v, options, optimize) + n, err = WriteJSON(ctx.writer, v, options, ctx.shouldOptimize()) if err != nil { ctx.StatusCode(http.StatusInternalServerError) return 0, err From a39807626dbab8b0b02af2f51fa04952727aa293 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 18:46:10 +0300 Subject: [PATCH 39/42] version 11 release Former-commit-id: 44de994cbb0a8dfb3793a593d5a292affc2ae9f9 --- FAQ.md | 2 +- HISTORY.md | 2 +- HISTORY_GR.md | 6 +++++- HISTORY_ID.md | 4 ++++ HISTORY_ZH.md | 4 ++++ README.md | 4 ++-- README_GR.md | 4 ++-- README_ID.md | 4 ++-- README_JPN.md | 4 ++-- README_PT_BR.md | 4 ++-- README_RU.md | 4 ++-- README_ZH.md | 2 +- VERSION | 2 +- _examples/tutorial/vuejs-todo-mvc/README.md | 2 +- 14 files changed, 30 insertions(+), 18 deletions(-) diff --git a/FAQ.md b/FAQ.md index f5c8110d..1f5daf7f 100644 --- a/FAQ.md +++ b/FAQ.md @@ -30,7 +30,7 @@ More than 100 practical examples, tutorials and articles at: - https://github.com/kataras/iris/tree/master/_examples - https://github.com/iris-contrib/examples -- https://iris-go.com/v10/recipe +- https://iris-go.com/v11/recipe - https://docs.iris-go.com (in-progress) - https://godoc.org/github.com/kataras/iris diff --git a/HISTORY.md b/HISTORY.md index 67c65700..2e985b37 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,7 +17,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you. -# Whenever | v11.0.0 +# Su, 21 October 2018 | v11.0.0 ## Breaking changes diff --git a/HISTORY_GR.md b/HISTORY_GR.md index 30f16745..98a1c416 100644 --- a/HISTORY_GR.md +++ b/HISTORY_GR.md @@ -17,9 +17,13 @@ **Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `go get -u github.com/kataras/iris` ή αφήστε το αυτόματο updater να το κάνει αυτό για σας. +# Su, 21 October 2018 | v11.0.0 + +Πατήστε [εδώ](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) για να διαβάσετε στα αγγλικά τις αλλαγές και τα νέα features που φέρνει νεότερη έκδοση του Iris, version 11. + # Sat, 11 August 2018 | v10.7.0 -Είμαι στην πραγματικά ευχάριστη θέση να σας ανακοινώσω το πρώτο στάδιο της σταθερής κυκλοφορίας της έκδοσης **10.7** του Iris Web Framework. +Είμαι στην, πραγματικά, ευχάριστη θέση να σας ανακοινώσω το πρώτο στάδιο της σταθερής κυκλοφορίας της έκδοσης **10.7** του Iris Web Framework. Η έκδοση 10.7.0 είναι μέρος των [επίσημων διανομών μας](https://github.com/kataras/iris/releases). diff --git a/HISTORY_ID.md b/HISTORY_ID.md index 12a588bc..36e30885 100644 --- a/HISTORY_ID.md +++ b/HISTORY_ID.md @@ -17,6 +17,10 @@ Developers tidak diwajibkan untuk melakukan upgrade apabila mereka tidak membutu **Cara Upgrade**: Bukan command-line anda dan eksekuis perintah ini: `go get -u github.com/kataras/iris` atau biarkan updater otomatis melakukannya untuk anda. +# Su, 21 October 2018 | v11.0.0 + +This history entry is not translated yet to the Indonesian language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) instead. + # Sat, 11 August 2018 | v10.7.0 This history entry is not translated yet to the Indonesian language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070) instead. diff --git a/HISTORY_ZH.md b/HISTORY_ZH.md index 653623f6..7ee04145 100644 --- a/HISTORY_ZH.md +++ b/HISTORY_ZH.md @@ -17,6 +17,10 @@ **如何升级**: 打开命令行执行以下命令: `go get -u github.com/kataras/iris` 或者等待自动更新。 +# Su, 21 October 2018 | v11.0.0 + +This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) instead. + # Sat, 11 August 2018 | v10.7.0 This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070) instead. diff --git a/README.md b/README.md index de91acb8..e5655639 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/v11/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris is a fast, simple yet fully featured and very efficient web framework for Go. @@ -1006,7 +1006,7 @@ Iris, unlike others, is 100% compatible with the standards and that's why the ma ## Support -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070) file is your best friend, it contains information about the latest features and changes +- [HISTORY](HISTORY.md#su-21-october-2018--v1100) file is your best friend, it contains information about the latest features and changes - Did you happen to find a bug? Post it at [github issues](https://github.com/kataras/iris/issues) - Do you have any questions or need to speak with someone experienced to solve a problem at real-time? Join us to the [community chat](https://chat.iris-go.com) - Complete our form-based user experience report by clicking [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_GR.md b/README_GR.md index 8fb8f43a..7cc0640a 100644 --- a/README_GR.md +++ b/README_GR.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Το Iris είναι ένα γρήγορο, απλό αλλά και πλήρως λειτουργικό και πολύ αποδοτικό web framework για τη Go. @@ -108,7 +108,7 @@ _Η τελευταία ενημέρωση έγινε την [Τρίτη, 21 Νο ## Υποστήριξη -- To [HISTORY](HISTORY_GR.md#sat-11-august-2018--v1070) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές +- To [HISTORY](HISTORY_GR.md#su-21-october-2018--v1100) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές - Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues) - Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com) - Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ID.md b/README_ID.md index 1839c15c..1c0f442e 100644 --- a/README_ID.md +++ b/README_ID.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris adalah web framework yang cepat, sederhana namun berfitur lengkap dan sangat efisien untuk Go. @@ -106,7 +106,7 @@ _Diperbarui pada: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## Dukungan -- File [HISTORY](HISTORY_ID.md#sat-11-august-2018--v1070) adalah sahabat anda, file tersebut memiliki informasi terkait fitur dan perubahan terbaru +- File [HISTORY](HISTORY_ID.md#su-21-october-2018--v1100) adalah sahabat anda, file tersebut memiliki informasi terkait fitur dan perubahan terbaru - Apakah anda menemukan bug? Laporkan itu melalui [github issues](https://github.com/kataras/iris/issues) - Apakah anda memiliki pertanyaan atau butuh untuk bicara kepada seseorang yang sudah berpengalaman untuk menyelesaikan masalah secara langsung? Gabung bersama kami di [community chat](https://chat.iris-go.com) - Lengkapi laporan user-experience berbasis formulir kami dengan tekan [disini](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_JPN.md b/README_JPN.md index e2da517d..ac0603be 100644 --- a/README_JPN.md +++ b/README_JPN.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Irisはシンプルで高速、それにも関わらず充実した機能を有する効率的なGo言語のウェブフレームワークです。 @@ -106,7 +106,7 @@ _Updated at: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## 支援 -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070)ファイルはあなたの友人です。このファイルには、機能に関する最新の情報や変更点が記載されています。 +- [HISTORY](HISTORY.md#su-21-october-2018--v1100)ファイルはあなたの友人です。このファイルには、機能に関する最新の情報や変更点が記載されています。 - バグを発見しましたか?[github issues](https://github.com/kataras/iris/issues)に投稿をお願い致します。 - 質問がありますか?または問題を即時に解決するため、熟練者に相談する必要がありますか?[community chat](https://chat.iris-go.com)に参加しましょう。 - [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)をクリックしてユーザーとしての体験を報告しましょう。 diff --git a/README_PT_BR.md b/README_PT_BR.md index cdaaf03c..22f0c4f2 100644 --- a/README_PT_BR.md +++ b/README_PT_BR.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris é um framework rápido, simples porém completo e muito eficiente para a linguagem Go. @@ -106,7 +106,7 @@ _Atualizado em : [Terça, 21 de Novembro de 2017](_benchmarks/README_UNIX.md)_ ## Apoie -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070) o arquivo HISTORY é o seu melhor amigo, ele contém informações sobre as últimas features e mudanças. +- [HISTORY](HISTORY.md#su-21-october-2018--v1100) o arquivo HISTORY é o seu melhor amigo, ele contém informações sobre as últimas features e mudanças. - Econtrou algum bug ? Poste-o nas [issues](https://github.com/kataras/iris/issues) - Possui alguma dúvida ou gostaria de falar com alguém experiente para resolver seu problema em tempo real ? Junte-se ao [chat da nossa comunidade](https://chat.iris-go.com). - Complete nosso formulário de experiência do usuário clicando [aqui](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_RU.md b/README_RU.md index 201e2d98..99317d9d 100644 --- a/README_RU.md +++ b/README_RU.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris - это быстрая, простая, но полнофункциональная и очень эффективная веб-платформа для Go. @@ -106,7 +106,7 @@ _Обновлено: [Вторник, 21 ноября 2017 г.](_benchmarks/READ ## Поддержка -- Файл [HISTORY](HISTORY.md#sat-11-august-2018--v1070) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях +- Файл [HISTORY](HISTORY.md#su-21-october-2018--v1100) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях - Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues) - У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com) - Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ZH.md b/README_ZH.md index 95721dd5..e6873a34 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris 是一款超快、简洁高效的 Go 语言 Web开发框架。 diff --git a/VERSION b/VERSION index 8274a13e..236610e0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -11.0.0:https://github.com/kataras/iris/blob/master/HISTORY.md#day??-dayN??-october-2018--v1100 \ No newline at end of file +11.0.0:https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v1100 \ No newline at end of file diff --git a/_examples/tutorial/vuejs-todo-mvc/README.md b/_examples/tutorial/vuejs-todo-mvc/README.md index 666f862d..b856373b 100644 --- a/_examples/tutorial/vuejs-todo-mvc/README.md +++ b/_examples/tutorial/vuejs-todo-mvc/README.md @@ -27,7 +27,7 @@ Many articles have been written, in the past, that lead developers not to use a You’ll need two dependencies: 1. Vue.js, for our client-side requirements. Download it from [here](https://vuejs.org/), latest v2. -2. The Iris Web Framework, for our server-side requirements. Can be found [here](https://github.com/kataras/iris), latest v10. +2. The Iris Web Framework, for our server-side requirements. Can be found [here](https://github.com/kataras/iris), latest v11. > If you have Go already installed then just execute `go get -u github.com/kataras/iris` to install the Iris Web Framework. From a0c8369927088fd76aee450085a42e608df97cee Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 19:01:56 +0300 Subject: [PATCH 40/42] add a HISTORY.md note about the new commits Former-commit-id: 5be77a64616dbb8b85f83fc60638d0566bf7c4ad --- HISTORY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 2e985b37..c8a79237 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -19,6 +19,8 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene # Su, 21 October 2018 | v11.0.0 +For the craziest of us, click [here](https://github.com/kataras/iris/compare/v10.7.0...v11) 🔥 to find out the commits and the code changes since our previous release. + ## Breaking changes - Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` From 8baaddc89268d1187482e88af4ea945920b9d9ac Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 19:21:23 +0300 Subject: [PATCH 41/42] fix no slash req path Former-commit-id: 3c3c00788e2481ee142b668d576cab5ff046f089 --- core/router/trie.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/router/trie.go b/core/router/trie.go index 2d11e257..9e98efbb 100644 --- a/core/router/trie.go +++ b/core/router/trie.go @@ -168,11 +168,12 @@ func (tr *trie) insert(path, routeName string, handlers context.Handlers) { func (tr *trie) search(q string, params *context.RequestParams) *trieNode { end := len(q) - n := tr.root - if end == 1 && q[0] == pathSepB { - return n.getChild(pathSep) + + if end == 0 || (end == 1 && q[0] == pathSepB) { + return tr.root.getChild(pathSep) } + n := tr.root start := 1 i := 1 var paramValues []string From e9a9f909934053981939b8693ab2a3cbc34d53bc Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 20:56:25 +0300 Subject: [PATCH 42/42] sync with kataras/muxie Former-commit-id: b414d7243e6c636731f2b0c19d35d3911bb4c951 --- core/router/trie.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/core/router/trie.go b/core/router/trie.go index 9e98efbb..b323ac06 100644 --- a/core/router/trie.go +++ b/core/router/trie.go @@ -45,7 +45,7 @@ func (tn *trieNode) hasChild(s string) bool { func (tn *trieNode) getChild(s string) *trieNode { if tn.children == nil { - tn.children = make(map[string]*trieNode) + return nil } return tn.children[s] @@ -87,6 +87,7 @@ type trie struct { // if true then it will handle any path if not other parent wildcard exists, // so even 404 (on http services) is up to it, see trie#insert. hasRootWildcard bool + hasRootSlash bool method string // subdomain is empty for default-hostname routes, @@ -117,6 +118,10 @@ func (tr *trie) insert(path, routeName string, handlers context.Handlers) { input := slowPathSplit(path) n := tr.root + if path == pathSep { + tr.hasRootSlash = true + } + var paramKeys []string for _, s := range input { @@ -170,7 +175,15 @@ func (tr *trie) search(q string, params *context.RequestParams) *trieNode { end := len(q) if end == 0 || (end == 1 && q[0] == pathSepB) { - return tr.root.getChild(pathSep) + // fixes only root wildcard but no / registered at. + if tr.hasRootSlash { + return tr.root.getChild(pathSep) + } else if tr.hasRootWildcard { + // no need to going through setting parameters, this one has not but it is wildcard. + return tr.root.getChild(WildcardParamStart) + } + + return nil } n := tr.root