{- |
Module      :  Servant.Checked.Exceptions

Copyright   :  Dennis Gosnell 2017
License     :  BSD3

Maintainer  :  Dennis Gosnell (cdep.illabout@gmail.com)
Stability   :  experimental
Portability :  unknown

This module gives you the ability to specify which errors are thrown by a
Servant api.  This is done with the 'Throws' data type.  Here is an example of
creating an api that uses 'Throws':

  type Api =
    \"author\" 'Servant.API.:>'
    'Servant.API.Capture' \"author-id\" AuthorId 'Servant.API.:>'
    'Throws' CouldNotConnectToDbError 'Servant.API.:>'
    'Throws' AuthorNotFoundError 'Servant.API.:>'
    'Servant.API.Get' \'['Servant.API.JSON'] Author

This api will return an @Author@ for a given @AuthorId@.  'Throws' is used
to indicate that this api will potentially return two different errors:
@CouldNotConnectToDbError@ and @AuthorNotFoundError@.

These two errors might be defined like this:

  data CouldNotConnectToDbError = CouldNotConnectToDbError
    deriving ('Eq', 'Read', 'Show')

  data AuthorNotFoundError = AuthorNotFoundError
    deriving ('Eq', 'Read', 'Show')

Writing the server handler for this api will look like the following.  Notice
how the 'Envelope' type is used:

    :: AuthorId
    -> 'Handler' ('Envelope' \'[DatabaseError, AuthorNotFoundError] Author)
  getAuthorHandler authorId = do
    eitherAuthor <- getAuthorFromDb authorId
    case eitherAuthor of
      Left NoDb -> pure $ 'toErrEnvelope' CouldNotConnectToDbError
      Left NoAuthor -> pure $ 'toErrEnvelope' AuthorNotFoundError
      Right author -> pure $ 'toSuccEnvelope' author

  getAuthorFromDb :: AuthorId -> Handler (Either DbErr Author)
  getAuthorFromDb = ...

  data DbErr = NoDb | NoAuthor

@'Envelope' \'[DatabaseError, AuthorNotFoundError] Author@ represents a
response that will contain an @Author@ on success, or contain either a
@DatabaseError@ or a @AuthorNotFoundError@ on error.

Under the hood, 'Envelope' is using an extensible sum-type ('OpenUnion') to
represent possible errors.  Working with an api that returns two possible
errors is just as easy as working with an api that returns three possible

Clients will also use the 'Envelope' type:

    :: AuthorId
    -> 'Servant.Client.ClientM' ('Envelope' \'[DatabaseError, AuthorNotFoundError] Author)
  getAuthor = 'Servant.Client.client' ('Data.Proxy.Proxy' :: 'Data.Proxy.Proxy' Api)

It is easy to do case analysis (similar to pattern matching) on the 'Envelope'
type with the 'catchesEnvelope' function.

Checkout the
<https://github.com/cdepillabout/servant-checked-exceptions/tree/master/example example>
in the repository on Github.  It includes a fleshed-out example of an
<https://github.com/cdepillabout/servant-checked-exceptions/blob/master/servant-checked-exceptions/example/Api.hs api>,
<https://github.com/cdepillabout/servant-checked-exceptions/blob/master/servant-checked-exceptions/example/Server.hs server>,
<https://github.com/cdepillabout/servant-checked-exceptions/blob/master/servant-checked-exceptions/example/Client.hs client>,
<https://github.com/cdepillabout/servant-checked-exceptions/blob/master/servant-checked-exceptions-core/example/Docs.hs documentation>.
The <https://github.com/cdepillabout/servant-checked-exceptions README.md>
shows how to compile and run the examples.

{-# LANGUAGE PackageImports #-}

module Servant.Checked.Exceptions (
    module Servant.Checked.Exceptions
  , module Servant.Checked.Exceptions.Internal
  ) where

import "servant-checked-exceptions-core" Servant.Checked.Exceptions
import Servant.Checked.Exceptions.Internal