content: {-# LANGUAGE ConstraintKinds       #-}
  {-# LANGUAGE DataKinds             #-}
  {-# LANGUAGE FlexibleContexts      #-}
  {-# LANGUAGE FlexibleInstances     #-}
  {-# LANGUAGE GADTs                 #-}
  {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE PolyKinds             #-}
  {-# LANGUAGE RankNTypes            #-}
  {-# LANGUAGE TypeFamilies          #-}
  {-# LANGUAGE TypeOperators         #-}
  {-# LANGUAGE UndecidableInstances  #-}
  {-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
  
  -- | This module provides typeclasses for describing protobuf service metadata.
  -- It is intended to be used by library authors to generating bindings against
  -- proto services for specific RPC backends.
  module Data.ProtoLens.Service.Types
    ( Service (..)
    , HasAllMethods
    , HasMethodImpl (..)
    , HasMethod
    , StreamingType (..)
    ) where
  
  import qualified Data.ByteString as B
  import Data.Kind (Constraint, Type)
  import Data.ProtoLens.Message (Message)
  import Data.Proxy (Proxy(..))
  import GHC.TypeLits
  
  
  -- | Reifies the fact that there is a 'HasMethod' instance for every symbol
  -- claimed by the 'ServiceMethods' associated type.
  class HasAllMethods s (ms :: [Symbol])
  instance HasAllMethods s '[]
  instance (HasAllMethods s ms, HasMethodImpl s m) => HasAllMethods s (m ': ms)
  
  
  -- | Metadata describing a protobuf service. The 's' parameter is a phantom
  -- type generated by proto-lens.
  --
  -- The 'ServiceName' and 'ServicePackage' associated type can be used to
  -- generate RPC endpoint paths.
  --
  -- 'ServiceMethods' is a promoted list containing every method defined on the
  -- service. As witnessed by the 'HasAllMethods' superclass constraint here,
  -- this type can be used to discover every instance of 'HasMethod' available
  -- for the service.
  class ( KnownSymbol (ServiceName s)
        , KnownSymbol (ServicePackage s)
        , HasAllMethods s (ServiceMethods s)
        ) => Service s where
    type ServiceName s    :: Symbol
    type ServicePackage s :: Symbol
    type ServiceMethods s :: [Symbol]
  
    packedServiceDescriptor :: Proxy s -> B.ByteString
  
  ------------------------------------------------------------------------------
  -- | Data type to be used as a promoted type for 'MethodStreamingType'.
  data StreamingType
    = NonStreaming
    | ClientStreaming
    | ServerStreaming
    | BiDiStreaming
    deriving (Eq, Ord, Enum, Bounded, Read, Show)
  
  
  -- | Metadata describing a service method. The 'MethodInput' and 'MethodOutput'
  -- type families correspond to the 'Message's generated by proto-lens for the
  -- RPC as described in the protobuf.
  --
  -- 'IsClientStreaming' and 'IsServerStreaming' can be used to dispatch on
  -- library code which wishes to provide different interfaces depending on the
  -- type of streaming of the method.
  --
  -- Library code should use 'HasMethod' instead of this class directly whenever
  -- the constraint will be exposed to the end user. 'HasMethod' provides
  -- substantially friendlier error messages when used incorrectly.
  class ( KnownSymbol m
        , KnownSymbol (MethodName s m)
        , Service s
        , Message (MethodInput  s m)
        , Message (MethodOutput s m)
        ) => HasMethodImpl s (m :: Symbol) where
    type MethodName          s m :: Symbol
    type MethodInput         s m :: Type
    type MethodOutput        s m :: Type
    type MethodStreamingType s m :: StreamingType
  
  
  -- | Helper constraint that expands to a user-friendly error message when 'm'
  -- isn't actually a method provided by service 's'.
  type HasMethod s m =
    ( RequireHasMethod s m (ListContains m (ServiceMethods s))
    , HasMethodImpl s m
    )
  
  
  -- | Outputs an error message saying that the given method wasn't found, and
  -- suggests alternatives the user might have wanted.
  type family RequireHasMethod s (m :: Symbol) (h :: Bool) :: Constraint where
    RequireHasMethod s m 'False = TypeError
         ( 'Text "No method "
     ':<>: 'ShowType m
     ':<>: 'Text " available for service '"
     ':<>: 'ShowType s
     ':<>: 'Text "'."
     ':$$: 'Text "Available methods are: "
     ':<>: ShowList (ServiceMethods s)
         )
    RequireHasMethod s m 'True = ()
  
  
  -- | Expands to 'True' when 'n' is in promoted list 'hs', 'False' otherwise.
  type family ListContains (n :: k) (hs :: [k]) :: Bool where
    ListContains n '[]       = 'False
    ListContains n (n ': hs) = 'True
    ListContains n (x ': hs) = ListContains n hs
  
  
  -- | Pretty prints a promoted list.
  type family ShowList (ls :: [k]) :: ErrorMessage where
    ShowList '[]  = 'Text ""
    ShowList '[x] = 'ShowType x
    ShowList (x ': xs) =
      'ShowType x ':<>: 'Text ", " ':<>: ShowList xs
  
  