packages feed

wai-effectful 1.0.1 → 1.0.2

raw patch · 6 files changed

+94/−58 lines, 6 filessetup-changedPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Effectful.Wai.Internal: Request :: Method -> HttpVersion -> ByteString -> ByteString -> RequestHeaders -> Bool -> SockAddr -> [Text] -> Query -> Eff es ByteString -> Vault -> RequestBodyLength -> Maybe ByteString -> Maybe ByteString -> Maybe ByteString -> Maybe ByteString -> Request (es :: [Effect])
+ Effectful.Wai.Internal: ResponseBuilder :: Status -> ResponseHeaders -> Builder -> Response (es :: [Effect])
+ Effectful.Wai.Internal: ResponseFile :: Status -> ResponseHeaders -> FilePath -> Maybe FilePart -> Response (es :: [Effect])
+ Effectful.Wai.Internal: ResponseRaw :: (Eff es ByteString -> (ByteString -> Eff es ()) -> Eff es ()) -> Response es -> Response (es :: [Effect])
+ Effectful.Wai.Internal: ResponseStream :: Status -> ResponseHeaders -> StreamingBody es -> Response (es :: [Effect])
+ Effectful.Wai.Internal: [httpVersion] :: Request (es :: [Effect]) -> HttpVersion
+ Effectful.Wai.Internal: [isSecure] :: Request (es :: [Effect]) -> Bool
+ Effectful.Wai.Internal: [pathInfo] :: Request (es :: [Effect]) -> [Text]
+ Effectful.Wai.Internal: [queryString] :: Request (es :: [Effect]) -> Query
+ Effectful.Wai.Internal: [rawPathInfo] :: Request (es :: [Effect]) -> ByteString
+ Effectful.Wai.Internal: [rawQueryString] :: Request (es :: [Effect]) -> ByteString
+ Effectful.Wai.Internal: [remoteHost] :: Request (es :: [Effect]) -> SockAddr
+ Effectful.Wai.Internal: [requestBodyLength] :: Request (es :: [Effect]) -> RequestBodyLength
+ Effectful.Wai.Internal: [requestBody] :: Request (es :: [Effect]) -> Eff es ByteString
+ Effectful.Wai.Internal: [requestHeaderHost] :: Request (es :: [Effect]) -> Maybe ByteString
+ Effectful.Wai.Internal: [requestHeaderRange] :: Request (es :: [Effect]) -> Maybe ByteString
+ Effectful.Wai.Internal: [requestHeaderReferer] :: Request (es :: [Effect]) -> Maybe ByteString
+ Effectful.Wai.Internal: [requestHeaderUserAgent] :: Request (es :: [Effect]) -> Maybe ByteString
+ Effectful.Wai.Internal: [requestHeaders] :: Request (es :: [Effect]) -> RequestHeaders
+ Effectful.Wai.Internal: [requestMethod] :: Request (es :: [Effect]) -> Method
+ Effectful.Wai.Internal: [vault] :: Request (es :: [Effect]) -> Vault
+ Effectful.Wai.Internal: data Request (es :: [Effect])
+ Effectful.Wai.Internal: data Response (es :: [Effect])
+ Effectful.Wai.Internal: getRequestBodyChunk :: forall (es :: [Effect]). Request es -> Eff es ByteString
+ Effectful.Wai.Internal: setRequestBodyChunks :: forall (es :: [Effect]). Eff es ByteString -> Request es -> Request es
+ Effectful.Wai.Internal: type StreamingBody (es :: [Effect]) = Builder -> Eff es () -> Eff es () -> Eff es ()

Files

