Compile error when doing IO while processing a POST with FormUrlEncoded

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Compile error when doing IO while processing a POST with FormUrlEncoded

Bastian Krol
Hi folks,

I'm trying to process a POST request with FormUrlEncoded request body and doing some IO in the process. The code is more or less a verbatim copy of https://github.com/haskell-servant/servant/issues/236#issuecomment-142882108.

It works but as soon as I try to do any IO in the do-block in the definition of server, I get a compile error. What I am actually trying to accomplish is to store stuff in a database but it does not matter what IO I'm trying to do. A putStrLn or similar IO statement triggers the compile problem. I'm sure this is totally due to my total lack of Haskell knowledge, especially regarding the IO monad and the do notation sugaring/desugaring. Also, I must admit, I do not really grasp the instance FromFormUrlEncoded User declaration which I copied from the GH issue :-/

Any help or pointers in the right direction are much appreciated <3

Here is my code. The compile error messages are included as comments:

{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TypeOperators         #-}

module Main where

import Control.Monad.IO.Class
import Data.Aeson
import Data.Monoid
import Data.Text hiding (map)
import GHC.Generics
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
import System.Random


type API = "login" :> ReqBody '[FormUrlEncoded] User :> Post '[JSON] Login


data Login = LoggedIn | NotLoggedIn
  deriving (Eq, Show)


instance ToJSON Login where
  toJSON = toJSON . show


data User = User
  { email :: Text
  , password :: Text
  } deriving (Eq, Show)


instance FromFormUrlEncoded User where
  fromFormUrlEncoded inputs =
    User <$> lkp "email" <*> lkp "password"

    where lkp input_label = case lookup input_label inputs of
                 Nothing -> Left $ unpack $ "label " <> input_label <> " not found"
                 Just v  -> Right $ v


-- Trying to do actual IO here, that is, uncommenting either the putStrLn or
-- the 
getStdGen results in:
-- Couldn't match type ‘Control.Monad.Trans.Except.ExceptT
--                        ServantErr IO Login’
--                with ‘IO Login’
-- Expected type: Server API
--   Actual type: User -> IO Login
-- In the expression: post
-- In an equation for ‘server’

server :: Server API
server =
  post
  where
  post user = do
    -- putStrLn "..."
    -- g <- getStdGen
    return LoggedIn


-- Writing the above without where results in a different compile error, but again,
-- only if there is an IO statement included:
-- Couldn't match type ‘IO Login’
--                    with ‘Control.Monad.Trans.Except.ExceptT ServantErr IO Login’
--     Expected type: User -> IO Login
--       Actual type: ServerT API Handler
--     The equation(s) for ‘server’ have one argument,
--     but its type ‘Server API’ has none
{-
server :: Server API
server user = do
  -- putStrLn "..."
  -- g <- getStdGen
  return LoggedIn
-}


api :: Proxy API
api = Proxy


app :: Application
app = serve api server


main :: IO ()
main = run 8081 $ app


Kind regards

  Bastian


--
Reply | Threaded
Open this post in threaded view
|

Re: Compile error when doing IO while processing a POST with FormUrlEncoded

Alp Mestanogullari
Hi Bastian,

In your request handlers, you can indeed perform IO, however they do not live in IO itself. You can find a lot more about this here: http://haskell-servant.readthedocs.io/en/stable/tutorial/Server.html#the-handler-monad

Long story short, you have to wrap any IO computation using 'liftIO'.

Regarding the FromFormUrlEncoded instance:

> instance FromFormUrlEncoded User where
>  fromFormUrlEncoded inputs =
>    User <$> lkp "email" <*> lkp "password"
>
>    where lkp input_label = case lookup input_label inputs of
>                 Nothing -> Left $ unpack $ "label " <> input_label <> " not found"
>                 Just v  -> Right $ v

Let us recall the signature of that method as well:

fromFormUrlEncoded :: [(Text, Text)] -> Either String a

So in our definition, 'inputs' is a list of pairs of text values. The first "component" of a pair is the input name in the form while the second is the input's value.

'lkp' is a handy local function that takes a form input name and looks it up in 'inputs':
- when we do find the input field we're looking for, we return it using Right
- when we do not find the input field we're looking for, we return an error message with Left.

Finally, all of this is put together in `User <$> lkp "email" <*> lkp "password"` by using the Applicative instance of 'Either String': if either of the lkp calls returns Left (i.e an error), then we get an error, but if both succeed, we get a nice little User value.

Let me know if anything I've said should be clarified further.



On Mon, Jul 11, 2016 at 10:44 PM, Bastian Krol <[hidden email]> wrote:
Hi folks,

I'm trying to process a POST request with FormUrlEncoded request body and doing some IO in the process. The code is more or less a verbatim copy of https://github.com/haskell-servant/servant/issues/236#issuecomment-142882108.

It works but as soon as I try to do any IO in the do-block in the definition of server, I get a compile error. What I am actually trying to accomplish is to store stuff in a database but it does not matter what IO I'm trying to do. A putStrLn or similar IO statement triggers the compile problem. I'm sure this is totally due to my total lack of Haskell knowledge, especially regarding the IO monad and the do notation sugaring/desugaring. Also, I must admit, I do not really grasp the instance FromFormUrlEncoded User declaration which I copied from the GH issue :-/

Any help or pointers in the right direction are much appreciated <3

Here is my code. The compile error messages are included as comments:

{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TypeOperators         #-}

module Main where

import Control.Monad.IO.Class
import Data.Aeson
import Data.Monoid
import Data.Text hiding (map)
import GHC.Generics
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
import System.Random


type API = "login" :> ReqBody '[FormUrlEncoded] User :> Post '[JSON] Login


data Login = LoggedIn | NotLoggedIn
  deriving (Eq, Show)


instance ToJSON Login where
  toJSON = toJSON . show


data User = User
  { email :: Text
  , password :: Text
  } deriving (Eq, Show)


instance FromFormUrlEncoded User where
  fromFormUrlEncoded inputs =
    User <$> lkp "email" <*> lkp "password"

    where lkp input_label = case lookup input_label inputs of
                 Nothing -> Left $ unpack $ "label " <> input_label <> " not found"
                 Just v  -> Right $ v


-- Trying to do actual IO here, that is, uncommenting either the putStrLn or
-- the 
getStdGen results in:
-- Couldn't match type ‘Control.Monad.Trans.Except.ExceptT
--                        ServantErr IO Login’
--                with ‘IO Login’
-- Expected type: Server API
--   Actual type: User -> IO Login
-- In the expression: post
-- In an equation for ‘server’

server :: Server API
server =
  post
  where
  post user = do
    -- putStrLn "..."
    -- g <- getStdGen
    return LoggedIn


-- Writing the above without where results in a different compile error, but again,
-- only if there is an IO statement included:
-- Couldn't match type ‘IO Login’
--                    with ‘Control.Monad.Trans.Except.ExceptT ServantErr IO Login’
--     Expected type: User -> IO Login
--       Actual type: ServerT API Handler
--     The equation(s) for ‘server’ have one argument,
--     but its type ‘Server API’ has none
{-
server :: Server API
server user = do
  -- putStrLn "..."
  -- g <- getStdGen
  return LoggedIn
-}


api :: Proxy API
api = Proxy


app :: Application
app = serve api server


main :: IO ()
main = run 8081 $ app


Kind regards

  Bastian


--



--
Alp Mestanogullari

--
Reply | Threaded
Open this post in threaded view
|

Re: Compile error when doing IO while processing a POST with FormUrlEncoded

Bastian Krol
> In your request handlers, you can indeed perform IO, however they do not live in IO itself. You can find a lot more about this here: http://haskell-servant.readthedocs.io/en/stable/tutorial/Server.html#the-handler-monad
>
> Long story short, you have to wrap any IO computation using 'liftIO'.

Oh yes, that was indeed the bit I was missing. I was pretty sure I
tried doing a liftIO in one of my various attempts and it didn't help,
maybe I had put it in the wrong place or something. This works fine
now:

[...]
post user = do
  liftIO $ putStrLn "..."
  g <- liftIO $ getStdGen
[...]


Also thanks for your explanation of the "instance FromFormUrlEncoded"
bit, that makes more sense now.

Kind regards

  Bastian

--