content: {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE InstanceSigs #-}
  {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE PolyKinds #-}
  {-# LANGUAGE RankNTypes #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE TypeApplications #-}
  {-# LANGUAGE TypeFamilies #-}
  {-# LANGUAGE TypeOperators #-}
  {-# LANGUAGE UndecidableInstances #-}
  {-# OPTIONS_GHC -fno-warn-orphans #-}
  
  {- |
  Module      :  Servant.Checked.Exceptions.Internal.Servant.Client
  
  Copyright   :  Dennis Gosnell 2017
  License     :  BSD3
  
  Maintainer  :  Dennis Gosnell (cdep.illabout@gmail.com)
  Stability   :  experimental
  Portability :  unknown
  
  This module only exports 'HasClient' instances for 'Throws' and 'Throwing'.
  -}
  
  module Servant.Checked.Exceptions.Internal.Servant.Client where
  
  import Data.Proxy (Proxy(Proxy))
  import Servant.API (Verb, (:>), (:<|>))
  import Servant.Client (HasClient(clientWithRoute, Client))
  import Servant.Client.Core
  
  import Servant.Checked.Exceptions.Internal.Envelope (Envelope)
  import Servant.Checked.Exceptions.Internal.Servant.API
         (NoThrow, Throws, Throwing, ThrowingNonterminal)
  
  -- TODO: Make sure to also account for when headers are being used.
  
  -- | Change a 'Throws' into 'Throwing'.
  instance (RunClient m, HasClient m (Throwing '[e] :> api)) => HasClient m (Throws e :> api) where
    type Client m (Throws e :> api) = Client m (Throwing '[e] :> api)
  
    clientWithRoute
      :: Proxy m
      -> Proxy (Throws e :> api)
      -> Request
      -> Client m (Throwing '[e] :> api)
    clientWithRoute p Proxy = clientWithRoute p (Proxy @(Throwing '[e] :> api))
  
    hoistClientMonad
      :: Proxy m
      -> Proxy (Throws e :> api)
      -> (forall x. mon x -> mon' x)
      -> Client mon (Throws e :> api)
      -> Client mon' (Throwing '[e] :> api)
    hoistClientMonad pm _ = hoistClientMonad pm (Proxy @(Throwing '[e] :> api))
  
  
  -- | When @'Throwing' es@ comes before a 'Verb', change it into the same 'Verb'
  -- but returning an @'Envelope' es@.
  instance (HasClient m (Verb method status ctypes (Envelope es a))) =>
      HasClient m (Throwing es :> Verb method status ctypes a) where
  
    type Client m (Throwing es :> Verb method status ctypes a) =
      Client m (Verb method status ctypes (Envelope es a))
  
    clientWithRoute
      :: Proxy m
      -> Proxy (Throwing es :> Verb method status ctypes a)
      -> Request
      -> Client m (Verb method status ctypes (Envelope es a))
    clientWithRoute p Proxy =
      clientWithRoute p (Proxy :: Proxy (Verb method status ctypes (Envelope es a)))
  
    hoistClientMonad
      :: Proxy m
      -> Proxy (Throwing es :> Verb method status ctypes a)
      -> (forall x. mon x -> mon' x)
      -> Client mon (Throwing es :> Verb method status ctypes a)
      -> Client mon' (Verb method status ctypes (Envelope es a))
    hoistClientMonad pm _ =
      hoistClientMonad pm (Proxy @(Verb method status ctypes (Envelope es a)))
  
  
  
  -- | When 'NoThrow' comes before a 'Verb', change it into the same 'Verb'
  -- but returning an @'Envelope' \'[]@.
  instance (RunClient m, HasClient m (Verb method status ctypes (Envelope '[] a))) =>
      HasClient m (NoThrow :> Verb method status ctypes a) where
  
    type Client m (NoThrow :> Verb method status ctypes a) =
      Client m (Verb method status ctypes (Envelope '[] a))
  
    clientWithRoute
      :: Proxy m
      -> Proxy (NoThrow :> Verb method status ctypes a)
      -> Request
      -> Client m (Verb method status ctypes (Envelope '[] a))
    clientWithRoute p Proxy =
      clientWithRoute p (Proxy :: Proxy (Verb method status ctypes (Envelope '[] a)))
  
    hoistClientMonad
      :: Proxy m
      -> Proxy (NoThrow :> Verb method status ctypes a)
      -> (forall x. mon x -> mon' x)
      -> Client mon (NoThrow :> Verb method status ctypes a)
      -> Client mon' (Verb method status ctypes (Envelope '[] a))
    hoistClientMonad pm _ =
      hoistClientMonad pm (Proxy @(Verb method status ctypes (Envelope '[] a)))
  
  
  -- | When @'Throwing' es@ comes before ':<|>', push @'Throwing' es@ into each
  -- branch of the API.
  instance (RunClient m, HasClient m ((Throwing es :> api1) :<|> (Throwing es :> api2))) =>
      HasClient m (Throwing es :> (api1 :<|> api2)) where
  
    type Client m (Throwing es :> (api1 :<|> api2)) =
      Client m ((Throwing es :> api1) :<|> (Throwing es :> api2))
  
    clientWithRoute
      :: Proxy m
      -> Proxy (Throwing es :> (api1 :<|> api2))
      -> Request
      -> Client m ((Throwing es :> api1) :<|> (Throwing es :> api2))
    clientWithRoute p _ =
      clientWithRoute p (Proxy :: Proxy ((Throwing es :> api1) :<|> (Throwing es :> api2)))
  
    hoistClientMonad
      :: Proxy m
      -> Proxy (Throwing es :> (api1 :<|> api2))
      -> (forall x. mon x -> mon' x)
      -> Client mon (Throwing es :> (api1 :<|> api2))
      -> Client mon' ((Throwing es :> api1) :<|> (Throwing es :> api2))
    hoistClientMonad pm _ =
      hoistClientMonad pm (Proxy @(Throwing es :> (api1 :<|> api2)))
  
  
  -- | When 'NoThrow' comes before ':<|>', push 'NoThrow' into each branch of the
  -- API.
  instance (RunClient m, HasClient m ((NoThrow :> api1) :<|> (NoThrow :> api2))) =>
      HasClient m (NoThrow :> (api1 :<|> api2)) where
  
    type Client m (NoThrow :> (api1 :<|> api2)) =
      Client m ((NoThrow :> api1) :<|> (NoThrow :> api2))
  
    clientWithRoute
      :: Proxy m
      -> Proxy (NoThrow :> (api1 :<|> api2))
      -> Request
      -> Client m ((NoThrow :> api1) :<|> (NoThrow :> api2))
    clientWithRoute p _ =
      clientWithRoute p (Proxy :: Proxy ((NoThrow :> api1) :<|> (NoThrow :> api2)))
  
    hoistClientMonad
      :: Proxy m
      -> Proxy (NoThrow :> (api1 :<|> api2))
      -> (forall x. mon x -> mon' x)
      -> Client mon (NoThrow :> (api1 :<|> api2))
      -> Client mon' ((NoThrow :> api1) :<|> (NoThrow :> api2))
    hoistClientMonad pm _ =
      hoistClientMonad pm (Proxy @(NoThrow :> (api1 :<|> api2)))
  
  
  -- | When a @'Throws' e@ comes immediately after a @'Throwing' es@, 'Snoc' the
  -- @e@ onto the @es@. Otherwise, if @'Throws' e@ comes before any other
  -- combinator, push it down so it is closer to the 'Verb'.
  instance (RunClient m, HasClient m (ThrowingNonterminal (Throwing es :> api :> apis))) =>
      HasClient m (Throwing es :> api :> apis) where
  
    type Client m (Throwing es :> api :> apis) =
      Client m (ThrowingNonterminal (Throwing es :> api :> apis))
  
    clientWithRoute
      :: Proxy m
      -> Proxy (Throwing es :> api :> apis)
      -> Request
      -> Client m (ThrowingNonterminal (Throwing es :> api :> apis))
    clientWithRoute p _ =
      clientWithRoute p (Proxy :: Proxy (ThrowingNonterminal (Throwing es :> api :> apis)))
  
    hoistClientMonad
      :: Proxy m
      -> Proxy (Throwing es :> api :> apis)
      -> (forall x. mon x -> mon' x)
      -> Client mon (Throwing es :> api :> apis)
      -> Client mon' (ThrowingNonterminal (Throwing es :> api :> apis))
    hoistClientMonad pm _ =
      hoistClientMonad pm (Proxy @(ThrowingNonterminal (Throwing es :> api :> apis)))
  
  
  -- | When 'NoThrow' comes before any other combinator, push it down so it is
  -- closer to the 'Verb'.
  instance (RunClient m, HasClient m (api :> NoThrow :> apis)) =>
      HasClient m (NoThrow :> api :> apis) where
  
    type Client m (NoThrow :> api :> apis) =
      Client m (api :> NoThrow :> apis)
  
    clientWithRoute
      :: Proxy m
      -> Proxy (NoThrow :> api :> apis)
      -> Request
      -> Client m (api :> NoThrow :> apis)
    clientWithRoute p _ =
      clientWithRoute p (Proxy :: Proxy (api :> NoThrow :> apis))
  
    hoistClientMonad
      :: Proxy m
      -> Proxy (NoThrow :> api :> apis)
      -> (forall x. mon x -> mon' x)
      -> Client mon (NoThrow :> api :> apis)
      -> Client mon' (api :> NoThrow :> apis)
    hoistClientMonad pm _ =
      hoistClientMonad pm (Proxy @(api :> NoThrow :> apis))
  