@@ -1,20 +1,176 @@
1# servant-client-ghcjs
1# servant-client-ghcjs
3−
3+Type safe querying of servant APIs from the browser.
5−This library lets you automatically derive Haskell functions that let you query each endpoint of a *servant* webservice. This library is specifically for ghcjs, as opposed to servant-client.
5+`servant-client-ghcjs` is much like `servant-client`, as both packages allow you to generate functions that query the endpoints of your servant API. Both packages should feel the same in usage. The big difference lies in how they perform the actual requests. `servant-client` (indirectly) uses your operating system's socket mechanisms, whereas `servant-client-ghcjs` uses your browser's [XHR](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest) mechanisms to send requests.
7−## Example
7+This guide assumes knowledge of servant. Reading its [documentation](haskell-servant.readthedocs.io) is recommended if you're new to the subject.
9−``` haskell
9+## Using servant-client-ghcjs
10−type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
10+`servant-client-ghcjs` should feel familiar if you've worked with `servant-client`.
11− :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books
13−myApi :: Proxy MyApi
12+Take the following API (taken from the [Querying an API](http://haskell-servant.readthedocs.io/en/stable/tutorial/Client.html) section in the servant documentation)
16−getAllBooks :: Manager -> BaseUrl -> ExceptT String IO [Book]
14+```haskell
17−postNewBook :: Book -> Manager -> BaseUrl -> ExceptT String IO Book
15+{-# LANGUAGE DataKinds #-}
18−-- 'client' allows you to produce operations to query an API from a client.
16+{-# LANGUAGE DeriveGeneric #-}
19−(getAllBooks :<|> postNewBook) = client myApi
17+{-# LANGUAGE PackageImports #-}
18+{-# LANGUAGE TypeOperators #-}
22+import "aeson" Data.Aeson
23+import "base" Data.Proxy
24+import "base" GHC.Generics
25+import "servant" Servant.API -- To define the API itself
26+import "servant-client-ghcjs" Servant.Client.Ghcjs -- To generate client functions
29+ "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
30+ :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
31+ :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
34+-- Data types used in the API
36+data Position = Position
39+ } deriving (Show, Generic)
41+instance FromJSON Position
43+newtype HelloMessage = HelloMessage { msg :: String }
44+ deriving (Show, Generic)
46+instance FromJSON HelloMessage
48+data ClientInfo = ClientInfo
49+ { clientName :: String
50+ , clientEmail :: String
52+ , clientInterestedIn :: [String]
55+instance ToJSON ClientInfo
62+ } deriving (Show, Generic)
64+instance FromJSON Email
67+Client functions are generated with the `client` function, like with `servant-client`:
75+ -> ClientM HelloMessage
77+marketing :: ClientInfo
83+position :<|> hello :<|> marketing = client api
86+To run these requests, they only need to be given to `runClientM`. The type of which is as follows:
89+runClientM :: ClientM a -> IO (Either ServantError a)
92+The requests can then be run as follows:
97+ ePos <- runClientM $ position 10 20
100+ eHelloMessage <- runClientM $ hello (Just "Servant")
103+ eEmail <- runClientM $ marketing ClientInfo
104+ { clientName = "Servant"
105+ , clientEmail = "servant@example.com"
107+ , clientInterestedIn = ["servant", "haskell", "type safety", "web apps"]
112+`runClientM` requires no URL, as it assumes that all requests are meant for the server that served the web page on which the code is being run. It is however possible to call REST APIs from other locations with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) using `runClientMOrigin`:
115+runClientMOrigin :: ClientM a -> ClientEnv -> IO (Either ServantError a)
118+Where `ClientEnv` holds a `BaseURL` that tells servant where to send the request to.
120+# Common client functions
121+Specifically in big applications it can be desirable to have client functions that work for both `servant-client` *and* `servant-client-ghcjs`. Luckily, the common bits of those two packages live in a parent package, called `servant-client-core`. This package holds the tools to create generic client functions. Generating clients this way is a bit different, though, as the client functions need to be generic in the monad `m` that runs the actual requests.
123+In the example below, the client functions are put in a data type called `APIClient`, which has `m` as a type parameter. The lowercase `apiClient` constructs this data type, demanding that `m` is indeed a monad that can run requests.
126+{-# LANGUAGE FlexibleContexts #-}
127+{-# LANGUAGE RecordWildCards #-}
128+{-# LANGUAGE ScopedTypeVariables #-}
129+{-# LANGUAGE TypeApplications #-}
132+import "servant-client-core" Servant.Client.Core
136+data APIClient m = APIClient
137+ { position :: Int -> Int -> m Position
138+ , hello :: Maybe String -> m HelloMessage
139+ , marketing :: ClientInfo -> m Email
146+apiClient = APIClient { .. }
150+ :<|> marketing = Proxy @API `clientIn` Proxy @m
153+The call site changes slightly too, as the functions now need to be taken from `apiClient`:
156+import "servant-client-ghcjs" Servant.Client.Ghcjs
160+ ePos <- runClientM $ position apiClient 10 20
164+Here's how the requests would be performed in regular `servant-client`:
167+import "servant-client" Servant.Client
168+import "http-client" Network.HTTP.Client ( newManager, defaultManagerSettings )
172+ mgr <- newManager defaultManagerSettings
173+ let clientBaseUrl = BaseUrl Http "www.example.com" 80 ""
174+ ePos <- runClientM (position apiClient 10 20) $ ClientEnv mgr clientBaseUrl