1
- { -# LANGUAGE DataKinds #-}
2
- { -# LANGUAGE DeriveGeneric #-}
3
- { -# LANGUAGE DeriveTraversable #-}
4
- { -# LANGUAGE FlexibleContexts #-}
5
- { -# LANGUAGE FlexibleInstances #-}
1
+ { -# LANGUAGE DataKinds #-}
2
+ { -# LANGUAGE DeriveDataTypeable #-}
3
+ { -# LANGUAGE DeriveGeneric #-}
4
+ { -# LANGUAGE DeriveTraversable #-}
5
+ { -# LANGUAGE FlexibleContexts #-}
6
+ { -# LANGUAGE FlexibleInstances #-}
6
7
{ -# LANGUAGE GeneralizedNewtypeDeriving #-}
7
- { -# LANGUAGE OverloadedStrings #-}
8
- { -# LANGUAGE RecordWildCards #-}
9
- { -# LANGUAGE TypeFamilies #-}
10
- { -# LANGUAGE TypeOperators #-}
11
- { -# LANGUAGE ViewPatterns #-}
8
+ { -# LANGUAGE OverloadedStrings #-}
9
+ { -# LANGUAGE RecordWildCards #-}
10
+ { -# LANGUAGE TypeFamilies #-}
11
+ { -# LANGUAGE TypeOperators #-}
12
+ { -# LANGUAGE ViewPatterns #-}
12
13
{ -# OPTIONS_GHC
13
- -fno-warn-unused-binds -fno-warn-unused-imports -fcontext- stack = 328 #-}
14
+ -fno-warn-unused-binds -fno-warn-unused-imports -freduction- depth = 328 #-}
14
15
15
16
module { {title} }.API
16
17
-- * Client and Server
17
- ( ServerConfig (..)
18
- , { {title} }Backend
18
+ ( Config (..)
19
+ , { {title} }Backend(..)
19
20
, create{ {title} }Client
20
21
, run{ {title} }Server
21
22
, run{ {title} }Client
22
23
, run{ {title} }ClientWithManager
24
+ , call{ {title} }
23
25
, { {title} }Client
26
+ , { {title} }ClientError(..)
24
27
-- ** Servant
25
28
, { {title} }API
26
29
) where
27
30
28
- import { {title} }.Types
29
-
30
- import Control.Monad.Except (ExceptT)
31
- import Control.Monad.IO.Class
32
- import Data.Aeson (Value)
33
- import Data.Coerce (coerce)
34
- import Data.Function ((& ))
35
- import qualified Data.Map as Map
36
- import Data.Monoid ((<> ))
37
- import Data.Proxy (Proxy(..))
38
- import Data.Text (Text)
39
- import qualified Data.Text as T
40
- import GHC.Exts (IsString(..))
41
- import GHC.Generics (Generic)
42
- import Network.HTTP.Client (Manager, defaultManagerSettings, newManager)
43
- import Network.HTTP.Types.Method (methodOptions)
44
- import qualified Network.Wai.Handler.Warp as Warp
45
- import Servant (ServantErr, serve)
46
- import Servant.API
47
- import Servant.API.Verbs (StdMethod(..), Verb)
48
- import Servant.Client (Scheme(Http), ServantError, client)
49
- import Servant.Common.BaseUrl (BaseUrl(..))
50
- import Web.HttpApiData
31
+ import { {title} }.Types
32
+
33
+ import Control.Monad.Catch (Exception, MonadThrow, throwM)
34
+ import Control.Monad.Except (ExceptT, runExceptT)
35
+ import Control.Monad.IO.Class
36
+ import Control.Monad.Trans.Reader (ReaderT (..))
37
+ import Data.Aeson (Value)
38
+ import Data.Coerce (coerce)
39
+ import Data.Data (Data)
40
+ import Data.Function ((& ))
41
+ import qualified Data.Map as Map
42
+ import Data.Monoid ((<> ))
43
+ import Data.Proxy (Proxy (..))
44
+ import Data.Text (Text)
45
+ import qualified Data.Text as T
46
+ import Data.UUID (UUID)
47
+ import GHC.Exts (IsString (..))
48
+ import GHC.Generics (Generic)
49
+ import Network.HTTP.Client (Manager, newManager)
50
+ import Network.HTTP.Client.TLS (tlsManagerSettings)
51
+ import Network.HTTP.Types.Method (methodOptions)
52
+ import qualified Network.Wai.Handler.Warp as Warp
53
+ import Servant (ServantErr, serve)
54
+ import Servant.API
55
+ import Servant.API.Verbs (StdMethod (..), Verb)
56
+ import Servant.Client (ClientEnv, Scheme (Http), ServantError, client,
57
+ mkClientEnv, parseBaseUrl)
58
+ import Servant.Client.Core (baseUrlPort, baseUrlHost)
59
+ import Servant.Client.Internal.HttpClient (ClientM (..))
60
+ import Servant.Server (Handler (..))
61
+ import Web.FormUrlEncoded
62
+ import Web.HttpApiData
51
63
52
64
53
65
{ {#apiInfo} }{ {#apis} }{ {#operations} }{ {#operation} }{ {#hasFormParams} }
54
66
data { {vendorExtensions.x-formName} } = { {vendorExtensions.x-formName} }
55
67
{ {{#formParams} }{ {vendorExtensions.x-formPrefix} }{ {vendorExtensions.x-formParamName} } :: { {dataType} }{ {#hasMore} }
56
68
, { {/hasMore} }{ {/formParams} }
57
- } deriving (Show, Eq, Generic)
58
-
59
- instance FromFormUrlEncoded { {vendorExtensions.x-formName} } where
60
- fromFormUrlEncoded inputs = { {vendorExtensions.x-formName} } <$> { {#formParams} }lookupEither "{ {baseName} }" inputs{ {#hasMore} } <*> { {/hasMore} }{ {/formParams} }
61
-
62
- instance ToFormUrlEncoded { {vendorExtensions.x-formName} } where
63
- toFormUrlEncoded value =
64
- [ { {#formParams} }("{ {baseName} }", toQueryParam $ { {vendorExtensions.x-formPrefix} }{ {vendorExtensions.x-formParamName} } value){ {#hasMore} }
65
- , { {/hasMore} }{ {/formParams} }
66
- ]{ {/hasFormParams} }{ {/operation} }{ {/operations} }{ {/apis} }{ {/apiInfo} }
67
-
68
- -- For the form data code generation.
69
- lookupEither :: FromHttpApiData b => Text -> [(Text, Text)] -> Either String b
70
- lookupEither key assocs =
71
- case lookup key assocs of
72
- Nothing -> Left $ "Could not find parameter " <> (T.unpack key) <> " in form data"
73
- Just value ->
74
- case parseQueryParam value of
75
- Left result -> Left $ T.unpack result
76
- Right result -> Right $ result
69
+ } deriving (Show, Eq, Generic, Data)
77
70
78
- { {#apiInfo} }
79
- -- | Servant type-level API, generated from the OpenAPI spec for { {title} }.
80
- type { {title} }API
81
- = { {#apis} }{ {#operations} }{ {#operation} }{ {& vendorExtensions.x-routeType} } -- '{ {operationId} }' route{ {#hasMore} }
82
- :<|> { {/hasMore} }{ {/operation} }{ {/operations} }{ {#hasMore} }
83
- :<|> { {/hasMore} }{ {/apis} }
84
- { {/apiInfo} }
85
-
86
- -- | Server or client configuration, specifying the host and port to query or serve on.
87
- data ServerConfig = ServerConfig
88
- { configHost :: String -- ^ Hostname to serve on, e.g. " 127.0.0.1"
89
- , configPort :: Int -- ^ Port to serve on, e.g. 8080
90
- } deriving (Eq, Ord, Show, Read)
71
+ instance FromForm { {vendorExtensions.x-formName} }
72
+ instance ToForm { {vendorExtensions.x-formName} }
73
+ { {/hasFormParams} }{ {/operation} }{ {/operations} }{ {/apis} }{ {/apiInfo} }
91
74
92
75
-- | List of elements parsed from a query.
93
76
newtype QueryList (p :: CollectionFormat) a = QueryList
@@ -139,6 +122,27 @@ formatSeparatedQueryList :: ToHttpApiData a => Char -> QueryList p a -> Text
139
122
formatSeparatedQueryList char = T.intercalate (T.singleton char) . map toQueryParam . fromQueryList
140
123
141
124
125
+ { {#apiInfo} }
126
+ -- | Servant type-level API, generated from the OpenAPI spec for { {title} }.
127
+ type { {title} }API
128
+ = { {#apis} }{ {#operations} }{ {#operation} }{ {& vendorExtensions.x-routeType} } -- '{ {operationId} }' route{ {#hasMore} }
129
+ :<|> { {/hasMore} }{ {/operation} }{ {/operations} }{ {#hasMore} }
130
+ :<|> { {/hasMore} }{ {/apis} }
131
+ { {/apiInfo} }
132
+
133
+
134
+ -- | Server or client configuration, specifying the host and port to query or serve on.
135
+ data Config = Config
136
+ { configUrl :: String -- ^ scheme://hostname:port/path, e.g. " http://localhost:8080/"
137
+ } deriving (Eq, Ord, Show, Read)
138
+
139
+
140
+ -- | Custom exception type for our errors.
141
+ newtype { {title} }ClientError = { {title} }ClientError ServantError
142
+ deriving (Show, Exception)
143
+ -- | Configuration, specifying the full url of the service.
144
+
145
+
142
146
{ {#apiInfo} }
143
147
-- | Backend for { {title} }.
144
148
-- The backend can be used both for the client and the server. The client generated from the { {title} } OpenAPI spec
@@ -151,22 +155,22 @@ data {{title}}Backend m = {{title}}Backend
151
155
}
152
156
153
157
newtype { {title} }Client a = { {title} }Client
154
- { runClient :: Manager - > BaseUrl -> ExceptT ServantError IO a
158
+ { runClient :: ClientEnv -> ExceptT ServantError IO a
155
159
} deriving Functor
156
160
157
161
instance Applicative { {title} }Client where
158
- pure x = { {title} }Client (\_ _ -> pure x)
162
+ pure x = { {title} }Client (\_ -> pure x)
159
163
({ {title} }Client f) <*> ({ {title} }Client x) =
160
- { {title} }Client (\manager url -> f manager url <*> x manager url )
164
+ { {title} }Client (\env -> f env <*> x env )
161
165
162
166
instance Monad { {title} }Client where
163
167
({ {title} }Client a) >>= f =
164
- { {title} }Client (\manager url -> do
165
- value <- a manager url
166
- runClient (f value) manager url )
168
+ { {title} }Client (\env -> do
169
+ value <- a env
170
+ runClient (f value) env )
167
171
168
172
instance MonadIO { { title} } Client where
169
- liftIO io = { { title} } Client (\_ _ - > liftIO io)
173
+ liftIO io = { { title} } Client (\_ - > liftIO io)
170
174
{ {/apiInfo} }
171
175
172
176
{ {#apiInfo} }
@@ -178,24 +182,41 @@ create{{title}}Client = {{title}}Backend{..}
178
182
{ {/hasMore} }{ {/apis} }) = client (Proxy :: Proxy { {title} }API)
179
183
180
184
-- | Run requests in the { {title} }Client monad.
181
- run{ {title} }Client :: ServerConfig -> { {title} }Client a -> ExceptT ServantError IO a
185
+ run{ {title} }Client :: Config -> { {title} }Client a -> ExceptT ServantError IO a
182
186
run{ {title} }Client clientConfig cl = do
183
- manager <- liftIO $ newManager defaultManagerSettings
187
+ manager <- liftIO $ newManager tlsManagerSettings
184
188
run{{title}}ClientWithManager manager clientConfig cl
185
189
186
190
-- | Run requests in the { { title} } Client monad using a custom manager.
187
- run{{title}}ClientWithManager :: Manager - > ServerConfig -> { {title} }Client a -> ExceptT ServantError IO a
188
- run{ {title} }ClientWithManager manager clientConfig cl =
189
- runClient cl manager $ BaseUrl Http (configHost clientConfig) (configPort clientConfig) ""
191
+ run{{title}}ClientWithManager :: Manager - > Config -> { {title} }Client a -> ExceptT ServantError IO a
192
+ run{ {title} }ClientWithManager manager Config{ ..} cl = do
193
+ url <- parseBaseUrl configUrl
194
+ runClient cl $ mkClientEnv manager url
195
+
196
+ -- | Like @runClient@, but returns the response or throws
197
+ -- a { { title} } ClientError
198
+ call{{title}}
199
+ :: (MonadIO m, MonadThrow m)
200
+ => ClientEnv -> { {title} }Client a -> m a
201
+ call{ {title} } env f = do
202
+ res <- liftIO $ runExceptT $ runClient f env
203
+ case res of
204
+ Left err - > throwM ({ {title} }ClientError err)
205
+ Right response -> pure response
190
206
{ {/apiInfo} }
191
207
192
208
{ {#apiInfo} }
193
209
-- | Run the { {title} } server at the provided host and port.
194
- run{ {title} }Server :: MonadIO m => ServerConfig -> { {title} }Backend (ExceptT ServantErr IO) -> m ()
195
- run{ {title} }Server ServerConfig{ ..} backend =
210
+ run{ {title} }Server
211
+ :: (MonadIO m, MonadThrow m)
212
+ => Config -> { {title} }Backend (ExceptT ServantErr IO) -> m ()
213
+ run{ {title} }Server Config{ ..} backend = do
214
+ url <- parseBaseUrl configUrl
215
+ let warpSettings = Warp.defaultSettings
216
+ & Warp.setPort (baseUrlPort url)
217
+ & Warp.setHost (fromString $ baseUrlHost url)
196
218
liftIO $ Warp.runSettings warpSettings $ serve (Proxy :: Proxy { { title} } API) (serverFromBackend backend)
197
219
where
198
- warpSettings = Warp.defaultSettings & Warp.setPort configPort & Warp.setHost (fromString configHost)
199
220
serverFromBackend { { title} } Backend{..} =
200
221
({{#apis}}{{#operations}}{{#operation}}coerce { { operationId} } { { #hasMore} } : <| >
201
222
{ {/hasMore} }{ {/operation} }{ {/operations} }{ {#hasMore} } :<|>
0 commit comments