2016-02-28 23:23:32 +01:00
|
|
|
{-# 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
|
|
|
|
|
2016-03-01 02:53:49 +01:00
|
|
|
-- | 'Context's are used to pass values to combinators. (They are __not__ meant
|
|
|
|
-- to be used to pass parameters to your handlers, i.e. they should not replace
|
|
|
|
-- any custom 'Control.Monad.Trans.Reader.ReaderT'-monad-stack that you're using
|
2016-04-28 16:26:27 +02:00
|
|
|
-- with 'Servant.Utils.Enter'.) If you don't use combinators that
|
2016-03-01 02:53:49 +01:00
|
|
|
-- require any context entries, you can just use 'Servant.Server.serve' as always.
|
|
|
|
--
|
|
|
|
-- If you are using combinators that require a non-empty 'Context' you have to
|
|
|
|
-- use 'Servant.Server.serveWithContext' and pass it a 'Context' that contains all
|
|
|
|
-- the values your combinators need. A 'Context' is essentially a heterogenous
|
|
|
|
-- list and accessing the elements is being done by type (see 'getContextEntry').
|
|
|
|
-- The parameter of the type 'Context' is a type-level list reflecting the types
|
|
|
|
-- of the contained context entries. To create a 'Context' with entries, use the
|
|
|
|
-- operator @(':.')@:
|
2016-02-28 23:23:32 +01:00
|
|
|
--
|
|
|
|
-- >>> :type True :. () :. EmptyContext
|
|
|
|
-- True :. () :. EmptyContext :: Context '[Bool, ()]
|
|
|
|
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
|
|
|
|
-- ...
|
2016-04-17 20:50:45 +02:00
|
|
|
-- ...No instance for (HasContextEntry '[] [Char])
|
2016-02-28 23:23:32 +01:00
|
|
|
-- ...
|
|
|
|
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
|