packages feed

gingersnap 0.2.2.2 → 0.2.2.3

raw patch · 3 files changed

+64/−23 lines, 3 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Gingersnap.Core: ReqObject :: ctx -> (HashMap Text Value) -> ReqObject ctx
+ Gingersnap.Core: ReqObject :: ctx -> HashMap Text Value -> ReqObject ctx
- Gingersnap.Core: createPool :: () => IO a -> a -> IO () -> Int -> NominalDiffTime -> Int -> IO Pool a
+ Gingersnap.Core: createPool :: IO a -> (a -> IO ()) -> Int -> NominalDiffTime -> Int -> IO (Pool a)
- Gingersnap.Core: ctxErr :: IsCtx ctx => ctx -> (CtxErrType ctx) -> (CtxErrType ctx)
+ Gingersnap.Core: ctxErr :: IsCtx ctx => ctx -> CtxErrType ctx -> CtxErrType ctx

Files

README.md view
@@ -18,12 +18,13 @@ ```haskell {-# LANGUAGE DeriveGeneric, OverloadedStrings #-} -import Data.Aeson (ToJSON, (.=))+import Control.Monad (when)+import Data.Aeson (ToJSON, (.=), FromJSON) import Database.PostgreSQL.Simple  -- For our automatic JSON instance: import GHC.Generics (Generic) import Gingersnap.Core-import Network.HTTP.Types.Status (internalServerError500)+import Network.HTTP.Types.Status (internalServerError500, unprocessableEntity422) import Snap.Core -- From the 'snap-server' package: import Snap.Http.Server (quickHttpServe)@@ -39,6 +40,7 @@  deriving (Show, Generic)  instance ToJSON Character+instance FromJSON Character  one :: Ctx -> Snap () one ctx =@@ -84,6 +86,7 @@         ("one", one ctx)       , ("two", two ctx)       , ("three", three ctx)+      , ("four", method POST $ four ctx)       ] ``` @@ -142,7 +145,7 @@ ```      $ curl 'localhost:8000/two'-    {"result":{"age":6,"name":"Calvin"}} ~ $+    {"result":{"age":6,"name":"Calvin"}}  Nice! This uses "inTransaction", another core tool in Gingersnap: @@ -162,6 +165,12 @@     (e.g. we could forget to commit/rollback in one case among many in a complicated     branching expression) +If there's an error while running the `(Connection -> IO Rsp)` action:+  - The transaction will be rolled back+  - The `Connection` will be returned to the connection pool+  - A JSON error response will be returned. If you're defining your own `ApiErr`+    instance, that'll be `apiErr_unexpectedError`+ ## Not everything's rspGood  What if we don't want to send a successful ok200 message? Or maybe we don't even@@ -204,7 +213,7 @@     [...]     < HTTP/1.1 500 Internal Server Error     [...]-    {"errorCode":6,"errorVals":[["n",0.250084751285613]],"errorMessage":"'n' too small"}+    {"errorCode":6,"errorVals":[["n",0.2500847]],"errorMessage":"'n' too small"}  If you'd like to write your own `ApiErr` instance (which you probably should if you're building something "real":@@ -212,6 +221,45 @@     for how to get a consistent JSON result.   - You'll want to make that type the "CtxErrType" associated type for IsCtx +## Getting JSON++We've sent a lot of JSON, but let's receive some!++```haskell+four :: Ctx -> Snap ()+four ctx = do+   o <- reqObject ctx+   character <- o .! "character"+   amtToAge <- o .! "amt_to_age"++   when (amtToAge < (0 :: Int)) $+      errorEarlyCode $+         DefaultApiErr_Custom unprocessableEntity422 "Can't age backwards!" []++   inTransaction ctx $ \conn -> do+      [Only newAge] <- query conn " SELECT ? + ? " (age character, amtToAge)+      pure $ rspGood $ character { age = newAge }+```++(Note with a custom `ApiErr` instance you won't have to specify the HTTP+response like 'unprocessableEntity422' etc.)++    $ curl 'localhost:8000/four' --data '{"character": {"name": "Calvin", "age": 6}, "amt_to_age": 1}'++    {"result":{"age":7,"name":"Calvin"}}++`(.!)` requires that the value be present and short-circuits with a JSON error+if it's not or if it's malformed (i.e. if `fromJSON` would fail)++`(.!?)` doesn't require the value to be present - it returns a `Maybe` value. If+the key is present but the value is malformed, however, it'll return a JSON error.++A few things to note:+  - If everything's not okay with the JSON inputs to the function, we never even+    make it to the DB.+  - `errorEarlyCode` is like (and uses) Snap's `finishWith`, but we don't have+    to worry about a resource leak since `inTransaction` is separate+ ## Not everything's JSON  A JSON API is usually JSON but if you'd like to e.g. send a CSV or a file there@@ -219,10 +267,10 @@  If you don't see a function you need it's easy to define your own. + ## Other things to discover  This tutorial is a work in progress, and these'll be the next concepts to be touched on: -  - reqObject and (.!) for receiving JSON   - returning JSON even when throwing an error
gingersnap.cabal view
@@ -1,5 +1,5 @@ name:                gingersnap-version:             0.2.2.2+version:             0.2.2.3 synopsis:   Tools for consistent and safe JSON APIs with snap-core and postgresql-simple description:
src/Gingersnap/Core.hs view
@@ -12,16 +12,16 @@  module Gingersnap.Core (    -- * Rsp-     Rsp(..)--   , rspGood+     rspGood    , rspBad    , rspBadCommit    , rspBadRollback    , rspGoodCSV    , rspGoodLBS    , rspEmptyGood+   , Rsp(..) +    -- * pureRsp    , pureRsp @@ -321,11 +321,7 @@          RspPayload_Empty -> "Empty"          RspPayload_Custom a b c -> "(Custom "++show (a,b,c)++")" --- | *If you hit the DB, use this function!*--- ---   This is a lot like 'withTransaction', but it allows us to rollback if we---   want, without throwing an error.---   (Don't use 'withTransaction'!)+-- | _If you hit the DB, use this function!_ --  --   NOTE this is for IO actions, not Snap actions. This is to ensure we can't --   call e.g. 'finishEarly' and never hit the 'end transaction' code!@@ -344,7 +340,7 @@ inTransaction_readOnly ctx f =    inTransactionMode ctx PSQL.Serializable PSQL.ReadOnly f --- | YOU SHOULD ONLY USE THIS ONCE+-- | _You should only use this once!_ --  --   This lets you do a write transaction during read-only mode (not a --     read-only transaction! A time where 'ctxGetReadOnlyMode' would return@@ -420,15 +416,12 @@       Snap.setHeader "Content-Type" "application/json"    Snap.writeLBS $ JSON.encode $ x --- | NOTE: be very careful to not use this with any setup/teardown block like 'withTransaction'---      - causes resource leaks---      - BUT! This should never happen to you because all your DB code should---        use 'inTransaction'!--- ---   NOTE: use 403 forbidden instead of unauthorized - unauth means not logged in at all------   Also note this returns any 'Snap x' so you can use it like a throw anywhere+-- | This returns any 'Snap x' so you can use it like a throw anywhere --     in your snap code+-- +--   NOTE: if you ever use 's 'withTransaction' (which we don't recommend!)+--     this function has the same caveats as 'finishWith'+ errorEarlyCode :: ApiErr ae => ae -> Snap x errorEarlyCode err = do    writeApiErr err