Skip to content

Commit

Permalink
Fix error handler redirecting (#275)
Browse files Browse the repository at this point in the history
* Fixes crash described in #296.

* Fix #211 - custom routers now get the same default error handling as normal routers

* Add changelog entry

* Fix #269 - you can now redirect within error handlers again

* Preserve backward compatibility in `initJester`

* Restore `initJester` proc for MatchProcSync matchers

Co-authored-by: Dominik Picheta <[email protected]>
  • Loading branch information
iffy and dom96 authored Jun 13, 2022
1 parent b00cdb5 commit d2210c6
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 25 deletions.
2 changes: 2 additions & 0 deletions changelog.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 0.6.0 - ??/07/2021

- **Breaking change:** the `@` operator used for retrieving request parameters now automatically decodes special characters using `decodeUrl`.
- Fix for [#211](https://github.com/dom96/jester/issues/211) - custom routers now have the same error handling as normal routes.
- Fix for [#269](https://github.com/dom96/jester/issues/269) - a bug that prevented redirecting from within error handlers.

## 0.5.0 - 17/10/2020

Expand Down
90 changes: 65 additions & 25 deletions jester.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ type
request: Request, error: RouteError
): Future[ResponseData] {.gcsafe, closure.}

MatchPair* = tuple
matcher: MatchProc
errorHandler: ErrorProc

MatchPairSync* = tuple
matcher: MatchProcSync
errorHandler: ErrorProc

Jester* = object
when not useHttpBeast:
httpServer*: AsyncHttpServer
Expand Down Expand Up @@ -453,6 +461,22 @@ proc initJester*(
result.matchers = @[]
result.errorHandlers = @[]

proc initJester*(
pair: MatchPair,
settings: Settings = newSettings()
): Jester =
result = initJester(settings)
result.register(pair.matcher)
result.register(pair.errorHandler)

proc initJester*(
pair: MatchPairSync, # TODO: Annoying nim bug: `MatchPair | MatchPairSync` doesn't work.
settings: Settings = newSettings()
): Jester =
result = initJester(settings)
result.register(pair.matcher)
result.register(pair.errorHandler)

proc initJester*(
matcher: MatchProc,
settings: Settings = newSettings()
Expand All @@ -461,7 +485,7 @@ proc initJester*(
result.register(matcher)

proc initJester*(
matcher: MatchProcSync, # TODO: Annoying nim bug: `MatchProc | MatchProcSync` doesn't work.
matcher: MatchProcSync,
settings: Settings = newSettings()
): Jester =
result = initJester(settings)
Expand Down Expand Up @@ -1306,7 +1330,7 @@ proc routesEx(name: string, body: NimNode): NimNode =
`afterRoutes`
)

let matchIdent = newIdentNode(name)
let matchIdent = newIdentNode(name & "Matcher")
let reqIdent = newIdentNode("request")
let needsAsync = needsAsync(body)
case needsAsync
Expand Down Expand Up @@ -1341,31 +1365,49 @@ proc routesEx(name: string, body: NimNode): NimNode =
# Error handler proc
let errorHandlerIdent = newIdentNode(name & "ErrorHandler")
let errorIdent = newIdentNode("error")
let exceptionIdent = newIdentNode("exception")
let resultIdent = newIdentNode("result")
var errorHandlerProc = quote do:
proc `errorHandlerIdent`(
`reqIdent`: Request, `errorIdent`: RouteError
): Future[ResponseData] {.gcsafe, async.} =
block `routesListIdent`:
`setDefaultRespIdent`()
case `errorIdent`.kind
of RouteException:
discard
of RouteCode:
discard
let allRoutesIdent = ident("allRoutes")
var exceptionStmts = newStmtList()
if exceptionBranches.len != 0:
var stmts = newStmtList()
for branch in exceptionBranches:
stmts.add(newIfStmt(branch))
errorHandlerProc[6][0][1][^1][1][1][0] = stmts
exceptionStmts.add(newIfStmt(branch))
var codeStmts = newStmtList()
if httpCodeBranches.len != 0:
var stmts = newStmtList()
for branch in httpCodeBranches:
stmts.add(newIfStmt(branch))
errorHandlerProc[6][0][1][^1][2][1][0] = stmts
codeStmts.add(newIfStmt(branch))
var errorHandlerProc = quote do:
proc `errorHandlerIdent`(
`reqIdent`: Request, `errorIdent`: RouteError
): Future[ResponseData] {.gcsafe, async.} =
block `allRoutesIdent`:
block `routesListIdent`:
`setDefaultRespIdent`()
case `errorIdent`.kind
of RouteException:
`exceptionStmts`
of RouteCode:
`codeStmts`
result.add(errorHandlerProc)

# Pair the matcher and error matcher
let pairIdent = newIdentNode(name)
let matchProcVarIdent = newIdentNode(name & "MatchProc")
let errorProcVarIdent = newIdentNode(name & "ErrorProc")
if needsAsync in {ImplicitTrue, ExplicitTrue}:
# TODO: I don't understand why I have to assign these procs to intermediate
# variables in order to get them into the tuple. It would be nice if it could
# just be:
# let `pairIdent`: MatchPair = (`matchIdent`, `errorHandlerIdent`)
result.add quote do:
let `matchProcVarIdent`: MatchProc = `matchIdent`
let `errorProcVarIdent`: ErrorProc = `errorHandlerIdent`
let `pairIdent`: MatchPair = (`matchProcVarIdent`, `errorProcVarIdent`)
else:
result.add quote do:
let `matchProcVarIdent`: MatchProcSync = `matchIdent`
let `errorProcVarIdent`: ErrorProc = `errorHandlerIdent`
let `pairIdent`: MatchPairSync = (`matchProcVarIdent`, `errorProcVarIdent`)


# TODO: Replace `body`, `headers`, `code` in routes with `result[i]` to
# get these shortcuts back without sacrificing usability.
# TODO2: Make sure you replace what `guessAction` used to do for this.
Expand All @@ -1376,13 +1418,11 @@ proc routesEx(name: string, body: NimNode): NimNode =
macro routes*(body: untyped) =
result = routesEx("match", body)
let jesIdent = genSym(nskVar, "jes")
let matchIdent = newIdentNode("match")
let errorHandlerIdent = newIdentNode("matchErrorHandler")
let pairIdent = newIdentNode("match")
let settingsIdent = newIdentNode("settings")
result.add(
quote do:
var `jesIdent` = initJester(`matchIdent`, `settingsIdent`)
`jesIdent`.register(`errorHandlerIdent`)
var `jesIdent` = initJester(`pairIdent`, `settingsIdent`)
)
result.add(
quote do:
Expand Down
25 changes: 25 additions & 0 deletions tests/customRouter.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import jester

router myrouter:
get "/":
resp "Hello world"

get "/404":
resp "you got 404"

get "/raise":
raise newException(Exception, "Foobar")

error Exception:
resp Http500, "Something bad happened: " & exception.msg

error Http404:
redirect uri("/404")

when isMainModule:
let s = newSettings(
Port(5454),
bindAddr="127.0.0.1",
)
var jest = initJester(myrouter, s)
jest.serve()
20 changes: 20 additions & 0 deletions tests/tester.nim
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,32 @@ proc issue150(useStdLib: bool) =
check resp.code == Http500
check (waitFor resp.body).startsWith("Something bad happened")

proc customRouterTest(useStdLib: bool) =
waitFor startServer("customRouter.nim", useStdLib)
var client = newAsyncHttpClient(maxRedirects = 0)

suite "customRouter useStdLib=" & $useStdLib:
test "error handler":
let resp = waitFor client.get(address & "/raise")
check resp.code == Http500
let body = (waitFor resp.body)
checkpoint body
check body.startsWith("Something bad happened: Foobar")

test "redirect in error":
let resp = waitFor client.get(address & "/definitely404route")
check resp.code == Http303
check resp.headers["location"] == address & "/404"
check (waitFor resp.body) == ""

when isMainModule:
try:
allTest(useStdLib=false) # Test HttpBeast.
allTest(useStdLib=true) # Test asynchttpserver.
issue150(useStdLib=false)
issue150(useStdLib=true)
customRouterTest(useStdLib=false)
customRouterTest(useStdLib=true)

# Verify that Nim in Action Tweeter still compiles.
test "Nim in Action - Tweeter":
Expand Down

0 comments on commit d2210c6

Please sign in to comment.