packages feed

pinch-0.4.0.0: src/Pinch/Client.hs

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs                     #-}
{-# LANGUAGE OverloadedStrings         #-}
{-# LANGUAGE RankNTypes                #-}
{-# LANGUAGE ScopedTypeVariables       #-}
{-# LANGUAGE TypeFamilies              #-}

module Pinch.Client
  (
    -- * Basic Thrift client
    Client
  , client
  , Channel
  , createChannel
  , createChannel1

  , ThriftCall(..)
  , ThriftClient(..)
  , callOrThrow

    -- * Multiplexing Client
  , MultiplexClient
  , multiplexClient


    -- * Errors
  , ApplicationException(..)
  , ExceptionType(..)
  , ThriftError(..)
  ) where

import           Control.Exception        (throwIO)

import qualified Data.Text                as T

import           Pinch.Internal.Exception
import           Pinch.Internal.Message
import           Pinch.Internal.Pinchable
import           Pinch.Internal.RPC
import           Pinch.Internal.TType

-- | A simple Thrift Client.
newtype Client = Client Channel

-- | Instantiates a new Thrift client.
client :: Channel -> Client
client = Client

-- | A call to a Thrift server resulting in the return datatype `a`.
data ThriftCall a where
  TCall :: (Pinchable req, Tag req ~ TStruct, Pinchable res, Tag res ~ TStruct)
    => !T.Text -> !req -> ThriftCall res
  TOneway :: (Pinchable req, Tag req ~ TStruct) => !T.Text -> !req -> ThriftCall ()

class ThriftClient c where
  -- | Calls a Thrift service and returns the result/error data structure.
  -- Application-level exceptions defined in the thrift service are returned
  -- as part of the result/error data structure.
  call :: c -> ThriftCall a -> IO a

instance ThriftClient Client where
  call (Client chan) tcall = do
    case tcall of
      TOneway m r -> do
        writeMessage chan $ Message m Oneway 0 (pinch r)
        pure ()
      TCall m r -> do
        writeMessage chan $ Message m Call 0 (pinch r)
        reply <- readMessage chan
        case reply of
          RREOF -> throwIO $ ThriftError $ "Reached EOF while awaiting reply"
          RRFailure err -> throwIO $ ThriftError $ "Could not read message: " <> T.pack err
          RRSuccess reply' -> case messageType reply' of
            Reply -> case runParser $ unpinch $ messagePayload reply' of
              Right x -> pure x
              Left err -> do
                throwIO $ ThriftError $ "Could not parse reply payload: " <> T.pack err
            Exception -> case runParser $ unpinch $ messagePayload reply' of
              Right (x :: ApplicationException) -> throwIO x
              Left err ->
                throwIO $ ThriftError $ "Could not parse application exception: " <> T.pack err
            t -> throwIO $ ThriftError $ "Expected reply or exception, got " <> T.pack (show t) <> "."

-- | Calls a Thrift service. If an application-level thrift exception as defined in the Thrift service definition
-- is returned by the server, it will be re-thrown using `throwIO`.
callOrThrow :: (ThriftClient c, ThriftResult a) => c -> ThriftCall a -> IO (ResultType a)
callOrThrow client' c = call client' c >>= unwrap

-- | A multiplexing thrift client.
data MultiplexClient = forall c . ThriftClient c => MultiplexClient c ServiceName

-- | Create a new multiplexing thrift client targeting the given service.
multiplexClient :: ThriftClient c => c -> ServiceName -> MultiplexClient
multiplexClient = MultiplexClient

instance ThriftClient MultiplexClient where
  call (MultiplexClient client' (ServiceName serviceName)) tcall = case tcall of
    TOneway r req -> call client' $ TOneway (serviceName <> ":" <> r) req
    TCall r req   -> call client' $ TCall (serviceName <> ":" <> r) req