From a8740fb8a3a289b951b54a7aa0be6f2209a2e278 Mon Sep 17 00:00:00 2001 From: Ethosa Date: Thu, 19 Dec 2024 01:06:59 +0700 Subject: [PATCH 1/4] fix(decorators): route decorators usage over mounts (also for #383) --- src/happyx/routing/mounting.nim | 11 +- tests/testc16.nim | 205 ++++++++++++++++---------------- 2 files changed, 110 insertions(+), 106 deletions(-) diff --git a/src/happyx/routing/mounting.nim b/src/happyx/routing/mounting.nim index 3bd2a3f6..9b1654d4 100644 --- a/src/happyx/routing/mounting.nim +++ b/src/happyx/routing/mounting.nim @@ -68,6 +68,8 @@ proc findAndReplaceMount*(body: NimNode) = var mountBody = copy(registeredMounts[$name]) mountBody.findAndReplaceMount() + echo mountBody.toStrLit + var decoratorsOffset = 0 for statement in mountBody: @@ -77,11 +79,6 @@ proc findAndReplaceMount*(body: NimNode) = statement[0] = newLit($route & $statement[0]) elif statement[1].kind in [nnkStrLit, nnkTripleStrLit]: statement[1] = newLit($route & $statement[1]) - # Add mount decorators - for decorator in nextRouteDecorators: - body.insert(i, decorator) - inc offset - echo statement.treeRepr # Add mount routes # @Decorator if statement.kind == nnkPrefix and statement[0] == ident"@": @@ -95,6 +92,10 @@ proc findAndReplaceMount*(body: NimNode) = inc decoratorsOffset # get / post / etc. elif statement.kind in [nnkCall, nnkCommand] and statement[0] != ident"mount": + # Add mount decorators + for decorator in nextRouteDecorators: + body.insert(i+decoratorsOffset, decorator) + inc offset body.insert(i+decoratorsOffset, statement) inc offset decoratorsOffset = 0 diff --git a/tests/testc16.nim b/tests/testc16.nim index e92e83d6..e71ce7a9 100644 --- a/tests/testc16.nim +++ b/tests/testc16.nim @@ -34,6 +34,8 @@ model Generics{JSON}[T]: mount Profile: + @AuthJWT + @RateLimit get "/": ## Profile main page return "Hello, world!" @@ -48,7 +50,6 @@ mount Profile: # echo rateLimits[key] return "Hello, world" - @Cached(10) get "/cached-test": ## Profile settings return "Hello, world" @@ -59,109 +60,111 @@ mount Profile: serve "127.0.0.1", 5000: + @Cached(10) mount "/profile" -> Profile - "/some": - ## Hello, world - return "Hi" - get "/calculate/$left:float/$operator:string/$right?:float": - ## Some - echo left - echo right - return fmt"{left + right}" - get "/auth[model:TestModel:json]": - ## User authorization - return "Hello, world!" - get "/arrQuery": - ## Parses array and simple queries - return { - "arr": queryArr?a, - "val": query?b - } - ws "/Hello": - discard - get "/": - ## Set bestFramework to **"HappyX!"** in cookies - ## - ## ```nim - ## echo "Hello, world!" - ## ``` - ## - ## Responds "Hello, world!" - echo inCookies - outCookies.add(setCookie("bestFramework", "HappyX!", secure = true, httpOnly = true)) - return "Hello, world!" - get "/setStatusCode": - ## Responds "Hello, world!" with 404 HttpCode - statusCode = 404 - if true: - if true: - for i in 0..1: - if true: - when true: - statusCode = 400 - return 1 - return "Hello, world!" + + # "/some": + # ## Hello, world + # return "Hi" + # get "/calculate/$left:float/$operator:string/$right?:float": + # ## Some + # echo left + # echo right + # return fmt"{left + right}" + # get "/auth[model:TestModel:json]": + # ## User authorization + # return "Hello, world!" + # get "/arrQuery": + # ## Parses array and simple queries + # return { + # "arr": queryArr?a, + # "val": query?b + # } + # ws "/Hello": + # discard + # get "/": + # ## Set bestFramework to **"HappyX!"** in cookies + # ## + # ## ```nim + # ## echo "Hello, world!" + # ## ``` + # ## + # ## Responds "Hello, world!" + # echo inCookies + # outCookies.add(setCookie("bestFramework", "HappyX!", secure = true, httpOnly = true)) + # return "Hello, world!" + # get "/setStatusCode": + # ## Responds "Hello, world!" with 404 HttpCode + # statusCode = 404 + # if true: + # if true: + # for i in 0..1: + # if true: + # when true: + # statusCode = 400 + # return 1 + # return "Hello, world!" - get "/headers": - outHeaders["Test"] = 10 - outHeaders["HappyXHeader"] = "Hello" - return "Hello, world!" + # get "/headers": + # outHeaders["Test"] = 10 + # outHeaders["HappyXHeader"] = "Hello" + # return "Hello, world!" - # default values is perSecond=60, fromAll=false - @RateLimit(perSecond = 2, fromAll = true) - get "/test/rate-limit": - outHeaders["Test"] = 10 - outHeaders["HappyXHeader"] = "Hello" - return "Hello, world!" - - post "/post": - ## Creates a new post - return "Hello, world!" - - put "/post/$id:int": - ## Edits a post - return "Hello, world!" + # # default values is perSecond=60, fromAll=false + # @RateLimit(perSecond = 2, fromAll = true) + # get "/test/rate-limit": + # outHeaders["Test"] = 10 + # outHeaders["HappyXHeader"] = "Hello" + # return "Hello, world!" + + # post "/post": + # ## Creates a new post + # return "Hello, world!" + + # put "/post/$id:int": + # ## Edits a post + # return "Hello, world!" - @Cached # Expires in 60 seconds by default - get "/cached/{i:int}": - await sleepAsync(1000) - if true: - if (query?test) == "hello": - return 100 - echo query?one - return i + # @Cached # Expires in 60 seconds by default + # get "/cached/{i:int}": + # await sleepAsync(1000) + # if true: + # if (query?test) == "hello": + # return 100 + # echo query?one + # return i - @Cached(120) # Expires in 60 seconds by default - get "/cached/{x}": - await sleepAsync(1000) - if hasKey(query, "key"): - return query["key"] - await sleepAsync(1000) - return x + # @Cached(120) # Expires in 60 seconds by default + # get "/cached/{x}": + # await sleepAsync(1000) + # if hasKey(query, "key"): + # return query["key"] + # await sleepAsync(1000) + # return x - @Cached(expires = 120) # Expires in 60 seconds by default - post "/cached/[m:TestModel]": - await sleepAsync(1000) - return m.username - - @AuthBasic - post "/test/basic-auth": - echo username # from @AuthBasic - echo password # from @AuthBasic - return "Hello, {username}!" - - # You should install jwt library (https://github.com/yglukhov/nim-jwt) - # to use these decorators - # Authorization: JWT_TOKEN - @AuthJWT(token) - post "/test/jwt": - if token.hasKey("name"): - return "Hello, " & token["name"].node.str - return "who are you???" - - # Authorization: Bearer JWT_TOKEN - @AuthBearerJWT(token) - post "/test/jwt-bearer": - if token.hasKey("name"): - return "Hello, " & token["name"].node.str - return "who are you???" + # @Cached(expires = 120) # Expires in 60 seconds by default + # post "/cached/[m:TestModel]": + # await sleepAsync(1000) + # return m.username + + # @AuthBasic + # post "/test/basic-auth": + # echo username # from @AuthBasic + # echo password # from @AuthBasic + # return "Hello, {username}!" + + # # You should install jwt library (https://github.com/yglukhov/nim-jwt) + # # to use these decorators + # # Authorization: JWT_TOKEN + # @AuthJWT(token) + # post "/test/jwt": + # if token.hasKey("name"): + # return "Hello, " & token["name"].node.str + # return "who are you???" + + # # Authorization: Bearer JWT_TOKEN + # @AuthBearerJWT(token) + # post "/test/jwt-bearer": + # if token.hasKey("name"): + # return "Hello, " & token["name"].node.str + # return "who are you???" From e2995bc415102c25a16e761545703e46f9343e55 Mon Sep 17 00:00:00 2001 From: Ethosa Date: Thu, 19 Dec 2024 01:08:14 +0700 Subject: [PATCH 2/4] tests: little refactor for testc16.nim --- tests/testc16.nim | 200 +++++++++++++++++++++++----------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/tests/testc16.nim b/tests/testc16.nim index e71ce7a9..8645e4a2 100644 --- a/tests/testc16.nim +++ b/tests/testc16.nim @@ -63,108 +63,108 @@ serve "127.0.0.1", 5000: @Cached(10) mount "/profile" -> Profile - # "/some": - # ## Hello, world - # return "Hi" - # get "/calculate/$left:float/$operator:string/$right?:float": - # ## Some - # echo left - # echo right - # return fmt"{left + right}" - # get "/auth[model:TestModel:json]": - # ## User authorization - # return "Hello, world!" - # get "/arrQuery": - # ## Parses array and simple queries - # return { - # "arr": queryArr?a, - # "val": query?b - # } - # ws "/Hello": - # discard - # get "/": - # ## Set bestFramework to **"HappyX!"** in cookies - # ## - # ## ```nim - # ## echo "Hello, world!" - # ## ``` - # ## - # ## Responds "Hello, world!" - # echo inCookies - # outCookies.add(setCookie("bestFramework", "HappyX!", secure = true, httpOnly = true)) - # return "Hello, world!" - # get "/setStatusCode": - # ## Responds "Hello, world!" with 404 HttpCode - # statusCode = 404 - # if true: - # if true: - # for i in 0..1: - # if true: - # when true: - # statusCode = 400 - # return 1 - # return "Hello, world!" + "/some": + ## Hello, world + return "Hi" + get "/calculate/$left:float/$operator:string/$right?:float": + ## Some + echo left + echo right + return fmt"{left + right}" + get "/auth[model:TestModel:json]": + ## User authorization + return "Hello, world!" + get "/arrQuery": + ## Parses array and simple queries + return { + "arr": queryArr?a, + "val": query?b + } + ws "/Hello": + discard + get "/": + ## Set bestFramework to **"HappyX!"** in cookies + ## + ## ```nim + ## echo "Hello, world!" + ## ``` + ## + ## Responds "Hello, world!" + echo inCookies + outCookies.add(setCookie("bestFramework", "HappyX!", secure = true, httpOnly = true)) + return "Hello, world!" + get "/setStatusCode": + ## Responds "Hello, world!" with 404 HttpCode + statusCode = 404 + if true: + if true: + for i in 0..1: + if true: + when true: + statusCode = 400 + return 1 + return "Hello, world!" - # get "/headers": - # outHeaders["Test"] = 10 - # outHeaders["HappyXHeader"] = "Hello" - # return "Hello, world!" + get "/headers": + outHeaders["Test"] = 10 + outHeaders["HappyXHeader"] = "Hello" + return "Hello, world!" - # # default values is perSecond=60, fromAll=false - # @RateLimit(perSecond = 2, fromAll = true) - # get "/test/rate-limit": - # outHeaders["Test"] = 10 - # outHeaders["HappyXHeader"] = "Hello" - # return "Hello, world!" - - # post "/post": - # ## Creates a new post - # return "Hello, world!" - - # put "/post/$id:int": - # ## Edits a post - # return "Hello, world!" + # default values is perSecond=60, fromAll=false + @RateLimit(perSecond = 2, fromAll = true) + get "/test/rate-limit": + outHeaders["Test"] = 10 + outHeaders["HappyXHeader"] = "Hello" + return "Hello, world!" + + post "/post": + ## Creates a new post + return "Hello, world!" + + put "/post/$id:int": + ## Edits a post + return "Hello, world!" - # @Cached # Expires in 60 seconds by default - # get "/cached/{i:int}": - # await sleepAsync(1000) - # if true: - # if (query?test) == "hello": - # return 100 - # echo query?one - # return i + @Cached # Expires in 60 seconds by default + get "/cached/{i:int}": + await sleepAsync(1000) + if true: + if (query?test) == "hello": + return 100 + echo query?one + return i - # @Cached(120) # Expires in 60 seconds by default - # get "/cached/{x}": - # await sleepAsync(1000) - # if hasKey(query, "key"): - # return query["key"] - # await sleepAsync(1000) - # return x + @Cached(120) # Expires in 60 seconds by default + get "/cached/{x}": + await sleepAsync(1000) + if hasKey(query, "key"): + return query["key"] + await sleepAsync(1000) + return x - # @Cached(expires = 120) # Expires in 60 seconds by default - # post "/cached/[m:TestModel]": - # await sleepAsync(1000) - # return m.username - - # @AuthBasic - # post "/test/basic-auth": - # echo username # from @AuthBasic - # echo password # from @AuthBasic - # return "Hello, {username}!" - - # # You should install jwt library (https://github.com/yglukhov/nim-jwt) - # # to use these decorators - # # Authorization: JWT_TOKEN - # @AuthJWT(token) - # post "/test/jwt": - # if token.hasKey("name"): - # return "Hello, " & token["name"].node.str - # return "who are you???" - - # # Authorization: Bearer JWT_TOKEN - # @AuthBearerJWT(token) - # post "/test/jwt-bearer": - # if token.hasKey("name"): - # return "Hello, " & token["name"].node.str - # return "who are you???" + @Cached(expires = 120) # Expires in 60 seconds by default + post "/cached/[m:TestModel]": + await sleepAsync(1000) + return m.username + + @AuthBasic + post "/test/basic-auth": + echo username # from @AuthBasic + echo password # from @AuthBasic + return "Hello, {username}!" + + # You should install jwt library (https://github.com/yglukhov/nim-jwt) + # to use these decorators + # Authorization: JWT_TOKEN + @AuthJWT(token) + post "/test/jwt": + if token.hasKey("name"): + return "Hello, " & token["name"].node.str + return "who are you???" + + # Authorization: Bearer JWT_TOKEN + @AuthBearerJWT(token) + post "/test/jwt-bearer": + if token.hasKey("name"): + return "Hello, " & token["name"].node.str + return "who are you???" From a5e398420dce5f60d53e7b99ecaeb5cd3a7c55f6 Mon Sep 17 00:00:00 2001 From: Ethosa Date: Thu, 19 Dec 2024 10:23:30 +0700 Subject: [PATCH 3/4] fix: solve #383 --- src/happyx/routing/decorators.nim | 16 ++++++++-------- src/happyx/ssr/server.nim | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/happyx/routing/decorators.nim b/src/happyx/routing/decorators.nim index 69d9a5cf..afdeda94 100644 --- a/src/happyx/routing/decorators.nim +++ b/src/happyx/routing/decorators.nim @@ -54,12 +54,12 @@ type arguments: seq[NimNode] ) CachedResult* = object - data*: string - headers*: HttpHeaders - statusCode*: HttpCode + cachedData*: string + cachedHeaders*: HttpHeaders + cachedStatusCode*: HttpCode CachedRoute* = object create_at*: float - res*: CachedResult + cachedResult*: CachedResult RateLimitInfo* = object amount*: int update_at*: float @@ -251,7 +251,7 @@ if rateLimits[key].amount > {perSecond}: usedVariables.add i[2] let cachedRoutesResult = newNimNode(nnkDotExpr).add( - newNimNode(nnkBracketExpr).add(ident"cachedRoutes", ident"routeKey"), ident"res" + newNimNode(nnkBracketExpr).add(ident"cachedRoutes", ident"routeKey"), ident"cachedResult" ) let cachedRoutesCreateAt = newNimNode(nnkDotExpr).add( newNimNode(nnkBracketExpr).add(ident"cachedRoutes", ident"routeKey"), ident"create_at" @@ -270,9 +270,9 @@ if rateLimits[key].amount > {perSecond}: newCall( "answer", ident"req", - newNimNode(nnkDotExpr).add(cachedRoutesResult, ident"data"), - newNimNode(nnkDotExpr).add(cachedRoutesResult, ident"statusCode"), - newNimNode(nnkDotExpr).add(cachedRoutesResult, ident"headers"), + newNimNode(nnkDotExpr).add(cachedRoutesResult, ident"cachedData"), + newNimNode(nnkDotExpr).add(cachedRoutesResult, ident"cachedStatusCode"), + newNimNode(nnkDotExpr).add(cachedRoutesResult, ident"cachedHeaders"), ), newNimNode(nnkBreakStmt).add(ident"__handleRequestBlock") ) diff --git a/src/happyx/ssr/server.nim b/src/happyx/ssr/server.nim index 50cddb8c..fbe10687 100755 --- a/src/happyx/ssr/server.nim +++ b/src/happyx/ssr/server.nim @@ -294,14 +294,14 @@ template answer*( when declared(thisRouteCanBeCached) and declared(routeKey) and not declared(thisIsCachedResponse): cachedRoutes[routeKey] = CachedRoute(create_at: cpuTime()) when message is string: - cachedRoutes[routeKey].res = CachedResult(data: message) + cachedRoutes[routeKey].cachedResult = CachedResult(cachedData: message) else: - cachedRoutes[routeKey].res = CachedResult(data: $message) - cachedRoutes[routeKey].res.statusCode = code + cachedRoutes[routeKey].cachedResult = CachedResult(cachedData: $message) + cachedRoutes[routeKey].cachedResult.cachedStatusCode = code when useHeaders: - cachedRoutes[routeKey].res.headers = h + cachedRoutes[routeKey].cachedResult.cachedHeaders = h else: - cachedRoutes[routeKey].res.headers = newHttpHeaders([ + cachedRoutes[routeKey].cachedResult.cachedHeaders = newHttpHeaders([ ("Content-Type", "text/plain;charset=utf-8") ]) @@ -420,11 +420,11 @@ template answer*( when declared(thisRouteCanBeCached) and declared(routeKey) and not declared(thisIsCachedResponse): cachedRoutes[routeKey] = CachedRoute(create_at: cpuTime()) when message is string: - cachedRoutes[routeKey].res = CachedResult(data: message) + cachedRoutes[routeKey].cachedResult = CachedResult(cachedData: message) else: - cachedRoutes[routeKey].res = CachedResult(data: $message) - cachedRoutes[routeKey].res.statusCode = code - cachedRoutes[routeKey].res.headers = h + cachedRoutes[routeKey].cachedResult = CachedResult(cachedData: $message) + cachedRoutes[routeKey].cachedResult.cachedStatusCode = code + cachedRoutes[routeKey].cachedResult.cachedHeaders = h # HTTPX when enableHttpx or enableBuiltin: From 28dc3c82196752baf38ad856fbcdb903c216adc1 Mon Sep 17 00:00:00 2001 From: Ethosa Date: Thu, 19 Dec 2024 10:25:35 +0700 Subject: [PATCH 4/4] chore: update version --- happyx.nimble | 2 +- src/happyx/core/constants.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/happyx.nimble b/happyx.nimble index 463bfde8..2e9fe3c4 100755 --- a/happyx.nimble +++ b/happyx.nimble @@ -2,7 +2,7 @@ description = "Macro-oriented asynchronous web-framework written with ♥" author = "HapticX" -version = "4.7.2" +version = "4.7.3" license = "MIT" srcDir = "src" installExt = @["nim"] diff --git a/src/happyx/core/constants.nim b/src/happyx/core/constants.nim index 939ba24b..28b9122b 100755 --- a/src/happyx/core/constants.nim +++ b/src/happyx/core/constants.nim @@ -110,7 +110,7 @@ const # Framework version HpxMajor* = 4 HpxMinor* = 7 - HpxPatch* = 2 + HpxPatch* = 3 HpxVersion* = $HpxMajor & "." & $HpxMinor & "." & $HpxPatch