Hello community,
here is the log from the commit of package ghc-pinch for openSUSE:Factory checked in at 2016-11-02 12:46:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ghc-pinch (Old)
and /work/SRC/openSUSE:Factory/.ghc-pinch.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ghc-pinch"
Changes:
--------
--- /work/SRC/openSUSE:Factory/ghc-pinch/ghc-pinch.changes 2016-10-22 13:16:04.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.ghc-pinch.new/ghc-pinch.changes 2016-11-02 12:46:58.000000000 +0100
@@ -1,0 +2,5 @@
+Thu Sep 15 06:41:03 UTC 2016 - psimons@suse.com
+
+- Update to version 0.3.0.1 revision 0 with cabal2obs.
+
+-------------------------------------------------------------------
Old:
----
pinch-0.2.0.2.tar.gz
New:
----
pinch-0.3.0.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ ghc-pinch.spec ++++++
--- /var/tmp/diff_new_pack.jvV1cH/_old 2016-11-02 12:47:00.000000000 +0100
+++ /var/tmp/diff_new_pack.jvV1cH/_new 2016-11-02 12:47:00.000000000 +0100
@@ -19,11 +19,11 @@
%global pkg_name pinch
%bcond_with tests
Name: ghc-%{pkg_name}
-Version: 0.2.0.2
+Version: 0.3.0.1
Release: 0
Summary: An alternative implementation of Thrift for Haskell
License: BSD-3-Clause
-Group: System/Libraries
+Group: Development/Languages/Other
Url: https://hackage.haskell.org/package/%{pkg_name}
Source0: https://hackage.haskell.org/package/%{pkg_name}-%{version}/%{pkg_name}-%{version}.tar.gz
BuildRequires: ghc-Cabal-devel
@@ -71,19 +71,15 @@
%prep
%setup -q -n %{pkg_name}-%{version}
-
%build
%ghc_lib_build
-
%install
%ghc_lib_install
-
%check
%cabal_test
-
%post devel
%ghc_pkg_recache
++++++ pinch-0.2.0.2.tar.gz -> pinch-0.3.0.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/CHANGES.md new/pinch-0.3.0.1/CHANGES.md
--- old/pinch-0.2.0.2/CHANGES.md 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/CHANGES.md 2016-07-13 06:11:26.000000000 +0200
@@ -1,15 +1,27 @@
-0.2.0.2
-=======
+0.3.0.1 (2016-07-12)
+====================
- Compile on 32-bit systems.
-0.2.0.1
-=======
+0.3.0.0 (2016-06-02)
+====================
+
+- Add support for the Thrift Compact Protocol (#2).
+- Add support for returning the leftover ByteString when parsing Thrift
+ payloads (#3).
+
+0.2.0.2 (2016-07-12)
+====================
+
+- Compile on 32-bit systems.
+
+0.2.0.1 (2016-05-23)
+====================
- Build with GHC 8.
-0.2.0.0
-=======
+0.2.0.0 (2015-12-27)
+====================
Breaking changes:
@@ -27,19 +39,19 @@
- Improve serialization and deserialization performance further by changing
the intermediate representation of lists, sets, and maps.
-0.1.0.2
-=======
+0.1.0.2 (2015-12-27)
+====================
- Loosen `vector` version constraint.
-0.1.0.1
-=======
+0.1.0.1 (2015-11-15)
+====================
- Fixed recursion in C pre-processor expansion. This can break the build on
some systems.
-0.1.0.0
-=======
+0.1.0.0 (2015-11-15)
+====================
- Initial release.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/README.md new/pinch-0.3.0.1/README.md
--- old/pinch-0.2.0.2/README.md 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/README.md 2016-07-13 06:08:03.000000000 +0200
@@ -69,11 +69,17 @@
For more information, check the documentation and the examples.
+Supported Protocols
+-------------------
+
+The following Thrift protocols are supported:
+
+- Binary
+- Compact
+
Caveats
-------
-- Only the Thrift Binary Protocol is supported right now. (Pull
- requests welcome.)
- There is no code generation or template haskell support yet so types from
the Thrift file will have to be translated by hand.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/bench/pinch-bench/Bench.hs new/pinch-0.3.0.1/bench/pinch-bench/Bench.hs
--- old/pinch-0.2.0.2/bench/pinch-bench/Bench.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/bench/pinch-bench/Bench.hs 2016-07-13 06:08:03.000000000 +0200
@@ -152,38 +152,57 @@
main :: IO ()
main = defaultMain
[ bgroup "A"
- [ env (generate :: IO A) $ \a -> bench "encode" $ whnf encode a
- , env generateEncodedA $ \bs -> bench "decode" $
- nf (P.decode P.binaryProtocol :: ByteString -> Either String A) bs
+ [ env (generate :: IO A) $ \a -> bgroup "encode"
+ [ bench "binary" $ whnf (P.encode P.binaryProtocol) a
+ , bench "compact" $ whnf (P.encode P.compactProtocol) a
+ ]
+ , bgroup "decode"
+ [ env (generateEncodedA P.binaryProtocol) $ \bs -> bench "binary" $
+ nf (P.decode P.binaryProtocol :: ByteString -> Either String A) bs
+ , env (generateEncodedA P.compactProtocol) $ \bs -> bench "compact" $
+ nf (P.decode P.compactProtocol :: ByteString -> Either String A) bs
+ ]
]
, bgroup "NestedMixed"
- [ env generateNestedMixedFields $ \ ~(f1, f2, f3) -> bench "encode" $
- whnf encode (NestedMixed f1 f2 f3)
- , env generateEncodedNestedMixed $ \bs -> bench "decode" $
- nf (P.decode P.binaryProtocol :: ByteString -> Either String NestedMixed) bs
+ [ env generateNestedMixedFields $ \ ~(f1, f2, f3) -> bgroup "encode"
+ [ bench "binary" $
+ whnf (P.encode P.binaryProtocol) (NestedMixed f1 f2 f3)
+ , bench "compact" $
+ whnf (P.encode P.compactProtocol) (NestedMixed f1 f2 f3)
+ ]
+ , bgroup "decode"
+ [ env (generateEncodedNestedMixed P.binaryProtocol) $ \bs -> bench "binary" $
+ nf (P.decode P.binaryProtocol :: ByteString -> Either String NestedMixed) bs
+ , env (generateEncodedNestedMixed P.compactProtocol) $ \bs -> bench "compact" $
+ nf (P.decode P.compactProtocol :: ByteString -> Either String NestedMixed) bs
+ ]
+
]
, bgroup "Struct"
- [ env structFields $ \ ~(f1, f2, f3) -> bench "encode" $
- whnf encode (Struct f1 f2 f3)
- , env generateEncodedStruct $ \bs -> bench "deode" $
- nf (P.decode P.binaryProtocol :: ByteString -> Either String Struct) bs
+ [ env structFields $ \ ~(f1, f2, f3) -> bgroup "encode"
+ [ bench "binary" $ whnf (P.encode P.binaryProtocol) (Struct f1 f2 f3)
+ , bench "compact" $ whnf (P.encode P.compactProtocol) (Struct f1 f2 f3)
+ ]
+ , bgroup "decode"
+ [ env (generateEncodedStruct P.binaryProtocol) $ \bs -> bench "binary" $
+ nf (P.decode P.binaryProtocol :: ByteString -> Either String Struct) bs
+ , env (generateEncodedStruct P.compactProtocol) $ \bs -> bench "compact" $
+ nf (P.decode P.compactProtocol :: ByteString -> Either String Struct) bs
+ ]
]
]
where
- generateEncodedNestedMixed = bracket_ stopProfTimer startProfTimer $ do
+ generateEncodedNestedMixed proto = bracket_ stopProfTimer startProfTimer $ do
(f1, f2, f3) <- generateNestedMixedFields
- return $ P.encode P.binaryProtocol (NestedMixed f1 f2 f3)
+ return $ P.encode proto (NestedMixed f1 f2 f3)
- generateEncodedA = bracket_ stopProfTimer startProfTimer $ do
+ generateEncodedA proto = bracket_ stopProfTimer startProfTimer $ do
a <- generate :: IO A
- return $ P.encode P.binaryProtocol a
+ return $ P.encode proto a
- generateEncodedStruct = bracket_ stopProfTimer startProfTimer $ do
+ generateEncodedStruct proto = bracket_ stopProfTimer startProfTimer $ do
(f1, f2, f3) <- structFields
- return $ P.encode P.binaryProtocol (Struct f1 f2 f3)
+ return $ P.encode proto (Struct f1 f2 f3)
generate :: QC.Arbitrary a => IO a
generate = QC.generate QC.arbitrary
-
- encode :: P.Pinchable a => a -> ByteString
- encode = P.encode P.binaryProtocol
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/pinch.cabal new/pinch-0.3.0.1/pinch.cabal
--- old/pinch-0.2.0.2/pinch.cabal 2016-07-13 06:05:59.000000000 +0200
+++ new/pinch-0.3.0.1/pinch.cabal 2016-07-13 06:11:11.000000000 +0200
@@ -3,7 +3,7 @@
-- see: https://github.com/sol/hpack
name: pinch
-version: 0.2.0.2
+version: 0.3.0.1
cabal-version: >= 1.10
build-type: Simple
license: BSD3
@@ -70,6 +70,7 @@
Pinch.Internal.Value
Pinch.Protocol
Pinch.Protocol.Binary
+ Pinch.Protocol.Compact
other-modules:
Pinch.Internal.Bits
Pinch.Internal.Pinchable.Parser
@@ -106,4 +107,5 @@
Pinch.Internal.Util
Pinch.Internal.ValueSpec
Pinch.Protocol.BinarySpec
+ Pinch.Protocol.CompactSpec
default-language: Haskell2010
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch/Internal/Builder.hs new/pinch-0.3.0.1/src/Pinch/Internal/Builder.hs
--- old/pinch-0.2.0.2/src/Pinch/Internal/Builder.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/src/Pinch/Internal/Builder.hs 2016-07-13 06:08:03.000000000 +0200
@@ -17,6 +17,7 @@
, append
, int8
+ , word8
, int16BE
, int32BE
, int64BE
@@ -95,6 +96,11 @@
int8 = primFixed BP.int8
{-# INLINE int8 #-}
+-- | Serialize a single unsigned byte.
+word8 :: Word8 -> Builder
+word8 = primFixed BP.word8
+{-# INLINE word8 #-}
+
-- | Serialize a signed 16-bit integer in big endian format.
int16BE :: Int16 -> Builder
int16BE = primFixed BP.int16BE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch/Internal/FoldList.hs new/pinch-0.3.0.1/src/Pinch/Internal/FoldList.hs
--- old/pinch-0.2.0.2/src/Pinch/Internal/FoldList.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/src/Pinch/Internal/FoldList.hs 2016-07-13 06:08:03.000000000 +0200
@@ -118,3 +118,11 @@
instance Hashable a => Hashable (FoldList a) where
hashWithSalt s (FoldList l) = l hashWithSalt s
+
+instance Monoid (FoldList a) where
+ mempty = FoldList (\_ r -> r)
+ {-# INLINE mempty #-}
+
+ FoldList f1 `mappend` FoldList f2 =
+ FoldList $ \cons nil -> f2 cons (f1 cons nil)
+ {-# INLINE mappend #-}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch/Internal/Parser.hs new/pinch-0.3.0.1/src/Pinch/Internal/Parser.hs
--- old/pinch-0.2.0.2/src/Pinch/Internal/Parser.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/src/Pinch/Internal/Parser.hs 2016-07-13 06:08:03.000000000 +0200
@@ -15,8 +15,10 @@
module Pinch.Internal.Parser
( Parser
, runParser
+ , runParser'
, int8
+ , word8
, int16
, int32
, int64
@@ -31,6 +33,7 @@
import Data.Bits ((.|.))
import Data.ByteString (ByteString)
import Data.Int (Int16, Int32, Int64, Int8)
+import Data.Word (Word8)
import Prelude hiding (take)
import qualified Control.Monad.ST as ST
@@ -98,6 +101,13 @@
{-# INLINE runParser #-}
+-- | Run the parser on the given ByteString. Return either the failure message
+-- or the result and any left-over content.
+runParser' :: Parser a -> ByteString -> Either String (ByteString, a)
+runParser' (Parser f) b = f b Left (\b' r -> Right (b', r))
+{-# INLINE runParser' #-}
+
+
-- | @take n@ gets exactly @n@ bytes or fails the parse.
take :: Int -> Parser ByteString
take n = Parser $ \b kFail kSucc ->
@@ -121,6 +131,12 @@
{-# INLINE int8 #-}
+-- | Produces the next byte and advances the parser.
+word8 :: Parser Word8
+word8 = fromIntegral <$> int8
+{-# INLINE word8 #-}
+
+
-- | Produces a signed 16-bit integer and advances the parser.
int16 :: Parser Int16
int16 = mk <$> take 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch/Internal/Pinchable.hs new/pinch-0.3.0.1/src/Pinch/Internal/Pinchable.hs
--- old/pinch-0.2.0.2/src/Pinch/Internal/Pinchable.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/src/Pinch/Internal/Pinchable.hs 2016-07-13 06:08:03.000000000 +0200
@@ -227,6 +227,7 @@
FL.foldl' (\m (!k, !v) -> mapInsert k v m) mapEmpty <$> FL.mapM go xs
where
go (MapItem k v) = (,) <$> checkedUnpinch k <*> checkedUnpinch v
+unpinchMap _ mapEmpty VNullMap = return mapEmpty
unpinchMap _ _ x = fail $ "Failed to read map. Got " ++ show x
instance IsTType a => Pinchable (Value a) where
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch/Internal/Value.hs new/pinch-0.3.0.1/src/Pinch/Internal/Value.hs
--- old/pinch-0.2.0.2/src/Pinch/Internal/Value.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/src/Pinch/Internal/Value.hs 2016-07-13 06:08:03.000000000 +0200
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
@@ -22,6 +23,10 @@
, valueTType
) where
+#if __GLASGOW_HASKELL__ < 709
+import Data.Monoid (mempty)
+#endif
+
import Control.DeepSeq (NFData (..))
import Data.ByteString (ByteString)
import Data.Hashable (Hashable (..))
@@ -70,6 +75,7 @@
VMap :: forall k v. (IsTType k, IsTType v)
=> !(FoldList (MapItem k v)) -> Value TMap
+ VNullMap :: Value TMap
VSet :: forall a. IsTType a => !(FoldList (Value a)) -> Value TSet
VList :: forall a. IsTType a => !(FoldList (Value a)) -> Value TList
deriving Typeable
@@ -89,6 +95,7 @@
go xs i (SomeValue val) = (show i ++ ": " ++ show val):xs
show (VMap x) = show x
+ show VNullMap = "[]"
show (VSet x) = show x
show (VList x) = show x
@@ -105,8 +112,10 @@
VList as == VList bs = areEqual1 as bs
VMap as == VMap bs = areEqual2 (toMap as) (toMap bs)
where
- toMap = F.foldl' (\m (MapItem k v) -> M.insert k v m) M.empty
- VSet as == VSet bs = areEqual1 (toSet as) (toSet bs)
+ toMap = M.toList . F.foldl' (\m (MapItem k v) -> M.insert k v m) M.empty
+ VNullMap == VMap xs = mempty == xs
+ VMap xs == VNullMap = xs == mempty
+ VSet as == VSet bs = areEqual1 (toSet as) (toSet bs)
_ == _ = False
toSet :: forall f x. (F.Foldable f, Hashable x, Eq x) => f x -> S.HashSet x
@@ -122,6 +131,7 @@
rnf (VBinary a) = rnf a
rnf (VStruct a) = rnf a
rnf (VMap as) = rnf as
+ rnf VNullMap = ()
rnf (VSet as) = rnf as
rnf (VList as) = rnf as
@@ -143,8 +153,8 @@
-- | Safely attempt to cast a Value into another.
castValue :: forall a b. (IsTType a, IsTType b) => Value a -> Maybe (Value b)
-castValue v = case ttypeEqT of
- Just (Refl :: a :~: b) -> Just v
+castValue v = case ttypeEqT :: Maybe (a :~: b) of
+ Just Refl -> Just v
Nothing -> Nothing
{-# INLINE castValue #-}
@@ -155,13 +165,13 @@
areEqual
:: forall a b. (IsTType a, IsTType b) => Value a -> Value b -> Bool
-areEqual l r = case ttypeEqT of
- Just (Refl :: a :~: b) -> l == r
+areEqual l r = case ttypeEqT :: Maybe (a :~: b) of
+ Just Refl -> l == r
Nothing -> False
{-# INLINE areEqual #-}
areEqual1
- :: forall a b f. (IsTType a, IsTType b, Eq (f (Value a)))
+ :: forall a b f. (IsTType a, IsTType b, F.Foldable f, Eq (f (Value a)))
=> f (Value a) -> f (Value b) -> Bool
areEqual1 l r = case ttypeEqT of
Just (Refl :: a :~: b) -> l == r
@@ -169,10 +179,9 @@
{-# INLINE areEqual1 #-}
areEqual2
- :: forall f k1 v1 k2 v2.
+ :: forall k1 v1 k2 v2.
( IsTType k1, IsTType v1, IsTType k2, IsTType v2
- , Eq (f (Value k1) (Value v1))
- ) => f (Value k1) (Value v1) -> f (Value k2) (Value v2) -> Bool
+ ) => [(Value k1, Value v1)] -> [(Value k2, Value v2)] -> Bool
areEqual2 l r = case ttypeEqT of
Just (Refl :: k1 :~: k2) -> case ttypeEqT of
Just (Refl :: v1 :~: v2) -> l == r
@@ -191,6 +200,7 @@
VInt64 x -> s `hashWithSalt` (6 :: Int) `hashWithSalt` x
VList x -> s `hashWithSalt` (7 :: Int) `hashWithSalt` x
VMap x -> s `hashWithSalt` (8 :: Int) `hashWithSalt` x
+ VNullMap -> s `hashWithSalt` (8 :: Int)
VSet x -> s `hashWithSalt` (9 :: Int) `hashWithSalt` x
VStruct fields ->
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch/Protocol/Binary.hs new/pinch-0.3.0.1/src/Pinch/Protocol/Binary.hs
--- old/pinch-0.2.0.2/src/Pinch/Protocol/Binary.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/src/Pinch/Protocol/Binary.hs 2016-07-13 06:08:03.000000000 +0200
@@ -31,7 +31,7 @@
import Pinch.Internal.Builder (Builder)
import Pinch.Internal.Message
-import Pinch.Internal.Parser (Parser, runParser)
+import Pinch.Internal.Parser (Parser, runParser, runParser')
import Pinch.Internal.TType
import Pinch.Internal.Value
import Pinch.Protocol (Protocol (..))
@@ -45,7 +45,7 @@
binaryProtocol :: Protocol
binaryProtocol = Protocol
{ serializeValue = binarySerialize
- , deserializeValue = binaryDeserialize ttype
+ , deserializeValue' = binaryDeserialize ttype
, serializeMessage = binarySerializeMessage
, deserializeMessage = binaryDeserializeMessage
}
@@ -102,8 +102,8 @@
------------------------------------------------------------------------------
-binaryDeserialize :: TType a -> ByteString -> Either String (Value a)
-binaryDeserialize t = runParser (binaryParser t)
+binaryDeserialize :: TType a -> ByteString -> Either String (ByteString, Value a)
+binaryDeserialize t = runParser' (binaryParser t)
binaryParser :: TType a -> Parser (Value a)
binaryParser typ = case typ of
@@ -263,6 +263,7 @@
{-# INLINE serializeStruct #-}
serializeMap :: Value TMap -> Builder
+serializeMap VNullMap = error "serializeMap: VNullMap"
serializeMap (VMap items) = serialize ttype ttype items
where
serialize
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch/Protocol/Compact.hs new/pinch-0.3.0.1/src/Pinch/Protocol/Compact.hs
--- old/pinch-0.2.0.2/src/Pinch/Protocol/Compact.hs 1970-01-01 01:00:00.000000000 +0100
+++ new/pinch-0.3.0.1/src/Pinch/Protocol/Compact.hs 2016-07-13 06:08:03.000000000 +0200
@@ -0,0 +1,475 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+-- |
+-- Module : Pinch.Protocol.Compact
+-- Copyright : (c) Ben Gamari 2015
+-- License : BSD3
+--
+-- Maintainer : Abhinav Gupta
+-- Stability : experimental
+--
+-- Implements the Thrift Compact Protocol as a 'Protocol'.
+module Pinch.Protocol.Compact (compactProtocol) where
+
+
+#if __GLASGOW_HASKELL__ < 709
+import Control.Applicative
+#endif
+
+import Control.Monad
+import Data.Bits hiding (shift)
+import Data.ByteString (ByteString)
+import Data.HashMap.Strict (HashMap)
+import Data.Int (Int16, Int32, Int64)
+import Data.List (sortBy)
+import Data.Monoid
+import Data.Ord (comparing)
+import Data.Typeable (Typeable)
+import Data.Word (Word64, Word8)
+
+import qualified Data.ByteString as B
+import qualified Data.HashMap.Strict as M
+import qualified Data.Text.Encoding as TE
+
+import Pinch.Internal.Builder (Builder)
+import Pinch.Internal.Message
+import Pinch.Internal.Parser (Parser, runParser, runParser')
+import Pinch.Internal.TType
+import Pinch.Internal.Value
+import Pinch.Protocol (Protocol (..))
+
+import qualified Pinch.Internal.Builder as BB
+import qualified Pinch.Internal.FoldList as FL
+import qualified Pinch.Internal.Parser as P
+
+
+-- | Provides an implementation of the Thrift Compact Protocol.
+compactProtocol :: Protocol
+compactProtocol = Protocol
+ { serializeValue = compactSerialize
+ , deserializeValue' = compactDeserialize ttype
+ , serializeMessage = compactSerializeMessage
+ , deserializeMessage = compactDeserializeMessage
+ }
+
+------------------------------------------------------------------------------
+
+protocolId, version :: Word8
+protocolId = 0x82
+version = 0x01
+
+compactSerializeMessage :: Message -> Builder
+compactSerializeMessage msg =
+ BB.word8 protocolId <>
+ BB.word8 ((version .&. 0x1f) .|. (messageCode (messageType msg) `shiftL` 5)) <>
+ serializeVarint (fromIntegral $ messageId msg) <>
+ string (TE.encodeUtf8 $ messageName msg) <>
+ compactSerialize (messagePayload msg)
+
+compactDeserializeMessage :: ByteString -> Either String Message
+compactDeserializeMessage = runParser compactMessageParser
+
+compactMessageParser :: Parser Message
+compactMessageParser = do
+ pid <- P.word8
+ when (pid /= protocolId) $ fail "Invalid protocol ID"
+ w <- P.word8
+ let ver = w .&. 0x1f
+ when (ver /= version) $ fail $ "Unsupported version: " ++ show ver
+ let code = w `shiftR` 5
+ msgId <- parseVarint
+ msgName <- TE.decodeUtf8 <$> (parseVarint >>= P.take . fromIntegral)
+ payload <- compactParser ttype
+ mtype <- case fromMessageCode code of
+ Nothing -> fail $ "unknown message type: " ++ show code
+ Just t -> return t
+ return Message { messageType = mtype
+ , messageId = fromIntegral msgId
+ , messageName = msgName
+ , messagePayload = payload
+ }
+
+
+------------------------------------------------------------------------------
+
+compactDeserialize :: TType a -> ByteString -> Either String (ByteString, Value a)
+compactDeserialize t = runParser' (compactParser t)
+
+compactParser :: TType a -> Parser (Value a)
+compactParser typ = case typ of
+ TBool -> do
+ n <- P.int8
+ return $ VBool (n == 1)
+ TByte -> parseByte
+ TDouble -> parseDouble
+ TInt16 -> parseInt16
+ TInt32 -> parseInt32
+ TInt64 -> parseInt64
+ TBinary -> parseBinary
+ TStruct -> parseStruct
+ TMap -> parseMap
+ TSet -> parseSet
+ TList -> parseList
+
+intToZigZag :: Int64 -> Int64
+intToZigZag n =
+ (n `shiftL` 1) `xor` (n `shiftR` 63)
+
+zigZagToInt :: Int64 -> Int64
+zigZagToInt n =
+ fromIntegral (n' `shiftR` 1) `xor` (-(n .&. 1))
+ where
+ n' = fromIntegral n :: Word64
+ -- ensure no sign extension
+
+parseVarint :: Parser Int64
+parseVarint = go 0 0
+ where
+ go !val !shift = do
+ when (shift >= 64) $ fail "parseVarint: too wide"
+ n <- P.word8
+ let val' = val .|. ((fromIntegral n .&. 0x7f) `shiftL` shift)
+ if testBit n 7
+ then go val' (shift + 7)
+ else return val'
+
+getCType :: Word8 -> Parser SomeCType
+getCType code =
+ maybe (fail $ "Unknown CType: " ++ show code) return $ fromCompactCode code
+
+parseByte :: Parser (Value TByte)
+parseByte = VByte <$> P.int8
+
+parseDouble :: Parser (Value TDouble)
+parseDouble = VDouble <$> P.double
+
+parseInt16 :: Parser (Value TInt16)
+parseInt16 = VInt16 . fromIntegral . zigZagToInt <$> parseVarint
+
+parseInt32 :: Parser (Value TInt32)
+parseInt32 = VInt32 . fromIntegral . zigZagToInt <$> parseVarint
+
+parseInt64 :: Parser (Value TInt64)
+parseInt64 = VInt64 . fromIntegral . zigZagToInt <$> parseVarint
+
+parseBinary :: Parser (Value TBinary)
+parseBinary = do
+ n <- parseVarint
+ when (n < 0) $
+ fail $ "parseBinary: invalid length " ++ show n
+ VBinary <$> P.take (fromIntegral n)
+
+
+parseMap :: Parser (Value TMap)
+parseMap = do
+ count <- parseVarint
+ case count of
+ 0 -> return VNullMap
+ _ -> do
+ tys <- P.word8
+ SomeCType kctype <- getCType (tys `shiftR` 4)
+ SomeCType vctype <- getCType (tys .&. 0x0f)
+
+ let ktype = cTypeToTType kctype
+ vtype = cTypeToTType vctype
+
+ items <- FL.replicateM (fromIntegral count) $
+ MapItem <$> compactParser ktype
+ <*> compactParser vtype
+ return $ VMap items
+
+
+parseCollection
+ :: (forall a. IsTType a => FL.FoldList (Value a) -> Value b)
+ -> Parser (Value b)
+parseCollection buildValue = do
+ sizeAndType <- P.word8
+ SomeCType ctype <- getCType (sizeAndType .&. 0x0f)
+ count <- case sizeAndType `shiftR` 4 of
+ 0xf -> parseVarint
+ n -> return $ fromIntegral n
+ let vtype = cTypeToTType ctype
+ buildValue <$> FL.replicateM (fromIntegral count) (compactParser vtype)
+
+parseSet :: Parser (Value TSet)
+parseSet = parseCollection VSet
+
+parseList :: Parser (Value TList)
+parseList = parseCollection VList
+
+parseStruct :: Parser (Value TStruct)
+parseStruct = loop M.empty 0
+ where
+ loop :: HashMap Int16 SomeValue -> Int16 -> Parser (Value TStruct)
+ loop fields lastFieldId = do
+ sizeAndType <- P.word8
+ SomeCType ctype <- getCType (sizeAndType .&. 0x0f)
+ case ctype of
+ CStop -> return (VStruct fields)
+ _ -> do
+ fieldId <- case sizeAndType `shiftR` 4 of
+ 0x0 -> fromIntegral . zigZagToInt <$> parseVarint
+ n -> return (lastFieldId + fromIntegral n)
+ value <- case ctype of
+ CBoolTrue -> return (SomeValue $ VBool True)
+ CBoolFalse -> return (SomeValue $ VBool False)
+ _ ->
+ let vtype = cTypeToTType ctype
+ in SomeValue <$> compactParser vtype
+ loop (M.insert fieldId value fields) fieldId
+
+
+------------------------------------------------------------------------------
+
+compactSerialize :: forall a. IsTType a => Value a -> Builder
+compactSerialize = case (ttype :: TType a) of
+ TBinary -> serializeBinary
+ TBool -> serializeBool
+ TByte -> serializeByte
+ TDouble -> serializeDouble
+ TInt16 -> serializeInt16
+ TInt32 -> serializeInt32
+ TInt64 -> serializeInt64
+ TStruct -> serializeStruct
+ TList -> serializeList
+ TMap -> serializeMap
+ TSet -> serializeSet
+{-# INLINE compactSerialize #-}
+
+serializeBinary :: Value TBinary -> Builder
+serializeBinary (VBinary x) = string x
+{-# INLINE serializeBinary #-}
+
+serializeBool :: Value TBool -> Builder
+serializeBool (VBool x) = compactCode $ if x then CBoolTrue else CBoolFalse
+{-# INLINE serializeBool #-}
+
+serializeByte :: Value TByte -> Builder
+serializeByte (VByte x) = BB.int8 x
+{-# INLINE serializeByte #-}
+
+serializeDouble :: Value TDouble -> Builder
+serializeDouble (VDouble x) = BB.doubleBE x
+{-# INLINE serializeDouble #-}
+
+serializeVarint :: Int64 -> Builder
+serializeVarint = go . fromIntegral
+ where
+ -- Ensure we don't sign extend
+ go :: Word64 -> Builder
+ go n
+ | complement 0x7f .&. n == 0 =
+ BB.word8 $ fromIntegral n
+ | otherwise =
+ BB.word8 (0x80 .|. (fromIntegral n .&. 0x7f)) <>
+ go (n `shiftR` 7)
+
+serializeInt16 :: Value TInt16 -> Builder
+serializeInt16 (VInt16 x) = serializeVarint $ intToZigZag $ fromIntegral x
+{-# INLINE serializeInt16 #-}
+
+serializeInt32 :: Value TInt32 -> Builder
+serializeInt32 (VInt32 x) = serializeVarint $ intToZigZag $ fromIntegral x
+{-# INLINE serializeInt32 #-}
+
+serializeInt64 :: Value TInt64 -> Builder
+serializeInt64 (VInt64 x) = serializeVarint $ intToZigZag x
+{-# INLINE serializeInt64 #-}
+
+serializeList :: Value TList -> Builder
+serializeList (VList xs) = serializeCollection ttype xs
+{-# INLINE serializeList #-}
+
+serializeSet :: Value TSet -> Builder
+serializeSet (VSet xs) = serializeCollection ttype xs
+{-# INLINE serializeSet #-}
+
+serializeStruct :: Value TStruct -> Builder
+serializeStruct (VStruct fields) =
+ loop 0 (sortBy (comparing fst) $ M.toList fields)
+ where
+ loop _ [] = compactCode CStop
+ loop lastFieldId ((fieldId, val) : rest) =
+ let x = case val of
+ SomeValue (VBool True) -> writeFieldHeader CBoolTrue
+ SomeValue (VBool False) -> writeFieldHeader CBoolFalse
+ SomeValue (v :: Value a) ->
+ writeFieldHeader (tTypeToCType (ttype :: TType a)) <> compactSerialize v
+ in x <> loop fieldId rest
+ where
+ writeFieldHeader :: CType a -> Builder
+ writeFieldHeader ccode
+ | fieldId > lastFieldId && fieldId - lastFieldId < 16
+ = compactCode' ccode (fromIntegral $ fieldId - lastFieldId)
+ | otherwise
+ = compactCode ccode <> serializeVarint (intToZigZag $ fromIntegral fieldId)
+{-# INLINE serializeStruct #-}
+
+serializeMap :: Value TMap -> Builder
+serializeMap VNullMap = BB.int8 0
+serializeMap (VMap items) = serialize ttype ttype items
+ where
+ serialize
+ :: (IsTType k, IsTType v)
+ => TType k -> TType v -> FL.FoldList (MapItem k v) -> Builder
+ serialize kt vt xs
+ | size == 0 = BB.int8 0
+ | otherwise =
+ serializeVarint (fromIntegral size) <> BB.word8 typeByte <> body
+ where
+ code = toCompactCode . tTypeToCType
+ typeByte = (code kt `shiftL` 4) .|. code vt
+ (body, size) = FL.foldl' go (mempty, 0 :: Int32) xs
+ go (prev, !c) (MapItem k v) =
+ ( prev <> compactSerialize k <> compactSerialize v
+ , c + 1
+ )
+{-# INLINE serializeMap #-}
+
+serializeCollection
+ :: IsTType a
+ => TType a -> FL.FoldList (Value a) -> Builder
+serializeCollection vtype xs =
+ let go (prev, !c) item = (prev <> compactSerialize item, c + 1)
+ (body, size) = FL.foldl' go (mempty, 0 :: Int32) xs
+ type_and_size
+ | size < 15 = typeCode' vtype (fromIntegral size)
+ | otherwise = typeCode' vtype 0xf <> serializeVarint (fromIntegral size)
+ in type_and_size <> body
+{-# INLINE serializeCollection #-}
+
+------------------------------------------------------------------------------
+
+
+messageCode :: MessageType -> Word8
+messageCode Call = 1
+messageCode Reply = 2
+messageCode Exception = 3
+messageCode Oneway = 4
+{-# INLINE messageCode #-}
+
+
+fromMessageCode :: Word8 -> Maybe MessageType
+fromMessageCode 1 = Just Call
+fromMessageCode 2 = Just Reply
+fromMessageCode 3 = Just Exception
+fromMessageCode 4 = Just Oneway
+fromMessageCode _ = Nothing
+{-# INLINE fromMessageCode #-}
+
+
+data TStop deriving (Typeable)
+
+instance IsTType TStop where
+ ttype = error "ttype TStop"
+
+-- | A compact message type.
+data CType a where
+ CStop :: CType TStop
+ CBoolTrue :: CType TBool
+ CBoolFalse :: CType TBool
+ CByte :: CType TByte
+ CInt16 :: CType TInt16
+ CInt32 :: CType TInt32
+ CInt64 :: CType TInt64
+ CDouble :: CType TDouble
+ CBinary :: CType TBinary
+ CList :: CType TList
+ CSet :: CType TSet
+ CMap :: CType TMap
+ CStruct :: CType TStruct
+
+
+data SomeCType where
+ SomeCType :: forall a. IsTType a => CType a -> SomeCType
+
+
+-- | Map a TType to its type code.
+toCompactCode :: CType a -> Word8
+toCompactCode CStop = 0
+toCompactCode CBoolTrue = 1
+toCompactCode CBoolFalse = 2
+toCompactCode CByte = 3
+toCompactCode CInt16 = 4
+toCompactCode CInt32 = 5
+toCompactCode CInt64 = 6
+toCompactCode CDouble = 7
+toCompactCode CBinary = 8
+toCompactCode CList = 9
+toCompactCode CSet = 10
+toCompactCode CMap = 11
+toCompactCode CStruct = 12
+{-# INLINE toCompactCode #-}
+
+
+-- | Map a type code to the corresponding TType.
+fromCompactCode :: Word8 -> Maybe SomeCType
+fromCompactCode 0 = Just $ SomeCType CStop
+fromCompactCode 1 = Just $ SomeCType CBoolTrue
+fromCompactCode 2 = Just $ SomeCType CBoolFalse
+fromCompactCode 3 = Just $ SomeCType CByte
+fromCompactCode 4 = Just $ SomeCType CInt16
+fromCompactCode 5 = Just $ SomeCType CInt32
+fromCompactCode 6 = Just $ SomeCType CInt64
+fromCompactCode 7 = Just $ SomeCType CDouble
+fromCompactCode 8 = Just $ SomeCType CBinary
+fromCompactCode 9 = Just $ SomeCType CList
+fromCompactCode 10 = Just $ SomeCType CSet
+fromCompactCode 11 = Just $ SomeCType CMap
+fromCompactCode 12 = Just $ SomeCType CStruct
+fromCompactCode _ = Nothing
+{-# INLINE fromCompactCode #-}
+
+tTypeToCType :: TType a -> CType a
+tTypeToCType TBool = CBoolTrue
+tTypeToCType TByte = CByte
+tTypeToCType TInt16 = CInt16
+tTypeToCType TInt32 = CInt32
+tTypeToCType TInt64 = CInt64
+tTypeToCType TDouble = CDouble
+tTypeToCType TBinary = CBinary
+tTypeToCType TList = CList
+tTypeToCType TSet = CSet
+tTypeToCType TMap = CMap
+tTypeToCType TStruct = CStruct
+
+cTypeToTType :: CType a -> TType a
+cTypeToTType CStop = error "cTypeToTType: CStop"
+cTypeToTType CBoolTrue = TBool
+cTypeToTType CBoolFalse = TBool
+cTypeToTType CByte = TByte
+cTypeToTType CInt16 = TInt16
+cTypeToTType CInt32 = TInt32
+cTypeToTType CInt64 = TInt64
+cTypeToTType CDouble = TDouble
+cTypeToTType CBinary = TBinary
+cTypeToTType CList = TList
+cTypeToTType CSet = TSet
+cTypeToTType CMap = TMap
+cTypeToTType CStruct = TStruct
+
+------------------------------------------------------------------------------
+
+
+string :: ByteString -> Builder
+string b = serializeVarint (fromIntegral $ B.length b) <> BB.byteString b
+{-# INLINE string #-}
+
+compactCode :: CType a -> Builder
+compactCode = BB.word8 . toCompactCode
+{-# INLINE compactCode #-}
+
+compactCode' :: CType a -- ^ The compact type code
+ -> Word8 -- ^ a four-bit (unshifted) payload
+ -> Builder
+compactCode' ty payload =
+ BB.word8 (toCompactCode ty .|. (fromIntegral payload `shiftL` 4))
+{-# INLINE compactCode' #-}
+
+typeCode' :: TType a -> Word8 -> Builder
+typeCode' ty = compactCode' (tTypeToCType ty)
+{-# INLINE typeCode' #-}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch/Protocol.hs new/pinch-0.3.0.1/src/Pinch/Protocol.hs
--- old/pinch-0.2.0.2/src/Pinch/Protocol.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/src/Pinch/Protocol.hs 2016-07-13 06:08:03.000000000 +0200
@@ -13,6 +13,7 @@
-- 'Pinch.Pinchable.Pinchable'.
module Pinch.Protocol
( Protocol(..)
+ , deserializeValue
) where
import Data.ByteString (ByteString)
@@ -33,9 +34,15 @@
--
-- Returns a @Builder@ and the total length of the serialized content.
- , deserializeValue
- :: forall a. IsTType a => ByteString -> Either String (Value a)
- -- ^ Reads a 'Value' from a ByteString.
+ , deserializeValue'
+ :: forall a. IsTType a => ByteString -> Either String (ByteString, Value a)
+ -- ^ Reads a 'Value' from a ByteString and returns leftovers from parse.
, deserializeMessage :: ByteString -> Either String Message
-- ^ Reads a 'Message' and its payload from a ByteString.
}
+
+
+-- | Reads a 'Value' from a ByteString.
+deserializeValue :: forall a. IsTType a
+ => Protocol -> ByteString -> Either String (Value a)
+deserializeValue proto = fmap snd . deserializeValue' proto
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/src/Pinch.hs new/pinch-0.3.0.1/src/Pinch.hs
--- old/pinch-0.2.0.2/src/Pinch.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/src/Pinch.hs 2016-07-13 06:08:03.000000000 +0200
@@ -112,6 +112,7 @@
, Protocol
, binaryProtocol
+ , compactProtocol
-- * TType
@@ -158,6 +159,7 @@
import Pinch.Internal.Value
import Pinch.Protocol
import Pinch.Protocol.Binary
+import Pinch.Protocol.Compact
------------------------------------------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/tests/Pinch/Internal/PinchableSpec.hs new/pinch-0.3.0.1/tests/Pinch/Internal/PinchableSpec.hs
--- old/pinch-0.2.0.2/tests/Pinch/Internal/PinchableSpec.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/tests/Pinch/Internal/PinchableSpec.hs 2016-07-13 06:08:03.000000000 +0200
@@ -307,6 +307,10 @@
(HM.fromList
[("a", 1), ("b", 2) :: (ByteString, Int16)])
+ it "can unpinch empty maps" $
+ unpinch' V.VNullMap `shouldBe`
+ Right (HM.empty :: HashMap ByteString Int16)
+
it "rejects key type mismatch" $
(unpinch' :: V.Value T.TMap -> Either String (HashMap Int32 Int16))
(vmap [(vbin "a", vi16 1)])
@@ -335,6 +339,10 @@
(M.fromList
[("a", 1), ("b", 2) :: (ByteString, Int16)])
+ it "can unpinch empty maps" $
+ unpinch' V.VNullMap `shouldBe`
+ Right (M.empty :: Map ByteString Int16)
+
it "rejects key type mismatch" $
(unpinch' :: V.Value T.TMap -> Either String (Map Int32 Int16))
(vmap [(vbin "a", vi16 1)])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/tests/Pinch/Protocol/BinarySpec.hs new/pinch-0.3.0.1/tests/Pinch/Protocol/BinarySpec.hs
--- old/pinch-0.2.0.2/tests/Pinch/Protocol/BinarySpec.hs 2016-07-13 06:05:47.000000000 +0200
+++ new/pinch-0.3.0.1/tests/Pinch/Protocol/BinarySpec.hs 2016-07-13 06:08:03.000000000 +0200
@@ -17,7 +17,7 @@
import Pinch.Internal.TType
import Pinch.Internal.Util
import Pinch.Internal.Value (SomeValue (..), Value (..))
-import Pinch.Protocol (Protocol (..))
+import Pinch.Protocol
import Pinch.Protocol.Binary (binaryProtocol)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pinch-0.2.0.2/tests/Pinch/Protocol/CompactSpec.hs new/pinch-0.3.0.1/tests/Pinch/Protocol/CompactSpec.hs
--- old/pinch-0.2.0.2/tests/Pinch/Protocol/CompactSpec.hs 1970-01-01 01:00:00.000000000 +0100
+++ new/pinch-0.3.0.1/tests/Pinch/Protocol/CompactSpec.hs 2016-07-13 06:08:03.000000000 +0200
@@ -0,0 +1,270 @@
+{-# LANGUAGE NegativeLiterals #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module Pinch.Protocol.CompactSpec (spec) where
+
+import Data.ByteString (ByteString)
+import Data.Word (Word8)
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import Test.QuickCheck
+
+import qualified Data.ByteString as B
+
+import Pinch.Arbitrary ()
+import Pinch.Internal.Builder (runBuilder)
+import Pinch.Internal.Message
+import Pinch.Internal.TType
+import Pinch.Internal.Util
+import Pinch.Internal.Value (SomeValue (..), Value (..))
+import Pinch.Protocol
+import Pinch.Protocol.Compact (compactProtocol)
+
+
+serialize :: IsTType a => Value a -> ByteString
+serialize = runBuilder . serializeValue compactProtocol
+
+
+deserialize :: IsTType a => ByteString -> Either String (Value a)
+deserialize = deserializeValue compactProtocol
+
+
+serializeMsg :: Message -> ByteString
+serializeMsg = runBuilder . serializeMessage compactProtocol
+
+deserializeMsg :: ByteString -> Either String Message
+deserializeMsg = deserializeMessage compactProtocol
+
+
+-- | For each given pair, verifies that parsing the byte array yields the
+-- value, and that serializing the value yields the byte array.
+readWriteCases :: IsTType a => [([Word8], Value a)] -> Expectation
+readWriteCases = mapM_ . uncurry $ \bytes value -> do
+ let bs = B.pack bytes
+ deserialize bs `shouldBe` Right value
+ serialize value `shouldBe` bs
+
+
+readWriteMessageCases :: [([Word8], Message)] -> Expectation
+readWriteMessageCases = mapM_ . uncurry $ \bytes msg -> do
+ let bs = B.pack bytes
+ deserializeMsg bs `shouldBe` Right msg
+ serializeMsg msg `shouldBe` bs
+
+
+-- | For each pair, verifies that if the given TType is parsed, the request
+-- fails to parse because the type ID was invalid.
+invalidTypeIDCases :: [(SomeTType, [Word8])] -> Expectation
+invalidTypeIDCases = mapM_ . uncurry $ \(SomeTType t) v -> go t v
+ where
+ go :: forall a. IsTType a => TType a -> [Word8] -> Expectation
+ go _ bytes =
+ case deserialize (B.pack bytes) :: Either String (Value a) of
+ Right v -> expectationFailure $
+ "Expected " ++ show bytes ++ " to fail to parse. " ++
+ "Got: " ++ show v
+ Left msg -> msg `shouldContain` "Unknown CType"
+
+
+-- | For each pair, verifies that if the given TType is parsed, the request
+-- fails to parse because the input was too short.
+tooShortCases :: [(SomeTType, [Word8])] -> Expectation
+tooShortCases = mapM_ . uncurry $ \(SomeTType t) v -> go t v
+ where
+ go :: forall a. IsTType a => TType a -> [Word8] -> Expectation
+ go _ bytes =
+ case deserialize (B.pack bytes) :: Either String (Value a) of
+ Right v -> expectationFailure $
+ "Expected " ++ show bytes ++ " to fail to parse. " ++
+ "Got: " ++ show v
+ Left msg -> msg `shouldContain` "Input is too short"
+
+
+spec :: Spec
+spec = describe "CompactProtocol" $ do
+
+ prop "can roundtrip values" $ \(SomeValue someVal) ->
+ deserialize (serialize someVal) === Right someVal
+
+ prop "can roundtrip messages" $ \(msg :: Message) ->
+ deserializeMsg (serializeMsg msg) == Right msg
+
+ it "can read and write booleans" $ readWriteCases
+ [ ([0x01], vbool True)
+ , ([0x02], vbool False)
+ ]
+
+ it "can read and write binary" $ readWriteCases
+ [ ([ 0x00 ], vbin "")
+ , ([ 0x05 -- length = 5
+ , 0x68, 0x65, 0x6c, 0x6c, 0x6f -- hello
+ ], vbin "hello")
+ ]
+
+ it "can read and write 8-bit integers" $ readWriteCases
+ [ ([0x01], vbyt 1)
+ , ([0x05], vbyt 5)
+ , ([0x7f], vbyt 127)
+ , ([0xff], vbyt -1)
+ , ([0x80], vbyt -128)
+ ]
+
+ it "can read and write 16-bit integers" $ readWriteCases
+ [ ([0x02], vi16 1)
+ , ([0xfe, 0x03], vi16 255)
+ , ([0x80, 0x04], vi16 256)
+ , ([0x82, 0x04], vi16 257)
+ , ([0xfe, 0xff, 0x03], vi16 32767)
+ , ([0x01], vi16 -1)
+ , ([0x03], vi16 -2)
+ , ([0xff, 0x03], vi16 -256)
+ , ([0xfd, 0x03], vi16 -255)
+ , ([0xff, 0xff, 0x03], vi16 -32768)
+ ]
+
+ it "can read and write 32-bit integers" $ readWriteCases
+ [ ([0x02], vi32 1)
+ , ([0xfe, 0x03], vi32 255)
+ , ([0xfe, 0xff, 0x07], vi32 65535)
+ , ([0xfe, 0xff, 0xff, 0x0f], vi32 16777215)
+ , ([0xfe, 0xff, 0xff, 0xff, 0x0f], vi32 2147483647)
+ , ([0x01], vi32 -1)
+ , ([0xff, 0x03], vi32 -256)
+ , ([0xff, 0xff, 0x07], vi32 -65536)
+ , ([0xff, 0xff, 0xff, 0x0f], vi32 -16777216)
+ , ([0xff, 0xff, 0xff, 0xff, 0x0f], vi32 -2147483648)
+ ]
+
+ it "can read and write 64-bit integers" $ readWriteCases
+ [ ([0x02], vi64 1)
+ , ([0xfe, 0xff, 0xff, 0xff, 0x1f], vi64 4294967295)
+ , ([0xfe, 0xff, 0xff, 0xff, 0xff, 0x3f], vi64 1099511627775)
+ , ([0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f], vi64 281474976710655)
+ , ([0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], vi64 72057594037927935)
+ , ([0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], vi64 9223372036854775807)
+ , ([0x01], vi64 -1)
+ , ([0xff, 0xff, 0xff, 0xff, 0x1f], vi64 -4294967296)
+ , ([0xff, 0xff, 0xff, 0xff, 0xff, 0x3f], vi64 -1099511627776)
+ , ([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f], vi64 -281474976710656)
+ , ([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], vi64 -72057594037927936)
+ , ([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], vi64 -9223372036854775808)
+ ]
+
+ it "can read and write doubles" $ readWriteCases
+ [ ([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], vdub 0.0)
+ , ([0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], vdub 1.0)
+ , ([0x3f, 0xf0, 0x00, 0x00, 0x00, 0x06, 0xdf, 0x38], vdub 1.0000000001)
+ , ([0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a], vdub 1.1)
+ , ([0xbf, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a], vdub -1.1)
+ , ([0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18], vdub 3.141592653589793)
+ , ([0xbf, 0xf0, 0x00, 0x00, 0x00, 0x06, 0xdf, 0x38], vdub -1.0000000001)
+ ]
+
+ it "can read and write structs" $ readWriteCases
+ [ ([0x00], vstruct [])
+
+ , ([ 0x15 -- ttype = i32, field ID = 1
+ , 0x54 -- 42
+ , 0x00 -- stop
+ ], vstruct [(1, vi32_ 42)])
+
+ , ([ 0x11 -- ttype = bool true, field ID = 1
+ , 0x52 -- ttype = bool false, field ID = 6
+ , 0x23, 0x2a -- ttype = byte, field ID = 8, byte 42
+ , 0x03, 0x40, 0x2b -- ttype = byte, field ID = 32, byte 42
+ , 0x00 -- stop
+ ], vstruct [(1, vbool_ True), (6, vbool_ False), (8, vbyt_ 42), (32, vbyt_ 43)])
+
+ , ([ 0x29 -- ttype = list, field ID = 2
+ , 0x28
+
+ , 0x03, 0x66, 0x6f, 0x6f -- "foo"
+ , 0x03, 0x62, 0x61, 0x72 -- "bar"
+
+ , 0x00 -- stop
+ ], vstruct
+ [ (2, vlist_ [vbin "foo", vbin "bar"])
+ ])
+ ]
+
+ it "can read and write maps" $ readWriteCases
+ [ ([ 0x00
+ ], vmap ([] :: [(Value TBool, Value TByte)]))
+ , ([ 0x01, 0x89 -- ktype = binary, vtype = list
+
+ -- "world"
+ , 0x05 -- length = 5
+ , 0x77, 0x6f, 0x72, 0x6c, 0x64 -- world
+
+ -- [1, 2, 3]
+ , 0x33 -- type = byte, count = 3
+ , 0x01, 0x02, 0x03 -- 1, 2, 3
+ ], vmap
+ [ (vbin "world", vlist [vbyt 1, vbyt 2, vbyt 3])
+ ])
+ ]
+
+ it "can read and write sets" $ readWriteCases
+ [ ([0x01
+ ], vset ([] :: [Value TBool]))
+ , ([ 0x11, 0x01
+ ], vset [vbool True])
+ ]
+
+ it "can read and write lists" $ readWriteCases
+ [ ([0x01
+ ], vlist ([] :: [Value TBool]))
+ , ([ 0x51, 0x01, 0x02, 0x02
+ , 0x01, 0x01
+ ], vlist
+ [ vbool True
+ , vbool False
+ , vbool False
+ , vbool True
+ , vbool True
+ ])
+ ]
+
+ it "fails if the input is too short" $ tooShortCases
+ [ (SomeTType TBool, [])
+ , (SomeTType TByte, [])
+ , (SomeTType TInt16, [])
+ , (SomeTType TInt32, [])
+ , (SomeTType TInt64, [])
+ , (SomeTType TDouble, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07])
+ , (SomeTType TBinary, [0x01])
+
+ , (SomeTType TMap, [0x02])
+ , (SomeTType TMap, [0x02, 0x33])
+ , (SomeTType TMap, [0x02, 0x33, 0x01])
+
+ , (SomeTType TSet, [0x2a])
+ , (SomeTType TSet, [0x2a, 0x33, 0x00])
+
+ , (SomeTType TList, [0x29])
+ , (SomeTType TList, [0x29, 0x33])
+ ]
+
+ it "denies invalid type IDs" $ invalidTypeIDCases
+ [ (SomeTType TStruct, [0x0d, 0x00, 0x01])
+ , (SomeTType TMap, [0x1a, 0xd1, 0x00])
+ , (SomeTType TSet, [0x1d])
+ , (SomeTType TList, [0x1d])
+ ]
+
+ it "can read and write messages" $ readWriteMessageCases
+ [ ([ 0x82 -- Protocol id
+ , 0x21 -- Version and Type = Call
+ , 0x2a -- seqId = 42
+ , 0x06 -- name length = 6
+ , 0x67, 0x65, 0x74, 0x46, 0x6f, 0x6f -- 'getFoo'
+ , 0x00 -- stop
+ ], Message "getFoo" Call 42 (vstruct []))
+ , ([ 0x82 -- Protocol id
+ , 0x41 -- Version and Type = Reply
+ , 0x01 -- seqId = 01
+ , 0x06 -- name length = 6
+ , 0x73, 0x65, 0x74, 0x42, 0x61, 0x72 -- 'setBar'
+ , 0x00 -- stop
+ ], Message "setBar" Reply 1 (vstruct []))
+ ]