Safe Haskell | None |
---|---|
Language | Haskell2010 |
Make calls to an API defined using Servant.API from your React.Flux stores.
You must call initAjax
from your main function for this to work.
For now, this module only works with JSON.
The general pattern I currently use is to create a store action to trigger the request and another store action to process the response. Only the store action to trigger the request is exported. This does lead to a bit of tedious boilerplate since you need actions for each request, so I am still searching for a better API that perhaps takes advantage of the servant API type-level computation to reduce the boilerplate. For now, until I figure out a better way, this direct approach does work. If you have any ideas for a good API, please open an issue on bitbucket (even if you don't have full code)!
As an example, say that the API consists of two methods:
type GetUser = "user" :> Capture "user_id" UserId :> Get '[JSON] User type SetUser = "user" :> Capture "user_id" UserId :> ReqBody '[JSON] User :> Post '[JSON] () type MyAPI = GetUser :<|> SetUser
I would create a store as follows:
data RequestStatus = NoPendingRequest | PendingRequest | PreviousRequestHadError String data UserStore = UserStore { users :: Map.HashMap UserId User , reqStatus :: RequestStatus } data UserStoreAction = RequestUser UserId | RequestUserResponse UserId (Either (Int,String) User) | UpdateUser UserId User | UpdateUserResponse UserId (Either (Int, String) ()) deriving (Show, Generic, NFData) cfg :: ApiRequestConfig MyAPI cfg = ApiRequestConfig "https://www.example.com" NoTimeout instance StoreData UserStore where type StoreAction UserStore = UserStoreAction transform (RequestUser uid) us = do request cfg (Proxy :: Proxy GetUser) uid $ \r -> return [SomeStoreAction userStore $ RequestUserResponse uid r] return $ us {reqStatus = PendingRequest} transform (RequestUserResponse _ (Left (_errCode, err))) us = return $ us {reqStatus = PreviousRequestHadError err} transform (RequestUserResponse uid (Right u)) us = return $ us { reqStatus = NoPendingRequest , users = Map.insert uid u (users us) } transform (UpdateUser uid u) us = do request cfg (Proxy :: Proxy SetUser) uid u $ \r -> return [SomeStoreAction userStore $ UpdateUserResponse uid r] return $ us { reqStatus = PendingRequest , users = Map.insert uid u (users us) } transform (UpdateUserResponse uid (Left (_errCode, err))) us = return $ us { reqStatus = PreviousRequestHadError err , users = Map.delete uid (users us) } transform (UpdateUserResponse _ (Right ())) us = return $ us { reqStatus = NoPendingRequest} userStore :: ReactStore UserStore userStore = mkStore $ UserStore Map.empty NoPendingRequest
- type HandleResponse a = Either (Int, String) a -> IO [SomeStoreAction]
- data RequestTimeout :: *
- data ApiRequestConfig api = ApiRequestConfig {
- urlPrefix :: JSString
- timeout :: RequestTimeout
- request :: (IsElem endpoint api, HasAjaxRequest endpoint) => ApiRequestConfig api -> Proxy endpoint -> MkRequest endpoint
- class HasAjaxRequest endpoint where
- type MkRequest endpoint
Documentation
type HandleResponse a = Either (Int, String) a -> IO [SomeStoreAction] Source #
When a response from the server arrives, it is either an error or a valid response. An error
is turned into a Left
value consisting of the HTTP response code and the response body. If
a 200 HTTP response is received, it is parsed to a value according to the servant API definition
and passed as a Right
value. You must then turn the response into a store action. I suggest
that you just pass the value along directly to a store action without any computation; the computation
can happen inside the store.
data RequestTimeout :: * #
An optional timeout to use for XMLHttpRequest.timeout
.
When a request times out, a status code of 504 is set in respStatus
and the
response handler executes.
data ApiRequestConfig api Source #
Settings for requests built using this module.
ApiRequestConfig | |
|
request :: (IsElem endpoint api, HasAjaxRequest endpoint) => ApiRequestConfig api -> Proxy endpoint -> MkRequest endpoint Source #
Make a request to a servant endpoint. You must call initAjax
from your main function for this to work.
request
takes the ApiRequestConfig
, a proxy for the endpoint,
parameters for the request (request body, query params, path captures, etc.), and value of type HandleResponse
.
The result of request
is then a value of type IO ()
. In order to type-check that the proper values for
the request body, path captures, etc. are passed, the MkRequest
associated type is used. MkRequest
expands
to a function with one argument for each path piece and an argument for the HandleResponse
. For example,
type GetUser = "user" :> Capture "user_id" UserId :> Get '[JSON] User type SetUser = "user" :> Capture "user_id" UserId :> ReqBody '[JSON] User :> Post '[JSON] () type MyAPI = GetUser :<|> SetUser
Then
MkRequest GetUser ~ UserId -> HandleResponse User -> IO () MkRequest SetUser ~ UserId -> User -> HandleResponse () -> IO ()
so that
request cfg (Proxy :: Proxy GetUser) :: UserId -> HandleResponse User -> IO () request cfg (Proxy :: Proxy SetUser) :: UserId -> User -> HandleResponse () -> IO ()
class HasAjaxRequest endpoint where Source #
A class very similar to Servant.Utils.Links. You shouldn't need to use this class directly: instead
use request
. Having said that, the MkRequest
type defined in this typeclass is important as it determines
what values you must pass to request
to obtain a proper request.
(KnownSymbol sym, ToHttpApiData a, HasAjaxRequest k sub) => HasAjaxRequest * ((:>) k * (QueryParam * sym a) sub) Source # | |
(KnownSymbol sym, HasAjaxRequest k sub) => HasAjaxRequest * ((:>) k * (QueryFlag sym) sub) Source # | |
(KnownSymbol sym, ToHttpApiData a, HasAjaxRequest k sub) => HasAjaxRequest * ((:>) k * (Header sym a) sub) Source # | |
(ToHttpApiData v, HasAjaxRequest k sub) => HasAjaxRequest * ((:>) k * (Capture * sym v) sub) Source # | |
(KnownSymbol sym, HasAjaxRequest k sub) => HasAjaxRequest * ((:>) k Symbol sym sub) Source # | |
(ToJSON a, HasAjaxRequest k sub) => HasAjaxRequest * ((:>) k * (ReqBody * ((:) * JSON ([] *)) a) sub) Source # | |
(ReflectMethod k1 m, FromJSON a) => HasAjaxRequest * (Verb * k1 m s ((:) * JSON ([] *)) a) Source # | |