mig-client-0.1.1.0: Build http-clients from API definition for mig servers
Safe HaskellSafe-Inferred
LanguageGHC2021

Mig.Client

Description

Functions to create http-clients from the same code as server or API schema

Synopsis

Documentation

class MapRequest a => ToClient a where Source #

Creates http-client from server definition.

The result adapts to decalred types. It creates so many client functions as the arity of the tuple in the result. The server can have more handlers than in the result Routes from result definition and server definition are matched in the same order as they are declared.

The type of the client is derived from the type signature of the result. The information on paths for handlers is derived from server definition.

To use the same code for both client and server it is convenient to declare signatures for handlers as type synonyms parameterized by server monad. And in the server implementation monad is going to be something IO-based but in client handlers it will be Client-monad.

For example for a server:

type Hello m = Capture "name" Text -> Get m (Resp Text)
type Add m = Query "a" Int -> Query "b" Int -> Get m (Resp Int)

server :: Server IO
server = "api" /.
  mconcat
   [ "hello" /. helloHandler
   , "add" /. addHandler
   ]

helloHandler :: Hello IO
helloHandler (Capture name) = Send $ pure $ ok $ "Hello " <> name

addHandler :: Add IO
addHandler (Query a) (Query b) = Send $ pure $ ok (a + b)

We can define the client and reuse type signatures that we have defined in the server code:

helloClient :: Hello Client
addClient :: Add Client

helloClient :| addClient = toClient server

If there is no definition for server. For example if we write implementation for some external server or API provided by third party we can use recursive definition in the server. For example if there is no haskell implementation for the server in the previous example. But we know the API of the application we can define client with recursive definition:

type Hello m = Capture "name" Text -> Get m (Resp Text)
type Add m = Query "a" Int -> Query "b" Int -> Get m (Resp Int)

helloClient :: Hello Client
addClient :: Add Client

helloClient :| addClient = toClient server

server :: Server Client
server = "api" /.
  mconcat
   [ "hello" /. helloClient
   , "add" /. addClient
   ]

The code does not get stuck into recursion loop because implementation of the route handlers is not needed to create client functions. The function toClient takes into account only type-signatures of the handlers and paths.

Methods

toClient :: Server m -> a Source #

converts to client function

clientArity :: Int Source #

how many routes client has

Instances

