Merge pull request #913 from haskell-servant/jkarni/expose-more-request-constructors
Streaming request body for servant-client-core
This commit is contained in:
commit
3750f22e01
8 changed files with 169 additions and 69 deletions
|
@ -1,14 +1,14 @@
|
||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
{-# LANGUAGE DeriveFunctor #-}
|
{-# LANGUAGE DeriveFoldable #-}
|
||||||
{-# LANGUAGE DeriveFoldable #-}
|
{-# LANGUAGE DeriveFunctor #-}
|
||||||
{-# LANGUAGE DeriveTraversable #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveTraversable #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE RankNTypes #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE RankNTypes #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
module Servant.Client.Core.Internal.Request where
|
module Servant.Client.Core.Internal.Request where
|
||||||
|
|
||||||
|
@ -16,9 +16,10 @@ import Prelude ()
|
||||||
import Prelude.Compat
|
import Prelude.Compat
|
||||||
|
|
||||||
import Control.Monad.Catch (Exception)
|
import Control.Monad.Catch (Exception)
|
||||||
import qualified Data.ByteString.Builder as Builder
|
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
|
import qualified Data.ByteString.Builder as Builder
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
|
import Data.Int (Int64)
|
||||||
import Data.Semigroup ((<>))
|
import Data.Semigroup ((<>))
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
@ -58,13 +59,19 @@ data RequestF a = Request
|
||||||
, requestHeaders :: Seq.Seq Header
|
, requestHeaders :: Seq.Seq Header
|
||||||
, requestHttpVersion :: HttpVersion
|
, requestHttpVersion :: HttpVersion
|
||||||
, requestMethod :: Method
|
, requestMethod :: Method
|
||||||
} deriving (Eq, Show, Functor, Generic, Typeable)
|
} deriving (Generic, Typeable)
|
||||||
|
|
||||||
type Request = RequestF Builder.Builder
|
type Request = RequestF Builder.Builder
|
||||||
|
|
||||||
-- | The request body. Currently only lazy ByteStrings are supported.
|
-- | The request body. A replica of the @http-client@ @RequestBody@.
|
||||||
newtype RequestBody = RequestBodyLBS LBS.ByteString
|
data RequestBody
|
||||||
deriving (Eq, Ord, Read, Show, Typeable)
|
= RequestBodyLBS LBS.ByteString
|
||||||
|
| RequestBodyBS BS.ByteString
|
||||||
|
| RequestBodyBuilder Int64 Builder.Builder
|
||||||
|
| RequestBodyStream Int64 ((IO BS.ByteString -> IO ()) -> IO ())
|
||||||
|
| RequestBodyStreamChunked ((IO BS.ByteString -> IO ()) -> IO ())
|
||||||
|
| RequestBodyIO (IO RequestBody)
|
||||||
|
deriving (Generic, Typeable)
|
||||||
|
|
||||||
data GenResponse a = Response
|
data GenResponse a = Response
|
||||||
{ responseStatusCode :: Status
|
{ responseStatusCode :: Status
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
module Servant.Client.Internal.XhrClient where
|
module Servant.Client.Internal.XhrClient where
|
||||||
|
|
||||||
import Control.Arrow
|
import Control.Arrow
|
||||||
import Data.ByteString.Builder (toLazyByteString)
|
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
import Control.Exception
|
import Control.Exception
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
|
@ -25,13 +24,15 @@ import Control.Monad.Error.Class (MonadError (..))
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
import Control.Monad.Trans.Control (MonadBaseControl (..))
|
import Control.Monad.Trans.Control (MonadBaseControl (..))
|
||||||
import Control.Monad.Trans.Except
|
import Control.Monad.Trans.Except
|
||||||
import qualified Data.ByteString.Char8 as BS
|
import Data.ByteString.Builder (toLazyByteString)
|
||||||
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import Data.CaseInsensitive
|
import Data.CaseInsensitive
|
||||||
import Data.Char
|
import Data.Char
|
||||||
import Data.Foldable (toList)
|
import Data.Foldable (toList)
|
||||||
import Data.Functor.Alt (Alt (..))
|
import Data.Functor.Alt (Alt (..))
|
||||||
|
import Data.IORef (modifyIORef, newIORef, readIORef)
|
||||||
import Data.Proxy (Proxy (..))
|
import Data.Proxy (Proxy (..))
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
import Data.String.Conversions
|
import Data.String.Conversions
|
||||||
import Foreign.StablePtr
|
import Foreign.StablePtr
|
||||||
import GHC.Generics
|
import GHC.Generics
|
||||||
|
@ -39,8 +40,8 @@ import GHCJS.Foreign.Callback
|
||||||
import GHCJS.Prim
|
import GHCJS.Prim
|
||||||
import GHCJS.Types
|
import GHCJS.Types
|
||||||
import JavaScript.Web.Location
|
import JavaScript.Web.Location
|
||||||
|
import Network.HTTP.Media (renderHeader)
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
import Network.HTTP.Media (renderHeader)
|
|
||||||
import Servant.Client.Core
|
import Servant.Client.Core
|
||||||
|
|
||||||
newtype JSXMLHttpRequest = JSXMLHttpRequest JSVal
|
newtype JSXMLHttpRequest = JSXMLHttpRequest JSVal
|
||||||
|
@ -152,7 +153,8 @@ performXhr xhr burl request = do
|
||||||
|
|
||||||
openXhr xhr (cs $ requestMethod request) (toUrl burl request) True
|
openXhr xhr (cs $ requestMethod request) (toUrl burl request) True
|
||||||
setHeaders xhr request
|
setHeaders xhr request
|
||||||
sendXhr xhr (toBody request)
|
body <- toBody request
|
||||||
|
sendXhr xhr body
|
||||||
takeMVar waiter
|
takeMVar waiter
|
||||||
|
|
||||||
freeStablePtr s
|
freeStablePtr s
|
||||||
|
@ -226,11 +228,31 @@ foreign import javascript unsafe "$1.send()"
|
||||||
foreign import javascript unsafe "$1.send($2)"
|
foreign import javascript unsafe "$1.send($2)"
|
||||||
js_sendXhrWithBody :: JSXMLHttpRequest -> JSVal -> IO ()
|
js_sendXhrWithBody :: JSXMLHttpRequest -> JSVal -> IO ()
|
||||||
|
|
||||||
toBody :: Request -> Maybe String
|
toBody :: Request -> IO (Maybe String)
|
||||||
toBody request = case requestBody request of
|
toBody request = case requestBody request of
|
||||||
Nothing -> Nothing
|
Nothing -> return Nothing
|
||||||
Just (RequestBodyLBS "", _) -> Nothing
|
Just (a, _) -> go a
|
||||||
Just (RequestBodyLBS x, _) -> Just $ cs x
|
|
||||||
|
where
|
||||||
|
go :: RequestBody -> IO (Maybe String)
|
||||||
|
go x = case x of
|
||||||
|
RequestBodyLBS x -> return $ mBody x
|
||||||
|
RequestBodyBS x -> return $ mBody x
|
||||||
|
RequestBodyBuilder _ x -> return $ mBody $ toLazyByteString x
|
||||||
|
RequestBodyStream _ x -> mBody <$> readBody x
|
||||||
|
RequestBodyStreamChunked x -> mBody <$> readBody x
|
||||||
|
RequestBodyIO x -> x >>= go
|
||||||
|
|
||||||
|
mBody :: ConvertibleStrings a String => a -> Maybe String
|
||||||
|
mBody x = let y = cs x in if y == "" then Nothing else Just y
|
||||||
|
|
||||||
|
readBody writer = do
|
||||||
|
m <- newIORef mempty
|
||||||
|
_ <- writer (\bsAct -> do
|
||||||
|
bs <- bsAct
|
||||||
|
modifyIORef m (<> bs))
|
||||||
|
readIORef m
|
||||||
|
|
||||||
|
|
||||||
-- * inspecting the xhr response
|
-- * inspecting the xhr response
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ library
|
||||||
|
|
||||||
test-suite spec
|
test-suite spec
|
||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall -rtsopts -with-rtsopts=-T
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
hs-source-dirs: test
|
hs-source-dirs: test
|
||||||
main-is: Spec.hs
|
main-is: Spec.hs
|
||||||
|
@ -112,14 +112,15 @@ test-suite spec
|
||||||
|
|
||||||
-- Additonal dependencies
|
-- Additonal dependencies
|
||||||
build-depends:
|
build-depends:
|
||||||
deepseq >= 1.3.0.2 && < 1.5
|
deepseq >= 1.3.0.2 && < 1.5
|
||||||
, generics-sop >= 0.3.1.0 && < 0.4
|
, generics-sop >= 0.3.1.0 && < 0.4
|
||||||
, hspec >= 2.4.4 && < 2.5
|
, hspec >= 2.4.4 && < 2.5
|
||||||
, HUnit >= 1.6 && < 1.7
|
, HUnit >= 1.6 && < 1.7
|
||||||
, network >= 2.6.3.2 && < 2.7
|
, random-bytestring >= 0.1 && < 0.2
|
||||||
, QuickCheck >= 2.10.1 && < 2.12
|
, network >= 2.6.3.2 && < 2.7
|
||||||
, servant == 0.13.*
|
, QuickCheck >= 2.10.1 && < 2.12
|
||||||
, servant-server == 0.13.*
|
, servant == 0.13.*
|
||||||
|
, servant-server == 0.13.*
|
||||||
|
|
||||||
build-tool-depends:
|
build-tool-depends:
|
||||||
hspec-discover:hspec-discover >= 2.4.4 && < 2.5
|
hspec-discover:hspec-discover >= 2.4.4 && < 2.5
|
||||||
|
|
|
@ -183,10 +183,18 @@ requestToClientRequest burl r = Client.defaultRequest
|
||||||
where
|
where
|
||||||
hs = toList $ requestAccept r
|
hs = toList $ requestAccept r
|
||||||
|
|
||||||
|
convertBody bd = case bd of
|
||||||
|
RequestBodyLBS body' -> Client.RequestBodyLBS body'
|
||||||
|
RequestBodyBS body' -> Client.RequestBodyBS body'
|
||||||
|
RequestBodyBuilder size body' -> Client.RequestBodyBuilder size body'
|
||||||
|
RequestBodyStream size body' -> Client.RequestBodyStream size body'
|
||||||
|
RequestBodyStreamChunked body' -> Client.RequestBodyStreamChunked body'
|
||||||
|
RequestBodyIO body' -> Client.RequestBodyIO (convertBody <$> body')
|
||||||
|
|
||||||
(body, contentTypeHdr) = case requestBody r of
|
(body, contentTypeHdr) = case requestBody r of
|
||||||
Nothing -> (Client.RequestBodyLBS "", Nothing)
|
Nothing -> (Client.RequestBodyLBS "", Nothing)
|
||||||
Just (RequestBodyLBS body', typ)
|
Just (body', typ)
|
||||||
-> (Client.RequestBodyLBS body', Just (hContentType, renderHeader typ))
|
-> (convertBody body', Just (hContentType, renderHeader typ))
|
||||||
|
|
||||||
isSecure = case baseUrlScheme burl of
|
isSecure = case baseUrlScheme burl of
|
||||||
Http -> False
|
Http -> False
|
||||||
|
|
|
@ -103,6 +103,9 @@ instance FromJSON Person
|
||||||
instance ToForm Person
|
instance ToForm Person
|
||||||
instance FromForm Person
|
instance FromForm Person
|
||||||
|
|
||||||
|
instance Arbitrary Person where
|
||||||
|
arbitrary = Person <$> arbitrary <*> arbitrary
|
||||||
|
|
||||||
alice :: Person
|
alice :: Person
|
||||||
alice = Person "Alice" 42
|
alice = Person "Alice" 42
|
||||||
|
|
||||||
|
|
|
@ -26,25 +26,26 @@
|
||||||
#include "overlapping-compat.h"
|
#include "overlapping-compat.h"
|
||||||
module Servant.StreamSpec (spec) where
|
module Servant.StreamSpec (spec) where
|
||||||
|
|
||||||
import Prelude ()
|
import Control.Monad (replicateM_, void)
|
||||||
import Prelude.Compat
|
import qualified Data.ByteString as BS
|
||||||
import Data.Proxy
|
import Data.Proxy
|
||||||
import qualified Network.HTTP.Client as C
|
import GHC.Stats (currentBytesUsed, getGCStats)
|
||||||
import System.IO.Unsafe (unsafePerformIO)
|
import qualified Network.HTTP.Client as C
|
||||||
|
import Prelude ()
|
||||||
|
import Prelude.Compat
|
||||||
|
import System.IO (IOMode (ReadMode), withFile)
|
||||||
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
import Test.QuickCheck
|
||||||
|
|
||||||
import Servant.API ((:<|>) ((:<|>)),
|
import Servant.API ((:<|>) ((:<|>)), (:>), JSON,
|
||||||
(:>),
|
NetstringFraming, NewlineFraming,
|
||||||
EmptyAPI, JSON,
|
OctetStream, ResultStream (..),
|
||||||
StreamGet,
|
StreamGenerator (..), StreamGet)
|
||||||
NewlineFraming,
|
|
||||||
NetstringFraming,
|
|
||||||
ResultStream(..),
|
|
||||||
StreamGenerator(..))
|
|
||||||
import Servant.Client
|
import Servant.Client
|
||||||
|
import Servant.ClientSpec (Person (..))
|
||||||
|
import qualified Servant.ClientSpec as CS
|
||||||
import Servant.Server
|
import Servant.Server
|
||||||
import qualified Servant.ClientSpec as CS
|
|
||||||
import Servant.ClientSpec (Person(..))
|
|
||||||
|
|
||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
|
@ -54,7 +55,7 @@ spec = describe "Servant.Stream" $ do
|
||||||
type StreamApi f =
|
type StreamApi f =
|
||||||
"streamGetNewline" :> StreamGet NewlineFraming JSON (f Person)
|
"streamGetNewline" :> StreamGet NewlineFraming JSON (f Person)
|
||||||
:<|> "streamGetNetstring" :> StreamGet NetstringFraming JSON (f Person)
|
:<|> "streamGetNetstring" :> StreamGet NetstringFraming JSON (f Person)
|
||||||
:<|> EmptyAPI
|
:<|> "streamALot" :> StreamGet NewlineFraming OctetStream (f BS.ByteString)
|
||||||
|
|
||||||
|
|
||||||
capi :: Proxy (StreamApi ResultStream)
|
capi :: Proxy (StreamApi ResultStream)
|
||||||
|
@ -63,12 +64,9 @@ capi = Proxy
|
||||||
sapi :: Proxy (StreamApi StreamGenerator)
|
sapi :: Proxy (StreamApi StreamGenerator)
|
||||||
sapi = Proxy
|
sapi = Proxy
|
||||||
|
|
||||||
|
getGetNL, getGetNS :: ClientM (ResultStream Person)
|
||||||
getGetNL :<|> getGetNS :<|> EmptyClient = client capi
|
getGetALot :: ClientM (ResultStream BS.ByteString)
|
||||||
|
getGetNL :<|> getGetNS :<|> getGetALot = client capi
|
||||||
|
|
||||||
getGetNL :: ClientM (ResultStream Person)
|
|
||||||
getGetNS :: ClientM (ResultStream Person)
|
|
||||||
|
|
||||||
alice :: Person
|
alice :: Person
|
||||||
alice = Person "Alice" 42
|
alice = Person "Alice" 42
|
||||||
|
@ -77,14 +75,24 @@ bob :: Person
|
||||||
bob = Person "Bob" 25
|
bob = Person "Bob" 25
|
||||||
|
|
||||||
server :: Application
|
server :: Application
|
||||||
server = serve sapi (
|
server = serve sapi
|
||||||
(return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
|
$ return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
|
||||||
:: Handler (StreamGenerator Person))
|
:<|> return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
|
||||||
:<|>
|
:<|> return (StreamGenerator lotsGenerator)
|
||||||
(return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
|
where
|
||||||
:: Handler (StreamGenerator Person))
|
lotsGenerator f r = do
|
||||||
:<|>
|
f ""
|
||||||
emptyServer)
|
withFile "/dev/urandom" ReadMode $
|
||||||
|
\handle -> streamFiveMBNTimes handle 1000 r
|
||||||
|
return ()
|
||||||
|
|
||||||
|
streamFiveMBNTimes handle left sink
|
||||||
|
| left <= 0 = return ""
|
||||||
|
| otherwise = do
|
||||||
|
msg <- BS.hGet handle (megabytes 5)
|
||||||
|
sink msg
|
||||||
|
streamFiveMBNTimes handle (left - 1) sink
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{-# NOINLINE manager' #-}
|
{-# NOINLINE manager' #-}
|
||||||
|
@ -94,20 +102,35 @@ manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
|
||||||
runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
|
runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
|
||||||
runClient x baseUrl' = runClientM x (mkClientEnv manager' baseUrl')
|
runClient x baseUrl' = runClientM x (mkClientEnv manager' baseUrl')
|
||||||
|
|
||||||
runResultStream :: ResultStream a -> IO (Maybe (Either String a), Maybe (Either String a), Maybe (Either String a), Maybe (Either String a))
|
runResultStream :: ResultStream a
|
||||||
runResultStream (ResultStream k) = k $ \act -> (,,,) <$> act <*> act <*> act <*> act
|
-> IO ( Maybe (Either String a)
|
||||||
|
, Maybe (Either String a)
|
||||||
|
, Maybe (Either String a)
|
||||||
|
, Maybe (Either String a))
|
||||||
|
runResultStream (ResultStream k)
|
||||||
|
= k $ \act -> (,,,) <$> act <*> act <*> act <*> act
|
||||||
|
|
||||||
streamSpec :: Spec
|
streamSpec :: Spec
|
||||||
streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
|
streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
|
||||||
|
|
||||||
it "Servant.API.StreamGet.Newline" $ \(_, baseUrl) -> do
|
it "works with Servant.API.StreamGet.Newline" $ \(_, baseUrl) -> do
|
||||||
Right res <- runClient getGetNL baseUrl
|
Right res <- runClient getGetNL baseUrl
|
||||||
let jra = Just (Right alice)
|
let jra = Just (Right alice)
|
||||||
jrb = Just (Right bob)
|
jrb = Just (Right bob)
|
||||||
runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
||||||
|
|
||||||
it "Servant.API.StreamGet.Netstring" $ \(_, baseUrl) -> do
|
it "works with Servant.API.StreamGet.Netstring" $ \(_, baseUrl) -> do
|
||||||
Right res <- runClient getGetNS baseUrl
|
Right res <- runClient getGetNS baseUrl
|
||||||
let jra = Just (Right alice)
|
let jra = Just (Right alice)
|
||||||
jrb = Just (Right bob)
|
jrb = Just (Right bob)
|
||||||
runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
||||||
|
|
||||||
|
it "streams in constant memory" $ \(_, baseUrl) -> do
|
||||||
|
Right (ResultStream res) <- runClient getGetALot baseUrl
|
||||||
|
let consumeNChunks n = replicateM_ n (res void)
|
||||||
|
consumeNChunks 900
|
||||||
|
memUsed <- currentBytesUsed <$> getGCStats
|
||||||
|
memUsed `shouldSatisfy` (< (megabytes 20))
|
||||||
|
|
||||||
|
megabytes :: Num a => a -> a
|
||||||
|
megabytes n = n * (1000 ^ 2)
|
||||||
|
|
34
stack-ghcjs.yaml
Normal file
34
stack-ghcjs.yaml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
resolver: lts-7.19
|
||||||
|
compiler: ghcjs-0.2.1.9007019_ghc-8.0.1
|
||||||
|
compiler-check: match-exact
|
||||||
|
|
||||||
|
setup-info:
|
||||||
|
ghcjs:
|
||||||
|
source:
|
||||||
|
ghcjs-0.2.1.9007019_ghc-8.0.1:
|
||||||
|
url: http://ghcjs.tolysz.org/ghc-8.0-2017-02-05-lts-7.19-9007019.tar.gz
|
||||||
|
sha1: d2cfc25f9cda32a25a87d9af68891b2186ee52f9
|
||||||
|
packages:
|
||||||
|
- servant-client-core/
|
||||||
|
- servant-client-ghcjs/
|
||||||
|
- servant/
|
||||||
|
|
||||||
|
extra-deps:
|
||||||
|
- cabal-doctest-1.0.6
|
||||||
|
- http-api-data-0.3.7.2
|
||||||
|
- http-types-0.12
|
||||||
|
- text-1.2.3.0
|
||||||
|
- aeson-1.2.4.0
|
||||||
|
- attoparsec-0.13.2.2
|
||||||
|
- attoparsec-iso8601-1.0.0.0
|
||||||
|
- base-compat-0.9.3
|
||||||
|
- case-insensitive-1.2.0.11
|
||||||
|
- generics-sop-0.3.2.0
|
||||||
|
- http-media-0.7.1.2
|
||||||
|
- mmorph-1.1.1
|
||||||
|
- natural-transformation-0.4
|
||||||
|
- safe-0.3.17
|
||||||
|
- vault-0.3.1.0
|
||||||
|
- th-abstraction-0.2.6.0
|
||||||
|
- git: https://github.com/ghcjs/ghcjs-base.git
|
||||||
|
commit: 3bb9ed0ffd3f384ed37456b4d6247be732c79c8e
|
|
@ -18,6 +18,8 @@ extra-deps:
|
||||||
- aeson-compat-0.3.7.1
|
- aeson-compat-0.3.7.1
|
||||||
- free-5.0.1
|
- free-5.0.1
|
||||||
- lens-4.16
|
- lens-4.16
|
||||||
|
- random-bytestring-0.1.3
|
||||||
|
- pcg-random-0.1.3.5
|
||||||
|
|
||||||
# allow-newer: true # ignores all bounds, that's a sledgehammer
|
# allow-newer: true # ignores all bounds, that's a sledgehammer
|
||||||
# - doc/tutorial/
|
# - doc/tutorial/
|
||||||
|
|
Loading…
Reference in a new issue