{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}

-- | Extras for working with servant
module Hercules.API.Servant
  ( noContent,

    -- * 'Generic'
    useApi,
    useApiE,
    enterApi,
    enterApiE,

    -- * Substitution
    Substitute,
    Placeholder,
  )
where

import Control.Monad (void)
import Data.Kind (Type)
import Servant.API
import Servant.API.Generic
import Prelude

-- | Postcomposes 'Servant.API.Generic.fromServant' to an accessor,
-- preserving the mode parameter, because otherwise the mode parameter
-- can not be inferred.
--
-- Ideally, this functionality would be built into a new combinator.
useApi ::
  forall subapi api mode.
  (GenericServant subapi mode) =>
  (api mode -> ToServant subapi mode) ->
  api mode ->
  subapi mode
useApi = (Servant.API.Generic.fromServant .)

-- | Like 'useApi' but constrains the @auth@ type variable that's passed to
-- subapis.
useApiE ::
  forall subapi api mode a.
  (GenericServant (subapi a) mode) =>
  (api a mode -> ToServant (subapi a) mode) ->
  api a mode ->
  subapi a mode
useApiE = useApi

-- | @flip 'useApi'
enterApi ::
  forall subapi api mode.
  (GenericServant subapi mode) =>
  api mode ->
  (api mode -> ToServant subapi mode) ->
  subapi mode
enterApi = flip useApi

-- | @flip 'useApiE'
enterApiE ::
  forall subapi api mode a.
  (GenericServant (subapi a) mode) =>
  api a mode ->
  (api a mode -> ToServant (subapi a) mode) ->
  subapi a mode
enterApiE = flip useApi

-- | 'Control.Monad.void' specialised to 'NoContent' to soothe the
-- compiler that rightfully warns about throwing away a do notation
-- result. By specialising, we make sure that we still get warnings
-- if the result type changes in the future. (We'll get an error)
noContent :: (Functor m) => m Servant.API.NoContent -> m ()
noContent = void

-- | A reference to the @subapi@ parameter in 'Substitute'
data Placeholder

-- | Replaces 'Placeholder' by @subapi@ in the API @target@.
type family Substitute (target :: k) subapi :: Type

type instance Substitute Placeholder subapi = subapi

type instance Substitute (a :> b) subapi = a :> Substitute b subapi

type instance Substitute (a :<|> b) subapi = Substitute a subapi :<|> Substitute b subapi