CHANGELOG.md view
@@ -5,6 +5,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [1.0.2] - 2026-11-07++### Added++- `Effectful.Wai.Internal` with the constructors of `Request` and `Response`.++### Fixed++- Lifted raw responses can read and write from different threads.+ ## [1.0.1] - 2026-10-01  ### Changed
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
src/Effectful/Wai.hs view
@@ -118,13 +118,11 @@ import Data.ByteString (ByteString) import Data.ByteString.Builder (Builder, lazyByteString) import Data.ByteString.Lazy (LazyByteString)-import Data.Text (Text)-import Data.Vault.Lazy (Vault) import Effectful-import Network.HTTP.Types (HttpVersion, Method, Query, RequestHeaders, ResponseHeaders, Status)-import Network.Socket (SockAddr)-import Network.Wai (FilePart (..), RequestBodyLength (..), ResponseReceived)+import Effectful.Wai.Internal+import Network.HTTP.Types (RequestHeaders, ResponseHeaders, Status) import Network.Wai qualified as Wai+import Network.Wai.Internal (FilePart (..), RequestBodyLength (..), ResponseReceived) import Network.Wai.Internal qualified as Wai import Prelude @@ -158,26 +156,6 @@ unliftMiddleware :: (IOE :> es) => (forall r. Eff es r -> IO r) -> Middleware es -> Wai.Middleware unliftMiddleware unlift middleware = unliftApplication unlift . middleware . liftApplication --- | Lifted 'Wai.Request'.-data Request es = Request-    { requestMethod :: Method-    , httpVersion :: HttpVersion-    , rawPathInfo :: ByteString-    , rawQueryString :: ByteString-    , requestHeaders :: RequestHeaders-    , isSecure :: Bool-    , remoteHost :: Network.Socket.SockAddr-    , pathInfo :: [Text]-    , queryString :: Query-    , requestBody :: Eff es ByteString-    , vault :: Vault-    , requestBodyLength :: RequestBodyLength-    , requestHeaderHost :: Maybe ByteString-    , requestHeaderRange :: Maybe ByteString-    , requestHeaderReferer :: Maybe ByteString-    , requestHeaderUserAgent :: Maybe ByteString-    }- liftRequest :: (IOE :> es) => Wai.Request -> Request es liftRequest Wai.Request{..} =     Request@@ -203,10 +181,6 @@ defaultRequest :: (IOE :> es) => Request es defaultRequest = liftRequest Wai.defaultRequest --- | Lifted 'Wai.getRequestBodyChunk'.-getRequestBodyChunk :: Request es -> Eff es ByteString-getRequestBodyChunk = requestBody- -- | Lifted 'Wai.strictRequestBody'. strictRequestBody :: (IOE :> es) => Request es -> Eff es LazyByteString strictRequestBody = withSeqEffToIO . (Wai.strictRequestBody .) . flip unliftRequest@@ -223,32 +197,22 @@ consumeRequestBodyLazy :: (IOE :> es) => Request es -> Eff es LazyByteString consumeRequestBodyLazy = lazyRequestBody --- | Lifted 'Wai.setRequestBodyChunks'.-setRequestBodyChunks :: Eff es ByteString -> Request es -> Request es-setRequestBodyChunks body req = req{requestBody = body}- -- | Lifted 'Wai.mapRequestHeaders'. mapRequestHeaders     :: (RequestHeaders -> RequestHeaders)     -> Request es     -> Request es-mapRequestHeaders f req = req{requestHeaders = f (requestHeaders req)}---- | Lifted 'Wai.Response'.-data Response es-    = ResponseFile Status ResponseHeaders FilePath (Maybe FilePart)-    | ResponseBuilder Status ResponseHeaders Builder-    | ResponseStream Status ResponseHeaders (StreamingBody es)-    | ResponseRaw (Eff es ByteString -> (ByteString -> Eff es ()) -> Eff es ()) (Response es)+mapRequestHeaders f req = req{requestHeaders = f $ requestHeaders req}  liftResponse :: forall es. (IOE :> es) => Wai.Response -> Response es liftResponse (Wai.ResponseFile s h fp part) = ResponseFile s h fp part liftResponse (Wai.ResponseBuilder s h b) = ResponseBuilder s h b-liftResponse (Wai.ResponseStream s h body) = ResponseStream s h (liftStreamingBody body)-liftResponse (Wai.ResponseRaw raw res) = ResponseRaw liftRaw (liftResponse res)+liftResponse (Wai.ResponseStream s h body) = ResponseStream s h $ liftStreamingBody body+liftResponse (Wai.ResponseRaw raw res) = ResponseRaw liftRaw $ liftResponse res   where     liftRaw :: Eff es ByteString -> (ByteString -> Eff es ()) -> Eff es ()-    liftRaw recv send = withRunInIO \unlift -> raw (unlift recv) (unlift . send)+    liftRaw recv send =+        withEffToIO (ConcUnlift Ephemeral Unlimited) \unlift -> raw (unlift recv) (unlift . send)  unliftResponse     :: forall es@@ -258,17 +222,14 @@     -> Wai.Response unliftResponse _ (ResponseFile s h fp part) = Wai.ResponseFile s h fp part unliftResponse _ (ResponseBuilder s h b) = Wai.ResponseBuilder s h b-unliftResponse unlift (ResponseStream s h body) = Wai.ResponseStream s h (unliftStreamingBody unlift body)-unliftResponse unlift (ResponseRaw raw res) = Wai.ResponseRaw unliftRaw (unliftResponse unlift res)+unliftResponse unlift (ResponseStream s h body) = Wai.ResponseStream s h $ unliftStreamingBody unlift body+unliftResponse unlift (ResponseRaw raw res) = Wai.ResponseRaw unliftRaw $ unliftResponse unlift res   where     unliftRaw :: IO ByteString -> (ByteString -> IO ()) -> IO ()     unliftRaw recv send = unlift $ raw (liftIO recv) (liftIO . send) --- | Lifted 'Wai.StreamingBody'.-type StreamingBody es = (Builder -> Eff es ()) -> Eff es () -> Eff es ()- liftStreamingBody :: (IOE :> es) => Wai.StreamingBody -> StreamingBody es-liftStreamingBody body send flush = withRunInIO \unlift -> body (unlift . send) (unlift flush)+liftStreamingBody body send flush = withRunInIO \unlift -> body (unlift . send) $ unlift flush  unliftStreamingBody     :: (IOE :> es)
+ src/Effectful/Wai/Internal.hs view
@@ -0,0 +1,49 @@+module Effectful.Wai.Internal where++import Data.ByteString (ByteString)+import Data.ByteString.Builder (Builder)+import Data.Text (Text)+import Data.Vault.Lazy (Vault)+import Effectful+import Network.HTTP.Types (HttpVersion, Method, Query, RequestHeaders, ResponseHeaders, Status)+import Network.Socket (SockAddr)+import Network.Wai (FilePart (..), RequestBodyLength (..))+import Prelude++-- | Lifted 'Network.Wai.Request'.+data Request es = Request+    { requestMethod :: Method+    , httpVersion :: HttpVersion+    , rawPathInfo :: ByteString+    , rawQueryString :: ByteString+    , requestHeaders :: RequestHeaders+    , isSecure :: Bool+    , remoteHost :: SockAddr+    , pathInfo :: [Text]+    , queryString :: Query+    , requestBody :: Eff es ByteString+    , vault :: Vault+    , requestBodyLength :: RequestBodyLength+    , requestHeaderHost :: Maybe ByteString+    , requestHeaderRange :: Maybe ByteString+    , requestHeaderReferer :: Maybe ByteString+    , requestHeaderUserAgent :: Maybe ByteString+    }++-- | Lifted 'Network.Wai.getRequestBodyChunk'.+getRequestBodyChunk :: Request es -> Eff es ByteString+getRequestBodyChunk = requestBody++-- | Lifted 'Network.Wai.setRequestBodyChunks'.+setRequestBodyChunks :: Eff es ByteString -> Request es -> Request es+setRequestBodyChunks body req = req{requestBody = body}++-- | Lifted 'Network.Wai.Response'.+data Response es+    = ResponseFile Status ResponseHeaders FilePath (Maybe FilePart)+    | ResponseBuilder Status ResponseHeaders Builder+    | ResponseStream Status ResponseHeaders (StreamingBody es)+    | ResponseRaw (Eff es ByteString -> (ByteString -> Eff es ()) -> Eff es ()) (Response es)++-- | Lifted 'Network.Wai.StreamingBody'.+type StreamingBody es = (Builder -> Eff es ()) -> Eff es () -> Eff es ()
test/Main.hs view
@@ -3,6 +3,8 @@  module Main where +import Control.Concurrent (forkFinally, newEmptyMVar, putMVar, takeMVar)+import Control.Monad (void) import Data.ByteString qualified as ByteString import Data.ByteString.Builder (toLazyByteString, word8) import Data.ByteString.Lazy qualified as LazyByteString@@ -16,6 +18,7 @@ import Effectful.Hspec import Effectful.Prim.IORef import Effectful.Wai+import Network.Wai.Internal qualified as Wai import Prelude hiding (readFile)  main :: IO ()@@ -38,7 +41,7 @@         prop "responseStream" \chunks -> do             body <- getBody $ responseStream undefined undefined \sendChunk _ ->                 for_ chunks $ sendChunk . foldMap word8-            body `shouldBe` ByteString.concat (map ByteString.pack chunks)+            body `shouldBe` ByteString.concat (fmap ByteString.pack chunks)         it "responseFile total" do             let fp = "LICENCE"             body <- getBody $ responseFile undefined undefined fp Nothing@@ -59,6 +62,19 @@                         }             let expected = ByteString.take count $ ByteString.drop offset totalBS             body `shouldBe` expected+    describe "liftResponse" do+        it "lifts a raw response that sends from another thread" do+            failure <- liftIO newEmptyMVar+            let raw recv send = do+                    chunk <- recv+                    void . forkFinally (send chunk) $+                        putMVar failure . either (Just . show) (const Nothing)+                fallback = Wai.ResponseBuilder undefined undefined mempty+            withSeqEffToIO \unlift ->+                case unliftResponse unlift . liftResponse $ Wai.ResponseRaw raw fallback of+                    Wai.ResponseRaw run _ -> run (pure "chunk") . const $ pure ()+                    _ -> fail "not a raw response"+            liftIO (takeMVar failure) >>= (`shouldBe` Nothing)     describe "lazyRequestBody" do         prop "works" \chunks -> do             req <- mkRequestFromChunks chunks@@ -67,15 +83,15 @@         it "is lazy" do             let req = setRequestBodyChunks (error "requestBody") defaultRequest             _ <- lazyRequestBody req-            return ()+            pure ()     describe "strictRequestBody" do         prop "works" $ \chunks -> do             req <- mkRequestFromChunks chunks             body <- strictRequestBody req-            body `shouldBe` LazyByteString.fromChunks (map ByteString.pack chunks)+            body `shouldBe` LazyByteString.fromChunks (fmap ByteString.pack chunks)  mkRequestFromChunks :: (IOE :> es, Prim :> es) => [[Word8]] -> Eff es (Request es) mkRequestFromChunks chunks = do-    ref <- newIORef . map ByteString.pack . filter (not . null) $ chunks+    ref <- newIORef . fmap ByteString.pack . filter (not . null) $ chunks     pure . flip setRequestBodyChunks defaultRequest . atomicModifyIORef ref $-        maybe mempty Tuple.swap . List.uncons+        foldMap Tuple.swap . List.uncons
wai-effectful.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: wai-effectful-version: 1.0.1+version: 1.0.2 synopsis: Effectful bindings for the wai library description:   Adaptation of the @<https://hackage.haskell.org/package/wai wai>@ library for the @<https://hackage.haskell.org/package/effectful effectful>@ ecosystem.@@ -64,6 +64,7 @@    exposed-modules:     Effectful.Wai+    Effectful.Wai.Internal  test-suite test   import: common@@ -72,4 +73,5 @@   main-is: Main.hs   build-depends:     hspec-effectful >=1.1.1 && <1.2,+    wai >=3.2.4 && <3.3,     wai-effectful,