Could someone explain what is going on in the default snap project template?
--------------------------------------------------------------------------
-- | Handle login submit
handleLoginSubmit :: Handler App (AuthManager App) ()
handleLoginSubmit =
loginUser "login" "password" Nothing
(\_ -> handleLogin err) (redirect "/")
where
err = Just "Unknown user or password"
-- | Render login form
handleLogin :: Maybe T.Text -> Handler App (AuthManager App) ()
handleLogin authError = heistLocal (bindSplices errs) $ render "login"
where
errs = [("loginError", textSplice c) | c <- maybeToList authError]
| The application's routes.
routes :: [(ByteString, Handler App App ())]
routes = [ ("/login", with auth handleLoginSubmit)
, ("/logout", with auth handleLogout)
, ("/new_user", with auth handleNewUser)
, ("", serveDirectory "static")
]
Why is it returning ()?
What is the standard signature of the "controller" (as you would call it in MVC speak) function?
How does it know to use index.tpl as a template for / route?
How do I get rid of authentication layer, say if I only want to make a simple personal web-app?
The documentation and a tutorial mainly covers the snapplets (or the templates), but it does not go over any of the Haskell. It would be nice to see an example of how to write a book-store like app, or a blog (the official snap website stores their blog entries in markdown - so I am not sure what's going on there).
I'm in the process of learning Haskell myself, and I know nothing about Snap, but I can do my best to answer what I can see:
1)
The Snap.Snaplet module defines a type: Handler b v a.
So, any Handler has three type parameters: b, v, and a.
Also, (Handler b v) is declared in the same module to be a Monad. That can probably tell you something about what the last a parameter is for.
IO is another example of a Monad.
IO () does "something" that has to do with IO, and then returns (), an empty value.
IO a does "something" that has to do with IO, and then returns something else of type a.
For example, it doesn't really make sense to do x <- putStrLn "text" because putStrLn has return type IO (). Technically, you can, but it's usually not useful. putStrLn does IO and that's all, declining to pass anything onward to future functions. Specifically, it prints something to the console, but it doesn't tell the rest of the program that it did so.
str <- getLine makes sense because getLine has type IO String. It does IO, and then tells str about a String. It produces a result that the rest of the function can use directly.
For Handler, you could forget what a handler does and look at it like this:
let M = Handler b v
M is declared to be a monad.
So, M a does "something" and then returns an a.
M () does "something" and doesn't return anything.
From that you can probably tell something about what functions like handleLogin are doing. It takes its arguments and probably does something to/with the Handler based on those. Afterwards, assuming there were no errors, the program moves to the next line without telling the function on the next line what happened.
There are some functions in Snap that return a Handler with something other than () for the last parameter. If you see a function like that, it means it carries a meaningful return value.
4)
I suspect the templates are more of examples than things you are meant to build from, but it's all manually written, so I'd think you could remove the authentication steps if you like. The type App is defined in "Application.hs" where you could remove its auth parameter. Then you could remove references to it in the rest of the project.
Related
I'm currently trying to implement a simple web server with servant. At the moment, I have a IO (Maybe String) that I want to expose via a GET endpoint (this might be a database lookup that may or may not return a result, hence IO and Maybe). If the Maybe contains a value, the response should contain this value with a 200 OK response status. If the Maybe is Nothing, a 404 Not Found should be returned.
So far I was following the tutorial, which also describes handling errors using throwError. However, I haven't managed to get it to compile. What I have is the following code:
type MaybeAPI = "maybe" :> Get '[ JSON] String
server :: Server MaybeAPI
server = stringHandler
maybeAPI :: Proxy MaybeAPI
maybeAPI = Proxy
app :: Application
app = serve maybeAPI server
stringHandler :: Handler String
stringHandler = liftIO $ fmap (\s -> (fromMaybe (throwError err404) s)) ioMaybeString
ioMaybeString :: IO (Maybe String)
ioMaybeString = return $ Just "foo"
runServer :: IO ()
runServer = run 8081 app
I know this is probably more verbose than it needs to be, but I guess it can be simplified as soon as it is working. The problem is the stringHandler, for which the compilation fails with:
No instance for (MonadError ServerError []) arising from a use of ‘throwError’
So my question is: Is this the way to implement such an endpoint in Servant? If so, how can I fix the implementation? My Haskell knowledge is rather limited and I never used throwError before, so it's entirely possible that I'm missing something here. Any help is appreciated!
As I mentioned in my comment, the problem is that in the offending line:
stringHandler :: Handler String
stringHandler = liftIO $ fmap (\s -> (fromMaybe (throwError err404) s)) ioMaybeString
s is a Maybe String, so to use it as the second argument to fromMaybe, the first argument must be a String - and throwError is never going to produce a string.
Although you talked about your code perhaps being too verbose and you would look at simplifying it later, I think part of the problem here is that in this particular handler you are trying to be too concise. Let's try to write this in a more basic, pseudo-imperative style. Since Handler is a monad, we can write this in a do block which checks the value of s and takes the appropriate action:
stringHandler :: Handler String
stringHandler = do
s <- liftIO ioMaybeString
case s of
Just str -> return str
Nothing -> throwError err404
Note that throwError can produce a value of type Handler a for any type it needs to be, which in this case is String. And that we have to use liftIO on ioMaybeString to lift it into the Handler monad, else this won't typecheck.
I can understand why you might have thought fromMaybe was a good fit here, but fundamentally it isn't - the reason being that it's a "pure" function, that doesn't involve IO at all, whereas when you're talking about throwing server errors then you are absolutely unavoidably doing IO. These things essentially can't mix within a single function. (Which makes the fmap inappropriate too - that can certainly be used to "lift" a pure computation to work on IO actions, but here, as I've said, the computation you needed fundamentally isn't pure.)
And if you want to make the stringHandler function above more concise, while I don't think it's really an improvement, you could still use >>= explicitly instead of the do block, without making the code too unreadable:
stringHandler = liftIO ioMaybeString >>= f
where f (Just str) = return str
f Nothing = throwError err404
I'm using Happstack to receive some parameters from an HTTP request then pass these parameters to a function that will retrieve data from the database and return this data in the HTTP response as follow:
myFunc :: IO String
myFunc = do r <- look "personId"
conn <- connectODBC "... my connection string ...";
vals <- quickQuery conn ("SELECT Name FROM Person where Id = ?") [(toSql r)];
return (processData vals)
handlers :: ServerPartT IO Response
handlers = do
x <- liftIO (myFunc);
decodeBody (defaultBodyPolicy "/tmp/" 0 1000 1000)
msum [
dir "getData" $ ok $ toResponse x
, ... other handlers ...
]
mainFunc = simpleHTTP nullConf handlers
But when I build the above code I get the following error:
No instance for (HasRqData IO) arising from a use of `look'
In a stmt of a 'do' block: r <- look "personId"
After reading questions on similar issues (like this one) I think I have to include HasRqData constraint somewhere, but I couldn't learn where and how.
As you may have guessed, this is too an issue with monads. There are a handful of them in happstack (HasRqData, among others), so you may well consider it a complicated case.
Let us begin with the innocent-looking look function.
look :: (Functor m, Monad m, HasRqData m) => String -> m String
Indeed, there is a non-trivial constraint HasRqData. Let us ask ourselves: what monads HaveRqData? (It so happens that IO has not!)
class HasRqData m where
...
Instances
HasRqData RqData
(MonadIO m, MonadPlus m) => HasRqData (ServerPartT m)
...
The other instances are derivative of these first two, so, it looks like we have to consider these two options first.
The RqData has limited effects — you can only do look and its derivatives, extracting information from the request at hand. As we want to also have other effects — querying the database, for one, — this is not enough for us.
The ServerPartT m is the general form of the same old friend of ours, ServerPart ≡ ServerPartT IO. It happens that it also HasRqData. This is not a coincidence, but rather an implication of the design of happstack — looks like the authors meant us to use this single monad everywhere, unless we need particular granularity. So, let's give it a try.
myFunc :: ServerPart String
myFunc = do r <- look "personId"
return undefined
This compiles.
Now, we don't even need to lift myFunc in handlers — our previous predicament solved itself. We will need to lift our access to the database though, by the same jar logic we discussed before.
I believe you can figure the details by yourself. In any case, let me know how it works out!
Here's a (simplified) test I have
it "asserts route access for valid arguments" $ do
-- ...
token <- getFlagTokenFromCsrf "1234"
get $ FlagMentorR flaggedId Flag token
statusIs 200
where I have getFlagTokenFromCsrf :: Text -> Handler Text
The error I get when I try to stack test is:
Couldn't match type ‘Yesod.Core.Types.HandlerT App IO Text’
with ‘Control.Monad.Trans.State.Lazy.StateT
(YesodExampleData App) IO Text’
Expected type: Control.Monad.Trans.State.Lazy.StateT
(YesodExampleData App) IO Text
Actual type: Handler Text
(Here's the full example)
It seems I am trying to do something rather similar. That is: Try to run arbitrary Handler a functions in unit tests.
Some approaches I've tried and thought about:
The closest I've gotten that actually worked for me:
import Application (handler)
spec = withApp $ do
it "" $ do
liftIO $ handler $ someHandler
Although it seems this calls makeFoundation under the hood, and I'd prefer if my handler would run in the context of the main webapp and not a separate instance.
To execute a handler in the main app's context, I've also tried to:
import Foundation (unsafeHandler)
import qualified Control.Monad.Trans.State as ST
spec = withApp $ do
it "" $ do
(YesodExampleData app _ _ _) <- ST.get
liftIO $ unsafeHandler app $ someHandler
Which has gotten me a type error like this:
[34 of 35] Compiling Handler.FooSpec ( /path/to/test/Handler/FooSpec.hs, /path/to/.stack-work/odir/Handler/FooSpec.o )
/path/to/test/Handler/FooSpec.hs:42:31:
Couldn't match type ‘Network.Wai.Internal.Request
-> (Network.Wai.Internal.Response
-> IO Network.Wai.Internal.ResponseReceived)
-> IO Network.Wai.Internal.ResponseReceived’
with ‘App’
Expected type: App
Actual type: Network.Wai.Application
Probable cause: ‘app’ is applied to too few arguments
In the first argument of ‘unsafeHandler’, namely ‘app’
In the expression: unsafeHandler app
Failed, modules loaded: Import, Utils, Foundation, [...]
I'm not entirely clear on what might be the difference between Network.Wai.Application and App, but that might be the key to making this work.
I've also considered to specify a route to the handler, and then it would be accessible via get SomeRouteR. Although I'd rather not have to do this if I can help it.
I'm also considering refactoring some of my Handler a functions to IO a, and then I can use liftIO to execute them in not just handlers but tests as well.
That's as far as I've gotten. If I figure something more out I intend to update this answer. I've also starred this question so I get notified if someone else figures out something more or better.
Edit: I've also asked this question on GitHub.
Edit:
I've found something rather promissing while trying to dig further in the direction of option #2:
import Foundation (unsafeHandler)
runHandler :: Handler a -> ST.StateT (YesodExampleData App) IO a
runHandler handler = do
foundation <- getTestYesod
liftIO $ unsafeHandler foundation handler
On which I was able to run test like so:
it "runHandler" $ do
let
testHandler :: Handler Int
testHandler = do
return 2
runHandler testHandler >>= (==? 2)
let
testHandler2 :: Handler String
testHandler2 = do
fmap show $ (Import.runDB) $ insert (def :: User)
runHandler testHandler2 >>= (==? ("UserKey {unUserKey = SqlBackendKey {unSqlBackendKey = 1}}"))
I am not a Yesod expert but what you are trying to do probably does not make sense: getFlagTokenFromCsrf is a server-side function that extracts some information from the request, hence it lives in Handler a monad because this is the monad for server-side code.
By contrast, Yesod tests are meant to be integration tests, asserting behaviour from the client-side against your Application (see https://hackage.haskell.org/package/yesod-test-1.5.3/docs/Yesod-Test.html). The functions there live in a different monad, namely YesodExample site a which manages requests on your behalf. This monad allows you to reuse part of your server code (routes, data types) and manage state of your DB but is otherwise totally uncorrelated with server-side operations.
I am not sure what you are trying to do but if you need some information generated on the server-side, you have to find another way to make it available on the client side.
Please forgive my ignorance but, is there is way to invoke a method that returns Handler () from a method that returns only IO ()
For example consider these two methods
onReceiveMessage :: IO ()
onReceiveMessage = do
_ <- putStrLn "Received Message"
_ <- doSomething
return ()
doSomething :: Handler ()
doSomething = return ()
This does not seem to compile. I tried to compile it in a few different ways but all in vein. I am sure it must be possible but just don't know how.
Any Idea?
UPDATE
Expanding on the previous example, lets say I have another function that takes in a value of the previous function and returns IO (). That also does not work.
onReceiveMessage :: IO ()
onReceiveMessage =
doSomething >>= doSomethingElse
doSomething :: Handler MessageStatus
doSomething = return MessageStatus.Success
doSomethingElse :: MessageStatus -> IO ()
doSomethingElse _ = return ()
This also does not seem to work. To invoke an IO action from Handler is possible using the liftIO function for e.g. the below function compiles and works fine. It invokes a IO () action from a function that returns Handler MessageStatus. This is achieved using the liftIO function.
doSomething' :: Handler MessageStatus
doSomething' = (liftIO $ putStrLn "hello world") >> return MessageStatus.Success
Do we have something similar to invoke Handler action from IO?
UPDATE 2
Giving more context and explaining how I solved the problem.
I was trying to listen to RabbitMQ using the amqp package in an Yesod application.
When I receive a message a callback is invoked.
The callback needs to have the signature (Message, Envelope) -> IO ().
From the callback I needed to execute some SQL's.
Now I didn't want to write code to parse the config file and manage my own Connection Pool.
Hence, I wanted to hookup my code with the method provided by Yesod called runDB.
But since its return values are wrapped in Handler I was not able to invoke it as is from the message callback.
What I ended up doing was
Get hold of the App (Foundation) object while its being built in the Application.hs and passed it to my code.
Then create a curried function for the message callback
In the curried function I hold on to the config object that was already build by Yesod, while building the foundation
It was breeze after I figured this out, Using the config object I could read all my settings from the settings.yml and even keep a parallel connection pool.
This connection pool will be used to fire all my queries from the message callback.
Not only this, but because I used this approach, I get logging for free and I can see all my queries on the console without writing a single line of code.
Overall I feel I might have over complicated things but currently I do not know of any better way of doing this. If anyone has a better idea, I would love to hear it.
First of all, your indentions look off. White space in Haskell is important. But more importantly, every statement in a single monadic action has to be within the same monad:
onReceiveMessage :: IO ()
onReceiveMessage = do
putStrLn "Received Message" :: IO ()
doSomething :: Handler () -- Illegal! Must be type IO ()
return () :: IO ()
doSomething :: Handler ()
doSomething = return ()
So no, you can not return a handler from an IO action.
UPDATE
The Handler type is actually an alias for a more complex type which you can think of as "wrapping" the IO monad. What it allows you to do is "lift" an IO action into the Handler monad, but this only goes one way. In order to convert a Handler action into an IO action, there is usually a function provided by the library that is something like runHandler :: Handler [-> other args] -> IO (). I'm not particularly familiar with Yesod, but the pattern is similar across many libraries. This function is used to convert an entire Handler action into an IO action, and is usually reserved for running the server itself.
The complicated answer is that the Handler type is what is known as a monad transformer. They can be pretty tricky to learn at first, but you can just think of it as a monad that contains another monad. Monads can not perform operations outside themselves, so IO can't perform Handler actions, but since Handler contains IO inside it, an IO action can be "lifted" up a level. The really useful thing about monad transformers is that they can be layered indefinitely, and it essentially lets you compose the behavior of different monads together. For instance, imagine if you had a Maybe action, but you also wanted State functionality, Writer functionality, and IO functionality. With monad transformers, this becomes possible, if a bit more complex. However, the order in which these are composed can often matter, since operations can be lifted, but not lowered.
In recent versions you can use Yesod.Core.Handler.handlerToIO to get back into your cozy Handler stack.
But i recommend decoupling messaging and processing:
getOneMsg :: Channel -> Text -> Handler (Message, Envelope)
getOneMsg chan queue = liftIO $ do
mbox <- newEmptyMVar
tag <- consumeMsgs chan queue NoAck (putMVar mbox)
reply <- takeMVar mbox
cancelConsumer chan tag
return reply
myHandler = do
... regular handler code ...
(msg, env) <- getOneMsg chan queue
... regular handler code ...
I want to write little integration tests for my Snap web handlers but I am stuck. Here is the scenario. I have a Snap web handler that (run-of-the-mill style) CRUDs up a type and it looks something like this:
create :: AppHandler ()
create = method POST $ do
lastName <- decodeUtf8 . fromJust <$> getParam "lastName"
firstName <- decodeUtf8 . fromJust <$> getParam "firstName"
createPerson $ Person firstName lastName
modifyResponse (setResponseCode 204)
The Snap.Test module has some things to help build up a request and I use it to make a request for my handler:
createOwnerReq :: RequestBuilder IO ()
createOwnerReq = postUrlEncoded "host/person/create" $
fromList [ ("firstName", ["Greg-Shaw"])
, ("lastName", ["Snoy'Sullivan"])
]
Here's the problem, I want to make a TestUnit TestCase for this handler so I need the run the handler on the createOwnerReq request. The module Snap.Test provides:
runHandler :: MonadIO a => RequestBuilder m () -> Snap a -> m Response
so
... do
resp <- runHandler createOwnerReq ???
But wait!!! My request handler is of type AppHandler () but runHandler requires a Handler of type Snap a.
How do I lift my AppHandler type into the Snap monad? Help please, this is kind of trippin' me out.
Ibolla's return create trick probably doesn't do what you want. It compiles correctly because runHandler takes a Snap a which will work on a Snap action with any return value. return create :: Snap (AppHandler ()), which is very different from the Snap () that you were probably expecting.
We are working on a Snap.Snaplet.Test equivalent that will wrap the runHandler function provided by Snap.Test to allow you to test Handlers. This will probably be included in the 0.10 release of the snap package.
In the interim, you can solve the problem manually by using runSnaplet to convert your SnapletInit into a Snap () action that can be passed to Snap.Test.runHandler. This won't let you test an individual Handler, but it will let you test any of the routes defined in your application's initializer.
EDIT: In snap-0.10, we added test support for snaplets.