How to get addRoutes to work in a snaplet (changing api's, heist 0.12)? - haskell-snap-framework

In
How to make nicEditor snaplet? (Several questions)
I followed the hints and put addRoutes into the snaplet initializer and it worked. After updating snap and heist (0.12) modules to newer, I have some difficulties with addRoutes. If addRoutes is removed from nicsnapInit (in the snaplet example) and put into routes-function in Application.hs, the route is initialized at the same time as other routes in App and route is found.
However, if addRoutes is in nicsnapInit, server just says
no handler accepted "/netext".
This looks like the route is not added.
How to use addRoutes in snaplet initializers at the moment (heist 0.12)?
Learned almost right away that Snap Framework: Custom snaplet handler won't render template has almost similar problem. There addTemplates was used just before addRoutes. After similar changes this seems to work, that is, with line
addTemplates h ""
where h is the parameter at
nicsnapInit :: HasHeist b => Snaplet (Heist b) -> SnapletInit b Nicsnap
and corresponding change to the application initializer.
This changes the url to "niced/netext", i.e. first the snaplet name and then the route that is defined in the snaplet.
Is it possible to add only the "/netext"-part?

Use nestSnaplet "" nicsnap $ nicsnapInit .... The first parameter to nestSnaplet is not the snaplet's name. It is the URL that all the snaplet's routes are relative to.

Related

Bind splice to tag in Heist

I want to use a website as a working example in order to help learn Haskell. I'm trying to follow the Heist tutorial from the Snap website, and display the result of a factorial function in a web page.
I can get the example function defined "server side" without the compiler complaining, but I cannot figure out how to bind the function to a tag which I can then place into the HTML. Specifically, this part works fine (in, say, Site.hs):
factSplice :: Splice Snap
factSplice = do
input <- getParamNode
let text = T.unpack $ X.nodeText input
n = read text :: Int
return [X.TextNode $ T.pack $ show $ product [1..n]]
But the really important part - how to evaluate this function as part of a web page (such as how to bind it to a tag like < fact />) - is cryptic. The instructions say to drop:
bindSplice "fact" factSplice templateState
somewhere in the code. But this alone is not sufficient. This statement is not an expression (stuff = bindSplice...), so it is not clear how or where to put it in the code. Moreover, it is not at all clear where "templateState" is supposed to come from. It almost seems like "templateState" is supposed to be a placeholder for default values like emptyTemplateState or defaultHeistState, but these both appear to have been deprecated years ago, and the latest version of Heist (0.14) does not recognize them.
MightyByte has commented on this kind of question several times in 2011, but the answers all gloss over exactly the confusing part, i.e. how to actually get data into a web page. Can anyone help?
-- UPDATE --
Thank you very much, mightybyte! Your explanation and some cursory poking around in the source code cleared up a lot of confusion, and I was able to get the factorial example from the Snap website tutorial working. Here is my solution - I'm a complete n00b, so apologies if the explanation seems pedantic or obvious.
I more or less used the addConfig approach that mightybyte suggested, simply copying the implementation of addAuthSplices from SpliceHelpers.hs. I started with the default project via "snap init", and defined a function addMySplices in Site.hs
addMySplices :: HasHeist b => Snaplet (Heist b) -> Initializer b v ()
addMySplices h = addConfig h sc
where
sc = mempty & scInterpretedSplices .~ is
is = do
"fact" ## factSplice
This uses lenses to access the fields of the SpliceConfig neutral element mempty, so I had to add Control.Lens to the dependencies in Site.hs, as well as Data.Monoid to put mempty in scope. I also changed the type signature of the factorial splice to factSplice :: Monad n => I.Splice n, but the function is otherwise unchanged from its form in the Heist tutorial. Then I put a call to addMySplices in the application initializer, right next to addAuthSplices in Site.hs
app :: SnapletInit App App
...
addAuthSplices h auth
addMySplices h
...
which results in factSplice being bound to the tag <fact>. Dropping <fact>8</fact> into one of the default templates renders 40320 on the page, as advertised.
This question from about a year ago contains a superficially similar solution, but does not work with the latest version of Heist; the difference is that some fields were made accessible through lenses instead of directly, which is explained on the Snap project blog in the announcement of Heist 0.14. In particular, hcCompliedSplices has been completely redefined - there's even a friendly warning about it in Types.hs.
If you look at the top level Heist module, you'll see the initHeist function. As the documentation points out, this is the main initialization function. And you are supposed to pass it all of your templates and splices. A look at the type signature tells us these are all bundled together in the HeistConfig data type.
initHeist :: Monad n => HeistConfig n -> EitherT [String] IO (HeistState n)
This gives you back a HeistState, which is what you pass to renderTemplate when you want to render a template. Also, HeistConfig contains a SpliceConfig field for all your splices.
But this is all the low level interface. Heist was designed to have no dependencies on Snap. It is a template library and can work with any web framework. (Or even standalone with no web framework at all for things like generating HTML email.) If you're using Heist with Snap, you'll probably want to use the convenience stuff that we supply which handles the details of calling initHeist, renderTemplate, etc.
For a working example of this, your best bet is to look at the project template that you get when you do "snap init". You can see that code on github here. If you look in Application.hs, you'll see that there's a line including the Heist snaplet in the application data structure. Then, if you look at the bottom of Site.hs, you'll see that there's a call to heistInit. This function is defined by the heist snaplet here. The snaplet takes care of the initialization, state management, on-the-fly template reloading, etc for you.
So, to put this all together...from the Heist API described above, we see that we need to define our splices inside HeistConfig. But our application will interact with all this stuff mostly through the snaplet's API. So if we look in the heist snaplet API for something involving HeistConfig/SpliceConfig, we find two relevant functions:
heistInit' :: FilePath -> HeistConfig (Handler b b) -> SnapletInit b (Heist b)
addConfig :: Snaplet (Heist b) -> SpliceConfig (Handler b b) -> Initializer b v ()
These type signatures suggest two ways of defining your splices. You could use heistInit' instead of heistInit and pass your HeistConfig to it up front. Or you could add your splices afterwards by calling addConfig with the resulting Snaplet (Heist b).

How to use Auth and PostgresqlSimple in one handler?

Alright, so there are a few problems actually, I'll try to describe each of them in isolation of the other, but in a sense they are all related.
I want to insert a user id, which I want to get from the AuthManager. Okay that's fair enough, we can just use currentUser and then do some maybe voodoo to get the actual unUid. But then is when the problem starts. Okay, so we want to use execute to exute a query:
execute "INSERT INTO events(title,recurring) VALUES(?,?);" (eventname,recurring)
boom. Okay, then when doing cabal install, it fails saying that there is no instance of HasPostgres available for the type I declared. Makes perfect sense since the type of my handler is ...-> ...-> Handler App (AuthManager App). This is the first problem: I don't know how to correctly make an instance so that the types are correct. As I tried, just copying the initial instance in Application.hs doesn't help us at all.
That brings us to other problems that arise when trying to find an alternative solution. So the solutions I tried is by just substituting the execute call with a with pg $ execute call. This has the following result:
Couldn't match type `App' with `AuthManager App'
Expected type: Handler App (AuthManager App) GHC.Int.Int64
Actual type: Handler App App GHC.Int.Int64
So again, to no avail. The next thing I tried is removing (AuthManager App) type, and just substituting it by App, so then I could replace AuthManager calls with with auth $ call. Then I realised this isn't the solution either, because once you get AuthManager out of there, you need to drop it from every function that calls it.
I need some help with how to fix this. Can I somehow lift the type to fix this? Or alternatively what is a good solution to this problem?
EDIT
Okay, so I've tried to remove AuthManager App out of my type so I can use postgres after all. Now this doesn't go as smoothly either as one would expect. The next part comes from a regular application generated by Snap init:
handleLoginSubmit :: Handler App App ()
handleLoginSubmit =
with auth $ loginUser "login" "password" Nothing
(\_ ->handleLogin err) (handleFoo)
where
err = Just "Unknown user or password"
Okay so both handleLoginand handleFoo now have types Handler App App (), but yet Haskell complains it expects the type Handler App (AuthManager App) ():
Couldn't match type `App' with `AuthManager App'
Expected type: Handler App (AuthManager App) ()
Actual type: Handler App App ()
--Definition of App:
data App = App
{ _heist :: Snaplet (Heist App)
, _sess :: Snaplet SessionManager
, _auth :: Snaplet (AuthManager App)
, _pg :: Snaplet Postgres
}
You want to use this type signature.
yourHandler :: Handler App App ()
with pg $ execute ...
with auth $ loginUser ...
The with function lets you raise a Handler b v function, which only has access to the v snaplet's data, into Handler b b, which has access to everything. And the only thing it needs to do that is the lens from b to v.
I wrote a snaplet based off the Postgres Snaplet, so this should work for you.
instance HasPostgres (Handler App (AuthManager App)) where
getPostgresState = withTop pg get
and then a route with the signature:
myRoute :: Handler App (AuthManager App) ()

Snap-Heist: why my template is not rendered?

I'm trying to render a template using Snap and Heist.
I'm sure my handler function is called correctly (if I replace handler function's content with undefined, it fails as expected. Debug.Trace.trace also works as expected).
This handler function consists of one line: render "template". But for some reason I'm getting No handler accepted <url> error instead of a template not found or something like that.
I think the problem here is that I'm placing my template in wrong directory, but there's no way to know where are searched for templates. So my question is:
Isn't this error message misleading? It should have been something like template not found: template.tpl
Where can I know which directories are searched for templates?
I think the snap application created by snap init is the problem. I only made slight modifications on it:
I added one more field to App record: _myapp :: Snaplet Myapp
In app initializer function, I added: n <- embedSnaplet "myapp" myapp myappInit and then passed n to record.
I created new file src/Myapp.hs.
Here are relevant parts in Myapp.hs:
myappInit = do
...
h <- nestSnaplet "" heist $ hesitInit "myapp_templates"
addRoutes routes
...
routes = [ ("/submit", submitHandler) ]
submitHandler = trace "rendering submit" $ render "submit"
But for some reason even though I see rendering submit printed to console when I go to http://0.0.0.0:8000/myapp/submit, I get No handler accepted "/hsnews/submit" message as HTTP response (instead of rendered template). I have submit.tpl and _submit.tpl in snaplets/heist/myapp_templates.
1.: I agree that there should be two different types of errors for rendering templates. Sadly as far as I know that isn't the case.
2.: Short answer: The directory used is written in your heistInit function. e.g.
h <- nestSnaplet "" heist $ heistInit "templates"
means that all the files in "snaplets/heist/templates/" can be accessed, including sub directories.
Long answer:
The default behaviour of the heist directory stucture works as follows:
All .tpl files are accessible from the snaplet/heist/templates/ directory. Meaning
addRoutes [("template", render "template")]
will access the file
snaplets/heist/templates/template.tpl
Which can be accessed at the url
http://localhost:8000/template
You can also use sub directories, e.g.:
addRoutes [("users", render "users/index")]
path = snaplets/heist/templates/users/index.tpl
url = http://localhost:8000/users
To modify this behaviour you can adjust the heistInit function.
Let's say you want the directory path to be "snaplets/heist/" instead of "snaplets/heist/templates/"
simply change:
h <- nestSnaplet "" heist $ heistInit "templates"
to this:
h <- nestSnaplet "" heist $ heistInit ""
The argument of heistInit is the directory location of your tpl files.
So you could adjust that however you wish.
I hope this answers your question.
Attic's answer is very good, but didn't solve my problem. This is because of a difference between nestSnaplet and embedSnaplet.
I don't think embedSnaplet's documentation mention this difference:
Runs another snaplet's initializer and returns the initialized Snaplet value. The difference between this and nestSnaplet is the first type parameter in the third argument. The "v1 v1" makes the child snaplet think that it is top-level, which means that it will not be able to use functionality provided by snaplets included above it in the snaplet tree. This strongly isolates the child snaplet, and allows you to eliminate the b type variable. The embedded snaplet can still get functionality from other snaplets, but only if it nests or embeds the snaplet itself.
but there is also this difference, in embedSnaplet, child snaplet has a new root directory in snaplets/embeddedSnapletName and all children snaplets of the embedded snaplet will use snaplets/embeddedSnapletName folder as root.
So I had to put my templates folder to snaplets/myapp/templates and that solved it.

