packages feed

gingersnap 0.1.3.0 → 0.1.4.0

raw patch · 3 files changed

+185/−5 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Gingersnap.Core: createPool :: () => IO a -> a -> IO () -> Int -> NominalDiffTime -> Int -> IO Pool a
+ Gingersnap.Core: data Connection
+ Gingersnap.Core: data Pool a

Files

+ README.md view
@@ -0,0 +1,170 @@+# Gingersnap++What _is_ Gingersnap? Not a web framework: that's Snap Core's job.+More a set of lightweight idioms for building a resource-safe JSON API with+Aeson and postgresql-simple.++As it's just a set of idioms, it's easy to only use 'em where you need 'em.+An app could have only a single endpoint that uses Gingersnap, with the rest+using plain Snap Core.++How do we use it? This README is also a Literate Haskell file so it's a full+example you can run with markdown-unlit. Let's get started:++## Imports at the top!++A few imports we'll need for this tutorial:++```haskell+{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}++import Data.Aeson (ToJSON)+import qualified Database.PostgreSQL.Simple as PSQL+ -- For our automatic JSON instance:+import GHC.Generics (Generic)+import Gingersnap.Core+import Snap.Core+-- From the 'snap-server' package:+import Snap.Http.Server (quickHttpServe)+```++## A first endpoint++Now that we've got our imports, let's jump into defining an endpoint. We'll+define a little bit of setup code later on in the file.++```haskell+data SomeData = SomeData Int Bool+ deriving (Show, Generic)++instance ToJSON SomeData++one :: Ctx -> Snap ()+one ctx =+   pureRsp ctx $ rspGood $ SomeData 5 True+```++You can run the code from this file with++    $ cabal update+    $ cabal install gingersnap snap-server+    $ ghci -pgmL markdown-unlit README.lhs   # The "-pgmL" is just for this README++And calling "main". In another window, if you call:++    $ curl 'localhost:8000/one'++You should get back:++    {"result":[5,true]}++A few things to notice:+  - The endpoint takes as an argument a "Ctx". We'll see the definition of that+    later.+  - The endpoint returns our data with "rspGood". More on that in a moment.+  - The response came wrapped in a "result" JSON object. You can customize that+    behavior but we'll use the default here.++So, what's "rspGood"? Well, it has the type++    rspGood :: ToJSON x => x -> Rsp++The "Rsp" type is one of the core types in Gingersnap. Keep an eye out for it later.++## Defining "main"++Let's now look at how we defined our main function.++```haskell+main :: IO ()+main = do+   ctx <- makeCtx+   quickHttpServe $ route [+        ("one", one ctx)+      , ("two", two ctx)+      ]+```++Other than "ctx", this isn't Gingersnap-specific at all: just a simple+Snap Core server. "makeCtx" is a function we define ourselves. It creates a+value of type "Ctx", which we define ourselves, and which is an instance of+"IsCtx".++## IsCtx++The idea of "IsCtx" is that it allows us to thread whatever data we need through+to our endpoints. We'll definitely need a database connection (pool), but it's a+typeclass, so you can define whatever other fields you'd like to pass to your+handlers in the type that's an instance of that class.++For example, if you're using the 'auto-update' package to efficiently run+periodic actions (like getting the current time), you may want to create another+set of fields in your "Ctx" type to easily thread auto-update's actions through+to your handlers, too.++So let's define our own! We unimaginitavely call it "Ctx":++```haskell+data Ctx = Ctx { ctx_db :: Pool PSQL.Connection }++instance IsCtx Ctx where+   ctxConnectionPool = ctx_db+```++And then define a simple "makeCtx":++```haskell+makeCtx :: IO Ctx+makeCtx = do++   -- Setting up the DB connection pool:+   let connString = " host=localhost port=5432 dbname=postgres user=postgres "+   pool <- createPool (PSQL.connectPostgreSQL connString) PSQL.close 1 5 20++   pure $ Ctx { ctx_db = pool }+```++And that's that!++## Talking to the database++Now that we've got (through Ctx) a DB connection pool, let's query the DB:+++```haskell+two :: Ctx -> Snap ()+two ctx = do+   inTransaction ctx $ \conn -> do+      [PSQL.Only x] <- PSQL.query_ conn " SELECT 2 + 2 "+      pure $ rspGood $ SomeData x True+```++    $ curl 'localhost:8000/two'+    {"result":[4,true]}++Nice! This uses "inTransaction", another core tool in Gingersnap:++    inTransaction :: IsCtx ctx => ctx -> (Connection -> IO Rsp) -> Snap ()++We've already seen IsCtx and Rsp, so the main thing to notice here is that the+action we pass to inTransaction is passed a Connection and is in IO, not in Snap+(or MonadSnap). This gives us a few nice things:+  - If you're in Snap, you can accidentally leak a Connection resource by calling+    finishWith while in the middle of a transaction or DB action. We don't have this+    problem.+  - We can choose at any time whether to commit or rollback. The "Rsp" type carries+    information about whether to commit or rollback (e.g. "rspGood" will commit,+    "rspBadRollback" won't). This is in contrast to "withTransaction", that'll+    only roll back if there's an exception, and in contrast to manually beginning+    a transaction, which provides no check that we properly commit or rollback+    (e.g. we could forget to commit/rollback in one case among many in a complicated+    statement)+++## Other things to discover++This tutorial is a work in progress, and these'll be the next concepts to be+touched on:++  - "Not everything's rspGood": rspBadRollback, ApiErr, and others+  - reqObject and (.!) for consuming JSON
gingersnap.cabal view
@@ -1,7 +1,9 @@ name:                gingersnap-version:             0.1.3.0+version:             0.1.4.0 synopsis:            snap-core + aeson + postgresql-simple = delicious-description:         JSON API idioms for snap-core, preventing DB connection leaks+description:+  JSON API idioms for snap-core, preventing DB connection leaks.+  See the README for a tutorial and example use. license:             BSD3 license-file:        LICENSE author:              Tinybop Labs, tom-bop@@ -10,7 +12,9 @@ -- copyright:            category:            Web build-type:          Simple-extra-source-files:  ChangeLog.md+extra-source-files:+  ChangeLog.md+  README.md cabal-version:       >=1.10  source-repository head
src/Gingersnap/Core.hs view
@@ -36,6 +36,12 @@    , RspPayload(..)    , ShouldCommitOrRollback(..) ++   -- * Reexports, for convenience+   , Pool+   , createPool+   , Connection+    -- Maybe?:    -- , module import Network.HTTP.Types.Status    -- , module Snap.Core@@ -49,7 +55,7 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8 import qualified Data.ByteString.Lazy as BSL-import Data.Pool (Pool)+import Data.Pool (Pool, createPool) import qualified Data.Pool as Pool import Database.PostgreSQL.Simple (Connection) import qualified Database.PostgreSQL.Simple as PSQL@@ -110,7 +116,7 @@ data RspPayload    = forall x. ToJSON x => RspPayload_Good x    | forall e. ApiErr e => RspPayload_Bad e-   -- First ByteString is MIME type; second is response body:+   -- | First ByteString is MIME type; second is response body    | RspPayload_Custom HTTP.Status BS.ByteString BSL.ByteString    | RspPayload_Empty -- This might be a dupe with '_Custom' but it's nice to have