Reputation:
I'm struggling with the basics of getting an API up and running using WAI. The main issue is dealing with IO
infecting everything. I believe that my problems will dissolve once I better understand Monads, but hopefully an answer to this question will be a good starting point.
The following is a short example that serves a static html page on the root url, and accepts a request with a username to /api/my-data
that should return the corresponding user's data. I can't figure out how to use the IO Bytestring
that is the request's body
to do a map lookup, retrieve the data, and send the result back encoded in json.
I've tried using fmap
to extract the Bytestring
and then unpack
to turn it into a string for lookup, but whatever I do, I end up chasing type errors related to the damn IO
monad.
Anyway, here is the relevant code:
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import qualified Data.Map as Map
import Data.Aeson
import Network.Wai
import Network.Wai.Parse
import Network.Wai.Middleware.Static
import Network.HTTP.Types
import Network.Wai.Handler.Warp (run)
userInfo :: Map.Map String (Map.Map String String)
userInfo = Map.fromList [("jsmith", Map.fromList [("firstName", "John"),
("lastName", "Smith"),
("email", "[email protected]"),
("password", "Testing012")]),
("jeff.walker", Map.fromList [("firstName", "Jeff"),
("lastName", "Walker"),
("email", "[email protected]"),
("password", "Testing012")])]
getUserInfo :: B.ByteString -> Map.Map String String
getUserInfo body =
case Map.lookup (B8.unpack body) userInfo of
(Just x) -> x
Nothing -> Map.empty
app :: Application
app request respond = do
case rawPathInfo request of
"/" -> respond index
"/api/my-data" -> respond $ myData (getUserInfo (requestBody request))
_ -> respond notFound
index :: Response
index = responseFile
status200
[("Content-Type", "text/html")]
"../client/index.html"
Nothing
myData :: IO (Map.Map String String) -> Response
myData user = responseLBS
status200
[("Content-Type", "application/json")]
(encode user)
notFound :: Response
notFound = responseLBS
status404
[("Content-Type", "text/plain")]
"404 - Not Found"
main :: IO ()
main = do
putStrLn $ "http://localhost:8080/"
run 8080 $ staticPolicy (addBase "../client/") $ app
This results in this error:
src/Core/Main.hs:32:54:
Couldn't match expected type ‘B8.ByteString’
with actual type ‘IO B8.ByteString’
In the first argument of ‘getUserInfo’, namely
‘(requestBody request)’
In the first argument of ‘myData’, namely
‘(getUserInfo (requestBody request))’
I can easily change the type of getUserInfo
and myData
to IO Bytestring -> IO (Map.Map String String)
and IO (Map.Map String String) -> Response
but then I end up with more type errors. Types are making my head spin.
Upvotes: 3
Views: 350
Reputation: 76240
Since requestBody
has the following type:
requestBody :: Request -> IO ByteString
the resulting expression can't be directly passed to getUserInfo
, which accepts a ByteString
.
What you can do is, given that Application
is simply Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
and in app
you are in the IO
monad, extract the ByteString
with the do
notation like this:
str <- requestBody request
and then pass str
to getUserInfo
, like this:
app :: Application
app request respond = do
str <- requestBody request
case rawPathInfo request of
"/" -> respond index
"/api/my-data" -> respond $ myData (getUserInfo str)
_ -> respond notFound
at this point myData
can simply accept a Map
:
myData :: Map.Map String String -> Response
You should definitely read more about monads and IO in general before going any deeper with WAI though.
Upvotes: 1