on develop help sign in
Removing direct execution of client in favour of incremental request builder
b88c75fBrendan Hay
core/gogol-core.cabal+4−4
@@ -46,18 +46,18 @@ library
46 build-depends:
46 build-depends:
47 aeson >= 0.8
47 aeson >= 0.8
48 , base >= 4.7 && < 5
48 , base >= 4.7 && < 5
49− , conduit
50 , bifunctors
49 , bifunctors
51 , bytestring
50 , bytestring
51+ , conduit
52+ , data-default-class
52 , dlist >= 0.7
53 , dlist >= 0.7
54+ , exceptions
53 , hashable >= 1.2
55 , hashable >= 1.2
54 , http-client
56 , http-client
55− , data-default-class
56− , resourcet
57− , http-conduit
58 , http-media
57 , http-media
59 , http-types
58 , http-types
60 , lens >= 4.4
59 , lens >= 4.4
60+ , resourcet
61 , scientific >= 0.3
61 , scientific >= 0.3
62 , servant >= 0.4.4
62 , servant >= 0.4.4
63 , text >= 1.1
63 , text >= 1.1
core/src/Network/Google/Client.hs+171−156
@@ -7,6 +7,7 @@
7{-# LANGUAGE LambdaCase #-}
7{-# LANGUAGE LambdaCase #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
9{-# LANGUAGE OverloadedStrings #-}
9{-# LANGUAGE OverloadedStrings #-}
10+{-# LANGUAGE RankNTypes #-}
10{-# LANGUAGE RecordWildCards #-}
11{-# LANGUAGE RecordWildCards #-}
11{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE TypeFamilies #-}
13{-# LANGUAGE TypeFamilies #-}
@@ -23,7 +24,8 @@
23--
24--
24module Network.Google.Client where
25module Network.Google.Client where
25
26
26−import Control.Exception
27+import Control.Monad.Catch
28+-- import Control.Exception
27import Control.Lens
29import Control.Lens
28import Control.Monad.Trans.Class
30import Control.Monad.Trans.Class
29import Control.Monad.Trans.Reader
31import Control.Monad.Trans.Reader
@@ -50,9 +52,8 @@ import GHC.Generics
50import GHC.TypeLits
52import GHC.TypeLits
51import Network.Google.Types
53import Network.Google.Types
52import Network.HTTP.Client (HttpException)
54import Network.HTTP.Client (HttpException)
53−import Network.HTTP.Conduit (Manager)
54−import qualified Network.HTTP.Conduit as Client
55+import qualified Network.HTTP.Client as Client
55−import Network.HTTP.Media
56+import Network.HTTP.Media hiding (Accept)
56import Network.HTTP.Types
57import Network.HTTP.Types
57import Servant.API hiding (Header)
58import Servant.API hiding (Header)
58
59
@@ -71,45 +72,6 @@ type ResponseBody = ResumableSource (ResourceT IO) ByteString
71data Body = Body -- MediaType
72data Body = Body -- MediaType
72 deriving (Eq, Show, Generic, Data, Typeable)
73 deriving (Eq, Show, Generic, Data, Typeable)
73
74
74−data Request = Request
75− { _rqHost :: ByteString
76− , _rqPort :: Int
77− , _rqBasePath :: ByteString
78− , _rqPath :: Builder
79− , _rqQuery :: DList (Text, Maybe Text)
80− , _rqHeaders :: [Header]
81− , _rqBody :: Maybe (MediaType, LBS.ByteString)
82− }
83−
84−initialRequest :: ByteString -- ^ Host.
85− -> Int -- ^ Port.
86− -> ByteString -- ^ Base Path.
87− -> Request
88−initialRequest h n p = Request
89− { _rqHost = h
90− , _rqPort = n
91− , _rqBasePath = p
92− , _rqPath = mempty
93− , _rqQuery = mempty
94− , _rqHeaders = mempty
95− , _rqBody = Nothing
96− }
97−
98−appendPath :: Request -> Builder -> Request
99−appendPath rq x = rq { _rqPath = _rqPath rq <> "/" <> x }
100−
101−appendPaths :: ToText a => Request -> [a] -> Request
102−appendPaths rq = appendPath rq . foldMap (mappend "/" . buildText)
103−
104−appendQuery :: Request -> Text -> Maybe Text -> Request
105−appendQuery rq k v = rq { _rqQuery = DList.snoc (_rqQuery rq) (k, v) }
106−
107−setBody :: Request -> MediaType -> LBS.ByteString -> Request
108−setBody rq c x = rq { _rqBody = Just (c, x) }
109−
110−-- setAccept :: Reuqest -> [MediaType] -> Request
111−-- setAccept rq xs = rq { _rqAccept = xs }
112−
113data Error
75data Error
114 = TransportError HttpException
76 = TransportError HttpException
115 | SerializeError SerializeError
77 | SerializeError SerializeError
@@ -167,50 +129,142 @@ instance AsError Error where
167 ServiceError e -> Right e
129 ServiceError e -> Right e
168 x -> Left x
130 x -> Left x
169
131
132+-- | Multiple path captures, with @[xs]@ forming @/<x1>/<x2>/<x2>/...@.
170data Captures (s :: Symbol) a
133data Captures (s :: Symbol) a
171 deriving (Typeable)
134 deriving (Typeable)
172
135
136+-- | Form a Google style sub-resource, such as @/<capture>:<mode>@.
173data CaptureMode (s :: Symbol) (m :: Symbol) a
137data CaptureMode (s :: Symbol) (m :: Symbol) a
174 deriving (Typeable)
138 deriving (Typeable)
175
139
176data MultipartRelated (cs :: [*]) m b
140data MultipartRelated (cs :: [*]) m b
177 deriving (Typeable)
141 deriving (Typeable)
178
142
179−instance ( MimeRender c m
143+-- | An intermediary request builder.
180− , GoogleClient fn
144+data RequestBuilder = RequestBuilder
181− ) => GoogleClient (MultipartRelated (c ': cs) m b :> fn) where
145+ { _rqHost :: ByteString
182− type Fn (MultipartRelated (c ': cs) m b :> fn) = m -> b -> Fn fn
146+ , _rqBasePath :: ByteString
147+ , _rqPath :: Builder
148+ , _rqQuery :: DList (Text, Maybe Text)
149+ , _rqBody :: Maybe (MediaType, LBS.ByteString)
150+ }
183
151
184− -- requestBuild Proxy rq m b = requestBuild (Proxy :: Proxy fn) $
152+-- | Construct a default request builder. Since the Google service descriptions
185− -- mimeRender p m
153+-- have http/https in the host (rootUrl), the scheme is assumed.
186− -- mimeRender (Proxy :: Proxy OctetStream) b
154+defaultRequest :: ByteString -- ^ Host. (rootUrl)
187− -- where
155+ -> ByteString -- ^ Path. (servicePath)
188− -- p = Proxy :: Proxy c
156+ -> RequestBuilder
157+defaultRequest h p = RequestBuilder
158+ { _rqHost = h
159+ , _rqBasePath = p
160+ , _rqPath = mempty
161+ , _rqQuery = mempty
162+ , _rqBody = Nothing
163+ }
164+
165+appendPath :: RequestBuilder -> Builder -> RequestBuilder
166+appendPath rq x = rq { _rqPath = _rqPath rq <> "/" <> x }
189
167
168+appendPaths :: ToText a => RequestBuilder -> [a] -> RequestBuilder
169+appendPaths rq = appendPath rq . foldMap (mappend "/" . buildText)
170+
171+appendQuery :: RequestBuilder -> Text -> Maybe Text -> RequestBuilder
172+appendQuery rq k v = rq { _rqQuery = DList.snoc (_rqQuery rq) (k, v) }
173+
174+setBody :: RequestBuilder -> MediaType -> LBS.ByteString -> RequestBuilder
175+setBody rq c x = rq { _rqBody = Just (c, x) }
176+
177+-- | A materialised 'http-client' request and associated response parser.
178+data Client a = Client
179+ { _clientAccept :: Maybe MediaType
180+ , _clientCheck :: Int -> Bool
181+ , _clientResponse :: ResponseBody -> ResourceT IO (Either SerializeError a)
182+ , _clientRequest :: ClientRequest
183+ }
184+
185+client :: (ResponseBody -> ResourceT IO (Either SerializeError a))
186+ -> Maybe MediaType
187+ -> Method
188+ -> [Int]
189+ -> RequestBuilder
190+ -> Client a
191+client f cs m ns rq = Client
192+ { _clientAccept = cs
193+ , _clientCheck = (`elem` ns)
194+ , _clientResponse = f
195+ , _clientRequest = clientRequest cs m rq
196+ }
197+
198+clientRequest :: Maybe MediaType -> Method -> RequestBuilder -> ClientRequest
199+clientRequest cs m RequestBuilder {..} = def
200+ { Client.method = m
201+ , Client.checkStatus = \_ _ _ -> Nothing
202+ }
203+
204+clientMime :: MimeUnrender c a
205+ => Proxy c
206+ -> Method
207+ -> [Int]
208+ -> RequestBuilder
209+ -> Client a
210+clientMime p = client go (Just (contentType p))
211+ where
212+ go b = do
213+ lbs <- sinkLBS b
214+ case mimeUnrender p lbs of
215+ Left e -> undefined
216+ Right x -> pure (Right x)
217+
218+clientStream :: Accept c
219+ => Proxy c
220+ -> Method
221+ -> [Int]
222+ -> RequestBuilder
223+ -> Client ResponseBody
224+clientStream p = client (pure . Right) (Just (contentType p))
225+
226+clientEmpty :: Method -> [Int] -> RequestBuilder -> Client ()
227+clientEmpty = client go Nothing
228+ where
229+ go b = closeResumableSource b >> pure (Right ())
230+
231+-- sinkLBS :: ResponseBody -> Client LBS.ByteString
232+sinkLBS = fmap LBS.fromChunks . ($$+- CL.consume)
190
233
191class GoogleRequest a where
234class GoogleRequest a where
192 type Rs a :: *
235 type Rs a :: *
193
236
194− request :: a -> IO (Either Error (Rs a))
237+ request :: a -> Client (Rs a)
195− requestWith :: Request -> a -> IO (Either Error (Rs a))
238+ requestWith :: RequestBuilder -> a -> Client (Rs a)
196
239
197class GoogleClient fn where
240class GoogleClient fn where
198 type Fn fn :: *
241 type Fn fn :: *
199
242
200− requestBuild :: Proxy fn -> Request -> Fn fn
243+ clientBuild :: Proxy fn -> RequestBuilder -> Fn fn
244+
245+instance ( MimeRender c m
246+ , GoogleClient fn
247+ ) => GoogleClient (MultipartRelated (c ': cs) m b :> fn) where
248+ type Fn (MultipartRelated (c ': cs) m b :> fn) = m -> b -> Fn fn
249+
250+ -- requestBuild Proxy rq m b = requestBuild (Proxy :: Proxy fn) $
251+ -- mimeRender p m
252+ -- mimeRender (Proxy :: Proxy OctetStream) b
253+ -- where
254+ -- p = Proxy :: Proxy c
201
255
202instance (KnownSymbol s, GoogleClient fn) => GoogleClient (s :> fn) where
256instance (KnownSymbol s, GoogleClient fn) => GoogleClient (s :> fn) where
203 type Fn (s :> fn) = Fn fn
257 type Fn (s :> fn) = Fn fn
204
258
205− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn) $
259+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn) $
206 appendPath rq (buildSymbol (Proxy :: Proxy s))
260 appendPath rq (buildSymbol (Proxy :: Proxy s))
207
261
208instance (GoogleClient a, GoogleClient b) => GoogleClient (a :<|> b) where
262instance (GoogleClient a, GoogleClient b) => GoogleClient (a :<|> b) where
209 type Fn (a :<|> b) = Fn a :<|> Fn b
263 type Fn (a :<|> b) = Fn a :<|> Fn b
210
264
211− requestBuild Proxy rq =
265+ clientBuild Proxy rq =
212− requestBuild (Proxy :: Proxy a) rq
266+ clientBuild (Proxy :: Proxy a) rq
213− :<|> requestBuild (Proxy :: Proxy b) rq
267+ :<|> clientBuild (Proxy :: Proxy b) rq
214
268
215instance ( KnownSymbol s
269instance ( KnownSymbol s
216 , ToText a
270 , ToText a
@@ -218,7 +272,7 @@ instance ( KnownSymbol s
218 ) => GoogleClient (Capture s a :> fn) where
272 ) => GoogleClient (Capture s a :> fn) where
219 type Fn (Capture s a :> fn) = a -> Fn fn
273 type Fn (Capture s a :> fn) = a -> Fn fn
220
274
221− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn)
275+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn)
222 . appendPath rq
276 . appendPath rq
223 . buildText
277 . buildText
224
278
@@ -228,7 +282,7 @@ instance ( KnownSymbol s
228 ) => GoogleClient (Captures s a :> fn) where
282 ) => GoogleClient (Captures s a :> fn) where
229 type Fn (Captures s a :> fn) = [a] -> Fn fn
283 type Fn (Captures s a :> fn) = [a] -> Fn fn
230
284
231− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn)
285+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn)
232 . appendPaths rq
286 . appendPaths rq
233
287
234instance ( KnownSymbol s
288instance ( KnownSymbol s
@@ -238,7 +292,7 @@ instance ( KnownSymbol s
238 ) => GoogleClient (CaptureMode s m a :> fn) where
292 ) => GoogleClient (CaptureMode s m a :> fn) where
239 type Fn (CaptureMode s m a :> fn) = a -> Fn fn
293 type Fn (CaptureMode s m a :> fn) = a -> Fn fn
240
294
241− requestBuild Proxy rq x = requestBuild (Proxy :: Proxy fn)
295+ clientBuild Proxy rq x = clientBuild (Proxy :: Proxy fn)
242 . appendPath rq
296 . appendPath rq
243 $ buildText x <> buildSymbol (Proxy :: Proxy m)
297 $ buildText x <> buildSymbol (Proxy :: Proxy m)
244
298
@@ -248,7 +302,7 @@ instance ( KnownSymbol s
248 ) => GoogleClient (QueryParam s a :> fn) where
302 ) => GoogleClient (QueryParam s a :> fn) where
249 type Fn (QueryParam s a :> fn) = Maybe a -> Fn fn
303 type Fn (QueryParam s a :> fn) = Maybe a -> Fn fn
250
304
251− requestBuild Proxy rq mx = requestBuild (Proxy :: Proxy fn) $
305+ clientBuild Proxy rq mx = clientBuild (Proxy :: Proxy fn) $
252 case mx of
306 case mx of
253 Nothing -> rq
307 Nothing -> rq
254 Just x -> appendQuery rq k v
308 Just x -> appendQuery rq k v
@@ -262,7 +316,7 @@ instance ( KnownSymbol s
262 ) => GoogleClient (QueryParams s a :> fn) where
316 ) => GoogleClient (QueryParams s a :> fn) where
263 type Fn (QueryParams s a :> fn) = [a] -> Fn fn
317 type Fn (QueryParams s a :> fn) = [a] -> Fn fn
264
318
265− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn) . foldl' go rq
319+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn) . foldl' go rq
266 where
320 where
267 go r = appendQuery r k . Just . toText
321 go r = appendQuery r k . Just . toText
268
322
@@ -273,7 +327,7 @@ instance ( MimeRender c a
273 ) => GoogleClient (ReqBody (c ': cs) a :> fn) where
327 ) => GoogleClient (ReqBody (c ': cs) a :> fn) where
274 type Fn (ReqBody (c ': cs) a :> fn) = a -> Fn fn
328 type Fn (ReqBody (c ': cs) a :> fn) = a -> Fn fn
275
329
276− requestBuild Proxy rq = requestBuild (Proxy :: Proxy fn)
330+ clientBuild Proxy rq = clientBuild (Proxy :: Proxy fn)
277 . setBody rq (contentType p)
331 . setBody rq (contentType p)
278 . mimeRender p
332 . mimeRender p
279 where
333 where
@@ -284,128 +338,89 @@ instance ( MimeRender c a
284instance MimeUnrender c a => GoogleClient (Get (c ': cs) a) where
338instance MimeUnrender c a => GoogleClient (Get (c ': cs) a) where
285 type Fn (Get (c ': cs) a) = Client a
339 type Fn (Get (c ': cs) a) = Client a
286
340
287− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodGet [200, 203]
341+ clientBuild Proxy = clientMime (Proxy :: Proxy c) methodGet [200, 203]
288
342
289−instance GoogleClient (Get (c ': cs) ResponseBody) where
343+instance Accept c => GoogleClient (Get (c ': cs) ResponseBody) where
290 type Fn (Get (c ': cs) ResponseBody) = Client ResponseBody
344 type Fn (Get (c ': cs) ResponseBody) = Client ResponseBody
291
345
292− requestBuild Proxy = requestBody (Proxy :: Proxy c) methodGet [200, 203]
346+ clientBuild Proxy = clientStream (Proxy :: Proxy c) methodGet [200, 203]
293
347
294instance GoogleClient (Get (c ': cs) ()) where
348instance GoogleClient (Get (c ': cs) ()) where
295 type Fn (Get (c ': cs) ()) = Client ()
349 type Fn (Get (c ': cs) ()) = Client ()
296
350
297− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodGet [204]
351+ clientBuild Proxy = clientEmpty methodGet [204]
298
352
299instance MimeUnrender c a => GoogleClient (Post (c ': cs) a) where
353instance MimeUnrender c a => GoogleClient (Post (c ': cs) a) where
300 type Fn (Post (c ': cs) a) = Client a
354 type Fn (Post (c ': cs) a) = Client a
301
355
302− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPost [200, 201]
356+ clientBuild Proxy = clientMime (Proxy :: Proxy c) methodPost [200, 201]
303
357
304instance GoogleClient (Post (c ': cs) ()) where
358instance GoogleClient (Post (c ': cs) ()) where
305 type Fn (Post (c ': cs) ()) = Client ()
359 type Fn (Post (c ': cs) ()) = Client ()
306
360
307− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPost [204]
361+ clientBuild Proxy = clientEmpty methodPost [204]
308
362
309instance MimeUnrender c a => GoogleClient (Put (c ': cs) a) where
363instance MimeUnrender c a => GoogleClient (Put (c ': cs) a) where
310 type Fn (Put (c ': cs) a) = Client a
364 type Fn (Put (c ': cs) a) = Client a
311
365
312− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPut [200, 201]
366+ clientBuild Proxy = clientMime (Proxy :: Proxy c) methodPut [200, 201]
313
367
314instance GoogleClient (Put (c ': cs) ()) where
368instance GoogleClient (Put (c ': cs) ()) where
315 type Fn (Put (c ': cs) ()) = Client ()
369 type Fn (Put (c ': cs) ()) = Client ()
316
370
317− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPut [204]
371+ clientBuild Proxy = clientEmpty methodPut [204]
318
372
319instance MimeUnrender c a => GoogleClient (Patch (c ': cs) a) where
373instance MimeUnrender c a => GoogleClient (Patch (c ': cs) a) where
320 type Fn (Patch (c ': cs) a) = Client a
374 type Fn (Patch (c ': cs) a) = Client a
321
375
322− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPatch [200, 201]
376+ clientBuild Proxy = clientMime (Proxy :: Proxy c) methodPatch [200, 201]
323
377
324instance GoogleClient (Patch (c ': cs) ()) where
378instance GoogleClient (Patch (c ': cs) ()) where
325 type Fn (Patch (c ': cs) ()) = Client ()
379 type Fn (Patch (c ': cs) ()) = Client ()
326
380
327− requestBuild Proxy = requestMime (Proxy :: Proxy c) methodPatch [204]
381+ clientBuild Proxy = clientEmpty methodPatch [204]
328−
382+
329−type Client a = ReaderT Manager IO (Either Error a)
383+-- perform :: Accept
330−
384+-- -> (ResponseBody -> Client (Either SerializeError a))
331−requestMime :: MimeUnrender c a
332− => Proxy c
333− -> Method
385+-- -> Method
334− -> [Int]
386+-- -> [Int]
335− -> Request
387+-- -> RequestBuilder
336− -> Client a
388+-- -> Client a
337−requestMime p = perform c go
338− where
339− c = contentType p
340−
341− go b = do
342− lbs <- sinkLBS b
343− case mimeUnrender c lbs of
344− Left e -> SerializeError
345− Right x -> undefined
389+-- perform a meth ns f rq = undefined -- do
346−
347−requestbody :: Accept c
348− => Proxy c
349− -> Method
350− -> [Int]
351− -> Request
352− -> Client ResponseBody
353−requestBody p = perform c (pure . Right)
354− where
355− c = contentType p
356−
357−-- performNoBody = httpNoBody
358−
359−perform :: Accept
360− -> (ResponseBody -> Client (Either SerializeError a))
361− -> Method
362− -> [Int]
363− -> Request
364− -> Client a
365−perform a meth ns f rq = do
366− m <- ask
390+ -- m <- ask
367− x <- lift . try $ Client.http m (clientRequest meth a rq)
391+ -- x <- lift . try $ Client.http m (clientRequest meth a rq)
368− either (failure . TransportError) success x
392+ -- either (failure . TransportError) success x
369− where
393+ -- where
370− failure = pure . Left
394+ -- failure = pure . Left
371−
395+
372− success rs = do
396+ -- success rs = do
373− let s = Client.responseStatus rs
397+ -- let s = Client.responseStatus rs
374− b = Client.responseBody rs
398+ -- b = Client.responseBody rs
375− hs = Client.responseHeaders rs
399+ -- hs = Client.responseHeaders rs
376− case content hs of
400+ -- case content hs of
377− unless (matches respCT (acceptCT)) $
401+ -- unless (matches respCT (acceptCT)) $
378− left $ UnsupportedContentType respCT respBody
402+ -- left $ UnsupportedContentType respCT respBody
379−
403+
380− Nothing -> failure (SerializeError' (SerializeError hs))
404+ -- Nothing -> failure (SerializeError' (SerializeError hs))
381−
405+
382− Just _ | statusInvalid s -> do
406+ -- Just _ | statusInvalid s -> do
383− lbs <- sinkLBS b
407+ -- lbs <- sinkLBS b
384− failure (ServiceError' (ServiceError s c hs lbs))
408+ -- failure (ServiceError' (ServiceError s c hs lbs))
385−
409+
386− Just c | contentInvalid c -> do
410+ -- Just c | contentInvalid c -> do
387− lbs <- sinkLBS b
411+ -- lbs <- sinkLBS b
388− failure (SerializeError' (SerializeError s c hs lbs))
412+ -- failure (SerializeError' (SerializeError s c hs lbs))
389−
413+
390−
414+
391− Just c -> first SerializeError' <$> f b
415+ -- Just c -> first SerializeError' <$> f b
392−
416+
393− content :: [Header] -> Maybe c
417+ -- content :: [Header] -> Maybe c
394− content = parseAccept
418+ -- content = parseAccept
395− . fromMaybe "application/octet-stream"
419+ -- . fromMaybe "application/octet-stream"
396− . lookup hContentType
420+ -- . lookup hContentType
397−
421+
398− statusCheck s = fromEnum s `elem` ns
422+ -- statusCheck s = fromEnum s `elem` ns
399− contentCheck = matches a
423+ -- contentCheck = matches a
400−
401−clientRequest :: Method -> MediaType -> Request -> ClientRequest
402−clientRequest m c Request {..} = def
403− { Client.method = m
404− , Client.checkStatus = \_ _ _ -> Nothing
405− }
406−
407−sinkLBS :: ResponseBody -> Client LBS.ByteString
408−sinkLBS = fmap LBS.fromChunks . ($$+- CL.consme)
409
424
410buildText :: ToText a => a -> Builder
425buildText :: ToText a => a -> Builder
411buildText = Build.fromText . toText
426buildText = Build.fromText . toText