servant-response-0.1: src/Servant/Response/Prelude.hs
{-# LANGUAGE DeriveGeneric, MultiParamTypeClasses, OverloadedStrings #-}
module Servant.Response.Prelude
( -- * 'Response' class
module Servant.Response
, -- * Useful reusable types and instances
UpdateResponse(..)
, LookupResponse(..)
) where
import Data.Aeson
import Data.Text
import GHC.Generics
import Network.HTTP.Types.Status
import Servant.Response
-- | A generic response type for any "effectul" operation
-- on a 'Resource', like adding, updating or deleting an item.
--
-- It simply holds a 'Bool' that indicates whether the operation
-- was successful or not, and if it wasn't, it'll embed a text
-- describing what went wrong and is meant to be tagged
-- (see the @o@ type parameter) with the operation it's associated to.
--
-- This lets us have different instances for the standard
-- /Add/ and /Update/ operations for example, where the former
-- should respond with HTTP status code /201/ if the entry was created,
-- whereas the latter should just use status code /200/.
--
-- You can of course skip this one and use a more appropriate
-- for your particular application.
data UpdateResponse o =
UpdateResponse { success :: !Bool
, msg :: !Text
}
deriving (Eq, Show, Generic)
-- | e.g:
--
-- > { "success" : false, "msg" : "couldn't add item: blabla"}
instance ToJSON (UpdateResponse o) where
-- | A generic response type for an operation performing
-- some kind of (potentially failing) lookup of an item
--
-- This is useful when writing a web application, where you
-- want to send for example a JSON message saying the item wasn't found
-- along with status 404 when the item isn't found, but just send the item
-- if it could be found. This is (purposefully) isomorphic to 'Maybe'.
data LookupResponse a =
NotFound
| Found !a
deriving (Eq, Show)
-- | If you have some type convertible to JSON,
-- you can wrap it in 'LookupResponse' whenever you are
-- looking up a value associated to some identifier
-- where the lookup may fail.
-- It'll send the JSON-encoded value if found or
--
-- > { "message" : "Not found" }
--
-- if not found. This makes sure you send /valid/
-- JSON through the wires even when the target doesn't exist.
instance ToJSON a => ToJSON (LookupResponse a) where
toJSON NotFound = object [ "message" .= ("Not found" :: Text) ]
toJSON (Found x) = toJSON x
-- | Make 'LookupResponse' a proper 'Response' for
-- 'Service.Context.Context' lookups returning a 'Maybe' value,
-- returning 404 when Nothing is returned, along with a not found
-- message in json. Used by 'View'.
instance ToJSON a => Response (LookupResponse a) (Maybe a) where
toResponse Nothing = (NotFound, status404)
toResponse (Just v) = (Found v, status200)
-- | Just send the list of entries as a JSON array,
-- with status code 200. Used by 'ListAll'.
instance ToJSON a => Response [a] [a] where
toResponse list = (list, status200)