pinch 0.4.3.0 → 0.5.0.0
raw patch · 6 files changed
+75/−58 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Pinch.Internal.Builder: unsafeRunBuilder :: Builder -> Ptr Word8 -> IO ()
- Pinch.Transport: cGetSome :: Connection c => c -> Int -> IO ByteString
+ Pinch.Transport: cGetSome :: Connection c => c -> IO ByteString
- Pinch.Transport: cPut :: Connection c => c -> ByteString -> IO ()
+ Pinch.Transport: cPut :: Connection c => c -> Builder -> IO ()
Files
- CHANGES.md +15/−0
- pinch.cabal +2/−1
- src/Pinch/Internal/Builder.hs +7/−0
- src/Pinch/Transport.hs +27/−50
- src/Pinch/Transport/Builder.hs +17/−0
- tests/Pinch/TransportSpec.hs +7/−7
CHANGES.md view
@@ -1,3 +1,18 @@+0.5.0.0 (2023-08-07)+=====================++- (**Breaking**) Transport:+ `cGetSome` no longer takes an int parameter.+ Implementations should return all available bytes (blocking if necessary)+ or an empty string for EOF. (#54)+- (**Breaking**) Transport:+ `cPut` accepts a `Builder `instead of a `ByteString`.+ Implementations should use `runBuilder` to turn it into a `ByteString`+ if needed. (#55)+- Add a `Pinch.Transport.Builder` module exporting the `Builder` type;+ a length-aware `ByteString` builder. (#55)++ 0.4.3.0 (2023-04-27) =====================
pinch.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: pinch-version: 0.4.3.0+version: 0.5.0.0 synopsis: An alternative implementation of Thrift for Haskell. description: This library provides machinery for types to specify how they can be serialized and deserialized into/from Thrift payloads. It makes no@@ -63,6 +63,7 @@ Pinch.Protocol.Compact Pinch.Server Pinch.Transport+ Pinch.Transport.Builder other-modules: Pinch.Internal.Bits Pinch.Internal.Pinchable.Parser
src/Pinch/Internal/Builder.hs view
@@ -13,6 +13,7 @@ module Pinch.Internal.Builder ( Builder , runBuilder+ , unsafeRunBuilder , append , int8@@ -47,6 +48,12 @@ runBuilder :: Builder -> ByteString runBuilder (B size fill) = BI.unsafeCreate size fill {-# INLINE runBuilder #-}++-- | Fill a buffer with the bytes of a builder. The buffer is assumed+-- to be big enough to hold the contents of the entire builder.+unsafeRunBuilder :: Builder -> Ptr Word8 -> IO ()+unsafeRunBuilder (B _size fill) = fill+{-# INLINE unsafeRunBuilder #-} -- | Append two Builders into one. append :: Builder -> Builder -> Builder
src/Pinch/Transport.hs view
@@ -19,18 +19,18 @@ import qualified Pinch.Internal.Builder as B class Connection c where- -- | Gets up to n bytes. Returns an empty bytestring if EOF is reached.- cGetSome :: c -> Int -> IO BS.ByteString- -- | Writes the given bytestring.- cPut :: c -> BS.ByteString -> IO ()+ -- | Returns available bytes, or an empty bytestring if EOF was reached.+ cGetSome :: c -> IO BS.ByteString+ -- | Writes the given builder.+ cPut :: c -> B.Builder -> IO () instance Connection Handle where- cPut = BS.hPut- cGetSome = BS.hGetSome+ cPut c b = BS.hPut c (B.runBuilder b)+ cGetSome h = BS.hGetSome h 1024 instance Connection Socket where- cPut = sendAll- cGetSome s n = recv s (min n 4096)+ cPut c b = sendAll c (B.runBuilder b)+ cGetSome s = recv s 4096 data ReadResult a = RRSuccess a@@ -47,28 +47,21 @@ -- | Creates a thrift framed transport. See also <https://github.com/apache/thrift/blob/master/doc/specs/thrift-rpc.md#framed-vs-unframed-transport>. framedTransport :: Connection c => c -> IO Transport-framedTransport c = pure $ Transport writeMsg readMsg where+framedTransport c = do+ readBuffer <- newIORef mempty+ pure $ Transport writeMsg (readMsg readBuffer) where writeMsg msg = do- cPut c $ B.runBuilder $ B.int32BE (fromIntegral $ B.getSize msg)- cPut c $ B.runBuilder msg-- readMsg p = do- szBs <- getExactly c 4- if BS.length szBs < 4- then- pure $ RREOF- else do- let sz = fromIntegral <$> G.runGet G.getInt32be szBs- case sz of- Right x -> do- msgBs <- getExactly c x- pure $ if BS.length msgBs < x- then- -- less data has been returned than expected. This means we have reached EOF.- RREOF- else- either RRFailure RRSuccess $ G.runGet p msgBs- Left s -> pure $ RRFailure $ "Invalid frame size: " ++ show s+ cPut c $ B.int32BE (fromIntegral $ B.getSize msg) <> msg+ readMsg readBuffer parser = do+ let + frameParser = do + size <- G.getInt32be+ G.isolate (fromIntegral size) parser+ + initial <- readIORef readBuffer+ (leftovers, r) <- runGetWith (cGetSome c) frameParser initial+ writeIORef readBuffer $! leftovers+ pure r -- | Creates a thrift unframed transport. See also <https://github.com/apache/thrift/blob/master/doc/specs/thrift-rpc.md#framed-vs-unframed-transport>. unframedTransport :: Connection c => c -> IO Transport@@ -80,15 +73,13 @@ readBuffer <- newIORef mempty pure $ Transport writeMsg (readMsg readBuffer) where- writeMsg msg = cPut c $ B.runBuilder msg+ writeMsg = cPut c readMsg buf p = do- bs <- readIORef buf- bs' <- if BS.null bs then getSome else pure bs- (leftOvers, r) <- runGetWith getSome p bs'- writeIORef buf leftOvers- pure $ r- getSome = cGetSome c 1024+ initial <- readIORef buf+ (leftovers, r) <- runGetWith (cGetSome c) p initial+ writeIORef buf $! leftovers+ pure r -- | Runs a Get parser incrementally, reading more input as necessary until a successful parse -- has been achieved.@@ -108,17 +99,3 @@ pure (bs, RREOF) else go $ cont bs- --- | Gets exactly n bytes. If EOF is reached, an empty string is returned.-getExactly :: Connection c => c -> Int -> IO BS.ByteString-getExactly c sz = B.runBuilder <$> go sz mempty- where- go :: Int -> B.Builder -> IO B.Builder- go n b = do- bs <- cGetSome c n- let b' = b <> B.byteString bs- case BS.length bs of- -- EOF, return what data we might have gotten so far- 0 -> pure mempty- n' | n' < n -> go (n - n') b'- _ | otherwise -> pure b'
+ src/Pinch/Transport/Builder.hs view
@@ -0,0 +1,17 @@+-- |+-- Module : Pinch.Transport.Builder+-- Copyright : (c) Abhinav Gupta 2023+-- License : BSD3+--+-- Maintainer : Abhinav Gupta <mail@abhinavg.net>+-- Stability : experimental+--+-- This module implements a ByteString builder very similar to+-- 'Data.ByteString.Builder' except that it keeps track of its final serialized+-- length. This allows it to allocate the target ByteString in one @malloc@ and+-- simply write the bytes to it.+module Pinch.Transport.Builder + ( module B+ ) where++import Pinch.Internal.Builder as B
tests/Pinch/TransportSpec.hs view
@@ -29,13 +29,13 @@ mGetContents = readIORef . contents instance Connection MemoryConnection where- cGetSome (MemoryConnection ref ch) n = do+ cGetSome (MemoryConnection ref ch) = do bytes <- readIORef ref- let (left, right) = BS.splitAt (min ch n) bytes+ let (left, right) = BS.splitAt ch bytes writeIORef ref right return left- cPut (MemoryConnection ref _) newBytes = do- modifyIORef ref (<> newBytes)+ cPut (MemoryConnection ref _) builder = do+ modifyIORef ref (<> B.runBuilder builder) transportSpec :: (forall c . Connection c => c -> IO Transport) -> Spec transportSpec t = do@@ -63,8 +63,8 @@ let payload = BS.pack [0x01, 0x05, 0x01, 0x08, 0xFF] buf <- newMemoryConnection 1 transp <- framedTransport buf- cPut buf $ BS.pack [0x00, 0x00, 0x00, 0x05]- cPut buf payload+ cPut buf $ B.byteString (BS.pack [0x00, 0x00, 0x00, 0x05])+ cPut buf $ B.byteString payload r <- readMessage transp (G.getBytes $ BS.length payload) r `shouldBe` RRSuccess payload @@ -84,7 +84,7 @@ ioProperty $ do buf <- newMemoryConnection 1 transp <- unframedTransport buf- cPut buf payload+ cPut buf $ B.byteString payload r <- readMessage transp (G.getBytes $ BS.length payload) pure $ r === RRSuccess payload