packages feed

servant-checked-exceptions-core-2.0.0.0: example/Api.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}

module Api where

import Data.Aeson
       (FromJSON(parseJSON), ToJSON(toJSON), Value, withText)
import Data.Aeson.Types (Parser)
import Data.String (IsString)
import Data.Text (unpack)
import Network.HTTP.Types (Status, status400, status404)
import Servant.API (Capture, JSON, Post, (:>), (:<|>))
import Text.Read (readMaybe)
import Web.HttpApiData (FromHttpApiData, ToHttpApiData)

import Servant.Checked.Exceptions (NoThrow, Throws)
import Servant.Checked.Exceptions.Internal.Servant.API (ErrStatus(toErrStatus))

---------
-- API --
---------

-- | This is our main 'Api' type.  We will create a server, a client, and
-- documentation for this api.
--
-- This api is composed of three routes, 'ApiStrictSearch', 'ApiLaxSearch', and
-- 'ApiNoErrSearch'.
type Api = ApiStrictSearch :<|> ApiLaxSearch :<|> ApiNoErrSearch

-- | This is a strict search api.  You pass it a @\"query\"@, and it returns a
-- 'SearchResponse'.  It potentially returns a 'BadSearchTermErr' if your query
-- is not the string @\"hello\"@.  It returns an 'IncorrectCapitialization'
-- error if your query is not capitalized like @\"Hello\"@.
--
-- Notice how we are using 'Throws' to indicate we will potentially throw an
-- error.  Also, notice how we can list multiple 'Throws'.
type ApiStrictSearch =
  "strict-search" :>
  Capture "query" SearchQuery :>
  Throws BadSearchTermErr :>
  Throws IncorrectCapitalization :>
  Post '[JSON] SearchResponse

-- | This is similar to 'ApiStrictSearch', but it doesn't force the query to be
-- capitalized correctly.  It only returns a 'BadSearchTermErr'.
type ApiLaxSearch =
  "lax-search" :>
  Capture "query" SearchQuery :>
  Throws BadSearchTermErr :>
  Post '[JSON] SearchResponse

-- | This is similar to 'ApiLaxSearch', but it doesn't force the query to use
-- correct terms.  It does not return an error.
type ApiNoErrSearch =
  "no-err-search" :>
  Capture "query" SearchQuery :>
  NoThrow :>
  Post '[JSON] SearchResponse

------------------------------
-- Parameters and Responses --
------------------------------

-- | This 'SearchQuery' type is just a newtype wrapper around a 'String'.
newtype SearchQuery = SearchQuery
  { unSearchQuery :: String
  } deriving ( Eq
             , FromHttpApiData
             , FromJSON
             , IsString
             , Ord
             , Read
             , Show
             , ToHttpApiData
             , ToJSON
             )

-- | This 'SearchResponse' type is just a newtype wrapper around a 'String'.
newtype SearchResponse = SearchResponse
  { unSearchResponse :: String
  } deriving ( Eq
             , FromHttpApiData
             , FromJSON
             , IsString
             , Ord
             , Read
             , Show
             , ToHttpApiData
             , ToJSON
             )

------------
-- Errors --
------------

-- | This error is returned when the search query is not the string @\"hello\"@.
data BadSearchTermErr = BadSearchTermErr deriving (Eq, Read, Show)

instance ToJSON BadSearchTermErr where
  toJSON :: BadSearchTermErr -> Value
  toJSON = toJSON . show

instance FromJSON BadSearchTermErr where
  parseJSON :: Value -> Parser BadSearchTermErr
  parseJSON = withText "BadSearchTermErr" $
    maybe (fail "could not parse as BadSearchTermErr") pure . readMaybe . unpack

instance ErrStatus BadSearchTermErr where
  toErrStatus :: BadSearchTermErr -> Status
  toErrStatus _ = status404

-- | This error is returned when the search query is @\"hello\"@, but it is not
-- capitalized correctly.  For example, the search query @\"hello\"@ will
-- return an 'IncorrectCapitialization' error.  However, the search query
-- @\"Hello\"@ will return a success.
data IncorrectCapitalization = IncorrectCapitalization deriving (Eq, Read, Show)

instance ToJSON IncorrectCapitalization where
  toJSON :: IncorrectCapitalization -> Value
  toJSON = toJSON . show

instance FromJSON IncorrectCapitalization where
  parseJSON :: Value -> Parser IncorrectCapitalization
  parseJSON = withText "IncorrectCapitalization" $
    maybe (fail "could not parse as IncorrectCapitalization") pure . readMaybe . unpack

instance ErrStatus IncorrectCapitalization where
  toErrStatus :: IncorrectCapitalization -> Status
  toErrStatus _ = status400

----------
-- Port --
----------

-- | The port to run the server on.
port :: Int
port = 8201