Use subsnaplet during snaplet initialization?

I have some snaplet like this:
data DB b = DB
{_pgsql :: Snaplet Postgresql
,dbCache :: Map Text Text
}
And I wish to fill dbCache from postgresql database. Seems that it is natural to do this during snaplet initialization.
initDB :: SnapletInit b (DB b)
initDB = makeSnaplet "db" "cached database" Nothing $ do
pgs <- nestSnaplet "pgsql" pgsql pgsInit
cache <- getSomeDataPlease pgs
return $ DB pgs cache
So, the question is: How is it possible to use pgs :: Snaplet Postgres within Initializer monad to read data from db?
The DB access functions provided by snaplet-postgresql-simple run in any monad that is an instance of the HasPostgres type class. Typically, this will be the Handler monad for your application.
You can't use Handler functions inside an Initializer. The whole point of the Initializer monad is to set up the initial state data type that is needed to run the web server and the Handler monad. So it's truly impossible to run handlers inside an initializer--unless of course you're running one web server from inside another web server...ick.
So you have two possible options. You could create a HasPostgres instance for your Initializer. But that doesn't make much sense unless you're connecting to a static server. This might be acceptable if you're doing debugging. Sometimes I'll do that for IO to make it trivial to test my database functions:
instance HasPostgres IO where
getPostgresState = do
pool <- createPool (connect $ ConnectInfo "127.0.0.1" ...) ...
return $ Postgres pool
But in general it won't make sense to make an instance like this for use in production code. This means that if you want to access the database in an Initializer you have to use the postgresql-simple functions directly rather than the wrappers provided by snaplet-postgresql-simple. That's why I exported the pgPool accessor function. It will look something like this:
initDB :: SnapletInit b (DB b)
initDB = makeSnaplet "db" "cached database" Nothing $ do
pgs <- nestSnaplet "pgsql" pgsql pgsInit
let pool = pgPool $ extract pgs
results <- liftIO $ withResource pool (\conn -> query_ conn myQuery)
You can see a real live example of this in snaplet-postgresql-simple's auth backend.
Update:
I just uploaded a new version of snaplet-postgresql-simple to hackage that provides a HasPostgres instance for ReaderT. This allows you to accomplish this more simply with runReaderT. There's a small code snippet of this in the documentation.

How to extract BrowserState from BrowserAction monad?

I'm coding a http client as a learning project with the use of network-http package (http://hackage.haskell.org/package/HTTP-4000.2.2) .
There is a Network.Browser module which defines getBrowserState function.
Probably just a lame beginner question but how can I get BrowserState record from BrowserAction monad if Network.Browser module does not export data constructor or record's lift function ?
import Network.Browser
-- getBrowserState :: BrowserAction t (BrowserState t)
extractBS :: BrowserAction t (BrowserState t) -> BrowserState t
-- ??? implementation ???
In addition what about further handling of BrowserState fields like bsCookies, bsDebug, bsProxy etc. ? (http://hackage.haskell.org/packages/archive/HTTP/4000.2.2/doc/html/src/Network-Browser.html#BrowserState)
What you're trying to do doesn't really make sense. A BrowserAction describes an action, while a BrowserState describes the current state of the browser at some point within an action.
The only way of getting something out of a BrowserAction is to run it with the browse function.
browse :: BrowserAction conn a -> IO a
It's essentially the same as why you can't get a Something out of an IO Something, except here you have the function browse which allows you to "get stuff out of" a browser action by running it.
For example, you can make an action that extracts the current browser state after the original action and run that:
browse (action >> getBrowserState) :: IO (BrowserState conn)
Note the IO in the type here, as running the action may have side effects.
That said, I suspect that what you really want is to make the code that needs the current browser state part of an action.
browse $ do action
state <- getBrowserState
-- do stuff with the state
Note that BrowserAction has a MonadIO instance, so you can still do IO stuff by using liftIO.
browse $ do -- browsing
liftIO $ putStrLn "foo"
-- more browsing
In other words, you shouldn't think of it as getting stuff out of a BrowserAction. You should instead think of how to make the browsing-related code part of a BrowserAction which you then run with browse.

Resources