Api Maker: Implementing APIs like never before
Api Maker let’s you implement APIs simply by implementing the class Request
for each possible
request. It is built on top of the Req library.
Lets say we want to implement a GET
request that returns orders taken in an account. This actually
implements the GET orders request of
oanda.com. So we create a data type structure
to hold what we want:
-- | GET Order(s) request
data GetOrder =
GetOrder AccountId OrderConfig
-- | This data type defines the header options that we can send
data OrderConfig = OrderConfig
{ ids :: Maybe [OrderId] -- ^ List of Order IDs to retrieve
, state :: Maybe OrderStateFilter -- ^ The state to filter the requested Orders by [default=PENDING]
, instrument :: Maybe InstrumentName -- ^ The instrument to filter the requested orders by
, orderCount :: Maybe Int -- ^ The maximum number of Orders to return [default=50, maximum=500]
, beforeID :: Maybe OrderId -- ^ The maximum Order ID to return. If not provided the most recent Orders in the Account are returned
} deriving (Show, Eq, Ord, ToJSON, Generic, NFData)
and then we implement the class
instance Request OandaConfig GetOrder where
type Method GetOrder = GET -- It's a GET request
type Body GetOrder = NoReqBody -- We do not send a body
type Response GetOrder = JsonResponse OrderList -- The response is a JSON list of orders
type Output GetOrder = OrderList -- We simply output the list in the `process` function without transformation
method _ GetOrder {} = GET -- GET request again
url cfg (GetOrder accId _) = ".../account/orders" -- The URL
body _ (GetOrder _ req) = NoReqBody -- No Body to send
response _ GetOrder {} = jsonResponse -- The expected response is in JSON format and is parsed using `aeson` with this function.
option _ (GetOrder _ cfg) = -- What header options to send
headerRFC3339DatetimeFormat <> configs
where
configs =
case cfg of
OrderConfig ids state instrument count beforeID ->
"ids" `queryParam` fmap (T.intercalate ";") ids <>
"state" `queryParam` fmap show state <>
"instrument" `queryParam` instrument <>
"count" `queryParam` count <>
"beforeID" `queryParam` beforeID
-- Defines how to process the Response. One might change it here, e.g. filter values that cannot be specified by the request options.
process _ GetOrder {} response = return $ responseBody response
The request State
monad is then run like this:
runSessReqM cfg $ runRequests $ do
orders <- mkReq $ GetOrder myAccountId (OrderConfig Nothing Nothing (Just "USD/EUR") (Just 100) Nothing)
print orders
This will create the session and run the requests. All HttpException
s errors are caught and
converted to exceptions. Thus runSessReqM
returns a Either HttpException a
. The function mkReq
is used to actually make the requests, the data-type GetOrder
defines what request to make and
uses the corresponding account ID and the configuration provided. The result is a list of Orders,
with type OrderList
.