Add router.rawApi() macro and tests. (#17)

This commit is contained in:
Eugene Kabanov 2021-11-29 17:14:10 +02:00 committed by GitHub
parent 5a828c3ef1
commit c41bc8aefc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 67 additions and 24 deletions

View File

@ -23,10 +23,14 @@ type
RestRouteKind* {.pure.} = enum
None, Handler, Redirect
RestRouterFlag* {.pure.} = enum
Raw
RestRoute* = object
requestPath*: SegmentedPath
routePath*: SegmentedPath
callback*: RestApiCallback
flags*: set[RestRouterFlag]
RestRouteItem* = object
case kind*: RestRouteKind
@ -37,6 +41,7 @@ type
of RestRouteKind.Redirect:
redirectPath*: SegmentedPath
path: SegmentedPath
flags*: set[RestRouterFlag]
RestRouter* = object
patternCallback*: PatternCallback
@ -50,18 +55,23 @@ proc init*(t: typedesc[RestRouter],
routes: initBTree[SegmentedPath, RestRouteItem]())
proc addRoute*(rr: var RestRouter, request: HttpMethod, path: string,
handler: RestApiCallback) {.raises: [Defect].} =
flags: set[RestRouterFlag], handler: RestApiCallback) {.
raises: [Defect].} =
let spath = SegmentedPath.init(request, path, rr.patternCallback)
let route = rr.routes.getOrDefault(spath,
RestRouteItem(kind: RestRouteKind.None))
case route.kind
of RestRouteKind.None:
let item = RestRouteItem(kind: RestRouteKind.Handler,
path: spath, callback: handler)
path: spath, flags: flags, callback: handler)
rr.routes.add(spath, item)
else:
raiseAssert("The route is already in the routing table")
proc addRoute*(rr: var RestRouter, request: HttpMethod, path: string,
handler: RestApiCallback) {.raises: [Defect].} =
addRoute(rr, request, path, {}, handler)
proc addRedirect*(rr: var RestRouter, request: HttpMethod, srcPath: string,
dstPath: string) {.raises: [Defect].} =
let spath = SegmentedPath.init(request, srcPath, rr.patternCallback)
@ -88,7 +98,7 @@ proc getRoute*(rr: RestRouter,
of RestRouteKind.Handler:
# Route handler was found
let item = RestRoute(requestPath: path, routePath: route.path,
callback: route.callback)
flags: route.flags, callback: route.callback)
return some(item)
of RestRouteKind.Redirect:
# Route redirection was found, so we perform path transformation
@ -149,8 +159,9 @@ macro redirect*(router: RestRouter, meth: static[HttpMethod],
echo "\n", fromPath, ": ", repr(res)
return res
macro api*(router: RestRouter, meth: static[HttpMethod],
path: static[string], body: untyped): untyped =
proc processApiCall(router: NimNode, meth: HttpMethod,
path: string, flags: set[RestRouterFlag],
body: NimNode): NimNode {.compileTime.} =
## Define REST API endpoint and implementation.
## Input and return parameters are defined using the ``do`` notation.
## For example:
@ -337,8 +348,16 @@ macro api*(router: RestRouter, meth: static[HttpMethod],
block:
`procBody`
`router`.addRoute(`methIdent`, `path`, `doMain`)
`router`.addRoute(`methIdent`, `path`, `flags`, `doMain`)
when defined(nimDumpRest):
echo "\n", path, ": ", repr(res)
return res
macro api*(router: RestRouter, meth: static[HttpMethod],
path: static[string], body: untyped): untyped =
return processApiCall(router, meth, path, {}, body)
macro rawApi*(router: RestRouter, meth: static[HttpMethod],
path: static[string], body: untyped): untyped =
return processApiCall(router, meth, path, {RestRouterFlag.Raw}, body)

View File

@ -44,24 +44,27 @@ proc processRestRequest*[T](server: T,
let queryParams = request.query
let optBody =
try:
await request.getContentBody()
except HttpCriticalError as exc:
debug "Unable to obtain request body", uri = $request.uri,
peer = $request.remoteAddress(), meth = $request.meth,
error_msg = $exc.msg
return await request.respond(Http400)
except RestBadRequestError as exc:
debug "Request has incorrect content type", uri = $request.uri,
peer = $request.remoteAddress(), meth = $request.meth,
error_msg = $exc.msg
return await request.respond(Http400)
except CatchableError as exc:
warn "Unexpected exception while getting request body",
uri = $request.uri, peer = $request.remoteAddress(),
meth = $request.meth, error_name = $exc.name,
error_msg = $exc.msg
return await request.respond(Http400)
if RestRouterFlag.Raw notin route.flags:
try:
await request.getContentBody()
except HttpCriticalError as exc:
debug "Unable to obtain request body", uri = $request.uri,
peer = $request.remoteAddress(), meth = $request.meth,
error_msg = $exc.msg
return await request.respond(Http400)
except RestBadRequestError as exc:
debug "Request has incorrect content type", uri = $request.uri,
peer = $request.remoteAddress(), meth = $request.meth,
error_msg = $exc.msg
return await request.respond(Http400)
except CatchableError as exc:
warn "Unexpected exception while getting request body",
uri = $request.uri, peer = $request.remoteAddress(),
meth = $request.meth, error_name = $exc.name,
error_msg = $exc.msg
return await request.respond(Http400)
else:
none[ContentBody]()
debug "Serving API request", peer = $request.remoteAddress(),
meth = $request.meth, uri = $request.uri,

View File

@ -606,6 +606,27 @@ suite "REST API server test suite":
finally:
await server.closeWait()
asyncTest "Handle raw requests inside api handler test":
var router = RestRouter.init(testValidate)
router.rawApi(MethodPost, "/test/post") do () -> RestApiResponse:
let contentType = request.headers.getString(ContentTypeHeader)
let acceptType = request.headers.getString(AcceptHeaderName)
let body = await request.getBody()
return
RestApiResponse.response(
"type[" & contentType & ":" & body.toHex() & "]")
var sres = RestServerRef.new(router, serverAddress)
let server = sres.get()
server.start()
try:
let res = await httpClient(serverAddress, MethodPost, "/test/post",
"0123456789", "application/octet-stream")
check:
res.status == 200
res.data == "type[application/octet-stream:30313233343536373839]"
finally:
await server.closeWait()
test "Leaks test":
check: