@hackage nested-routes1.0

Declarative, compositional Wai responses

A method to writing Wai responses

This library attempts to make it easier to write nice Wai response handlers by giving us a Sinatra/ Scotty-like syntax for declaring HTTP-verb oriented routes, in addition to file-extension handling and rose-tree like composition. Not only do we have literal route specification, like Scotty & Spock, but we can also embed Attoparsec parsers directly in our routes, with our handlers reflecting their results. As an example:

router :: Application
router = route handlers
  where
    handlers = do
      handle o
        (get $ text "home")
        Nothing
      handle ("foo" </> "bar")
        (get $ text "foobar") $ Just $
        handle (p ("baz", double) </> o)
          (\d -> get $ text $ LT.pack (show d) <> " bazs")
          Nothing
      handle (p ("num",double) </> o)
        (\d -> get $ text $ LT.pack $ show d) $ Just $ do
        handle "bar"
           (\d -> get $ do
                    text $ (LT.pack $ show d) <> " bars")
                    json $ (LT.pack $ show d) <> " bars!")
           Nothing
        handle (r ("email", mkRegex "(^[-a-zA-Z0-9_.]+@[-a-zA-Z0-9]+\\.[-a-zA-Z0-9.]+$)") </> o)
           (\d e -> get $ textOnly $ (LT.pack $ show d) <> " " <> (LT.pack $ show e)

The route specification syntax is a little strange right now - l specifies a "literal chunk" of a handlable url (ie - l "foo" </> l "bar" </> o would represent the url /foo/bar), while p represents a "parsable" url chunk, which expects a pair - the left element being merely a reference name for the parser during internal plumbing, and the right being the actual Parser. o represents the end of a url string, and can be used alone in a handler to capture requests to the root path.

Each route being handled needs some kind of content. For every parsed url chunk, the route expects a function of arity matching 1-for-1 with the parsed contents. For example, d -> ... in the demonstration above is such a function, where d :: Double.

Internally, we match against both the file extension and Accept headers in the HTTP request - the Accept header may override the file extension.

When we test our application:

 λ> curl localhost:3000/ -H "Accept: text/plain, */*"
 ↪ "home"

requests may end with index

 λ> curl localhost:3000/index -H "Accept: text/plain, */*"
 ↪ "home"

and specify the file extension

 λ> curl localhost:3000/index.txt -H "Accept: text/plain, */*"
 ↪ "home"

each responding with the "closest" available file type

 λ> curl localhost:3000/index.html -H "Accept: text/html, */*"
 ↪ "home"
 λ> curl localhost:3000/foo/bar -H "Accept: text/plain, */*"
 ↪ "foobar"
 λ> curl localhost:3000/foo/bar.txt -H "Accept: text/plain, */*"
 ↪ "foobar"
 λ> curl localhost:3000/foo/bar/5678.5678 -H "Accept: text/plain, */*"
 ↪ "5678.5678 bazs"
 λ> curl localhost:3000/1234.1234 -H "Accept: text/plain, */*"
 ↪ "1234.1234"
 λ> curl localhost:3000/2e5 -H "Accept: text/plain, */*"
 ↪ "200000.0"
 λ> curl localhost:3000/1234.1234/bar -H "Accept: text/plain, */*"
 ↪ "1234.1234 bars"