git.iko.soy/typeable/servant
on master help sign in
servant-client-ghcjs: README
commitbd5286471800c383f75ea785ba5877a8989b5fc8 authorFalco Peijnenburg <falco@lumiguide.nl> dateSat 21 Oct 2017, 23:02 (9 years ago) parent3f905ea
1 file changed+168−12
servant-client-ghcjs/README.md+168−12
servant-client-ghcjs/README.md+168−12
@@ -1,20 +1,176 @@
11# servant-client-ghcjs
22
3−![servant](https://raw.githubusercontent.com/haskell-servant/servant/master/servant.png)
3+Type safe querying of servant APIs from the browser.
44
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.
66
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.
88
9−``` haskell
10−type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
11− :<|> "books" :> ReqBody Book :> Post '[JSON] Book -- POST /books
9+## Using servant-client-ghcjs
10+`servant-client-ghcjs` should feel familiar if you've worked with `servant-client`.
1211
13−myApi :: Proxy MyApi
14−myApi = Proxy
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)
1513
16−getAllBooks :: Manager -> BaseUrl -> ExceptT String IO [Book]
17−postNewBook :: Book -> Manager -> BaseUrl -> ExceptT String IO Book
18−-- 'client' allows you to produce operations to query an API from a client.
19−(getAllBooks :<|> postNewBook) = client myApi
14+```haskell
15+{-# LANGUAGE DataKinds #-}
16+{-# LANGUAGE DeriveGeneric #-}
17+{-# LANGUAGE PackageImports #-}
18+{-# LANGUAGE TypeOperators #-}
19+
20+module Main where
21+
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
27+
28+type API =
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
32+
33+
34+-- Data types used in the API
35+
36+data Position = Position
37+ { xCoord :: Int
38+ , yCoord :: Int
39+ } deriving (Show, Generic)
40+
41+instance FromJSON Position
42+
43+newtype HelloMessage = HelloMessage { msg :: String }
44+ deriving (Show, Generic)
45+
46+instance FromJSON HelloMessage
47+
48+data ClientInfo = ClientInfo
49+ { clientName :: String
50+ , clientEmail :: String
51+ , clientAge :: Int
52+ , clientInterestedIn :: [String]
53+ } deriving Generic
54+
55+instance ToJSON ClientInfo
56+
57+data Email = Email
58+ { from :: String
59+ , to :: String
60+ , subject :: String
61+ , body :: String
62+ } deriving (Show, Generic)
63+
64+instance FromJSON Email
65+```
66+
67+Client functions are generated with the `client` function, like with `servant-client`:
68+
69+```haskell
70+position :: Int
71+ -> Int
72+ -> ClientM Position
73+
74+hello :: Maybe String
75+ -> ClientM HelloMessage
76+
77+marketing :: ClientInfo
78+ -> ClientM Email
79+
80+api :: Proxy API
81+api = Proxy
82+
83+position :<|> hello :<|> marketing = client api
84+```
85+
86+To run these requests, they only need to be given to `runClientM`. The type of which is as follows:
87+
88+```haskell
89+runClientM :: ClientM a -> IO (Either ServantError a)
90+```
91+
92+The requests can then be run as follows:
93+
94+```haskell
95+main :: IO ()
96+main = do
97+ ePos <- runClientM $ position 10 20
98+ print ePos
99+
100+ eHelloMessage <- runClientM $ hello (Just "Servant")
101+ print eHelloMessage
102+
103+ eEmail <- runClientM $ marketing ClientInfo
104+ { clientName = "Servant"
105+ , clientEmail = "servant@example.com"
106+ , clientAge = 3
107+ , clientInterestedIn = ["servant", "haskell", "type safety", "web apps"]
108+ }
109+ print eEmail
110+```
111+
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`:
113+
114+```haskell
115+runClientMOrigin :: ClientM a -> ClientEnv -> IO (Either ServantError a)
116+```
117+
118+Where `ClientEnv` holds a `BaseURL` that tells servant where to send the request to.
119+
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.
122+
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.
124+
125+```haskell
126+{-# LANGUAGE FlexibleContexts #-}
127+{-# LANGUAGE RecordWildCards #-}
128+{-# LANGUAGE ScopedTypeVariables #-}
129+{-# LANGUAGE TypeApplications #-}
130+
131+...
132+import "servant-client-core" Servant.Client.Core
133+
134+...
135+
136+data APIClient m = APIClient
137+ { position :: Int -> Int -> m Position
138+ , hello :: Maybe String -> m HelloMessage
139+ , marketing :: ClientInfo -> m Email
140+ }
141+
142+apiClient
143+ :: forall m
144+ . RunClient m
145+ => APIClient m
146+apiClient = APIClient { .. }
147+ where
148+ position
149+ :<|> hello
150+ :<|> marketing = Proxy @API `clientIn` Proxy @m
151+```
152+
153+The call site changes slightly too, as the functions now need to be taken from `apiClient`:
154+
155+```haskell
156+import "servant-client-ghcjs" Servant.Client.Ghcjs
157+
158+main :: IO ()
159+main = do
160+ ePos <- runClientM $ position apiClient 10 20
161+ print ePos
162+```
163+
164+Here's how the requests would be performed in regular `servant-client`:
165+
166+```haskell
167+import "servant-client" Servant.Client
168+import "http-client" Network.HTTP.Client ( newManager, defaultManagerSettings )
169+
170+main :: IO ()
171+main = do
172+ mgr <- newManager defaultManagerSettings
173+ let clientBaseUrl = BaseUrl Http "www.example.com" 80 ""
174+ ePos <- runClientM (position apiClient 10 20) $ ClientEnv mgr clientBaseUrl
175+ print ePos
20176```