rosencrantz icon indicating copy to clipboard operation
rosencrantz copied to clipboard

Feature Request: flat path handler

Open jivank opened this issue 5 years ago • 2 comments

It would be nice to have a way to handle paths in a flatter way, so no need to nest pathChunk and segment handlers.

Here is a very simple example. I am not too versed with macros or what else Nim can provide. It would be ideal to be able to use a proc with any amount of defined params, or maybe a custom table function that can access the param like a map in jester (@"somepathvar")

import asynchttpserver, asyncdispatch, rosencrantz, sequtils, strutils, sugar

proc pathParse*(s: string, p: proc(params: seq[string]): Handler ): Handler =
    proc h(req: ref Request, ctx: Context): Future[Context] {.async.} =
        let expectedParts = s.split("/")
        let requestedParts = req.url.path.split("/")
        var params = newSeq[string]()
        if len(expectedParts) != len(requestedParts):
            return ctx.reject()
        for (expected, requested) in zip(expectedParts,requestedParts):
            if expected.startsWith("@"):
                params.add(requested)
                continue
            if expected != requested:
                return ctx.reject()
        let handler = p(params)
        let newCtx = await handler(req,ctx)
        return newCtx
    return h


let handler = get[(pathParse("/a/@b/c", (parts) => ok(parts[0])))] ~ 
    post[(pathParse("/",(parts) => ok"posted"))]

let server = newAsyncHttpServer()

waitFor server.serve(Port(8080), handler)

jivank avatar Jan 14 '20 08:01 jivank

In general, the design of rosencrantz encourages composition of handlers, but yours is a nice addition - sometimes it can just be more convenient to parse the whole path at once. Maybe p could take a table instead of a seq, in order to access parameters by name, like this:

import asynchttpserver, asyncdispatch, rosencrantz, sequtils, strutils, sugar, tables

proc pathParse*(s: string, p: proc(params: TableRef[string, string]): Handler): Handler =
    proc h(req: ref Request, ctx: Context): Future[Context] {.async.} =
        let expectedParts = s.split("/")
        let requestedParts = req.url.path.split("/")
        var params = newTable[string, string]()
        if len(expectedParts) != len(requestedParts):
            return ctx.reject()
        for (expected, requested) in zip(expectedParts,requestedParts):
            if expected.startsWith("@"):
                params[expected[1 .. high(expected)]] = requested
                continue
            if expected != requested:
                return ctx.reject()
        let handler = p(params)
        let newCtx = await handler(req,ctx)
        return newCtx

    return h


let handler =
    get[(pathParse("/a/@b/c", (parts) => ok(parts["b"])))] ~
    post[(pathParse("/", (parts) => ok("posted")))]

let server = newAsyncHttpServer()

waitFor server.serve(Port(8080), handler)

Actually, one should split only the remaning part of the path - you can use ctx.position to see the part of the path that has yet to be parsed. This would allow nesting this handler together with the other ones.

Would you mind opening a PR and adding a test and mention the new handler in the README?

andreaferretti avatar Jan 16 '20 15:01 andreaferretti

Sure I will work on a PR.

jivank avatar Jan 17 '20 06:01 jivank