{-# LANGUAGE TypeFamilies #-}
-- |
-- Module      :  Pinch
-- Copyright   :  (c) Abhinav Gupta 2015
-- License     :  BSD3
--
-- Maintainer  :  Abhinav Gupta <mail@abhinavg.net>
-- Stability   :  experimental
--
-- Pinch defines machinery to specify how types can be encoded into or decoded
-- from Thrift payloads.
--
module Pinch
    (

    -- * Serializing and deserializing

    -- $encodeDecodeValues

      encode
    , decode
    , decodeWithLeftovers

    -- * RPC

    -- $rpc

    , encodeMessage
    , decodeMessage

    -- * Pinchable

    , Pinchable(..)
    , Parser
    , runParser

    -- ** Automatically deriving instances

    -- | Pinch supports deriving instances of 'Pinchable' automatically for
    -- types that implement the @Generic@ typeclass provided that they follow
    -- the outlined patterns in their constructors.

    -- *** Structs and exceptions
    -- $genericStruct

    -- *** Unions
    -- $genericUnion

    , Field(..)
    , getField
    , putField
    , field

    , Void(..)

    -- *** Enums
    -- $genericEnum

    , Enumeration(..)
    , enum

    -- ** Manually writing instances

    -- | Instances of 'Pinchable' can be constructed by composing together
    -- existing instances and using the '.=', '.:', etc. helpers.

    -- *** Structs and exceptions
    -- $struct

    -- *** Unions
    -- $union

    -- *** Enums
    -- $enum

    -- ** Helpers

    -- *** @pinch@

    , (.=)
    , (?=)
    , struct
    , union
    , FieldPair

    -- *** @unpinch@

    , (.:)
    , (.:?)

    -- * Value

    -- | 'Value' is an intermediate representation of Thrift payloads tagged
    -- with TType tags. Types that want to be serialized into\/deserialized
    -- from Thrift payloads need only define a way to convert themselves to
    -- and from 'Value' objects via 'Pinchable'.

    , Value
    , SomeValue(..)

    -- * Messages

    , Message
    , mkMessage
    , messageName
    , messageType
    , messageId
    , getMessageBody

    , MessageType(..)

    -- * Protocols

    , Protocol
    , binaryProtocol
    , compactProtocol

    -- * TType

    -- | TType is used to refer to the Thrift protocol-level type of a value.

    , TType
    , IsTType(..)

    -- ** Tags

    -- | TType tags allow writing code that depends on knowing the @TType@ of
    -- values, or asserting conditions on it, at compile time.
    --
    -- For example, values in a map, list, or set must all have the same TType.
    -- This is enforced at the type level by parameterizing 'Value' over these
    -- tags.

    , TBool
    , TByte
    , TDouble
    , TEnum
    , TInt16
    , TInt32
    , TInt64
    , TBinary
    , TStruct
    , TUnion
    , TException
    , TMap
    , TSet
    , TList
    ) where

import Control.Monad
import Data.ByteString    (ByteString)
import Data.Int           (Int32)
import Data.Serialize.Get (runGetState)
import Data.Text          (Text)
import Data.Tuple         (swap)

import Pinch.Internal.Builder   (runBuilder)
import Pinch.Internal.Generic
import Pinch.Internal.Message
import Pinch.Internal.Pinchable
import Pinch.Internal.TType
import Pinch.Internal.Value
import Pinch.Protocol
import Pinch.Protocol.Binary
import Pinch.Protocol.Compact

------------------------------------------------------------------------------

-- $encodeDecodeValues
--
-- Types that can be serialized and deserialized into\/from Thrift values
-- implement the 'Pinchable' typeclass. Instances may be derived automatically
-- using generics, or written out by hand.
--
-- The 'Pinchable' typeclass converts objects into and from 'Value' objects,
-- which act as a direct mapping to the Thrift wire representation.  A
-- 'Protocol' is responsible for converting 'Value' objects to and from
-- bytestrings.
--
-- The 'encode' and 'decode' methods may be used on objects that implement the
-- 'Pinchable' typeclass to get the wire representation directly.
--
-- > +------------+   Pinchable                    Protocol    +------------+
-- > |            |               +------------+               |            |
-- > |            +----pinch------>            +---serialize--->            |
-- > | Your Type  |               |  Value a   |               | ByteString |
-- > |            <---unpinch-----+            <--deserialize--+            |
-- > |            |               +------------+               |            |
-- > |            |                                            |            |
-- > |            +-------------------encode------------------->            |
-- > |            |                                            |            |
-- > |            <-------------------decode-------------------+            |
-- > +------------+                                            +------------+

-- | Encode the given 'Pinchable' value using the given 'Protocol'.
--
-- >>> unpack $ encode binaryProtocol ["a" :: ByteString, "b"]
-- [11,0,0,0,2,0,0,0,1,97,0,0,0,1,98]
--
encode :: Pinchable a => Protocol -> a -> ByteString
encode :: forall a. Pinchable a => Protocol -> a -> ByteString
encode Protocol
p = Builder -> ByteString
runBuilder forall b c a. (b -> c) -> (a -> b) -> a -> c
. Protocol -> forall a. IsTType a => Value a -> Builder
serializeValue Protocol
p forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Pinchable a => a -> Value (Tag a)
pinch
{-# INLINE encode #-}

-- | Decode a 'Pinchable' value from the using the given 'Protocol'.
--
-- >>> let s = pack [11,0,0,0,2,0,0,0,1,97,0,0,0,1,98]
-- >>> decode binaryProtocol s :: Either String [ByteString]
-- Right ["a","b"]
--
decode :: Pinchable a => Protocol -> ByteString -> Either String a
decode :: forall a. Pinchable a => Protocol -> ByteString -> Either String a
decode Protocol
p = forall a.
IsTType a =>
Protocol -> ByteString -> Either String (Value a)
deserializeValue Protocol
p forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> forall a. Parser a -> Either String a
runParser forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Pinchable a => Value (Tag a) -> Parser a
unpinch
{-# INLINE decode #-}

-- | Decode a 'Pinchable' value from the using the given 'Protocol'
-- and also return the "unparsed" portion of the bytestring.
--
-- >>> let s = pack [3,0,0,0,5,1,2,3,4,5,0,0,0]
-- >>> decodeWithLeftovers binaryProtocol s :: Either String (ByteString, [Int8])
-- Right ("\NUL\NUL\NUL",[1,2,3,4,5])
--
decodeWithLeftovers :: Pinchable a => Protocol -> ByteString -> Either String (ByteString, a)
decodeWithLeftovers :: forall a.
Pinchable a =>
Protocol -> ByteString -> Either String (ByteString, a)
decodeWithLeftovers Protocol
p ByteString
bs = (forall a b. (a, b) -> (b, a)
swap forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a.
Get a -> ByteString -> Int -> Either String (a, ByteString)
runGetState (Protocol -> forall a. IsTType a => Get (Value a)
deserializeValue' Protocol
p) ByteString
bs Int
0) forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
traverse (forall a. Parser a -> Either String a
runParser forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Pinchable a => Value (Tag a) -> Parser a
unpinch)
{-# INLINE decodeWithLeftovers #-}

------------------------------------------------------------------------------

-- $rpc
--
-- Thrift requests implicitly form a struct and responses implicitly form a
-- union. To send\/receive the request\/response, it must be wrapped inside a
-- 'Message'. The 'Message' contains information like the method name, the
-- message ID (to match out of order responses with requests), and whether
-- it contains a request or a response.
--
-- Requests and responses may be wrapped into @Message@ objects using the
-- 'mkMessage' function. The message body can be retrieved back using the
-- 'getMessageBody' function. The 'encodeMessage' and 'decodeMessage'
-- functions may be used to encode and decode messages into\/from bytestrings.
--
-- Consider the service method,
--
-- > User getUser(1: string userName, 2: list<Attribute> attributes)
-- >   throws (1: UserDoesNotExist doesNotExist,
-- >           2: InternalError internalError)
--
-- The request and response for this method implictly take the form:
--
-- > struct getUserRequest {
-- >   1: string userName
-- >   2: list<Attribute> attributes
-- > }
--
-- > union getUserResponse {
-- >   0: User success
-- >   1: UserDoesNotExist doesNotExist
-- >   2: InternalError InternalError
-- > }
--
-- (Note that the field ID 0 is reserved for the return value of the method.)
--
-- Given corresponding data types @GetUserRequest@ and @GetUserResponse@, the
-- client can do something similar to,
--
-- @
-- let req = GetUserRequest "jsmith" []
--     msg = 'mkMessage' "getUser" 'Call' 0 req
-- response <- sendToServer ('encodeMessage' msg)
-- case 'decodeMessage' response of
--     Left err -> handleError err
--     Right msg -> case 'getMessageBody' msg of
--         Left err -> handleError err
--         Right (res :: GetUserResponse) -> handleResponse res
-- @
--
-- Similarly, on the server side,
--
-- @
-- case decodeMessage request of
--     Left err -> handleError err
--     Right msg -> case 'messageName' msg of
--         "getUser" -> case getMessageBody msg of
--             Left err -> handleError err
--             Right (req :: GetUserRequest) -> do
--                 let mid = 'messageId' msg
--                 res <- handleGetUser req
--                 return (mkMessage "getUser" 'Reply' mid res)
--                 -- Note that the response MUST contain the same
--                 -- message ID as its request.
--         _ -> handleUnknownMethod
-- @

-- | Encode the 'Message' using the given 'Protocol'.
--
-- @
-- let request = GetUserRequest (putField "jsmith") (putField [])
--     message = 'mkMessage' "getUser" Call 42 request
-- in encodeMessage binaryProtocol message
-- @
--
encodeMessage :: Protocol -> Message -> ByteString
encodeMessage :: Protocol -> Message -> ByteString
encodeMessage Protocol
p = Builder -> ByteString
runBuilder forall b c a. (b -> c) -> (a -> b) -> a -> c
. Protocol -> Message -> Builder
serializeMessage Protocol
p
{-# INLINE encodeMessage #-}

-- | Decode a 'Message' using the given 'Protocol'.
--
-- >>> decodeMessage binaryProtocol bs >>= getMessageBody :: Either String GetUserRequest
-- Right (GetUserRequest {userName = Field "jsmith", userAttributes = Field []})
--
decodeMessage :: Protocol -> ByteString -> Either String Message
decodeMessage :: Protocol -> ByteString -> Either String Message
decodeMessage = Protocol -> ByteString -> Either String Message
deserializeMessage
{-# INLINE decodeMessage #-}

-- | Build a @Message@.
mkMessage
    :: (Pinchable a, Tag a ~ TStruct)
    => Text
    -- ^ Name of the target method.
    -> MessageType
    -- ^ Type of the message.
    -> Int32
    -- ^ Message ID.
    -> a
    -- ^ Message payload. This must be an object which serializes into a
    -- struct.
    -> Message
mkMessage :: forall a.
(Pinchable a, Tag a ~ TStruct) =>
Text -> MessageType -> Int32 -> a -> Message
mkMessage Text
name MessageType
typ Int32
mid a
body = Text -> MessageType -> Int32 -> Value TStruct -> Message
Message Text
name MessageType
typ Int32
mid (forall a. Pinchable a => a -> Value (Tag a)
pinch a
body)
{-# INLINE mkMessage #-}

-- | Read the message contents.
--
-- This returns a @Left@ result if the message contents do not match the
-- requested type.
getMessageBody
    :: (Pinchable a, Tag a ~ TStruct) => Message -> Either String a
getMessageBody :: forall a.
(Pinchable a, Tag a ~ TStruct) =>
Message -> Either String a
getMessageBody = forall a. Parser a -> Either String a
runParser forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Pinchable a => Value (Tag a) -> Parser a
unpinch forall b c a. (b -> c) -> (a -> b) -> a -> c
. Message -> Value TStruct
messagePayload
{-# INLINE getMessageBody #-}

------------------------------------------------------------------------------

-- $genericStruct
--
-- Given the struct,
--
-- > struct User {
-- >   1: required string name
-- >   2: optional string emailAddress
-- > }
--
-- A @Pinchable@ instance for it can be automatically derived by wrapping
-- fields of the data type with the 'Field' type and specifying the field
-- identifier as a type-level numeral. Fields which hold a @Maybe@ value are
-- considered optional.
--
-- @
-- data User = User
--     { userName         :: 'Field' 1 Text
--     , userEmailAddress :: Field 2 (Maybe Text)
--     }
--   deriving (Generic)
--
-- instance Pinchable User
-- @
--
-- The @DeriveGeneric@ extension is required to automatically derive instances
-- of the @Generic@ typeclass and the @DataKinds@ extension is required to use
-- type-level numerals.

------------------------------------------------------------------------------

-- $genericUnion
--
-- As with structs and exceptions, fields of the data type representing a
-- union must be tagged with 'Field', but to satisfy the property of a union
-- that only one value is set at a time, they must be on separate
-- constructors.
--
-- For example, given the union,
--
-- > union Item {
-- >   1: binary bin
-- >   2: string str
-- >   3: i32    int
-- > }
--
-- A @Pinchable@ instance can be derived like so,
--
-- > data Item
-- >     = ItemBin (Field 1 ByteString)
-- >     | ItemStr (Field 2 Text)
-- >     | ItemInt (Field 3 Int32)
-- >   deriving (Generic)
-- >
-- > instance Pinchable Item
--
-- The @DeriveGeneric@ extension is required to automatically derive instances
-- of the @Generic@ typeclass and the @DataKinds@ extension is required to use
-- type-level numerals.
--
-- If the union represents the response of a service method which returns a
-- @void@ result, the type 'Void' may be used.
--
-- @
-- data GetFooResponse
--   = GetFooDoesNotExist  (Field 1 FooDoesNotExist)
--   | GetFooInternalError (Field 2 InternalError)
--   | GetFooSuccess 'Void'
-- @

------------------------------------------------------------------------------

-- $genericEnum
--
-- Given the enum,
--
-- > enum Op {
-- >   Add, Sub, Mul, Div
-- > }
--
-- A @Pinchable@ instance can be derived for it by creating one constructor
-- for each of the enum values and providing it a single 'Enumeration'
-- argument tagged with the enum value.
--
-- @
-- data Op
--     = OpAdd ('Enumeration' 0)
--     | OpSub (Enumeration 1)
--     | OpMul (Enumeration 2)
--     | OpDiv (Enumeration 3)
--   deriving (Generic)
--
-- instance Pinchable Op
-- @
--
-- Note that you need to know the values assigned to the enums. If not
-- specified, Thrift automatically assigns incrementing values to the items in
-- the order they appear starting at 0.
--
-- The @DeriveGeneric@ extension is required to automatically derive instances
-- of the @Generic@ typeclass and the @DataKinds@ extension is required to use
-- type-level numerals.

------------------------------------------------------------------------------

-- $struct
--
-- Given a Thrift struct,
--
-- > struct Post {
-- >   1: optional string subject
-- >   2: required string body
-- > }
--
-- The 'Pinchable' instance for it will be,
--
-- @
-- data Post = Post
--     { postSubject :: Maybe Text
--     , postBody    :: Text
--     }
--
-- instance 'Pinchable' Post where
--     type 'Tag' Post = 'TStruct'
--
--     pinch (Post subject body) =
--         'struct' [ 1 '?=' subject
--                , 2 '.=' body
--                ]
--
--     unpinch value =
--         Post \<$\> value '.:?' 1
--              \<*\> value '.:'  2
-- @
--

------------------------------------------------------------------------------

-- $union
--
-- Given a Thrift union,
--
-- > union PostBody {
-- >   1: string markdown
-- >   2: binary rtf
-- > }
--
-- The 'Pinchable' instance for it will be,
--
-- @
-- data PostBody
--     = PostBodyMarkdown Text
--     | PostBodyRtf ByteString
--
-- instance Pinchable PostBody where
--     type Tag PostBody = 'TUnion'
--
--     pinch (PostBodyMarkdown markdownBody) =
--         'union' 1 markdownBody
--     pinch (PostBodyRtf rtfBody) =
--         union 2 rtfBody
--
--     unpinch v = PostBodyMarkdown \<$\> v .: 1
--             \<|\> PostBodyRtf      \<$\> v .: 2
-- @

------------------------------------------------------------------------------

-- $enum
--
-- Given an enum,
--
-- > enum Role {
-- >   DISABLED = 0,
-- >   USER,
-- >   ADMIN,
-- > }
--
-- The 'Pinchable' instance for it will be,
--
-- > data Role = RoleDisabled | RoleUser | RoleAdmin
-- >
-- > instance Pinchable Role where
-- >     type Tag Role = TEnum
-- >
-- >     pinch RoleDisabled = pinch (0 :: Int32)
-- >     pinch RoleUser     = pinch (1 :: Int32)
-- >     pinch RoleAdmin    = pinch (2 :: Int32)
-- >
-- >     unpinch v = do
-- >        value <- unpinch v
-- >        case (value :: Int32) of
-- >            0 -> Right RoleDisabled
-- >            1 -> Right RoleUser
-- >            2 -> Right RoleAdmin
-- >            _ -> Left $ "Unknown role: " ++ show value