{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
#include "overlapping-compat.h"
module Servant.Server.Internal.Context where
import Data.Proxy
import GHC.TypeLits
data Context contextTypes where
EmptyContext :: Context '[]
(:.) :: x -> Context xs -> Context (x ': xs)
infixr 5 :.
instance Show (Context '[]) where
show EmptyContext = "EmptyContext"
instance (Show a, Show (Context as)) => Show (Context (a ': as)) where
showsPrec outerPrecedence (a :. as) =
showParen (outerPrecedence > 5) $
shows a . showString " :. " . shows as
instance Eq (Context '[]) where
_ == _ = True
instance (Eq a, Eq (Context as)) => Eq (Context (a ': as)) where
x1 :. y1 == x2 :. y2 = x1 == x2 && y1 == y2
-- | This class is used to access context entries in 'Context's. 'getContextEntry'
-- returns the first value where the type matches:
--
-- >>> getContextEntry (True :. False :. EmptyContext) :: Bool
-- True
--
-- If the 'Context' does not contain an entry of the requested type, you'll get
-- an error:
--
-- >>> getContextEntry (True :. False :. EmptyContext) :: String
-- ...
-- ...No instance for (HasContextEntry '[] [Char])
-- ...
class HasContextEntry (context :: [*]) (val :: *) where
getContextEntry :: Context context -> val
instance OVERLAPPABLE_
HasContextEntry xs val => HasContextEntry (notIt ': xs) val where
getContextEntry (_ :. xs) = getContextEntry xs
instance OVERLAPPING_
HasContextEntry (val ': xs) val where
getContextEntry (x :. _) = x
-- * support for named subcontexts
-- | Normally context entries are accessed by their types. In case you need
-- to have multiple values of the same type in your 'Context' and need to access
-- them, we provide 'NamedContext'. You can think of it as sub-namespaces for
-- 'Context's.
data NamedContext (name :: Symbol) (subContext :: [*])
= NamedContext (Context subContext)
-- | 'descendIntoNamedContext' allows you to access `NamedContext's. Usually you
-- won't have to use it yourself but instead use a combinator like
-- 'Servant.API.WithNamedContext.WithNamedContext'.
--
-- This is how 'descendIntoNamedContext' works:
--
-- >>> :set -XFlexibleContexts
-- >>> let subContext = True :. EmptyContext
-- >>> :type subContext
-- subContext :: Context '[Bool]
-- >>> let parentContext = False :. (NamedContext subContext :: NamedContext "subContext" '[Bool]) :. EmptyContext
-- >>> :type parentContext
-- parentContext :: Context '[Bool, NamedContext "subContext" '[Bool]]
-- >>> descendIntoNamedContext (Proxy :: Proxy "subContext") parentContext :: Context '[Bool]
-- True :. EmptyContext
descendIntoNamedContext :: forall context name subContext .
HasContextEntry context (NamedContext name subContext) =>
Proxy (name :: Symbol) -> Context context -> Context subContext
descendIntoNamedContext Proxy context =
let NamedContext subContext = getContextEntry context :: NamedContext name subContext
in subContext