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 and Regular Expressions directly in our routes, with our handlers reflecting their results. You can find more information on the demo.

As an example:

router :: Application
router = route handlers
  where
    handlers = do
      handle o
        (Just $ get $ text "home")
        Nothing
      handle ("foo" </> "bar")
        (Just $ get $ text "foobar") $ Just $
        handle (p ("baz", double) </> o)
          (Just $ \d -> get $ text $ LT.pack (show d) <> " bazs")
          Nothing
      handle (p ("num",double) </> o)
        (Just $ \d -> get $ text $ LT.pack $ show d) $ Just $ do
        handle "bar"
           (Just $ \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)
           (Just $ \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"