Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 41 additions & 10 deletions jester.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export request
export strtabs
export tables
export httpcore
export options
export MultiData
export HttpMethod
export asyncdispatch
Expand Down Expand Up @@ -42,6 +43,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 @@ -451,18 +460,20 @@ proc initJester*(
result.errorHandlers = @[]

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

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

proc serve*(
self: var Jester
Expand Down Expand Up @@ -538,7 +549,7 @@ template resp*(code: HttpCode,
content: string): typed =
## Sets ``(code, headers, content)`` as the response.
bind TCActionSend
result = (TCActionSend, code, none[RawHeaders](), content, true)
result = (TCActionSend, code, result[2], content, true)
for header in headers:
setHeader(result[2], header[0], header[1])
break route
Expand Down Expand Up @@ -725,6 +736,8 @@ template uri*(address = "", absolute = true, addScriptName = true): untyped =

template responseHeaders*(): var ResponseHeaders =
## Access the Option[RawHeaders] response headers
if result[2].isNone:
result[2] = some[RawHeaders](@[])
result[2]

proc daysForward*(days: int): DateTime =
Expand Down Expand Up @@ -1303,7 +1316,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 @@ -1363,6 +1376,26 @@ proc routesEx(name: string, body: NimNode): NimNode =
errorHandlerProc[6][0][1][^1][2][1][0] = stmts
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 @@ -1373,13 +1406,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
21 changes: 21 additions & 0 deletions tests/alltest.nim
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ routes:

get "/halt":
resp "<h1>Not halted!</h1>"

before re"/halt-before/.*?":
halt Http502, "Halted!"

get "/halt-before/@something":
resp "Should never reach this"

before re"/halt-before/.*?":
halt Http502, "Halted!"
Expand All @@ -61,6 +67,16 @@ routes:

get "/redirect/@url/?":
redirect(uri(@"url"))

get "/redirect-halt/@url/?":
redirect(uri(@"url"))
resp "ok"

before re"/redirect-before/.*?":
redirect(uri("/nowhere"))

get "/redirect-before/@url/?":
resp "should not get here"

get "/redirect-halt/@url/?":
redirect(uri(@"url"))
Expand Down Expand Up @@ -223,3 +239,8 @@ routes:

get "/issue157":
resp(Http200, [("Content-Type","text/css")] , "foo")

get "/manyheaders":
setHeader(responseHeaders, "foo", "foo")
setHeader(responseHeaders, "bar", "bar")
resp Http200, {"Content-Type": "text/plain"}, "result"
20 changes: 20 additions & 0 deletions tests/customRouter.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import jester

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

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

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

when isMainModule:
let s = newSettings(
Port(5454),
bindAddr="127.0.0.1",
)
var jest = initJester(myrouter, s)
# jest.register(myrouterErrorHandler)
jest.serve()
37 changes: 37 additions & 0 deletions tests/tester.nim
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ proc allTest(useStdLib: bool) =
let resp = waitFor client.get(address & "/foo/halt")
check resp.status.startsWith("502")
check (waitFor resp.body) == "I'm sorry, this page has been halted."

test "/halt-before":
let resp = waitFor client.request(address & "/foo/halt-before/something", HttpGet)
let body = waitFor resp.body
check body == "Halted!"

test "/halt-before":
let resp = waitFor client.request(address & "/foo/halt-before/something", HttpGet)
Expand All @@ -109,6 +114,17 @@ proc allTest(useStdLib: bool) =
test "/redirect":
let resp = waitFor client.request(address & "/foo/redirect/halt", HttpGet)
check resp.headers["location"] == "http://localhost:5454/foo/halt"

test "/redirect-halt":
let resp = waitFor client.request(address & "/foo/redirect-halt/halt", HttpGet)
check resp.headers["location"] == "http://localhost:5454/foo/halt"
check (waitFor resp.body) == ""

test "/redirect-before":
let resp = waitFor client.request(address & "/foo/redirect-before/anywhere", HttpGet)
check resp.headers["location"] == "http://localhost:5454/foo/nowhere"
let body = waitFor resp.body
check body == ""

test "/redirect-halt":
let resp = waitFor client.request(address & "/foo/redirect-halt/halt", HttpGet)
Expand Down Expand Up @@ -154,6 +170,13 @@ proc allTest(useStdLib: bool) =
let resp = waitFor client.get(address & "/foo/issue157")
let headers = resp.headers
check headers["Content-Type"] == "text/css"

test "resp doesn't overwrite headers":
let resp = waitFor client.get(address & "/foo/manyheaders")
let headers = resp.headers
check headers["foo"] == "foo"
check headers["bar"] == "bar"
check headers["Content-Type"] == "text/plain"

suite "static":
test "index.html":
Expand Down Expand Up @@ -253,12 +276,26 @@ 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")

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