Instances details
(ToClient a, ToClient b) => ToClient (a :| b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> a :| b Source #

clientArity :: Int Source #

ToClient b => ToClient (RawResponse -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> RawResponse -> b Source #

clientArity :: Int Source #

(ToRespBody media a, ToClient b) => ToClient (Body media a -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> Body media a -> b Source #

clientArity :: Int Source #

(KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (Capture sym a -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> Capture sym a -> b Source #

clientArity :: Int Source #

(KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (Header sym a -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> Header sym a -> b Source #

clientArity :: Int Source #

ToClient b => ToClient (IsSecure -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> IsSecure -> b Source #

clientArity :: Int Source #

(KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (Optional sym a -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> Optional sym a -> b Source #

clientArity :: Int Source #

(KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (OptionalHeader sym a -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> OptionalHeader sym a -> b Source #

clientArity :: Int Source #

ToClient b => ToClient (PathInfo -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> PathInfo -> b Source #

clientArity :: Int Source #

(KnownSymbol sym, ToHttpApiData a, ToClient b) => ToClient (Query sym a -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> Query sym a -> b Source #

clientArity :: Int Source #

(KnownSymbol sym, ToClient b) => ToClient (QueryFlag sym -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> QueryFlag sym -> b Source #

clientArity :: Int Source #

ToClient b => ToClient (RawRequest -> b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> RawRequest -> b Source #

clientArity :: Int Source #

(ToClient a, ToClient b) => ToClient (a, b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> (a, b) Source #

clientArity :: Int Source #

(ToClient a, ToClient b, ToClient c) => ToClient (a, b, c) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> (a, b, c) Source #

clientArity :: Int Source #

(ToClient a, ToClient b, ToClient c, ToClient d) => ToClient (a, b, c, d) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> (a, b, c, d) Source #

clientArity :: Int Source #

(IsMethod method, FromReqBody (RespMedia a) (RespBody a), IsResp a) => ToClient (Send method Client a) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> Send method Client a Source #

clientArity :: Int Source #

newtype Client a Source #

The client monad. All errors are unified to Lazy.ByteString.

Instances

Instances details
MonadIO Client Source # 
Instance details

Defined in Mig.Client

Methods

liftIO :: IO a -> Client a #

Applicative Client Source # 
Instance details

Defined in Mig.Client

Methods

pure :: a -> Client a #

(<*>) :: Client (a -> b) -> Client a -> Client b #

liftA2 :: (a -> b -> c) -> Client a -> Client b -> Client c #

(*>) :: Client a -> Client b -> Client b #

(<*) :: Client a -> Client b -> Client a #

Functor Client Source # 
Instance details

Defined in Mig.Client

Methods

fmap :: (a -> b) -> Client a -> Client b #

(<$) :: a -> Client b -> Client a #

Monad Client Source # 
Instance details

Defined in Mig.Client

Methods

(>>=) :: Client a -> (a -> Client b) -> Client b #

(>>) :: Client a -> Client b -> Client b #

return :: a -> Client a #

(ToRespBody (RespMedia a) (RespError a), IsResp a) => FromClient (Send method Client a) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (Send method Client a) Source #

Methods

fromClient :: Send method Client a -> ClientResult (Send method Client a) Source #

(IsMethod method, FromReqBody (RespMedia a) (RespBody a), IsResp a) => ToClient (Send method Client a) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> Send method Client a Source #

clientArity :: Int Source #

type ClientResult (Send method Client a) Source # 
Instance details

Defined in Mig.Client

data ClientConfig Source #

Config to run the clients

Constructors

ClientConfig 

Fields

Instances

Instances details
MonadReader ClientConfig Client' Source # 
Instance details

Defined in Mig.Client

runClient :: ClientConfig -> Client a -> IO (RespOr AnyMedia ByteString a) Source #

Runs client. It calls client handler and fetches the result.

data a :| b #

Infix synonym for pair. It can be useful to stack together many client functions in the output of toClient function.

Constructors

a :| b 

Instances

Instances details
(ToUrl a, ToUrl b) => ToUrl (a :| b) 
Instance details

Defined in Mig.Core.Class.Url

Methods

toUrl :: forall (m :: Type -> Type). Server m -> a :| b #

mapUrl :: (Url -> Url) -> (a :| b) -> a :| b #

urlArity :: Int #

(ToClient a, ToClient b) => ToClient (a :| b) Source # 
Instance details

Defined in Mig.Client

Methods

toClient :: forall (m :: Type -> Type). Server m -> a :| b Source #

clientArity :: Int Source #

class FromClient a where Source #

Class to strip away all newtype wrappers that serve for API-definition. For example it converts the types signature for client function:

Capture "foo" Text -> Header "bar" Int -> Get Client (Resp a)

to the version without HTTP-newtype wrappers:

Text -> Int -> Client' (Resp a)

The instances are defined for all HTTP-newtype wrappers. Also we can use function getRespOrValue if we do not need the http information of response.

Associated Types

type ClientResult a :: Type Source #

Methods

fromClient :: a -> ClientResult a Source #

Instances

Instances details
FromClient b => FromClient (RawResponse -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (RawResponse -> b) Source #

FromClient b => FromClient (Body media a -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (Body media a -> b) Source #

Methods

fromClient :: (Body media a -> b) -> ClientResult (Body media a -> b) Source #

FromClient b => FromClient (Capture sym a -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (Capture sym a -> b) Source #

Methods

fromClient :: (Capture sym a -> b) -> ClientResult (Capture sym a -> b) Source #

FromClient b => FromClient (Header sym a -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (Header sym a -> b) Source #

Methods

fromClient :: (Header sym a -> b) -> ClientResult (Header sym a -> b) Source #

FromClient b => FromClient (IsSecure -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (IsSecure -> b) Source #

Methods

fromClient :: (IsSecure -> b) -> ClientResult (IsSecure -> b) Source #

FromClient b => FromClient (Optional sym a -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (Optional sym a -> b) Source #

Methods

fromClient :: (Optional sym a -> b) -> ClientResult (Optional sym a -> b) Source #

FromClient b => FromClient (OptionalHeader sym a -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (OptionalHeader sym a -> b) Source #

Methods

fromClient :: (OptionalHeader sym a -> b) -> ClientResult (OptionalHeader sym a -> b) Source #

FromClient b => FromClient (PathInfo -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (PathInfo -> b) Source #

Methods

fromClient :: (PathInfo -> b) -> ClientResult (PathInfo -> b) Source #

FromClient b => FromClient (Query sym a -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (Query sym a -> b) Source #

Methods

fromClient :: (Query sym a -> b) -> ClientResult (Query sym a -> b) Source #

FromClient b => FromClient (QueryFlag a -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (QueryFlag a -> b) Source #

Methods

fromClient :: (QueryFlag a -> b) -> ClientResult (QueryFlag a -> b) Source #

FromClient b => FromClient (RawRequest -> b) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (RawRequest -> b) Source #

(ToRespBody (RespMedia a) (RespError a), IsResp a) => FromClient (Send method Client a) Source # 
Instance details

Defined in Mig.Client

Associated Types

type ClientResult (Send method Client a) Source #

Methods

fromClient :: Send method Client a -> ClientResult (Send method Client a) Source #

getRespOrValue :: RespOr media ByteString a -> Either ByteString a Source #

If we need only value from the server and not HTTP-info (status, or headers) we can omit that data with this function

newtype Client' a Source #

ClientConfig in ReaderT IO monad. It encapsulates typical execution of client functions

Constructors

Client' (ReaderT ClientConfig IO a) 

Instances

Instances details
MonadIO Client' Source # 
Instance details

Defined in Mig.Client

Methods

liftIO :: IO a -> Client' a #

Applicative Client' Source # 
Instance details

Defined in Mig.Client

Methods

pure :: a -> Client' a #

(<*>) :: Client' (a -> b) -> Client' a -> Client' b #

liftA2 :: (a -> b -> c) -> Client' a -> Client' b -> Client' c #

(*>) :: Client' a -> Client' b -> Client' b #

(<*) :: Client' a -> Client' b -> Client' a #

Functor Client' Source # 
Instance details

Defined in Mig.Client

Methods

fmap :: (a -> b) -> Client' a -> Client' b #

(<$) :: a -> Client' b -> Client' a #

Monad Client' Source # 
Instance details

Defined in Mig.Client

Methods

(>>=) :: Client' a -> (a -> Client' b) -> Client' b #

(>>) :: Client' a -> Client' b -> Client' b #

return :: a -> Client' a #

MonadReader ClientConfig Client' Source # 
Instance details

Defined in Mig.Client

runClient' :: ClientConfig -> Client' a -> IO a Source #

Runs the client call

class Monad m => MonadIO (m :: Type -> Type) where #

Monads in which IO computations may be embedded. Any monad built by applying a sequence of monad transformers to the IO monad will be an instance of this class.

Instances should satisfy the following laws, which state that liftIO is a transformer of monads:

Methods

liftIO :: IO a -> m a #

Lift a computation from the IO monad. This allows us to run IO computations in any monadic stack, so long as it supports these kinds of operations (i.e. IO is the base monad for the stack).

Example

Expand
import Control.Monad.Trans.State -- from the "transformers" library

printState :: Show s => StateT s IO ()
printState = do
  state <- get
  liftIO $ print state

Had we omitted liftIO, we would have ended up with this error:

• Couldn't match type ‘IO’ with ‘StateT s IO’
 Expected type: StateT s IO ()
   Actual type: IO ()

The important part here is the mismatch between StateT s IO () and IO ().

Luckily, we know of a function that takes an IO a and returns an (m a): liftIO, enabling us to run the program and see the expected results:

> evalStateT printState "hello"
"hello"

> evalStateT printState 3
3

Instances

Instances details
MonadIO IO

Since: base-4.9.0.0

Instance details

Defined in Control.Monad.IO.Class

Methods

liftIO :: IO a -> IO a #

MonadIO Client Source # 
Instance details

Defined in Mig.Client

Methods

liftIO :: IO a -> Client a #

MonadIO Client' Source # 
Instance details

Defined in Mig.Client

Methods

liftIO :: IO a -> Client' a #

MonadIO Q 
Instance details

Defined in Language.Haskell.TH.Syntax

Methods

liftIO :: IO a -> Q a #

(Functor f, MonadIO m) => MonadIO (FreeT f m) 
Instance details

Defined in Control.Monad.Trans.Free

Methods

liftIO :: IO a -> FreeT f m a #

(Error e, MonadIO m) => MonadIO (ErrorT e m) 
Instance details

Defined in Control.Monad.Trans.Error

Methods

liftIO :: IO a -> ErrorT e m a #

MonadIO m => MonadIO (ReaderT r m) 
Instance details

Defined in Control.Monad.Trans.Reader

Methods

liftIO :: IO a -> ReaderT r m a #

MonadIO m => MonadIO (Send method m) 
Instance details

Defined in Mig.Core.Types.Route

Methods

liftIO :: IO a -> Send method m a #

type ClientOr a = Client' (Either ByteString a) Source #

Helper type-synonym for convenience