summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwinterland <>2021-01-12 13:49:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2021-01-12 13:49:00 (GMT)
commit890059b1155f4476bd35f58cb54cc848d5076cd9 (patch)
tree6e8e8bc99126b4d99f3a85ecdf1440b9b48b4727
version 0.1.0.00.1.0.0
-rwxr-xr-xCHANGELOG.md5
-rw-r--r--LICENSE30
-rw-r--r--Setup.hs2
-rw-r--r--Z-MessagePack.cabal105
-rw-r--r--Z/Data/MessagePack.hs97
-rw-r--r--Z/Data/MessagePack/Base.hs1410
-rw-r--r--Z/Data/MessagePack/Builder.hs211
-rw-r--r--Z/Data/MessagePack/Value.hs166
-rw-r--r--test/Spec.hs2
-rw-r--r--test/Z/Data/MessagePack/BaseSpec.hs307
10 files changed, 2335 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100755
index 0000000..b7ee0c9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for Z-Redis
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..79a5f30
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2017-2020, Dong Han
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of winter nor the names of other
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
index 0000000..9a994af
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Z-MessagePack.cabal b/Z-MessagePack.cabal
new file mode 100644
index 0000000..4fca67e
--- /dev/null
+++ b/Z-MessagePack.cabal
@@ -0,0 +1,105 @@
+cabal-version: >=1.10
+name: Z-MessagePack
+version: 0.1.0.0
+synopsis: MessagePack
+description: MessagePack binary serialization format.
+license: BSD3
+license-file: LICENSE
+author: Dong Han
+maintainer: winterland1989@gmail.com
+copyright: (c) Hideyuki Tanaka, 2009-2015, (c) Dong Han, 2020
+category: Data
+build-type: Simple
+homepage: https://github.com/haskell-Z/z-redis
+bug-reports: https://github.com/haskell-Z/z-redis/issues
+
+extra-source-files: CHANGELOG.md
+
+library
+ exposed-modules: Z.Data.MessagePack
+ Z.Data.MessagePack.Base
+ Z.Data.MessagePack.Builder
+ Z.Data.MessagePack.Value
+
+ -- other-modules:
+ -- other-extensions:
+ build-depends: base >=4.14 && <4.15
+ , deepseq >= 1.4 && < 1.5
+ , primitive >= 0.7.1 && < 0.7.2
+ , QuickCheck >= 2.10
+ , scientific == 0.3.*
+ , hashable == 1.3.*
+ , unordered-containers == 0.2.*
+ , containers == 0.6.*
+ , integer-gmp == 1.*
+ , tagged == 0.8.*
+ , time >= 1.9 && < 2.0
+ , Z-Data == 0.4.*
+ , Z-IO == 0.4.*
+
+ -- hs-source-dirs:
+ default-language: Haskell2010
+ default-extensions: BangPatterns
+ BinaryLiterals
+ CApiFFI
+ ConstraintKinds
+ CPP
+ DerivingStrategies
+ DeriveGeneric
+ DeriveAnyClass
+ DefaultSignatures
+ DataKinds
+ ExistentialQuantification
+ FlexibleContexts
+ FlexibleInstances
+ GeneralizedNewtypeDeriving
+ KindSignatures
+ MagicHash
+ MultiParamTypeClasses
+ MultiWayIf
+ PartialTypeSignatures
+ PatternSynonyms
+ PolyKinds
+ QuantifiedConstraints
+ QuasiQuotes
+ OverloadedStrings
+ RankNTypes
+ RecordWildCards
+ ScopedTypeVariables
+ StandaloneDeriving
+ TemplateHaskell
+ TypeApplications
+ TypeFamilyDependencies
+ TypeFamilies
+ TypeOperators
+ TupleSections
+ UnboxedTuples
+ UnliftedFFITypes
+ ViewPatterns
+
+
+test-suite Z-MessagePack-Test
+ type: exitcode-stdio-1.0
+ main-is: Spec.hs
+ hs-source-dirs: test/
+ build-depends: base
+ , Z-IO
+ , Z-Data
+ , Z-MessagePack
+ , hspec >= 2.5.4
+ , hashable
+ , unordered-containers
+ , containers
+ , HUnit
+ , QuickCheck >= 2.10
+ , quickcheck-instances
+ , scientific
+ , primitive
+ , time
+
+ other-modules: Z.Data.MessagePack.BaseSpec
+
+ ghc-options: -threaded
+ default-language: Haskell2010
+ build-tool-depends: hspec-discover:hspec-discover == 2.*
+
diff --git a/Z/Data/MessagePack.hs b/Z/Data/MessagePack.hs
new file mode 100644
index 0000000..dc5a039
--- /dev/null
+++ b/Z/Data/MessagePack.hs
@@ -0,0 +1,97 @@
+{-|
+Module : Z.Data.MessagePack
+Description : Fast MessagePack serialization/deserialization
+Copyright : (c) Dong Han, 2019
+License : BSD
+Maintainer : winterland1989@gmail.com
+Stability : experimental
+Portability : non-portable
+
+This module provides an interface similar to 'Z.Data.JSON', to work with MessagePack binary format.
+
+ * @Maybe a@ convert to 'Nil' in 'Nothing' case, and @a@ in 'Just' case.
+ * Use 'Int64'(signed) or 'Word64'(unsigned) type to marshall int type format, smaller types will sliently truncate when overflow.
+ * Use 'Double' to marshall float type format, 'Float' may lost precision.
+ * Use 'Scientific' to marshall 'Ext' @0x00\/0x01@ type.
+ * Use 'SystemTime' to marshall 'Ext' @0xFF@ type.
+ * Record's field label are preserved.
+
+ * We use MessagePack extension type -1 to encode\/decode 'SystemTime' and 'UTCTime':
+
+ +--------+--------+--------+-----------------------------------+------------------------------+
+ | 0xc7 | 12 | -1 |nanoseconds in 32-bit unsigned int | seconds in 64-bit signed int |
+ +--------+--------+--------+-----------------------------------+------------------------------+
+
+ * We deliberately use ext type 0x00(positive) and 0x01(negative) to represent large numbers('Integer', 'Scientific', 'Fixed', 'DiffTime'...):
+
+ +--------+--------+--------+-----------------------------------------+---------------------------------------+
+ | 0xc7 |XXXXXXXX| 0x00 | base10 exponent(MessagePack int format) | coefficient(big endian 256-base limbs |
+ +--------+--------+--------+-----------------------------------------+---------------------------------------+
+
+ Use a MessagePack implementation supporting ext type to marshall it, result value is coefficient * (10 ^ exponent).
+
+The easiest way to use the library is to define target data type, deriving 'GHC.Generics.Generic' and 'MessagePack' instances, e.g.
+
+@
+{-# LANGUAGE DeriveGeneric, DeriveAnyClass, DerivingStrategies #-}
+
+import GHC.Generics (Generic)
+import qualified Z.Data.MessagePack as MessagePack
+import qualified Z.Data.Text as T
+
+data Person = Person {name :: T.Text, age :: Int}
+ deriving (Show, Generic)
+ deriving anyclass (MessagePack.MessagePack)
+
+> MessagePack.encode Person{ name="Alice", age=16 }
+> [130,164,110,97,109,101,165,65,108,105,99,101,163,97,103,101,16]
+@
+
+MessagePack is a schemaless format, which means the encoded data can be recovered into some form('Value' in haskell case)
+without providing data definition, e.g. the data encoded above:
+
+> [130, 164, 110, 97, 109, 101, 165, 65, 108, 105, 99, 101, 163, 97, 103, 101, 16]
+> 0x82 0xA4 'n' 'a' 'm' 'e' 0xA5 'A' 'l' 'i' 'c' 'e' 0xA3 'a' 'g' 'e' int
+> map str str str 16
+> 2kvs 4bytes 5bytes 3bytes
+
+
+This property makes it suitable for passing data across language boundary, e.g. from a static typed language to a dynamic one,
+at the cost of a lower space efficiency(i.e. type tag and field label).
+
+-}
+
+module Z.Data.MessagePack
+ ( -- * MessagePack Class
+ MessagePack(..), Value(..), defaultSettings, Settings(..), JSON.snakeCase, JSON.trainCase
+ -- * Encode & Decode
+ , readMessagePackFile, writeMessagePackFile
+ , decode, decode', decodeChunks, decodeChunks', encode, encodeChunks
+ , DecodeError, ParseError
+ -- * parse into MessagePack Value
+ , parseValue, parseValue', parseValueChunks, parseValueChunks'
+ -- * Generic FromValue, ToValue & EncodeMessagePack
+ , gToValue, gFromValue, gEncodeMessagePack
+ -- * Convert 'Value' to Haskell data
+ , convertValue, Converter(..), fail', (<?>), prependContext
+ , PathElement(..), ConvertError(..)
+ , typeMismatch, fromNil, withBool
+ , withStr, withBin, withArray, withKeyValues, withFlatMap, withFlatMapR
+ , (.:), (.:?), (.:!), convertField, convertFieldMaybe, convertFieldMaybe'
+ -- * Helper for manually writing instance.
+ , (.=), object, (.!), object', KVItem
+ ) where
+
+import Z.Data.MessagePack.Base
+import qualified Z.Data.JSON as JSON
+import Z.Data.CBytes (CBytes)
+import Z.IO
+import qualified Z.IO.FileSystem as FS
+
+-- | Decode a 'MessagePack' instance from file.
+readMessagePackFile :: (HasCallStack, MessagePack a) => CBytes -> IO a
+readMessagePackFile p = unwrap . decode' =<< FS.readFile p
+
+-- | Encode a 'MessagePack' instance to file.
+writeMessagePackFile :: (HasCallStack, MessagePack a) => CBytes -> a -> IO ()
+writeMessagePackFile p x = FS.writeFile p (encode x)
diff --git a/Z/Data/MessagePack/Base.hs b/Z/Data/MessagePack/Base.hs
new file mode 100644
index 0000000..cfd8e23
--- /dev/null
+++ b/Z/Data/MessagePack/Base.hs
@@ -0,0 +1,1410 @@
+{-|
+Module : Z.Data.MessagePack.Base
+Description : Fast MessagePack serialization/deserialization
+Copyright : (c) Dong Han, 2020
+License : BSD
+Maintainer : winterland1989@gmail.com
+Stability : experimental
+Portability : non-portable
+
+This module provides 'Converter' to convert 'Value' to haskell data types, and various tools to help user define 'MessagePack' instance.
+
+-}
+
+module Z.Data.MessagePack.Base
+ ( -- * MessagePack Class
+ MessagePack(..), Value(..), defaultSettings, Settings(..)
+ -- * Encode & Decode
+ , decode, decode', decodeChunks, decodeChunks', encode, encodeChunks
+ , DecodeError, P.ParseError
+ -- * parse into MessagePack Value
+ , MV.parseValue, MV.parseValue', MV.parseValueChunks, MV.parseValueChunks'
+ -- * Generic FromValue, ToValue & EncodeMessagePack
+ , gToValue, gFromValue, gEncodeMessagePack
+ -- * Convert 'Value' to Haskell data
+ , convertValue, Converter(..), fail', (<?>), prependContext
+ , PathElement(..), ConvertError(..)
+ , typeMismatch, fromNil, withBool
+ , withStr, withBin, withArray, withKeyValues, withFlatMap, withFlatMapR
+ , (.:), (.:?), (.:!), convertField, convertFieldMaybe, convertFieldMaybe'
+ -- * Helper for manually writing instance.
+ , (.=), object, (.!), object', KVItem
+ ) where
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.ST
+import Data.Char (ord)
+import Data.Data
+import Data.Fixed
+import Data.Functor.Compose
+import Data.Functor.Const
+import Data.Functor.Identity
+import Data.Functor.Product
+import Data.Functor.Sum
+import qualified Data.Foldable as Foldable
+import Data.Hashable
+import qualified Data.HashMap.Strict as HM
+import qualified Data.HashSet as HS
+import qualified Data.IntMap as IM
+import qualified Data.IntSet as IS
+import qualified Data.Map.Strict as M
+import qualified Data.Sequence as Seq
+import qualified Data.Set as Set
+import qualified Data.Tree as Tree
+import GHC.Int
+import GHC.Exts
+import Data.List.NonEmpty (NonEmpty (..))
+import qualified Data.List.NonEmpty as NonEmpty
+import qualified Data.Monoid as Monoid
+import qualified Data.Primitive.ByteArray as A
+import qualified Data.Primitive.SmallArray as A
+import Data.Primitive.Types (Prim)
+import Data.Proxy (Proxy (..))
+import Data.Ratio (Ratio, denominator, numerator, (%))
+import Data.Scientific (Scientific, coefficient, base10Exponent)
+import qualified Data.Scientific as Sci
+import qualified Data.Semigroup as Semigroup
+import Data.Tagged (Tagged (..))
+import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
+import Data.Time.Calendar (CalendarDiffDays (..), DayOfWeek (..))
+import Data.Time.LocalTime (CalendarDiffTime (..))
+import Data.Time.Clock.System (SystemTime (..), systemToUTCTime, utcToSystemTime)
+import Data.Version (Version, parseVersion)
+import Data.Word
+import Foreign.C.Types
+import GHC.Exts (Proxy#, proxy#)
+import GHC.Generics
+import GHC.Natural
+import GHC.Integer.GMP.Internals
+import System.Exit
+import Text.ParserCombinators.ReadP (readP_to_S)
+import qualified Z.Data.Array as A
+import qualified Z.Data.Builder as B
+import Z.Data.Generics.Utils
+import Z.Data.JSON.Converter
+import qualified Z.Data.MessagePack.Builder as MB
+import Z.Data.MessagePack.Value (Value (..))
+import qualified Z.Data.MessagePack.Value as MV
+import qualified Z.Data.Parser as P
+import qualified Z.Data.Parser.Numeric as P
+import qualified Z.Data.Text.Base as T
+import qualified Z.Data.Text as T
+import qualified Z.Data.Text.Print as T
+import qualified Z.Data.Vector.Base as V
+import qualified Z.Data.Vector.Extra as V
+import qualified Z.Data.Vector.FlatIntMap as FIM
+import qualified Z.Data.Vector.FlatIntSet as FIS
+import qualified Z.Data.Vector.FlatMap as FM
+import qualified Z.Data.Vector.FlatSet as FS
+
+--------------------------------------------------------------------------------
+
+-- | Type class for encode & decode MessagePack.
+class MessagePack a where
+ fromValue :: Value -> Converter a
+ default fromValue :: (Generic a, GFromValue (Rep a)) => Value -> Converter a
+ fromValue v = to <$> gFromValue defaultSettings v
+ {-# INLINABLE fromValue #-}
+
+ toValue :: a -> Value
+ default toValue :: (Generic a, GToValue (Rep a)) => a -> Value
+ toValue = gToValue defaultSettings . from
+ {-# INLINABLE toValue #-}
+
+ encodeMessagePack :: a -> B.Builder ()
+ default encodeMessagePack :: (Generic a, GEncodeMessagePack (Rep a)) => a -> B.Builder ()
+ encodeMessagePack = gEncodeMessagePack defaultSettings . from
+ {-# INLINABLE encodeMessagePack #-}
+
+--------------------------------------------------------------------------------
+
+-- There're two possible failures here:
+--
+-- * 'P.ParseError' is an error during parsing bytes to 'Value'.
+-- * 'ConvertError' is an error when converting 'Value' to target data type.
+type DecodeError = Either P.ParseError ConvertError
+
+-- | Decode a MessagePack doc, trailing bytes are not allowed.
+decode' :: MessagePack a => V.Bytes -> Either DecodeError a
+{-# INLINE decode' #-}
+decode' bs = case P.parse' (MV.value <* P.endOfInput) bs of
+ Left pErr -> Left (Left pErr)
+ Right v -> case convertValue v of
+ Left cErr -> Left (Right cErr)
+ Right r -> Right r
+
+-- | Decode a MessagePack bytes, return any trailing bytes.
+decode :: MessagePack a => V.Bytes -> (V.Bytes, Either DecodeError a)
+{-# INLINE decode #-}
+decode bs = case P.parse MV.value bs of
+ (bs', Left pErr) -> (bs', Left (Left pErr))
+ (bs', Right v) -> case convertValue v of
+ Left cErr -> (bs', Left (Right cErr))
+ Right r -> (bs', Right r)
+
+-- | Decode MessagePack doc chunks, return trailing bytes.
+decodeChunks :: (MessagePack a, Monad m) => m V.Bytes -> V.Bytes -> m (V.Bytes, Either DecodeError a)
+{-# INLINE decodeChunks #-}
+decodeChunks mb bs = do
+ mr <- P.parseChunks MV.value mb bs
+ case mr of
+ (bs', Left pErr) -> pure (bs', Left (Left pErr))
+ (bs', Right v) ->
+ case convertValue v of
+ Left cErr -> pure (bs', Left (Right cErr))
+ Right r -> pure (bs', Right r)
+
+-- | Decode MessagePack doc chunks, trailing bytes are not allowed.
+decodeChunks' :: (MessagePack a, Monad m) => m V.Bytes -> V.Bytes -> m (Either DecodeError a)
+{-# INLINE decodeChunks' #-}
+decodeChunks' mb bs = do
+ mr <- P.parseChunks (MV.value <* P.endOfInput) mb bs
+ case mr of
+ (_, Left pErr) -> pure (Left (Left pErr))
+ (_, Right v) ->
+ case convertValue v of
+ Left cErr -> pure (Left (Right cErr))
+ Right r -> pure (Right r)
+
+-- | Directly encode data to MessagePack bytes.
+encode :: MessagePack a => a -> V.Bytes
+{-# INLINE encode #-}
+encode = B.build . encodeMessagePack
+
+-- | Encode data to MessagePack bytes chunks.
+encodeChunks :: MessagePack a => a -> [V.Bytes]
+{-# INLINE encodeChunks #-}
+encodeChunks = B.buildChunks . encodeMessagePack
+
+-- | Run a 'Converter' with input value.
+convertValue :: (MessagePack a) => Value -> Either ConvertError a
+{-# INLINE convertValue #-}
+convertValue = convert fromValue
+
+--------------------------------------------------------------------------------
+
+-- | Produce an error message like @converting XXX failed, expected XXX, encountered XXX@.
+typeMismatch :: T.Text -- ^ The name of the type you are trying to convert.
+ -> T.Text -- ^ The MessagePack value type you expecting to meet.
+ -> Value -- ^ The actual value encountered.
+ -> Converter a
+{-# INLINE typeMismatch #-}
+typeMismatch name expected v =
+ fail' $ T.concat ["converting ", name, " failed, expected ", expected, ", encountered ", actual]
+ where
+ actual = case v of
+ Nil -> "Nil"
+ Bool _ -> "Bool"
+ Int _ -> "Int"
+ Float _ -> "Float"
+ Double _ -> "Double"
+ Str _ -> "Str"
+ Bin _ -> "Bin"
+ Array _ -> "Array"
+ Map _ -> "Map"
+ Ext _ _ -> "Ext"
+
+fromNil :: T.Text -> a -> Value -> Converter a
+{-# INLINE fromNil #-}
+fromNil _ a Nil = pure a
+fromNil c _ v = typeMismatch c "Nil" v
+
+withBool :: T.Text -> (Bool -> Converter a) -> Value -> Converter a
+{-# INLINE withBool #-}
+withBool _ f (Bool x) = f x
+withBool name _ v = typeMismatch name "Bool" v
+
+withStr :: T.Text -> (T.Text -> Converter a) -> Value -> Converter a
+{-# INLINE withStr #-}
+withStr _ f (Str x) = f x
+withStr name _ v = typeMismatch name "Str" v
+
+withBin :: T.Text -> (V.Bytes -> Converter a) -> Value -> Converter a
+{-# INLINE withBin #-}
+withBin _ f (Bin x) = f x
+withBin name _ v = typeMismatch name "Bin" v
+
+-- | @'withBoundedScientific' name f value@ applies @f@ to the 'Scientific' number
+-- when @value@ is a 'Ext' @0x00\/0x01@ with exponent less than or equal to 1024.
+withBoundedScientific :: T.Text -> (Scientific -> Converter a) -> Value -> Converter a
+{-# INLINE withBoundedScientific #-}
+withBoundedScientific name f v = withScientific name f' v
+ where
+ f' x | e <= 1024 = f x
+ | otherwise = fail' . B.unsafeBuildText $ do
+ "converting "
+ T.text name
+ " failed, found a number with exponent "
+ T.int e
+ ", but it must not be greater than 1024"
+ where e = base10Exponent x
+
+-- | @'withScientific' name f value@ applies @f@ to the 'Scientific' number
+-- when @value@ is a 'Ext' @0x00@, fails using 'typeMismatch' otherwise.
+--
+-- /Warning/: If you are converting from a scientific to an unbounded
+-- type such as 'Integer' you may want to add a restriction on the
+-- size of the exponent (see 'withBoundedScientific') to prevent
+-- malicious input from filling up the memory of the target system.
+--
+-- ==== Error message example
+--
+-- > withScientific "MyType" f (Str "oops")
+-- > -- Error: "converting MyType failed, expected Ext 0x00/0x01, but encountered Str"
+withScientific :: T.Text -> (Scientific -> Converter a) -> Value -> Converter a
+{-# INLINE withScientific #-}
+withScientific name f (Ext tag x) | tag <= 0x01 = do
+ case P.parse MV.value x of
+ (rest, Right (Int d)) -> mkSci (fromIntegral d) rest
+ (_, Right v) -> typeMismatch (name <> "(exponent)") "Int" v
+ (_, Left e) -> fail' (T.concat ["converting ", name, " failed: ", T.toText e])
+ where
+ mkSci !e (V.PrimVector (A.PrimArray ba#) (I# s#) (I# l#)) =
+ let !c = importIntegerFromByteArray ba# (int2Word# s#) (int2Word# l#) 1#
+ in if tag == 0x01 then f (negate (Sci.scientific c e))
+ else f (Sci.scientific c e)
+withScientific name _ v = typeMismatch name "Ext 0x00/0x01" v
+
+withSystemTime :: T.Text -> (SystemTime -> Converter a) -> Value -> Converter a
+{-# INLINE withSystemTime #-}
+withSystemTime name f (Ext tag x) | tag == 0xFF = do
+ case P.parse' (do
+ !ns <- P.decodePrimBE @Word32
+ !s <- P.decodePrimBE
+ pure (MkSystemTime s (fromIntegral ns))) x of
+ Left e -> fail' ("parse Ext 0xFF timestamp format failed: " <> T.toText e)
+ Right v -> f v
+withSystemTime name _ v = typeMismatch name "Ext 0x00" v
+
+withArray :: T.Text -> (V.Vector Value -> Converter a) -> Value -> Converter a
+{-# INLINE withArray #-}
+withArray _ f (Array arr) = f arr
+withArray name _ v = typeMismatch name "Arr" v
+
+-- | Directly use 'Map' as key-values for further converting.
+withKeyValues :: T.Text -> (V.Vector (Value, Value) -> Converter a) -> Value -> Converter a
+{-# INLINE withKeyValues #-}
+withKeyValues _ f (Map kvs) = f kvs
+withKeyValues name _ v = typeMismatch name "Map" v
+
+-- | Take a 'Map' as an 'FM.FlatMap Value Value', on key duplication prefer first one.
+withFlatMap :: T.Text -> (FM.FlatMap Value Value -> Converter a) -> Value -> Converter a
+{-# INLINE withFlatMap #-}
+withFlatMap _ f (Map obj) = f (FM.packVector obj)
+withFlatMap name _ v = typeMismatch name "Map" v
+
+-- | Take a 'Map' as an 'FM.FlatMap Value Value', on key duplication prefer last one.
+withFlatMapR :: T.Text -> (FM.FlatMap Value Value -> Converter a) -> Value -> Converter a
+{-# INLINE withFlatMapR #-}
+withFlatMapR _ f (Map obj) = f (FM.packVectorR obj)
+withFlatMapR name _ v = typeMismatch name "Map" v
+
+-- | Retrieve the value associated with the given key of an 'Map'.
+-- The result is 'empty' if the key is not present or the value cannot
+-- be converted to the desired type.
+--
+-- This accessor is appropriate if the key and value /must/ be present
+-- in an object for it to be valid. If the key and value are
+-- optional, use '.:?' instead.
+(.:) :: (MessagePack a) => FM.FlatMap Value Value -> T.Text -> Converter a
+{-# INLINE (.:) #-}
+(.:) = convertField fromValue
+
+-- | Retrieve the value associated with the given key of an 'Map'. The
+-- result is 'Nothing' if the key is not present or if its value is 'Nil',
+-- or fail if the value cannot be converted to the desired type.
+--
+-- This accessor is most useful if the key and value can be absent
+-- from an object without affecting its validity. If the key and
+-- value are mandatory, use '.:' instead.
+(.:?) :: (MessagePack a) => FM.FlatMap Value Value -> T.Text -> Converter (Maybe a)
+{-# INLINE (.:?) #-}
+(.:?) = convertFieldMaybe fromValue
+
+-- | Retrieve the value associated with the given key of an 'Map'.
+-- The result is 'Nothing' if the key is not present or fail if the
+-- value cannot be converted to the desired type.
+--
+-- This differs from '.:?' by attempting to convert 'Nil' the same as any
+-- other MessagePack value, instead of interpreting it as 'Nothing'.
+(.:!) :: (MessagePack a) => FM.FlatMap Value Value -> T.Text -> Converter (Maybe a)
+{-# INLINE (.:!) #-}
+(.:!) = convertFieldMaybe' fromValue
+
+convertField :: (Value -> Converter a) -- ^ the field converter (value part of a key value pair)
+ -> FM.FlatMap Value Value -> T.Text -> Converter a
+{-# INLINE convertField #-}
+convertField p obj key = case FM.lookup (Str key) obj of
+ Just v -> p v <?> Key key
+ _ -> fail' (T.concat $ ["key ", key, " not present"])
+
+-- | Variant of '.:?' with explicit converter function.
+convertFieldMaybe :: (Value -> Converter a) -> FM.FlatMap Value Value -> T.Text -> Converter (Maybe a)
+{-# INLINE convertFieldMaybe #-}
+convertFieldMaybe p obj key = case FM.lookup (Str key) obj of
+ Just Nil -> pure Nothing
+ Just v -> Just <$> p v <?> Key key
+ _ -> pure Nothing
+
+-- | Variant of '.:!' with explicit converter function.
+convertFieldMaybe' :: (Value -> Converter a) -> FM.FlatMap Value Value -> T.Text -> Converter (Maybe a)
+{-# INLINE convertFieldMaybe' #-}
+convertFieldMaybe' p obj key = case FM.lookup (Str key) obj of
+ Just v -> Just <$> p v <?> Key key
+ _ -> pure Nothing
+
+--------------------------------------------------------------------------------
+
+-- | A newtype for 'B.Builder', whose semigroup's instance is to connect kv builder and sum kv length.
+data KVItem = KVItem {-# UNPACK #-} !Int (B.Builder ())
+
+instance Semigroup KVItem where
+ {-# INLINE (<>) #-}
+ KVItem siza a <> KVItem sizb b = KVItem (siza+sizb) (a >> b)
+
+-- | Connect key and value to a 'KVItem' using 'B.colon', key will be escaped.
+(.!) :: MessagePack v => T.Text -> v -> KVItem
+{-# INLINE (.!) #-}
+k .! v = KVItem 1 (MB.str k >> encodeMessagePack v)
+infixr 8 .!
+
+-- | Write map header and 'KVItem's.
+object' :: KVItem -> B.Builder ()
+{-# INLINE object' #-}
+object' (KVItem siz kvb) = MB.mapHeader siz >> kvb
+
+-- | Connect key and value to a tuple to be used with 'object'.
+(.=) :: MessagePack v => T.Text -> v -> (Value, Value)
+{-# INLINE (.=) #-}
+k .= v = (Str k, toValue v)
+infixr 8 .=
+
+-- | Alias for @Map . pack@.
+object :: [(Value, Value)] -> Value
+{-# INLINE object #-}
+object = Map . V.pack
+
+--------------------------------------------------------------------------------
+-- | Generic encode/decode Settings
+--
+data Settings = Settings
+ { fieldFmt :: String -> T.Text -- ^ format field labels
+ , constrFmt :: String -> T.Text -- ^ format constructor names
+ , missingKeyAsNil :: Bool -- ^ take missing field as 'Nil'?
+ }
+
+-- | @Settings T.pack T.pack False@
+defaultSettings :: Settings
+defaultSettings = Settings T.pack T.pack False
+
+--------------------------------------------------------------------------------
+-- GToValue
+--------------------------------------------------------------------------------
+
+class GToValue f where
+ gToValue :: Settings -> f a -> Value
+
+--------------------------------------------------------------------------------
+-- Selectors
+
+type family Field f where
+ Field (a :*: b) = Field a
+ Field (S1 (MetaSel Nothing u ss ds) f) = Value
+ Field (S1 (MetaSel (Just l) u ss ds) f) = (Value, Value)
+
+class GWriteFields f where
+ gWriteFields :: Settings -> A.SmallMutableArray s (Field f) -> Int -> f a -> ST s ()
+
+instance (ProductSize a, GWriteFields a, GWriteFields b, Field a ~ Field b) => GWriteFields (a :*: b) where
+ {-# INLINE gWriteFields #-}
+ gWriteFields s marr idx (a :*: b) = do
+ gWriteFields s marr idx a
+ gWriteFields s marr (idx + productSize (proxy# :: Proxy# a)) b
+
+instance (GToValue f) => GWriteFields (S1 (MetaSel Nothing u ss ds) f) where
+ {-# INLINE gWriteFields #-}
+ gWriteFields s marr idx (M1 x) = A.writeSmallArray marr idx (gToValue s x)
+
+instance (GToValue f, Selector (MetaSel (Just l) u ss ds)) => GWriteFields (S1 (MetaSel (Just l) u ss ds) f) where
+ {-# INLINE gWriteFields #-}
+ gWriteFields s marr idx m1@(M1 x) = A.writeSmallArray marr idx ((Str $ (fieldFmt s) (selName m1)), gToValue s x)
+
+instance (GToValue f, Selector (MetaSel (Just l) u ss ds)) => GToValue (S1 (MetaSel (Just l) u ss ds) f) where
+ {-# INLINE gToValue #-}
+ gToValue s m1@(M1 x) =
+ let k = fieldFmt s $ selName m1
+ v = gToValue s x
+ in Map (V.singleton (Str k, v))
+
+instance GToValue f => GToValue (S1 (MetaSel Nothing u ss ds) f) where
+ {-# INLINE gToValue #-}
+ gToValue s (M1 x) = gToValue s x
+
+instance MessagePack a => GToValue (K1 i a) where
+ {-# INLINE gToValue #-}
+ gToValue _ (K1 x) = toValue x
+
+class GMergeFields f where
+ gMergeFields :: Proxy# f -> A.SmallMutableArray s (Field f) -> ST s Value
+
+instance GMergeFields a => GMergeFields (a :*: b) where
+ {-# INLINE gMergeFields #-}
+ gMergeFields _ = gMergeFields (proxy# :: Proxy# a)
+
+instance GMergeFields (S1 (MetaSel Nothing u ss ds) f) where
+ {-# INLINE gMergeFields #-}
+ gMergeFields _ marr = do
+ arr <- A.unsafeFreezeSmallArray marr
+ let l = A.sizeofSmallArray arr
+ pure (Array (V.Vector arr 0 l))
+
+instance GMergeFields (S1 (MetaSel (Just l) u ss ds) f) where
+ {-# INLINE gMergeFields #-}
+ gMergeFields _ marr = do
+ arr <- A.unsafeFreezeSmallArray marr
+ let l = A.sizeofSmallArray arr
+ pure (Map (V.Vector arr 0 l))
+
+--------------------------------------------------------------------------------
+-- Constructors
+
+class GConstrToValue f where
+ gConstrToValue :: Bool -> Settings -> f a -> Value
+
+instance GConstrToValue V1 where
+ {-# INLINE gConstrToValue #-}
+ gConstrToValue _ _ _ = error "Z.Data.MessagePack.Base: empty data type"
+
+instance (GConstrToValue f, GConstrToValue g) => GConstrToValue (f :+: g) where
+ {-# INLINE gConstrToValue #-}
+ gConstrToValue _ s (L1 x) = gConstrToValue True s x
+ gConstrToValue _ s (R1 x) = gConstrToValue True s x
+
+-- | Constructor without payload, convert to String
+instance (Constructor c) => GConstrToValue (C1 c U1) where
+ {-# INLINE gConstrToValue #-}
+ gConstrToValue _ s (M1 _) = Str . constrFmt s $ conName (undefined :: t c U1 a)
+
+-- | Constructor with a single payload
+instance (Constructor c, GToValue (S1 sc f)) => GConstrToValue (C1 c (S1 sc f)) where
+ {-# INLINE gConstrToValue #-}
+ gConstrToValue False s (M1 x) = gToValue s x
+ gConstrToValue True s (M1 x) =
+ let !k = constrFmt s $ conName @c undefined
+ !v = gToValue s x
+ in Map (V.singleton (Str k, v))
+
+-- | Constructor with multiple payloads
+instance (ProductSize (a :*: b), GWriteFields (a :*: b), GMergeFields (a :*: b), Constructor c)
+ => GConstrToValue (C1 c (a :*: b)) where
+ {-# INLINE gConstrToValue #-}
+ gConstrToValue False s (M1 x) = runST (do
+ marr <- A.newSmallArray (productSize (proxy# :: Proxy# (a :*: b))) undefined
+ gWriteFields s marr 0 x
+ gMergeFields (proxy# :: Proxy# (a :*: b)) marr)
+ gConstrToValue True s (M1 x) =
+ let !k = constrFmt s $ conName @c undefined
+ !v = runST (do
+ marr <- A.newSmallArray (productSize (proxy# :: Proxy# (a :*: b))) undefined
+ gWriteFields s marr 0 x
+ gMergeFields (proxy# :: Proxy# (a :*: b)) marr)
+ in Map (V.singleton (Str k, v))
+
+--------------------------------------------------------------------------------
+-- Data types
+instance GConstrToValue f => GToValue (D1 c f) where
+ {-# INLINE gToValue #-}
+ gToValue s (M1 x) = gConstrToValue False s x
+
+--------------------------------------------------------------------------------
+-- MessagePack
+--------------------------------------------------------------------------------
+
+class GEncodeMessagePack f where
+ gEncodeMessagePack :: Settings -> f a -> B.Builder ()
+
+--------------------------------------------------------------------------------
+-- Selectors
+
+instance (GEncodeMessagePack f, Selector (MetaSel (Just l) u ss ds)) => GEncodeMessagePack (S1 (MetaSel (Just l) u ss ds) f) where
+ {-# INLINE gEncodeMessagePack #-}
+ gEncodeMessagePack s m1@(M1 x) = (MB.str . fieldFmt s $ selName m1) >> gEncodeMessagePack s x
+
+instance GEncodeMessagePack f => GEncodeMessagePack (S1 (MetaSel Nothing u ss ds) f) where
+ {-# INLINE gEncodeMessagePack #-}
+ gEncodeMessagePack s (M1 x) = gEncodeMessagePack s x
+
+instance (GEncodeMessagePack a, GEncodeMessagePack b) => GEncodeMessagePack (a :*: b) where
+ {-# INLINE gEncodeMessagePack #-}
+ gEncodeMessagePack s (a :*: b) = gEncodeMessagePack s a >> gEncodeMessagePack s b
+
+instance MessagePack a => GEncodeMessagePack (K1 i a) where
+ {-# INLINE gEncodeMessagePack #-}
+ gEncodeMessagePack _ (K1 x) = encodeMessagePack x
+
+class GAddProductSize (f :: * -> *) where
+ gAddProductSize :: Proxy# f -> Int -> B.Builder ()
+
+instance GAddProductSize a => GAddProductSize (a :*: b) where
+ {-# INLINE gAddProductSize #-}
+ gAddProductSize _ = gAddProductSize (proxy# :: Proxy# a)
+
+instance GAddProductSize (S1 (MetaSel Nothing u ss ds) f) where
+ {-# INLINE gAddProductSize #-}
+ gAddProductSize _ = MB.arrayHeader
+
+instance GAddProductSize (S1 (MetaSel (Just l) u ss ds) f) where
+ {-# INLINE gAddProductSize #-}
+ gAddProductSize _ = MB.mapHeader
+
+--------------------------------------------------------------------------------
+-- Constructors
+
+class GConstrEncodeMessagePack f where
+ gConstrEncodeMessagePack :: Bool -> Settings -> f a -> B.Builder ()
+
+instance GConstrEncodeMessagePack V1 where
+ {-# INLINE gConstrEncodeMessagePack #-}
+ gConstrEncodeMessagePack _ _ _ = error "Z.Data.MessagePack.Base: empty data type"
+
+instance (GConstrEncodeMessagePack f, GConstrEncodeMessagePack g) => GConstrEncodeMessagePack (f :+: g) where
+ {-# INLINE gConstrEncodeMessagePack #-}
+ gConstrEncodeMessagePack _ s (L1 x) = gConstrEncodeMessagePack True s x
+ gConstrEncodeMessagePack _ s (R1 x) = gConstrEncodeMessagePack True s x
+
+-- | Constructor without payload, convert to String
+instance (Constructor c) => GConstrEncodeMessagePack (C1 c U1) where
+ {-# INLINE gConstrEncodeMessagePack #-}
+ -- There should be no chars need escaping in constructor name
+ gConstrEncodeMessagePack _ s (M1 _) = MB.str . constrFmt s $ conName (undefined :: t c U1 a)
+
+-- | Constructor with a single payload
+instance (Constructor c, GEncodeMessagePack (S1 (MetaSel Nothing u ss ds) f))
+ => GConstrEncodeMessagePack (C1 c (S1 (MetaSel Nothing u ss ds) f)) where
+ {-# INLINE gConstrEncodeMessagePack #-}
+ gConstrEncodeMessagePack False s (M1 x) = do
+ gEncodeMessagePack s x
+ gConstrEncodeMessagePack True s (M1 x) = do
+ MB.mapHeader 1
+ MB.str (constrFmt s $ conName @c undefined)
+ gEncodeMessagePack s x
+
+instance (Constructor c, GEncodeMessagePack (S1 (MetaSel (Just l) u ss ds) f))
+ => GConstrEncodeMessagePack (C1 c (S1 (MetaSel (Just l) u ss ds) f)) where
+ {-# INLINE gConstrEncodeMessagePack #-}
+ gConstrEncodeMessagePack False s (M1 x) = do
+ MB.mapHeader 1
+ gEncodeMessagePack s x
+ gConstrEncodeMessagePack True s (M1 x) = do
+ MB.mapHeader 1
+ MB.str (constrFmt s $ conName @c undefined)
+ MB.mapHeader 1
+ gEncodeMessagePack s x
+
+-- | Constructor with multiple payloads
+instance (GEncodeMessagePack (a :*: b), GAddProductSize (a :*: b), ProductSize (a :*: b), Constructor c)
+ => GConstrEncodeMessagePack (C1 c (a :*: b)) where
+ {-# INLINE gConstrEncodeMessagePack #-}
+ gConstrEncodeMessagePack False s (M1 x) = do
+ gAddProductSize (proxy# :: Proxy# (a :*: b)) (productSize (proxy# :: Proxy# (a :*: b)))
+ gEncodeMessagePack s x
+ gConstrEncodeMessagePack True s (M1 x) = do
+ MB.mapHeader 1
+ MB.str (constrFmt s $ conName @c @_ @_ @_ undefined)
+ gAddProductSize (proxy# :: Proxy# (a :*: b)) (productSize (proxy# :: Proxy# (a :*: b)))
+ gEncodeMessagePack s x
+
+--------------------------------------------------------------------------------
+-- Data types
+instance GConstrEncodeMessagePack f => GEncodeMessagePack (D1 c f) where
+ {-# INLINE gEncodeMessagePack #-}
+ gEncodeMessagePack s (M1 x) = gConstrEncodeMessagePack False s x
+
+--------------------------------------------------------------------------------
+-- GFromValue
+--------------------------------------------------------------------------------
+
+class GFromValue f where
+ gFromValue :: Settings -> Value -> Converter (f a)
+
+--------------------------------------------------------------------------------
+-- Selectors
+
+type family LookupTable f where
+ LookupTable (a :*: b) = LookupTable a
+ LookupTable (S1 (MetaSel Nothing u ss ds) f) = V.Vector Value
+ LookupTable (S1 (MetaSel (Just l) u ss ds) f) = FM.FlatMap Value Value
+
+class GFromFields f where
+ gFromFields :: Settings -> LookupTable f -> Int -> Converter (f a)
+
+instance (ProductSize a, GFromFields a, GFromFields b, LookupTable a ~ LookupTable b)
+ => GFromFields (a :*: b) where
+ {-# INLINE gFromFields #-}
+ gFromFields s v idx = do
+ a <- gFromFields s v idx
+ b <- gFromFields s v (idx + productSize (proxy# :: Proxy# a))
+ pure (a :*: b)
+
+instance (GFromValue f) => GFromFields (S1 (MetaSel Nothing u ss ds) f) where
+ {-# INLINE gFromFields #-}
+ gFromFields s v idx = do
+ v' <- V.unsafeIndexM v idx
+ M1 <$> gFromValue s v' <?> Index idx
+
+instance (GFromValue f, Selector (MetaSel (Just l) u ss ds)) => GFromFields (S1 (MetaSel (Just l) u ss ds) f) where
+ {-# INLINE gFromFields #-}
+ gFromFields s v _ = do
+ case FM.lookup (Str fn) v of
+ Just v' -> M1 <$> gFromValue s v' <?> Key fn
+ _ | missingKeyAsNil s -> M1 <$> gFromValue s Nil <?> Key fn
+ | otherwise -> fail' ("Z.Data.MessagePack.Base: missing field " <> fn)
+ where
+ fn = (fieldFmt s) (selName (undefined :: S1 (MetaSel (Just l) u ss ds) f a))
+
+instance GFromValue f => GFromValue (S1 (MetaSel Nothing u ss ds) f) where
+ {-# INLINE gFromValue #-}
+ gFromValue s x = M1 <$> gFromValue s x
+
+instance (GFromValue f, Selector (MetaSel (Just l) u ss ds)) => GFromValue (S1 (MetaSel (Just l) u ss ds) f) where
+ {-# INLINE gFromValue #-}
+ gFromValue s (Map v) = do
+ case FM.lookup (Str fn) (FM.packVectorR v) of
+ Just v' -> M1 <$> gFromValue s v' <?> Key fn
+ _ | missingKeyAsNil s -> M1 <$> gFromValue s Nil <?> Key fn
+ | otherwise -> fail' ("Z.Data.MessagePack.Base: missing field " <> fn)
+ where fn = (fieldFmt s) (selName (undefined :: S1 (MetaSel (Just l) u ss ds) f a))
+ gFromValue s v = typeMismatch ("field " <> fn) "Map" v <?> Key fn
+ where fn = (fieldFmt s) (selName (undefined :: S1 (MetaSel (Just l) u ss ds) f a))
+
+instance MessagePack a => GFromValue (K1 i a) where
+ {-# INLINE gFromValue #-}
+ gFromValue _ x = K1 <$> fromValue x
+
+class GBuildLookup f where
+ gBuildLookup :: Proxy# f -> Int -> T.Text -> Value -> Converter (LookupTable f)
+
+instance (GBuildLookup a, GBuildLookup b) => GBuildLookup (a :*: b) where
+ {-# INLINE gBuildLookup #-}
+ gBuildLookup _ siz = gBuildLookup (proxy# :: Proxy# a) siz
+
+instance GBuildLookup (S1 (MetaSel Nothing u ss ds) f) where
+ {-# INLINE gBuildLookup #-}
+ gBuildLookup _ siz name (Array v)
+ -- we have to check size here to use 'unsafeIndexM' later
+ | siz' /= siz = fail' . B.unsafeBuildText $ do
+ "converting "
+ T.text name
+ " failed, product size mismatch, expected "
+ T.int siz
+ ", get"
+ T.int siz'
+ | otherwise = pure v
+ where siz' = V.length v
+ gBuildLookup _ _ name x = typeMismatch name "Array" x
+
+instance GBuildLookup (S1 ((MetaSel (Just l) u ss ds)) f) where
+ {-# INLINE gBuildLookup #-}
+ -- we don't check size, so that duplicated keys are preserved
+ gBuildLookup _ _ _ (Map v) = pure $! FM.packVectorR v
+ gBuildLookup _ _ name x = typeMismatch name "Map" x
+
+--------------------------------------------------------------------------------
+-- Constructors
+
+class GConstrFromValue f where
+ gConstrFromValue :: Bool -- ^ Is this a sum type(more than one constructor)?
+ -> Settings -> Value -> Converter (f a)
+
+instance GConstrFromValue V1 where
+ {-# INLINE gConstrFromValue #-}
+ gConstrFromValue _ _ _ = error "Z.Data.MessagePack.Base: empty data type"
+
+instance (GConstrFromValue f, GConstrFromValue g) => GConstrFromValue (f :+: g) where
+ {-# INLINE gConstrFromValue #-}
+ gConstrFromValue _ s x = (L1 <$> gConstrFromValue True s x) <|> (R1 <$> gConstrFromValue True s x)
+
+-- | Constructor without payload, convert to String
+instance (Constructor c) => GConstrFromValue (C1 c U1) where
+ {-# INLINE gConstrFromValue #-}
+ gConstrFromValue _ s (Str x)
+ | cn == x = pure (M1 U1)
+ | otherwise = fail' . T.concat $ ["converting ", cn', "failed, unknown constructor name ", x]
+ where cn = constrFmt s $ conName (undefined :: t c U1 a)
+ cn' = T.pack $ conName (undefined :: t c U1 a)
+ gConstrFromValue _ _ v = typeMismatch cn' "String" v
+ where cn' = T.pack $ conName (undefined :: t c U1 a)
+
+-- | Constructor with a single payload
+instance (Constructor c, GFromValue (S1 sc f)) => GConstrFromValue (C1 c (S1 sc f)) where
+ {-# INLINE gConstrFromValue #-}
+ -- | Single constructor
+ gConstrFromValue False s x = M1 <$> gFromValue s x
+ gConstrFromValue True s x = case x of
+ Map v -> case V.indexM v 0 of
+ Just (Str k, v')
+ | k == cn -> M1 <$> gFromValue s v' <?> Key cn
+ _ -> fail' .T.concat $ ["converting ", cn', " failed, constructor not found"]
+ _ -> typeMismatch cn' "Map" x
+ where cn = constrFmt s $ conName @c undefined
+ cn' = T.pack $ conName @c undefined
+
+-- | Constructor with multiple payloads
+instance (ProductSize (a :*: b), GFromFields (a :*: b), GBuildLookup (a :*: b), Constructor c)
+ => GConstrFromValue (C1 c (a :*: b)) where
+ {-# INLINE gConstrFromValue #-}
+ gConstrFromValue False s x = do
+ t <- gBuildLookup p (productSize p) cn' x
+ M1 <$> gFromFields s t 0
+ where cn' = T.pack $ conName @c undefined
+ p = proxy# :: Proxy# (a :*: b)
+ gConstrFromValue True s x = case x of
+ Map v -> case V.indexM v 0 of
+ Just (Str k, v')
+ | k == cn -> do t <- gBuildLookup p (productSize p) cn' v'
+ M1 <$> gFromFields s t 0
+ _ -> fail' .T.concat $ ["converting ", cn', " failed, constructor not found"]
+ _ -> typeMismatch cn' "Map" x
+ where cn = constrFmt s $ conName @c undefined
+ cn' = T.pack $ conName @c undefined
+ p = proxy# :: Proxy# (a :*: b)
+
+--------------------------------------------------------------------------------
+-- Data types
+instance GConstrFromValue f => GFromValue (D1 c f) where
+ {-# INLINE gFromValue #-}
+ gFromValue s x = M1 <$> gConstrFromValue False s x
+
+--------------------------------------------------------------------------------
+-- Built-in Instances
+--------------------------------------------------------------------------------
+-- | Use 'Nil' as @Proxy a@
+instance MessagePack (Proxy a) where
+ {-# INLINE fromValue #-}; fromValue = fromNil "Proxy" Proxy;
+ {-# INLINE toValue #-}; toValue _ = Nil;
+ {-# INLINE encodeMessagePack #-}; encodeMessagePack _ = MB.nil;
+
+instance MessagePack Value where
+ {-# INLINE fromValue #-}; fromValue = pure;
+ {-# INLINE toValue #-}; toValue = id;
+ {-# INLINE encodeMessagePack #-}; encodeMessagePack = MB.value;
+
+instance MessagePack T.Text where
+ {-# INLINE fromValue #-}; fromValue = withStr "Text" pure;
+ {-# INLINE toValue #-}; toValue = Str;
+ {-# INLINE encodeMessagePack #-}; encodeMessagePack = MB.str;
+
+-- | Note this instance doesn't reject large input
+instance MessagePack Scientific where
+ {-# INLINE fromValue #-}
+ fromValue = withScientific "Data.Scientific.Scientific" pure
+ {-# INLINE toValue #-}
+ toValue x = MB.scientificValue (coefficient x) (fromIntegral $ base10Exponent x)
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack x = MB.scientific (coefficient x) (fromIntegral $ base10Exponent x)
+
+-- | default instance prefer later key
+instance (Ord a, MessagePack a, MessagePack b) => MessagePack (FM.FlatMap a b) where
+ {-# INLINE fromValue #-}
+ fromValue = withFlatMapR "Z.Data.Vector.FlatMap.FlatMap" $ \ m ->
+ let kvs = V.unpack (FM.sortedKeyValues m)
+ in FM.packR <$> (forM kvs $ \ (k, v) -> do
+ k' <- fromValue k
+ v' <- fromValue v <?> Key (T.toText k)
+ return (k', v'))
+ {-# INLINE toValue #-}
+ toValue = Map . V.map (\ (k, v) -> (toValue k, toValue v)) . FM.sortedKeyValues
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.map encodeMessagePack encodeMessagePack . FM.sortedKeyValues
+
+instance (Ord a, MessagePack a) => MessagePack (FS.FlatSet a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Vector.FlatSet.FlatSet" $ \ vs ->
+ FS.packRN (V.length vs) <$>
+ (zipWithM (\ k v -> fromValue v <?> Index k) [0..] (V.unpack vs))
+ {-# INLINE toValue #-}
+ toValue = Array . V.map' toValue . FS.sortedValues
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.array encodeMessagePack . FS.sortedValues
+
+-- | default instance prefer later key
+instance (Eq a, Hashable a, MessagePack a, MessagePack b) => MessagePack (HM.HashMap a b) where
+ {-# INLINE fromValue #-}
+ fromValue = withKeyValues "Data.HashMap.HashMap" $ \ kvs ->
+ HM.fromList <$> (forM (V.unpack kvs) $ \ (k, v) -> do
+ !k' <- fromValue k
+ !v' <- fromValue v <?> Key (T.toText k)
+ return (k', v'))
+ {-# INLINE toValue #-}
+ toValue = Map . V.pack . map (\ (k,v) -> (toValue k, toValue v)) . HM.toList
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.map' encodeMessagePack encodeMessagePack . HM.toList
+
+instance (Ord a, MessagePack a, MessagePack b) => MessagePack (M.Map a b) where
+ {-# INLINE fromValue #-}
+ fromValue = withKeyValues "Data.HashMap.HashMap" $ \ kvs ->
+ M.fromList <$> (forM (V.unpack kvs) $ \ (k, v) -> do
+ !k' <- fromValue k
+ !v' <- fromValue v <?> Key (T.toText k)
+ return (k', v'))
+ {-# INLINE toValue #-}
+ toValue = Map . V.pack . map (\ (k,v) -> (toValue k, toValue v)) . M.toList
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.map' encodeMessagePack encodeMessagePack . M.toList
+
+instance MessagePack a => MessagePack (FIM.FlatIntMap a) where
+ {-# INLINE fromValue #-}
+ fromValue = withFlatMapR "Z.Data.Vector.FlatIntMap.FlatIntMap" $ \ m ->
+ let kvs = FM.sortedKeyValues m
+ in FIM.packVectorR <$> (forM kvs $ \ (k, v) -> do
+ case k of
+ Int k' -> do
+ v' <- fromValue v <?> Key (T.toText k)
+ return (V.IPair (fromIntegral k') v')
+ _ -> fail' ("converting Z.Data.Vector.FlatIntMap.FlatIntMap failed, unexpected key " <> (T.toText k)))
+ {-# INLINE toValue #-}
+ toValue = Map . V.map' toKV . FIM.sortedKeyValues
+ where toKV (V.IPair i x) = let !k = Int (fromIntegral i)
+ !v = toValue x
+ in (k, v)
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack m = do
+ let kvs = FIM.sortedKeyValues m
+ MB.mapHeader (V.length kvs)
+ V.traverseVec_ (\ (V.IPair k v) -> MB.int (fromIntegral k) >> encodeMessagePack v) kvs
+
+instance MessagePack a => MessagePack (IM.IntMap a) where
+ {-# INLINE fromValue #-}
+ fromValue = withKeyValues "Data.IntMap.IntMap" $ \ kvs ->
+ IM.fromList <$> (forM (V.unpack kvs) $ \ (k, v) -> do
+ case k of
+ Int k' -> do
+ v' <- fromValue v <?> Key (T.toText k)
+ return (fromIntegral k', v')
+ _ -> fail' ("converting Data.IntMap.IntMap failed, unexpected key " <> (T.toText k)))
+ {-# INLINE toValue #-}
+ toValue = Map . V.pack . map toKV . IM.toList
+ where toKV (i, x) = let !k = Int (fromIntegral i)
+ !v = toValue x
+ in (k, v)
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack m = do
+ MB.mapHeader (IM.size m)
+ mapM_ (\ (k, v) -> MB.int (fromIntegral k) >> encodeMessagePack v) (IM.toList m)
+
+instance MessagePack FIS.FlatIntSet where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Vector.FlatIntSet.FlatIntSet" $ \ vs ->
+ FIS.packRN (V.length vs) <$> zipWithM (\ k v -> fromValue v <?> Index k) [0..] (V.unpack vs)
+ {-# INLINE toValue #-}
+ toValue = toValue . FIS.sortedValues
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack . FIS.sortedValues
+
+instance MessagePack IS.IntSet where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Data.IntSet.IntSet" $ \ vs ->
+ IS.fromList <$> zipWithM (\ k v -> fromValue v <?> Index k) [0..] (V.unpack vs)
+ {-# INLINE toValue #-}
+ toValue = toValue . IS.toList
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack . IS.toList
+
+instance (Ord a, MessagePack a) => MessagePack (Set.Set a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Data.Set.Set" $ \ vs ->
+ Set.fromList <$> zipWithM (\ k v -> fromValue v <?> Index k) [0..] (V.unpack vs)
+ {-# INLINE toValue #-}
+ toValue = toValue . Set.toList
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack . Set.toList
+
+instance MessagePack a => MessagePack (Seq.Seq a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Data.Seq.Seq" $ \ vs ->
+ Seq.fromList <$> zipWithM (\ k v -> fromValue v <?> Index k) [0..] (V.unpack vs)
+ {-# INLINE toValue #-}
+ toValue = toValue . Foldable.toList
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack . Foldable.toList
+
+instance MessagePack a => MessagePack (Tree.Tree a) where
+ {-# INLINE fromValue #-}
+ fromValue = withFlatMapR "Data.Tree" $ \obj -> do
+ !n <- obj .: "rootLabel"
+ !d <- obj .: "subForest"
+ pure (Tree.Node n d)
+ {-# INLINE toValue #-}
+ toValue x = object [ "rootLabel" .= (Tree.rootLabel x) , "subForest" .= (Tree.subForest x) ]
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack x = object' ( "rootLabel" .! (Tree.rootLabel x) <> "subForest" .! (Tree.subForest x) )
+
+instance MessagePack a => MessagePack (A.Array a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Array.Array"
+ (V.traverseWithIndex $ \ k v -> fromValue v <?> Index k)
+ {-# INLINE toValue #-}
+ toValue = Array . V.map toValue
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.array encodeMessagePack
+
+instance MessagePack a => MessagePack (A.SmallArray a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Array.SmallArray"
+ (V.traverseWithIndex $ \ k v -> fromValue v <?> Index k)
+ {-# INLINE toValue #-}
+ toValue = Array . V.map toValue
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.array encodeMessagePack
+
+instance (Prim a, MessagePack a) => MessagePack (A.PrimArray a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Array.PrimArray"
+ (V.traverseWithIndex $ \ k v -> fromValue v <?> Index k)
+ {-# INLINE toValue #-}
+ toValue = Array . V.map toValue
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.array encodeMessagePack
+
+instance (A.PrimUnlifted a, MessagePack a) => MessagePack (A.UnliftedArray a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Array.UnliftedArray"
+ (V.traverseWithIndex $ \ k v -> fromValue v <?> Index k)
+ {-# INLINE toValue #-}
+ toValue = Array . V.map toValue
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.array encodeMessagePack
+
+instance MessagePack A.ByteArray where
+ {-# INLINE fromValue #-}
+ fromValue = withBin "ByteArray" $ \ (V.PrimVector pa@(A.PrimArray ba#) s l) ->
+ if A.sizeofArr pa == l && s == 0
+ then pure (A.ByteArray ba#)
+ else pure $! A.cloneByteArray (A.ByteArray ba#) s l
+ {-# INLINE toValue #-}
+ toValue (A.ByteArray ba#) = Bin (V.arrVec (A.PrimArray ba#))
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack (A.ByteArray ba#) = MB.bin (V.arrVec (A.PrimArray ba#))
+
+instance (Prim a, MessagePack a) => MessagePack (V.PrimVector a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Vector.PrimVector"
+ (V.traverseWithIndex $ \ k v -> fromValue v <?> Index k)
+ {-# INLINE toValue #-}
+ toValue = Array . V.map toValue
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.array encodeMessagePack
+
+instance MessagePack a => MessagePack (V.Vector a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Vector.Vector"
+ (V.traverseWithIndex $ \ k v -> fromValue v <?> Index k)
+ {-# INLINE toValue #-}
+ toValue = Array . V.map toValue
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.array encodeMessagePack
+
+instance (Eq a, Hashable a, MessagePack a) => MessagePack (HS.HashSet a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "Z.Data.Vector.FlatSet.FlatSet" $ \ vs ->
+ HS.fromList <$>
+ (zipWithM (\ k v -> fromValue v <?> Index k) [0..] (V.unpack vs))
+ {-# INLINE toValue #-}
+ toValue = toValue . HS.toList
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack . HS.toList
+
+instance MessagePack a => MessagePack [a] where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "[a]" $ \ vs ->
+ zipWithM (\ k v -> fromValue v <?> Index k) [0..] (V.unpack vs)
+ {-# INLINE toValue #-}
+ toValue = Array . V.pack . map toValue
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.array' encodeMessagePack
+
+instance MessagePack a => MessagePack (NonEmpty a) where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "NonEmpty" $ \ vs -> do
+ l <- zipWithM (\ k v -> fromValue v <?> Index k) [0..] (V.unpack vs)
+ case l of (x:xs) -> pure (x :| xs)
+ _ -> fail' "unexpected empty array"
+ {-# INLINE toValue #-}
+ toValue = toValue . NonEmpty.toList
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack . NonEmpty.toList
+
+instance MessagePack Bool where
+ {-# INLINE fromValue #-}; fromValue = withBool "Bool" pure;
+ {-# INLINE toValue #-}; toValue = Bool;
+ {-# INLINE encodeMessagePack #-}; encodeMessagePack = MB.bool
+
+instance MessagePack Char where
+ {-# INLINE fromValue #-}
+ fromValue = withStr "Char" $ \ t ->
+ if (T.length t == 1)
+ then pure (T.head t)
+ else fail' (T.concat ["converting Char failed, expected a string of length 1"])
+ {-# INLINE toValue #-}
+ toValue = Str . T.singleton
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.str . T.singleton
+
+instance MessagePack Double where
+ {-# INLINE fromValue #-}
+ fromValue (Float d) = pure $! realToFrac d
+ fromValue (Double d) = pure d
+ fromValue v = typeMismatch "Double" "Float or Double" v
+ {-# INLINE toValue #-}; toValue = Double;
+ {-# INLINE encodeMessagePack #-}; encodeMessagePack = MB.double;
+
+instance MessagePack Float where
+ {-# INLINE fromValue #-};
+ fromValue (Float d) = pure d
+ fromValue (Double d) = pure $! realToFrac d
+ fromValue v = typeMismatch "Float" "Float or Double" v
+ {-# INLINE toValue #-}; toValue = Float;
+ {-# INLINE encodeMessagePack #-}; encodeMessagePack = MB.float;
+
+#define INT_MessagePack_INSTANCE(typ) \
+ instance MessagePack typ where \
+ {-# INLINE fromValue #-}; \
+ fromValue (Int x) = pure $! fromIntegral x; \
+ fromValue v = typeMismatch "##typ##" "Int" v; \
+ {-# INLINE toValue #-}; toValue = Int . fromIntegral; \
+ {-# INLINE encodeMessagePack #-}; encodeMessagePack = MB.int . fromIntegral;
+INT_MessagePack_INSTANCE(Int )
+INT_MessagePack_INSTANCE(Int8 )
+INT_MessagePack_INSTANCE(Int16 )
+INT_MessagePack_INSTANCE(Int32 )
+INT_MessagePack_INSTANCE(Int64 )
+INT_MessagePack_INSTANCE(Word )
+INT_MessagePack_INSTANCE(Word8 )
+INT_MessagePack_INSTANCE(Word16)
+INT_MessagePack_INSTANCE(Word32)
+INT_MessagePack_INSTANCE(Word64)
+
+-- | This instance includes a bounds check to prevent maliciously
+-- large inputs to fill up the memory of the target system. You can
+-- newtype 'Integer' and provide your own instance using
+-- 'withScientific' if you want to allow larger inputs.
+instance MessagePack Integer where
+ {-# INLINE fromValue #-}
+ fromValue = withBoundedScientific "Integer" $ \ n ->
+ case Sci.floatingOrInteger n :: Either Double Integer of
+ Right x -> pure x
+ Left _ -> fail' . B.unsafeBuildText $ do
+ "converting Integer failed, unexpected floating number "
+ T.scientific n
+ {-# INLINE toValue #-}
+ toValue x = MB.scientificValue x 0
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack x = MB.scientific x 0
+
+-- | This instance includes a bounds check to prevent maliciously
+-- large inputs to fill up the memory of the target system. You can
+-- newtype 'Natural' and provide your own instance using
+-- 'withScientific' if you want to allow larger inputs.
+instance MessagePack Natural where
+ {-# INLINE fromValue #-}
+ fromValue = withBoundedScientific "Natural" $ \ n ->
+ if n < 0
+ then fail' . B.unsafeBuildText $ do
+ "converting Natural failed, unexpected negative number "
+ T.scientific n
+ else case Sci.floatingOrInteger n :: Either Double Natural of
+ Right x -> pure x
+ Left _ -> fail' . B.unsafeBuildText $ do
+ "converting Natural failed, unexpected floating number "
+ T.scientific n
+ {-# INLINE toValue #-}
+ toValue x = MB.scientificValue (fromIntegral x) 0
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack x = MB.scientific (fromIntegral x) 0
+
+instance MessagePack Ordering where
+ {-# INLINE fromValue #-}
+ fromValue = withStr "Ordering" $ \ s ->
+ case s of
+ "LT" -> pure LT
+ "EQ" -> pure EQ
+ "GT" -> pure GT
+ _ -> fail' . T.concat $ ["converting Ordering failed, unexpected ",
+ s, " expected \"LT\", \"EQ\", or \"GT\""]
+ {-# INLINE toValue #-}
+ toValue LT = Str "LT"
+ toValue EQ = Str "EQ"
+ toValue GT = Str "GT"
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack LT = MB.str "LT"
+ encodeMessagePack EQ = MB.str "EQ"
+ encodeMessagePack GT = MB.str "GT"
+
+instance MessagePack () where
+ {-# INLINE fromValue #-}
+ fromValue = withArray "()" $ \ v ->
+ if V.null v
+ then pure ()
+ else fail' "converting () failed, expected an empty array"
+ {-# INLINE toValue #-}
+ toValue () = Array V.empty
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack () = MB.arrayHeader 0
+
+instance MessagePack ExitCode where
+ {-# INLINE fromValue #-}
+ fromValue (Str "ExitSuccess") = return ExitSuccess
+ fromValue (Int x) = return (ExitFailure (fromIntegral x))
+ fromValue _ = fail' "converting ExitCode failed, expected a string or number"
+
+ {-# INLINE toValue #-}
+ toValue ExitSuccess = Str "ExitSuccess"
+ toValue (ExitFailure n) = Int (fromIntegral n)
+
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack ExitSuccess = MB.str "ExitSuccess"
+ encodeMessagePack (ExitFailure n) = B.int n
+
+instance MessagePack Version where
+ {-# INLINE fromValue #-}
+ fromValue = withStr "Version" (go . readP_to_S parseVersion . T.unpack)
+ where
+ go [(v,[])] = pure v
+ go (_ : xs) = go xs
+ go _ = fail "converting Version failed"
+ {-# INLINE toValue #-}
+ toValue = Str . T.pack . show
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = MB.str' . show
+
+instance MessagePack a => MessagePack (Maybe a) where
+ {-# INLINE fromValue #-}
+ fromValue Nil = pure Nothing
+ fromValue v = Just <$> fromValue v
+ {-# INLINE toValue #-}
+ toValue Nothing = Nil
+ toValue (Just x) = toValue x
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack Nothing = MB.nil
+ encodeMessagePack (Just x) = encodeMessagePack x
+
+instance (MessagePack a, Integral a) => MessagePack (Ratio a) where
+ {-# INLINE fromValue #-}
+ fromValue = withFlatMapR "Rational" $ \obj -> do
+ !n <- obj .: "numerator"
+ !d <- obj .: "denominator"
+ if d == 0
+ then fail' "Ratio denominator was 0"
+ else pure (n % d)
+ {-# INLINE toValue #-}
+ toValue x = object [ "numerator" .= (numerator x) , "denominator" .= (denominator x) ]
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack x = object' ( "numerator" .! (numerator x) <> "denominator" .! (denominator x) )
+
+-- | This instance includes a bounds check to prevent maliciously
+-- large inputs to fill up the memory of the target system. You can
+-- newtype 'Fixed' and provide your own instance using
+-- 'withScientific' if you want to allow larger inputs.
+instance HasResolution a => MessagePack (Fixed a) where
+ {-# INLINE fromValue #-}
+ fromValue = withBoundedScientific "Data.Fixed" $ pure . realToFrac
+ {-# INLINE toValue #-}
+ toValue = toValue @Scientific . realToFrac
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack @Scientific . realToFrac
+
+--------------------------------------------------------------------------------
+
+-- | MessagePack extension type @Ext 0xFF@
+instance MessagePack UTCTime where
+ {-# INLINE fromValue #-}
+ fromValue = withSystemTime "UTCTime" $ pure . systemToUTCTime
+ {-# INLINE toValue #-}
+ toValue t = let (MkSystemTime s ns) = utcToSystemTime t in MB.timestampValue s (fromIntegral ns)
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack t = let (MkSystemTime s ns) = utcToSystemTime t in MB.timestamp s (fromIntegral ns)
+
+-- | MessagePack extension type @Ext 0xFF@
+instance MessagePack SystemTime where
+ {-# INLINE fromValue #-}
+ fromValue = withSystemTime "UTCTime" $ pure
+ {-# INLINE toValue #-}
+ toValue (MkSystemTime s ns) = MB.timestampValue s (fromIntegral ns)
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack (MkSystemTime s ns) = MB.timestamp s (fromIntegral ns)
+
+-- | @YYYY-MM-DDTHH:MM:SS.SSSZ@
+instance MessagePack ZonedTime where
+ {-# INLINE fromValue #-}
+ fromValue = withStr "ZonedTime" $ \ t ->
+ case P.parse' (P.zonedTime <* P.endOfInput) (T.getUTF8Bytes t) of
+ Left err -> fail' $ "could not parse date as ZonedTime: " <> T.toText err
+ Right r -> return r
+ {-# INLINE toValue #-}
+ toValue t = Str (B.unsafeBuildText (B.zonedTime t))
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack t = MB.str (B.unsafeBuildText (B.zonedTime t))
+
+-- | @YYYY-MM-DD@
+instance MessagePack Day where
+ {-# INLINE fromValue #-}
+ fromValue = withStr "Day" $ \ t ->
+ case P.parse' (P.day <* P.endOfInput) (T.getUTF8Bytes t) of
+ Left err -> fail' $ "could not parse date as Day: " <> T.toText err
+ Right r -> return r
+ {-# INLINE toValue #-}
+ toValue t = Str (B.unsafeBuildText (B.day t))
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack t = MB.str (B.unsafeBuildText (B.day t))
+
+-- | @YYYY-MM-DDTHH:MM:SS.SSSZ@
+instance MessagePack LocalTime where
+ {-# INLINE fromValue #-}
+ fromValue = withStr "LocalTime" $ \ t ->
+ case P.parse' (P.localTime <* P.endOfInput) (T.getUTF8Bytes t) of
+ Left err -> fail' $ "could not parse date as LocalTime: " <> T.toText err
+ Right r -> return r
+ {-# INLINE toValue #-}
+ toValue t = Str (B.unsafeBuildText (B.localTime t))
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack t = MB.str (B.unsafeBuildText (B.localTime t))
+
+-- | @HH:MM:SS.SSS@
+instance MessagePack TimeOfDay where
+ {-# INLINE fromValue #-}
+ fromValue = withStr "TimeOfDay" $ \ t ->
+ case P.parse' (P.timeOfDay <* P.endOfInput) (T.getUTF8Bytes t) of
+ Left err -> fail' $ "could not parse time as TimeOfDay: " <> T.toText err
+ Right r -> return r
+ {-# INLINE toValue #-}
+ toValue t = Str (B.unsafeBuildText (B.timeOfDay t))
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack t = MB.str (B.unsafeBuildText (B.timeOfDay t))
+
+-- | This instance includes a bounds check to prevent maliciously
+-- large inputs to fill up the memory of the target system. You can
+-- newtype 'NominalDiffTime' and provide your own instance using
+-- 'withScientific' if you want to allow larger inputs.
+instance MessagePack NominalDiffTime where
+ {-# INLINE fromValue #-}
+ fromValue = withBoundedScientific "NominalDiffTime" $ pure . realToFrac
+ {-# INLINE toValue #-}
+ toValue = toValue @Scientific . realToFrac
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack @Scientific . realToFrac
+
+-- | This instance includes a bounds check to prevent maliciously
+-- large inputs to fill up the memory of the target system. You can
+-- newtype 'DiffTime' and provide your own instance using
+-- 'withScientific' if you want to allow larger inputs.
+instance MessagePack DiffTime where
+ {-# INLINE fromValue #-}
+ fromValue = withBoundedScientific "DiffTime" $ pure . realToFrac
+ {-# INLINE toValue #-}
+ toValue = toValue @Scientific . realToFrac
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack = encodeMessagePack @Scientific . realToFrac
+
+instance MessagePack CalendarDiffTime where
+ {-# INLINE fromValue #-}
+ fromValue = withFlatMapR "CalendarDiffTime" $ \ v ->
+ CalendarDiffTime <$> v .: "months" <*> v .: "time"
+ {-# INLINE toValue #-}
+ toValue (CalendarDiffTime m nt) = object [ "months" .= m , "time" .= nt ]
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack (CalendarDiffTime m nt) = object' ("months" .! m <> "time" .! nt)
+
+instance MessagePack CalendarDiffDays where
+ {-# INLINE fromValue #-}
+ fromValue = withFlatMapR "CalendarDiffDays" $ \ v ->
+ CalendarDiffDays <$> v .: "months" <*> v .: "days"
+ {-# INLINE toValue #-}
+ toValue (CalendarDiffDays m d) = object ["months" .= m, "days" .= d]
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack (CalendarDiffDays m d) = object' ("months" .! m <> "days" .! d)
+
+instance MessagePack DayOfWeek where
+ {-# INLINE fromValue #-}
+ fromValue (Str "monday" ) = pure Monday
+ fromValue (Str "tuesday" ) = pure Tuesday
+ fromValue (Str "wednesday") = pure Wednesday
+ fromValue (Str "thursday" ) = pure Thursday
+ fromValue (Str "friday" ) = pure Friday
+ fromValue (Str "saturday" ) = pure Saturday
+ fromValue (Str "sunday" ) = pure Sunday
+ fromValue (Str _ ) = fail' "converting DayOfWeek failed, value should be one of weekdays"
+ fromValue v = typeMismatch "DayOfWeek" "String" v
+ {-# INLINE toValue #-}
+ toValue Monday = Str "monday"
+ toValue Tuesday = Str "tuesday"
+ toValue Wednesday = Str "wednesday"
+ toValue Thursday = Str "thursday"
+ toValue Friday = Str "friday"
+ toValue Saturday = Str "saturday"
+ toValue Sunday = Str "sunday"
+ {-# INLINE encodeMessagePack #-}
+ encodeMessagePack Monday = MB.str "monday"
+ encodeMessagePack Tuesday = MB.str "tuesday"
+ encodeMessagePack Wednesday = MB.str "wednesday"
+ encodeMessagePack Thursday = MB.str "thursday"
+ encodeMessagePack Friday = MB.str "friday"
+ encodeMessagePack Saturday = MB.str "saturday"
+ encodeMessagePack Sunday = MB.str "sunday"
+
+--------------------------------------------------------------------------------
+
+deriving newtype instance MessagePack (f (g a)) => MessagePack (Compose f g a)
+deriving newtype instance MessagePack a => MessagePack (Semigroup.Min a)
+deriving newtype instance MessagePack a => MessagePack (Semigroup.Max a)
+deriving newtype instance MessagePack a => MessagePack (Semigroup.First a)
+deriving newtype instance MessagePack a => MessagePack (Semigroup.Last a)
+deriving newtype instance MessagePack a => MessagePack (Semigroup.WrappedMonoid a)
+deriving newtype instance MessagePack a => MessagePack (Semigroup.Dual a)
+deriving newtype instance MessagePack a => MessagePack (Monoid.First a)
+deriving newtype instance MessagePack a => MessagePack (Monoid.Last a)
+deriving newtype instance MessagePack a => MessagePack (Identity a)
+deriving newtype instance MessagePack a => MessagePack (Const a b)
+deriving newtype instance MessagePack b => MessagePack (Tagged a b)
+
+--------------------------------------------------------------------------------
+
+deriving newtype instance MessagePack CChar
+deriving newtype instance MessagePack CSChar
+deriving newtype instance MessagePack CUChar
+deriving newtype instance MessagePack CShort
+deriving newtype instance MessagePack CUShort
+deriving newtype instance MessagePack CInt
+deriving newtype instance MessagePack CUInt
+deriving newtype instance MessagePack CLong
+deriving newtype instance MessagePack CULong
+deriving newtype instance MessagePack CPtrdiff
+deriving newtype instance MessagePack CSize
+deriving newtype instance MessagePack CWchar
+deriving newtype instance MessagePack CSigAtomic
+deriving newtype instance MessagePack CLLong
+deriving newtype instance MessagePack CULLong
+deriving newtype instance MessagePack CBool
+deriving newtype instance MessagePack CIntPtr
+deriving newtype instance MessagePack CUIntPtr
+deriving newtype instance MessagePack CIntMax
+deriving newtype instance MessagePack CUIntMax
+deriving newtype instance MessagePack CClock
+deriving newtype instance MessagePack CTime
+deriving newtype instance MessagePack CUSeconds
+deriving newtype instance MessagePack CSUSeconds
+deriving newtype instance MessagePack CFloat
+deriving newtype instance MessagePack CDouble
+
+--------------------------------------------------------------------------------
+
+deriving anyclass instance (MessagePack (f a), MessagePack (g a), MessagePack a) => MessagePack (Sum f g a)
+deriving anyclass instance (MessagePack a, MessagePack b) => MessagePack (Either a b)
+deriving anyclass instance (MessagePack (f a), MessagePack (g a)) => MessagePack (Product f g a)
+
+deriving anyclass instance (MessagePack a, MessagePack b) => MessagePack (a, b)
+deriving anyclass instance (MessagePack a, MessagePack b, MessagePack c) => MessagePack (a, b, c)
+deriving anyclass instance (MessagePack a, MessagePack b, MessagePack c, MessagePack d) => MessagePack (a, b, c, d)
+deriving anyclass instance (MessagePack a, MessagePack b, MessagePack c, MessagePack d, MessagePack e) => MessagePack (a, b, c, d, e)
+deriving anyclass instance (MessagePack a, MessagePack b, MessagePack c, MessagePack d, MessagePack e, MessagePack f) => MessagePack (a, b, c, d, e, f)
+deriving anyclass instance (MessagePack a, MessagePack b, MessagePack c, MessagePack d, MessagePack e, MessagePack f, MessagePack g) => MessagePack (a, b, c, d, e, f, g)
diff --git a/Z/Data/MessagePack/Builder.hs b/Z/Data/MessagePack/Builder.hs
new file mode 100644
index 0000000..287cf9d
--- /dev/null
+++ b/Z/Data/MessagePack/Builder.hs
@@ -0,0 +1,211 @@
+{-|
+Module : Z.Data.MessagePack.Builder
+Description : MessagePack builders
+Copyright : (c) Hideyuki Tanaka 2009-2015
+ , (c) Dong Han 2020
+License : BSD3
+
+'Builder's to encode in MessagePack format.
+
+-}
+
+module Z.Data.MessagePack.Builder where
+
+import Control.Monad
+import Data.Bits
+import GHC.Int
+import Data.Word
+import Data.Primitive.PrimArray
+import GHC.Exts
+import GHC.Integer.GMP.Internals
+import Prelude hiding (map)
+import Z.Data.Array.Unaligned
+import qualified Z.Data.Text as T
+import qualified Z.Data.Builder as B
+import qualified Z.Data.Vector as V
+import Z.Data.MessagePack.Value hiding (value)
+
+value :: Value -> B.Builder ()
+{-# INLINABLE value #-}
+value v = case v of
+ Nil -> nil
+ Bool b -> bool b
+ Int n -> int n
+ Float f -> float f
+ Double d -> double d
+ Str t -> str t
+ Bin b -> bin b
+ Array a -> array value a
+ Map m -> map value value m
+ Ext b r -> ext b r
+
+nil :: B.Builder ()
+{-# INLINE nil #-}
+nil = B.word8 0xC0
+
+bool :: Bool -> B.Builder ()
+{-# INLINE bool #-}
+bool False = B.word8 0xC2
+bool True = B.word8 0xC3
+
+int :: Int64 -> B.Builder ()
+{-# INLINE int #-}
+int n
+ | -0x20 <= n && n < 0x80 = B.word8 (fromIntegral n)
+ | 0 <= n && n < 0x100 = B.word8 0xCC >> B.word8 (fromIntegral n)
+ | 0 <= n && n < 0x10000 = B.word8 0xCD >> B.encodePrimBE @Word16 (fromIntegral n)
+ | 0 <= n && n < 0x100000000 = B.word8 0xCE >> B.encodePrimBE @Word32 (fromIntegral n)
+ | 0 <= n = B.word8 0xCF >> B.encodePrimBE @Word64 (fromIntegral n)
+ | -0x80 <= n = B.word8 0xD0 >> B.word8 (fromIntegral n)
+ | -0x8000 <= n = B.word8 0xD1 >> B.encodePrimBE @Word16 (fromIntegral n)
+ | -0x80000000 <= n = B.word8 0xD2 >> B.encodePrimBE @Word32 (fromIntegral n)
+ | otherwise = B.word8 0xD3 >> B.encodePrimBE @Word64 (fromIntegral n)
+
+float :: Float -> B.Builder ()
+{-# INLINE float #-}
+float f = B.word8 0xCA >> B.encodePrimBE f
+
+double :: Double -> B.Builder ()
+{-# INLINE double #-}
+double d = B.word8 0xCB >> B.encodePrimBE d
+
+
+-- | Construct a scientific value, see 'scientific'.
+scientificValue :: Integer -> Int64 -> Value
+{-# INLINE scientificValue #-}
+scientificValue 0 _ = Ext 0x00 (V.pack [0x00, 0x00])
+scientificValue c e = Ext (if c > 0 then 0x00 else 0x01) . B.build $ do
+ int e
+ B.writeN (I# (word2Int# siz#)) $ \ (MutablePrimArray mba#) (I# off#) ->
+ void (exportIntegerToMutableByteArray c mba# (int2Word# off#) 1#)
+ where
+ siz# = sizeInBaseInteger c 256#
+
+-- | Write a scientific value in ext 0x00(positive) and 0x01(negative) format, e.g.
+--
+-- +--------+--------+--------+--------+
+-- | 0xD5 | 0x00 | 0x00 | 0x00 |
+-- +--------+--------+--------+--------+
+--
+--
+-- +--------+--------+--------+-----------------------------------------+---------------------------------------+
+-- | 0xC7 |XXXXXXXX| 0x00 | base10 exponent(MessagePack int format) | coefficient(big endian 256-base limbs |
+-- +--------+--------+--------+-----------------------------------------+---------------------------------------+
+--
+scientific :: Integer -> Int64 -> B.Builder ()
+{-# INLINE scientific #-}
+scientific 0 _ = B.encodePrim @(Word8, Word8, Word8, Word8) (0xD5, 0x00, 0x00, 0x00)
+scientific c e = do
+ case (I# (word2Int# siz#)) + intSiz e of
+ 1 -> B.word8 0xD4
+ 2 -> B.word8 0xD5
+ 4 -> B.word8 0xD6
+ 8 -> B.word8 0xD7
+ 16 -> B.word8 0xD8
+ siz' | siz' < 0x100 -> B.word8 0xC7 >> B.word8 (fromIntegral siz')
+ | siz' < 0x10000 -> B.word8 0xC8 >> B.encodePrimBE @Word16 (fromIntegral siz')
+ | otherwise -> B.word8 0xC9 >> B.encodePrimBE @Word32 (fromIntegral siz')
+ B.word8 (if c > 0 then 0x00 else 0x01)
+ int e
+ B.writeN (I# (word2Int# siz#)) $ \ (MutablePrimArray mba#) (I# off#) ->
+ void (exportIntegerToMutableByteArray c mba# (int2Word# off#) 1#)
+ where
+ siz# = sizeInBaseInteger c 256#
+ intSiz :: Int64 -> Int
+ intSiz n
+ | -0x20 <= n && n < 0x80 = 1
+ | 0 <= n && n < 0x100 = 2
+ | 0 <= n && n < 0x10000 = 3
+ | 0 <= n && n < 0x100000000 = 5
+ | 0 <= n = 9
+ | -0x80 <= n = 2
+ | -0x8000 <= n = 3
+ | -0x80000000 <= n = 5
+ | otherwise = 9
+
+-- | Construct a timestamp(seconds, nanoseconds) value.
+timestampValue :: Int64 -> Int32 -> Value
+{-# INLINE timestampValue #-}
+timestampValue s ns = Ext 0xFF (B.build $ B.encodePrimBE ns >> B.encodePrimBE s)
+
+-- | Write a timestamp(seconds, nanoseconds) in ext 0xFF format, e.g.
+timestamp :: Int64 -> Int32 -> B.Builder ()
+{-# INLINE timestamp #-}
+timestamp s ns = B.encodePrim @(Word8, Word8, Word8, (BE Int32), (BE Int64)) (0xC7, 0x0C, 0xFF, (BE ns), (BE s))
+
+str' :: String -> B.Builder ()
+{-# INLINE str' #-}
+str' = str . T.pack
+
+str :: T.Text -> B.Builder ()
+{-# INLINE str #-}
+str t = do
+ let bs = T.getUTF8Bytes t
+ case V.length bs of
+ len | len <= 31 -> B.word8 (0xA0 .|. fromIntegral len)
+ | len < 0x100 -> B.word8 0xD9 >> B.word8 (fromIntegral len)
+ | len < 0x10000 -> B.word8 0xDA >> B.encodePrimBE @Word16 (fromIntegral len)
+ | otherwise -> B.word8 0xDB >> B.encodePrimBE @Word32 (fromIntegral len)
+ B.bytes bs
+
+bin :: V.Bytes -> B.Builder ()
+{-# INLINE bin #-}
+bin bs = do
+ case V.length bs of
+ len | len < 0x100 -> B.word8 0xC4 >> B.word8 (fromIntegral len)
+ | len < 0x10000 -> B.word8 0xC5 >> B.encodePrimBE @Word16 (fromIntegral len)
+ | otherwise -> B.word8 0xC6 >> B.encodePrimBE @Word32 (fromIntegral len)
+ B.bytes bs
+
+array :: V.Vec v a => (a -> B.Builder ()) -> v a -> B.Builder ()
+{-# INLINE array #-}
+array p xs = do
+ arrayHeader (V.length xs)
+ V.traverseVec_ p xs
+
+array' :: (a -> B.Builder ()) -> [a] -> B.Builder ()
+{-# INLINE array' #-}
+array' p xs = do
+ arrayHeader (length xs)
+ mapM_ p xs
+
+arrayHeader :: Int -> B.Builder ()
+{-# INLINE arrayHeader #-}
+arrayHeader len
+ | len <= 15 = B.word8 (0x90 .|. fromIntegral len)
+ | len < 0x10000 = B.word8 0xDC >> B.encodePrimBE @Word16 (fromIntegral len)
+ | otherwise = B.word8 0xDD >> B.encodePrimBE @Word32 (fromIntegral len)
+
+map :: (a -> B.Builder ()) -> (b -> B.Builder ()) -> V.Vector (a, b) -> B.Builder ()
+{-# INLINE map #-}
+map p q xs = do
+ mapHeader (V.length xs)
+ V.traverseVec_ (\(a, b) -> p a >> q b) xs
+
+map' :: (a -> B.Builder ()) -> (b -> B.Builder ()) -> [(a, b)] -> B.Builder ()
+{-# INLINE map' #-}
+map' p q xs = do
+ mapHeader (length xs)
+ mapM_ (\(a, b) -> p a >> q b) xs
+
+mapHeader :: Int -> B.Builder ()
+{-# INLINE mapHeader #-}
+mapHeader len
+ | len <= 15 = B.word8 (0x80 .|. fromIntegral len)
+ | len < 0x10000 = B.word8 0xDE >> B.encodePrimBE @Word16 (fromIntegral len)
+ | otherwise = B.word8 0xDF >> B.encodePrimBE @Word32 (fromIntegral len)
+
+ext :: Word8 -> V.Bytes -> B.Builder ()
+{-# INLINABLE ext #-}
+ext typ dat = do
+ case V.length dat of
+ 1 -> B.word8 0xD4
+ 2 -> B.word8 0xD5
+ 4 -> B.word8 0xD6
+ 8 -> B.word8 0xD7
+ 16 -> B.word8 0xD8
+ len | len < 0x100 -> B.word8 0xC7 >> B.word8 (fromIntegral len)
+ | len < 0x10000 -> B.word8 0xC8 >> B.encodePrimBE @Word16 (fromIntegral len)
+ | otherwise -> B.word8 0xC9 >> B.encodePrimBE @Word32 (fromIntegral len)
+ B.word8 typ
+ B.bytes dat
diff --git a/Z/Data/MessagePack/Value.hs b/Z/Data/MessagePack/Value.hs
new file mode 100644
index 0000000..42e37f4
--- /dev/null
+++ b/Z/Data/MessagePack/Value.hs
@@ -0,0 +1,166 @@
+{- |
+Module : Z.Data.MessagePack.Value
+Description : MessagePack object definition and parser
+Copyright : (c) Hideyuki Tanaka 2009-2015
+ , (c) Dong Han 2020
+License : BSD3
+-}
+module Z.Data.MessagePack.Value(
+ -- * MessagePack Value
+ Value(..)
+ -- * parse into Message Value
+ , parseValue
+ , parseValue'
+ , parseValueChunks
+ , parseValueChunks'
+ -- * Value Parsers
+ , value
+ ) where
+
+import Control.DeepSeq
+import Control.Monad
+import Data.Bits
+import Data.Int
+import Data.Word
+import GHC.Generics (Generic)
+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
+import qualified Test.QuickCheck.Gen as Gen
+import Prelude hiding (map)
+import qualified Z.Data.Text as T
+import qualified Z.Data.Parser as P
+import qualified Z.Data.Vector as V
+
+
+-- | Representation of MessagePack data.
+data Value
+ = Bool !Bool -- ^ true or false
+ | Int {-# UNPACK #-} !Int64 -- ^ an integer
+ | Float {-# UNPACK #-} !Float -- ^ a floating point number
+ | Double {-# UNPACK #-} !Double -- ^ a floating point number
+ | Str {-# UNPACK #-} !T.Text -- ^ a UTF-8 string
+ | Bin {-# UNPACK #-} !V.Bytes -- ^ a byte array
+ | Array {-# UNPACK #-} !(V.Vector Value) -- ^ a sequence of objects
+ | Map {-# UNPACK #-} !(V.Vector (Value, Value)) -- ^ key-value pairs of objects
+ | Ext {-# UNPACK #-} !Word8 -- ^ type tag
+ {-# UNPACK #-} !V.Bytes -- ^ data payload
+ | Nil -- ^ nil
+ deriving (Show, Eq, Ord, Generic)
+ deriving anyclass T.Print
+
+instance NFData Value where
+ rnf obj = case obj of
+ Array a -> rnf a
+ Map m -> rnf m
+ _ -> ()
+
+instance Arbitrary Value where
+ arbitrary = Gen.sized $ \n -> Gen.oneof
+ [ Bool <$> arbitrary
+ , Int <$> negatives
+ , Float <$> arbitrary
+ , Double <$> arbitrary
+ , Str <$> arbitrary
+ , Bin <$> arbitrary
+ , Array <$> Gen.resize (n `div` 2) arbitrary
+ , Map <$> Gen.resize (n `div` 4) arbitrary
+ , Ext <$> arbitrary <*> arbitrary
+ , pure Nil
+ ]
+ where negatives = Gen.choose (minBound, -1)
+
+
+value :: P.Parser Value
+{-# INLINABLE value #-}
+value = do
+ tag <- P.anyWord8
+ case tag of
+ -- Nil
+ 0xC0 -> return Nil
+
+ -- Bool
+ 0xC2 -> return (Bool False)
+ 0xC3 -> return (Bool True)
+
+ -- Integer
+ c | c .&. 0x80 == 0x00 -> return (Int (fromIntegral c))
+ | c .&. 0xE0 == 0xE0 -> return (Int (fromIntegral (fromIntegral c :: Int8)))
+
+ 0xCC -> Int . fromIntegral <$> P.anyWord8
+ 0xCD -> Int . fromIntegral <$> P.decodePrimBE @Word16
+ 0xCE -> Int . fromIntegral <$> P.decodePrimBE @Word32
+ 0xCF -> Int . fromIntegral <$> P.decodePrimBE @Word64
+
+ 0xD0 -> Int . fromIntegral <$> P.decodePrim @Int8
+ 0xD1 -> Int . fromIntegral <$> P.decodePrimBE @Int16
+ 0xD2 -> Int . fromIntegral <$> P.decodePrimBE @Int32
+ 0xD3 -> Int . fromIntegral <$> P.decodePrimBE @Int64
+
+ -- Float
+ 0xCA -> Float <$> P.decodePrimBE @Float
+ -- Double
+ 0xCB -> Double <$> P.decodePrimBE @Double
+
+ -- String
+ t | t .&. 0xE0 == 0xA0 -> str (t .&. 0x1F)
+ 0xD9 -> str =<< P.anyWord8
+ 0xDA -> str =<< P.decodePrimBE @Word16
+ 0xDB -> str =<< P.decodePrimBE @Word32
+
+ -- Binary
+ 0xC4 -> bin =<< P.anyWord8
+ 0xC5 -> bin =<< P.decodePrimBE @Word16
+ 0xC6 -> bin =<< P.decodePrimBE @Word32
+
+ -- Array
+ t | t .&. 0xF0 == 0x90 -> array (t .&. 0x0F)
+ 0xDC -> array =<< P.decodePrimBE @Word16
+ 0xDD -> array =<< P.decodePrimBE @Word32
+
+ -- Map
+ t | t .&. 0xF0 == 0x80 -> map (t .&. 0x0F)
+ 0xDE -> map =<< P.decodePrimBE @Word16
+ 0xDF -> map =<< P.decodePrimBE @Word32
+
+ -- Ext
+ 0xD4 -> ext (1 :: Int)
+ 0xD5 -> ext (2 :: Int)
+ 0xD6 -> ext (4 :: Int)
+ 0xD7 -> ext (8 :: Int)
+ 0xD8 -> ext (16 :: Int)
+ 0xC7 -> ext =<< P.anyWord8
+ 0xC8 -> ext =<< P.decodePrimBE @Word16
+ 0xC9 -> ext =<< P.decodePrimBE @Word32
+
+ -- impossible
+ x -> P.fail' ("Z.Data.MessagePack: unknown tag " <> T.toText x)
+
+ where
+ str !l = do
+ bs <- P.take (fromIntegral l)
+ case T.validateMaybe bs of
+ Just t -> return (Str t)
+ _ -> P.fail' "Z.Data.MessagePack: illegal UTF8 Bytes"
+ bin !l = Bin <$> P.take (fromIntegral l)
+ array !l = Array . V.packN (fromIntegral l) <$> replicateM (fromIntegral l) value
+ map !l = Map . V.packN (fromIntegral l) <$> replicateM (fromIntegral l) ((,) <$> value <*> value)
+ ext !l = Ext <$> P.decodePrim <*> P.take (fromIntegral l)
+
+-- | Parse 'Value' without consuming trailing bytes.
+parseValue :: V.Bytes -> (V.Bytes, Either P.ParseError Value)
+{-# INLINE parseValue #-}
+parseValue = P.parse value
+
+-- | Parse 'Value', if there're bytes left, parsing will fail.
+parseValue' :: V.Bytes -> Either P.ParseError Value
+{-# INLINE parseValue' #-}
+parseValue' = P.parse' (value <* P.endOfInput)
+
+-- | Increamental parse 'Value' without consuming trailing bytes.
+parseValueChunks :: Monad m => m V.Bytes -> V.Bytes -> m (V.Bytes, Either P.ParseError Value)
+{-# INLINE parseValueChunks #-}
+parseValueChunks = P.parseChunks value
+
+-- | Increamental parse 'Value', if there're bytes left, parsing will fail.
+parseValueChunks' :: Monad m => m V.Bytes -> V.Bytes -> m (Either P.ParseError Value)
+{-# INLINE parseValueChunks' #-}
+parseValueChunks' mi inp = snd <$> P.parseChunks (value <* P.endOfInput) mi inp
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
index 0000000..ff51e4c
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+-- file test/Spec.hs
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/Z/Data/MessagePack/BaseSpec.hs b/test/Z/Data/MessagePack/BaseSpec.hs
new file mode 100644
index 0000000..7a75cd1
--- /dev/null
+++ b/test/Z/Data/MessagePack/BaseSpec.hs
@@ -0,0 +1,307 @@
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Z.Data.MessagePack.BaseSpec where
+
+import qualified Data.List as L
+import Data.Word
+import Data.Int
+import Data.Either
+import GHC.Generics
+import qualified Data.HashMap.Strict as HM
+import qualified Data.HashSet as HS
+import qualified Data.IntMap as IM
+import qualified Data.IntSet as IS
+import qualified Data.Map.Strict as M
+import qualified Data.Sequence as Seq
+import qualified Data.Set as Set
+import qualified Data.Tree as Tree
+import qualified Z.Data.Text as T
+import qualified Z.Data.Vector as V
+import qualified Z.Data.Builder as B
+import Data.Time (Day, DiffTime, LocalTime, NominalDiffTime, TimeOfDay, UTCTime, ZonedTime)
+import Data.Time.Calendar (CalendarDiffDays (..), DayOfWeek (..))
+import Data.Time.LocalTime (CalendarDiffTime (..))
+import Data.Time.Clock.System (SystemTime (..), systemToUTCTime, utcToSystemTime)
+import Test.QuickCheck
+import Test.QuickCheck.Function
+import Test.QuickCheck.Instances
+import Test.QuickCheck.Property
+import Test.Hspec
+import Test.Hspec.QuickCheck
+import qualified Z.Data.MessagePack as MessagePack
+import Z.Data.MessagePack (MessagePack(..), Value(..))
+
+
+data T a
+ = Nullary
+ | Unary Int
+ | Product T.Text (Maybe Char) a
+ | Record { testOne :: Double
+ , testTwo :: Maybe Bool
+ , testThree :: Maybe a
+ }
+
+ | RecordII { testFour :: Double }
+ | List [a]
+ deriving (Show, Eq, Generic, MessagePack)
+
+mid :: MessagePack a => a -> a
+mid = fromRight (error "decode failed") . MessagePack.decode' . MessagePack.encode
+
+intMid :: Int64 -> Int64
+intMid = mid
+
+ns :: UTCTime -> UTCTime
+ns = systemToUTCTime . utcToSystemTime
+
+encode' :: MessagePack a => a -> V.Bytes
+encode' = MessagePack.encode . toValue
+
+spec :: Spec
+spec = modifyMaxSuccess (*5) . modifyMaxSize (*5) $ do
+ describe "MessagePack Base instances" $ do
+
+ it "Nullary constructor are encoded as text" $
+ MessagePack.encode (Nullary :: T Integer) === B.build (do
+ B.word8 0xA7
+ "Nullary"
+ )
+
+ it "Unary constructor are encoded as single field object" $
+ MessagePack.encode (Unary 256 :: T ()) === B.build (do
+ B.word8 0x81
+ B.word8 0xA5
+ "Unary"
+ B.word8 0xCD
+ B.word8 0x01
+ B.word8 0x00
+ )
+
+ it "Product are encoded as array" $
+ MessagePack.encode (Product "ABC" (Just 'x') (256::Integer)) === B.build (do
+ B.word8 0x81
+ B.word8 0xA7
+ "Product"
+ B.word8 0x93
+ B.word8 0xA3
+ "ABC"
+ B.word8 0xA1
+ "x"
+ B.word8 0xC7
+ B.word8 0x03
+ B.word8 0x00
+ B.word8 0x00
+ B.word8 0x01
+ B.word8 0x00
+ )
+
+ it "Record are encoded as key values" $
+ MessagePack.encode (Record 0.123456 Nothing (Just (256::Integer))) === B.build (do
+ B.word8 0x81
+ B.word8 0xA6
+ "Record"
+ B.word8 0x83
+ B.word8 0xA7
+ "testOne"
+ B.word8 0xCB
+ B.encodePrimBE @Double 0.123456
+ B.word8 0xA7
+ "testTwo"
+ B.word8 0xC0
+ B.word8 0xA9
+ "testThree"
+ B.word8 0xC7
+ B.word8 0x03
+ B.word8 0x00
+ B.word8 0x00
+ B.word8 0x01
+ B.word8 0x00
+ )
+
+ it "Record are encoded as key values(single field)" $
+ MessagePack.encode (RecordII 0.123456 :: T ()) === B.build (do
+ B.word8 0x81
+ B.word8 0xA8
+ "RecordII"
+ B.word8 0x81
+ B.word8 0xA8
+ "testFour"
+ B.word8 0xCB
+ B.encodePrimBE @Double 0.123456
+ )
+
+ -- tests from MessagePack suit
+ it "int" $ property $ \(a :: Int ) -> a `shouldBe` mid a
+ it "int8" $ property $ \(a :: Int8 ) -> a `shouldBe` mid a
+ it "int16" $ property $ \(a :: Int16 ) -> a `shouldBe` mid a
+ it "int32" $ property $ \(a :: Int32 ) -> a `shouldBe` mid a
+ it "int64" $ property $ \(a :: Int64 ) -> a `shouldBe` mid a
+ it "word" $ property $ \(a :: Word ) -> a `shouldBe` mid a
+ it "word8" $ property $ \(a :: Word8 ) -> a `shouldBe` mid a
+ it "word16" $ property $ \(a :: Word16) -> a `shouldBe` mid a
+ it "word32" $ property $ \(a :: Word32) -> a `shouldBe` mid a
+ it "word64" $ property $ \(a :: Word64) -> a `shouldBe` mid a
+
+ it "()" $ property $ \(a :: ()) -> a `shouldBe` mid a
+ it "bool" $ property $ \(a :: Bool) -> a `shouldBe` mid a
+ it "float" $ property $ \(a :: Float) -> a `shouldBe` mid a
+ it "integer" $ property $ \(a :: Integer) -> a `shouldBe` mid a
+ it "double" $ property $ \(a :: Double) -> a `shouldBe` mid a
+ it "string" $ property $ \(a :: String) -> a `shouldBe` mid a
+ it "bytes" $ property $ \(a :: V.Bytes) -> a `shouldBe` mid a
+ it "primvector" $ property $ \(a :: V.PrimVector Int) -> a `shouldBe` mid a
+ it "vector" $ property $ \(a :: V.Vector [Integer]) -> a `shouldBe` mid a
+ it "maybe int" $ property $ \(a :: (Maybe Int)) -> a `shouldBe` mid a
+ it "[int]" $ property $ \(a :: [Int]) -> a `shouldBe` mid a
+ it "[string]" $ property $ \(a :: [String]) -> a `shouldBe` mid a
+ it "(int, int)" $ property $ \(a :: (Int, Int)) -> a `shouldBe` mid a
+ it "(int, int, int)" $ property $ \(a :: (Int, Int, Int)) -> a `shouldBe` mid a
+ it "(int, int, int, int)" $ property $ \(a :: (Int, Int, Int, Int)) -> a `shouldBe` mid a
+ it "(int, int, int, int, int)" $ property $ \(a :: (Int, Int, Int, Int, Int)) -> a `shouldBe` mid a
+ it "(int, int, int, int, int, int)" $ property $ \(a :: (Int, Int, Int, Int, Int, Int)) -> a `shouldBe` mid a
+ it "(int, int, int, int, int, int, int)" $ property $ \(a :: (Int, Int, Int, Int, Int, Int, Int)) -> a `shouldBe` mid a
+ it "(int, double)" $ property $ \(a :: (Int, Double)) -> a `shouldBe` mid a
+ it "[(int, double)]" $ property $ \(a :: [(Int, Double)]) -> a `shouldBe` mid a
+ it "[(string, string)]" $ property $ \(a :: [(String, String)]) -> a `shouldBe` mid a
+ it "HashMap Text Int" $ property $ \(a :: HM.HashMap T.Text Int) -> a `shouldBe` mid a
+ it "HashSet Text" $ property $ \(a :: HS.HashSet T.Text) -> a `shouldBe` mid a
+ it "Map Text Int" $ property $ \(a :: M.Map T.Text Int) -> a `shouldBe` mid a
+ it "IntMap Int" $ property $ \(a :: IM.IntMap Int) -> a `shouldBe` mid a
+ it "Set Int" $ property $ \(a :: Set.Set Int) -> a `shouldBe` mid a
+ it "IntSet" $ property $ \(a :: IS.IntSet) -> a `shouldBe` mid a
+ it "Seq Int" $ property $ \(a :: Seq.Seq Int) -> a `shouldBe` mid a
+ it "Tree Int" $ property $ \(a :: Tree.Tree Int) -> a `shouldBe` mid a
+ it "maybe int" $ property $ \(a :: Maybe Int) -> a `shouldBe` mid a
+ it "maybe nil" $ property $ \(a :: Maybe ()) -> a `shouldBe` mid a
+ it "maybe bool" $ property $ \(a :: Maybe Bool) -> a `shouldBe` mid a
+ it "maybe double" $ property $ \(a :: Maybe Double) -> a `shouldBe` mid a
+ it "maybe string" $ property $ \(a :: Maybe String) -> a `shouldBe` mid a
+ it "maybe bytes" $ property $ \ (a :: Maybe V.Bytes) -> a `shouldBe` mid a
+ it "maybe [int]" $ property $ \(a :: Maybe [Int]) -> a `shouldBe` mid a
+ it "maybe [string]" $ property $ \(a :: Maybe [String]) -> a `shouldBe` mid a
+ it "maybe (int, int)" $ property $ \(a :: Maybe (Int, Int)) -> a `shouldBe` mid a
+ it "maybe (int, int, int)" $ property $ \(a :: Maybe (Int, Int, Int)) -> a `shouldBe` mid a
+ it "maybe (int, int, int, int)" $ property $ \(a :: Maybe (Int, Int, Int, Int)) -> a `shouldBe` mid a
+ it "maybe (int, int, int, int, int)" $ property $ \(a :: Maybe (Int, Int, Int, Int, Int)) -> a `shouldBe` mid a
+ it "maybe [(int, double)]" $ property $ \(a :: Maybe [(Int, Double)]) -> a `shouldBe` mid a
+ it "maybe [(string, string)]" $ property $ \(a :: Maybe [(String, String)]) -> a `shouldBe` mid a
+ it "either int float" $ property $ \(a :: Either Int Float) -> a `shouldBe` mid a
+ it "Day" $ property $ \(a :: Day) -> a `shouldBe` mid a
+ it "DiffTime" $ property $ \(a :: DiffTime) -> a `shouldBe` mid a
+ it "LocalTime" $ property $ \(a :: LocalTime) -> a `shouldBe` mid a
+ it "NominalDiffTime" $ property $ \(a :: NominalDiffTime) -> a `shouldBe` mid a
+ it "TimeOfDay" $ property $ \(a :: TimeOfDay) -> a `shouldBe` mid a
+ it "UTCTime" $ property $ \(a :: UTCTime) -> ns a `shouldBe` mid (ns a)
+ it "SystemTime" $ property $ \(a :: SystemTime) -> a `shouldBe` mid a
+ it "CalendarDiffDays" $ property $ \(a :: CalendarDiffDays) -> a `shouldBe` mid a
+ it "DayOfWeek" $ property $ \(a :: DayOfWeek) -> a `shouldBe` mid a
+ it "CalendarDiffTime" $ property $ \(a :: CalendarDiffTime) -> a `shouldBe` mid a
+ it "arbitrary message" $ property $ \(a :: Value) -> a `shouldBe` mid a
+
+ describe "MessagePack Base instances encodeMessagePack == encodeMessagePack . toValue" $ do
+
+ it "Nullary constructor are encoded as text" $
+ MessagePack.encode (Nullary :: T Integer) ===
+ encode' (Nullary :: T Integer)
+
+ it "Unary constructor are encoded as single field object" $
+ MessagePack.encode (Unary 123456 :: T Integer) ===
+ encode' (Unary 123456 :: T Integer)
+
+ it "Product are encoded as array" $
+ MessagePack.encode (Product "ABC" (Just 'x') (123456::Integer)) ===
+ encode' (Product "ABC" (Just 'x') (123456::Integer))
+
+ it "Record are encoded as key values" $
+ MessagePack.encode (Record 0.123456 Nothing (Just (123456::Integer))) ===
+ encode' (Record 0.123456 Nothing (Just (123456::Integer)))
+
+ it "Record are encoded as key values(single field)" $
+ MessagePack.encode (RecordII 0.123456 :: T Integer) ===
+ encode' (RecordII 0.123456 :: T Integer)
+
+ it "List are encode as array" $
+ MessagePack.encode (List [Nullary
+ , Unary 123456
+ , (Product "ABC" (Just 'x') (123456::Integer))
+ , (Record 0.123456 Nothing (Just (123456::Integer)))]) ===
+ encode' (List [Nullary
+ , Unary 123456
+ , (Product "ABC" (Just 'x') (123456::Integer))
+ , (Record 0.123456 Nothing (Just (123456::Integer)))])
+
+ it "control characters are escaped" $
+ MessagePack.encode (T.pack $ map toEnum [0..0x1F]) ===
+ encode' (T.pack $ map toEnum [0..0x1F])
+
+ -- tests from MessagePack suit
+ it "int" $ property $ \(a :: Int ) -> encode' a === MessagePack.encode a
+ it "int8" $ property $ \(a :: Int8 ) -> encode' a === MessagePack.encode a
+ it "int16" $ property $ \(a :: Int16 ) -> encode' a === MessagePack.encode a
+ it "int32" $ property $ \(a :: Int32 ) -> encode' a === MessagePack.encode a
+ it "int64" $ property $ \(a :: Int64 ) -> encode' a === MessagePack.encode a
+ it "word" $ property $ \(a :: Word ) -> encode' a === MessagePack.encode a
+ it "word8" $ property $ \(a :: Word8 ) -> encode' a === MessagePack.encode a
+ it "word16" $ property $ \(a :: Word16) -> encode' a === MessagePack.encode a
+ it "word32" $ property $ \(a :: Word32) -> encode' a === MessagePack.encode a
+ it "word64" $ property $ \(a :: Word64) -> encode' a === MessagePack.encode a
+
+ it "()" $ property $ \(a :: ()) -> encode' a === MessagePack.encode a
+ it "bool" $ property $ \(a :: Bool) -> encode' a === MessagePack.encode a
+ it "float" $ property $ \(a :: Float) -> encode' a === MessagePack.encode a
+ it "double" $ property $ \(a :: Double) -> encode' a === MessagePack.encode a
+ it "integer" $ property $ \(a :: Integer) -> encode' a === MessagePack.encode a
+ it "string" $ property $ \(a :: String) -> encode' a === MessagePack.encode a
+ it "bytes" $ property $ \(a :: V.Bytes) -> encode' a === MessagePack.encode a
+ it "primvector" $ property $ \(a :: V.PrimVector Int) -> encode' a === MessagePack.encode a
+ it "vector" $ property $ \(a :: V.Vector [Integer]) -> encode' a === MessagePack.encode a
+ it "maybe int" $ property $ \(a :: (Maybe Int)) -> encode' a === MessagePack.encode a
+ it "[int]" $ property $ \(a :: [Int]) -> encode' a === MessagePack.encode a
+ it "[string]" $ property $ \(a :: [String]) -> encode' a === MessagePack.encode a
+ it "(int, int)" $ property $ \(a :: (Int, Int)) -> encode' a === MessagePack.encode a
+ it "(int, int, int)" $ property $ \(a :: (Int, Int, Int)) -> encode' a === MessagePack.encode a
+ it "(int, int, int, int)" $ property $ \(a :: (Int, Int, Int, Int)) -> encode' a === MessagePack.encode a
+ it "(int, int, int, int, int)" $ property $ \(a :: (Int, Int, Int, Int, Int)) -> encode' a === MessagePack.encode a
+ it "(int, int, int, int, int, int)" $ property $ \(a :: (Int, Int, Int, Int, Int, Int)) -> encode' a === MessagePack.encode a
+ it "(int, int, int, int, int, int, int)" $ property $ \(a :: (Int, Int, Int, Int, Int, Int, Int)) -> encode' a === MessagePack.encode a
+ it "(int, double)" $ property $ \(a :: (Int, Double)) -> encode' a === MessagePack.encode a
+ it "[(int, double)]" $ property $ \(a :: [(Int, Double)]) -> encode' a === MessagePack.encode a
+ it "[(string, string)]" $ property $ \(a :: [(String, String)]) -> encode' a === MessagePack.encode a
+ it "HashMap Text Int" $ property $ \(a :: HM.HashMap T.Text Int) -> encode' a === MessagePack.encode a
+ it "HashSet Text" $ property $ \(a :: HS.HashSet T.Text) -> encode' a === MessagePack.encode a
+ it "Map Text Int" $ property $ \(a :: M.Map T.Text Int) -> encode' a === MessagePack.encode a
+ it "IntMap Int" $ property $ \(a :: IM.IntMap Int) -> encode' a === MessagePack.encode a
+ it "Set Int" $ property $ \(a :: Set.Set Int) -> encode' a === MessagePack.encode a
+ it "IntSet" $ property $ \(a :: IS.IntSet) -> encode' a === MessagePack.encode a
+ it "Seq Int" $ property $ \(a :: Seq.Seq Int) -> encode' a === MessagePack.encode a
+ it "Tree Int" $ property $ \(a :: Tree.Tree Int) -> encode' a === MessagePack.encode a
+ it "maybe int" $ property $ \(a :: Maybe Int) -> encode' a === MessagePack.encode a
+ it "maybe nil" $ property $ \(a :: Maybe ()) -> encode' a === MessagePack.encode a
+ it "maybe bool" $ property $ \(a :: Maybe Bool) -> encode' a === MessagePack.encode a
+ it "maybe double" $ property $ \(a :: Maybe Double) -> encode' a === MessagePack.encode a
+ it "maybe string" $ property $ \(a :: Maybe String) -> encode' a === MessagePack.encode a
+ it "maybe bytes" $ property $ \ (a :: Maybe V.Bytes) -> encode' a === MessagePack.encode a
+ it "maybe [int]" $ property $ \(a :: Maybe [Int]) -> encode' a === MessagePack.encode a
+ it "maybe [string]" $ property $ \(a :: Maybe [String]) -> encode' a === MessagePack.encode a
+ it "maybe (int, int)" $ property $ \(a :: Maybe (Int, Int)) -> encode' a === MessagePack.encode a
+ it "maybe (int, int, int)" $ property $ \(a :: Maybe (Int, Int, Int)) -> encode' a === MessagePack.encode a
+ it "maybe (int, int, int, int)" $ property $ \(a :: Maybe (Int, Int, Int, Int)) -> encode' a === MessagePack.encode a
+ it "maybe (int, int, int, int, int)" $ property $ \(a :: Maybe (Int, Int, Int, Int, Int)) -> encode' a === MessagePack.encode a
+ it "maybe [(int, double)]" $ property $ \(a :: Maybe [(Int, Double)]) -> encode' a === MessagePack.encode a
+ it "maybe [(string, string)]" $ property $ \(a :: Maybe [(String, String)]) -> encode' a === MessagePack.encode a
+ it "either int float" $ property $ \(a :: Either Int Float) -> encode' a === MessagePack.encode a
+ it "Day" $ property $ \(a :: Day) -> encode' a === MessagePack.encode a
+ it "DiffTime" $ property $ \(a :: DiffTime) -> encode' a === MessagePack.encode a
+ it "LocalTime" $ property $ \(a :: LocalTime) -> encode' a === MessagePack.encode a
+ it "NominalDiffTime" $ property $ \(a :: NominalDiffTime) -> encode' a === MessagePack.encode a
+ it "TimeOfDay" $ property $ \(a :: TimeOfDay) -> encode' a === MessagePack.encode a
+ it "UTCTime" $ property $ \(a :: UTCTime) -> encode' a === MessagePack.encode a
+ it "SystemTime" $ property $ \(a :: SystemTime) -> encode' a === MessagePack.encode a
+ it "CalendarDiffDays" $ property $ \(a :: CalendarDiffDays) -> encode' a === MessagePack.encode a
+ it "DayOfWeek" $ property $ \(a :: DayOfWeek) -> encode' a === MessagePack.encode a
+ it "CalendarDiffTime" $ property $ \(a :: CalendarDiffTime) -> encode' a === MessagePack.encode a
+ it "arbitrary message" $ property $ \(a :: Value) -> encode' a === MessagePack.encode a