packages feed

servant-hateoas-0.3.4: src/Servant/Hateoas/Layer/Type.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveAnyClass #-}

module Servant.Hateoas.Layer.Type
(
  -- * Type
  Layer(..),

  -- ** Getter
  LayerApiCs, RelativeChildren, LayerVerb,

  -- * API-construction
  LayerApi, MkPrefix, type (++),

  -- * Intermediate
  Intermediate(..), GetIntermediate
)
where

import Servant
import Servant.Server.Internal.Router
import Servant.Hateoas.Internal.Sym
import Servant.Hateoas.Resource
import Data.Aeson
import Data.Kind

-- | Convenience alias for 'AppendList'.
type (++) xs ys = AppendList xs ys

-- | Data-kind for a layer in an API.
--
-- ==== __Example__
--
-- @
-- ''Layer' '['Sym' \"api\", 'Sym' \"user\"] '['Capture' \"id\" 'Int', 'Sym' \"vip\"] 'GetIntermediate'
-- @
--
-- Represents the API
--
-- @
-- \"api\" :> \"user\" :> 'GetIntermediate'
-- @ with children
--
-- @
-- \"api\" :> \"user\" :> 'Capture' \"id\" 'Int' :> 'GetIntermediate'
-- @ and
--
-- @
-- \"api\" :> \"user\" :> \"vip\" :> 'GetIntermediate'
-- @
data Layer = Layer
  { api              :: [Type]      -- ^ The API of this layer represented as list. Folding it with ':>' results in the actual API, see 'MkPrefix'.
  , relativeChildren :: [Type]      -- ^ All immediate children of this layer.
  , verb             :: Type        -- ^ The 'Verb' for this layer.
  }

-- | Type-level getter for the API of a 'Layer'.
type family LayerApiCs (a :: Layer) where
  LayerApiCs ('Layer api _ _) = api

-- | Type-level getter for the children of a 'Layer'.
type family RelativeChildren (a :: Layer) where
  RelativeChildren ('Layer _ children _) = children

-- | Type-level getter for the verb of a 'Layer'.
type family LayerVerb (a :: Layer) where
  LayerVerb ('Layer _ _ verb) = verb

-- | Constructs the actual API of a 'Layer'.
type family LayerApi (a :: Layer) where
  LayerApi ('Layer api _ verb) = MkPrefix api verb

-- | Folds a list of path segments into an API by intercalating '(:>)'.
--
-- ==== __Example__
--
-- @
-- 'MkPrefix' '['Sym' \"api\", 'Sym' \"user\"] 'GetIntermediate'
-- @ resolves to
--
-- @
-- 'Sym' \"api\" :> 'Sym' \"user\" :> 'GetIntermediate'
-- @
type MkPrefix :: [Type] -> Type -> Type
type family MkPrefix prefix api where
  MkPrefix (Sym x      ': xs) api = x :> MkPrefix xs api
  MkPrefix (x          ': xs) api = x :> MkPrefix xs api
  MkPrefix '[]                api = api

instance HasServer (MkPrefix apiCs verb) context => HasServer ('Layer apiCs cs verb) context where
  type ServerT ('Layer apiCs cs verb) m = ServerT (MkPrefix apiCs verb) m
  route _ = route (Proxy @(MkPrefix apiCs verb))
  hoistServerWithContext _ = hoistServerWithContext (Proxy @(MkPrefix apiCs verb))

instance HasServer ('[] :: [Layer]) context where
  type ServerT '[] m = ServerT EmptyAPI m
  route _ = route (Proxy @EmptyAPI)
  hoistServerWithContext _ = hoistServerWithContext (Proxy @EmptyAPI)

instance (HasServer l context, HasServer ls context) => HasServer (l ': ls :: [Layer]) context where
  type ServerT (l ': ls) m = ServerT l m :<|> ServerT ls m
  route _ ctx delayed = route (Proxy @l) ctx ((\(sl :<|> _) -> sl) <$> delayed) `choice` route (Proxy @ls) ctx ((\(_ :<|> sls) -> sls) <$> delayed)
  hoistServerWithContext _ ctx f (sl :<|> sls) = hoistServerWithContext (Proxy @l) ctx f sl :<|> hoistServerWithContext (Proxy @ls) ctx f sls

-- | A response type for a 'Layer' that does not contain any data.
newtype Intermediate = Intermediate ()
  deriving newtype (Show, Eq, Ord, ToJSON)
  deriving anyclass (ToResource res)

type GetIntermediate = Get '[] Intermediate