2 {-# LANGUAGE DefaultSignatures #-}
3 {-# LANGUAGE DeriveGeneric #-}
4 {-# LANGUAGE FlexibleContexts #-}
5 {-# LANGUAGE PolyKinds #-}
6 {-# LANGUAGE ScopedTypeVariables #-}
7 {-# LANGUAGE TypeApplications #-}
8 {-# LANGUAGE TypeFamilies #-}
9 {-# LANGUAGE TypeOperators #-}
13 -- Copyright: (c) 2019 Oleg Grenrus
15 -- Structurally tag binary serialisation stream.
16 -- Useful when most 'Binary' instances are 'Generic' derived.
18 -- Say you have a data type
21 -- data Record = Record
22 -- { _recordFields :: HM.HashMap Text (Integer, ByteString)
23 -- , _recordEnabled :: Bool
25 -- deriving (Eq, Show, Generic)
27 -- instance 'Binary' Record
28 -- instance 'Structured' Record
31 -- then you can serialise and deserialise @Record@ values with a structure tag by simply
34 -- 'structuredEncode' record :: 'LBS.ByteString'
35 -- 'structuredDecode' lbs :: IO Record
38 -- If structure of @Record@ changes in between, deserialisation will fail early.
40 -- Technically, 'Structured' is not related to 'Binary', and may
41 -- be useful in other uses.
42 module Distribution
.Utils
.Structured
43 ( -- * Encoding and decoding
45 -- | These functions operate like @binary@'s counterparts,
46 -- but the serialised version has a structure hash in front.
48 , structuredEncodeFile
50 , structuredDecodeOrFailIO
51 , structuredDecodeFileOrFail
54 , Structured
(structure
)
75 import Data
.Int (Int16
, Int32
, Int64
, Int8
)
76 import Data
.List
.NonEmpty
(NonEmpty
)
77 import Data
.Proxy
(Proxy
(..))
78 import Data
.Ratio (Ratio)
79 import Data
.Word
(Word
, Word16
, Word32
, Word64
, Word8
)
81 import qualified Control
.Monad
.Trans
.State
.Strict
as State
83 import Control
.Exception
(ErrorCall
(..), catch, evaluate
)
87 import qualified Data
.ByteString
as BS
88 import qualified Data
.ByteString
.Lazy
as LBS
89 #if MIN_VERSION_bytestring
(0,10,4)
90 import qualified Data
.ByteString
.Builder
as Builder
92 import qualified Data
.ByteString
.Lazy
.Builder
as Builder
94 import qualified Data
.IntMap
as IM
95 import qualified Data
.IntSet
as IS
96 import qualified Data
.Map
as Map
97 import qualified Data
.Sequence
as Seq
98 import qualified Data
.Set
as Set
99 import qualified Data
.Text
as T
100 import qualified Data
.Text
.Lazy
as LT
101 import qualified Data
.Time
as Time
102 import qualified Distribution
.Compat
.Binary
as Binary
104 #ifdef MIN_VERSION_aeson
105 import qualified Data
.Aeson
as Aeson
108 import Data
.Kind
(Type
)
109 import Data
.Typeable
(TypeRep
, Typeable
, typeRep
)
111 import Distribution
.Utils
.MD5
113 import Data
.Monoid
(mconcat
)
115 import qualified Data
.Foldable
116 import qualified Data
.Semigroup
118 -------------------------------------------------------------------------------
120 -------------------------------------------------------------------------------
122 type TypeName
= String
123 type ConstructorName
= String
125 -- | A semantic version of a data type. Usually 0.
126 type TypeVersion
= Word32
128 -- | Structure of a datatype.
130 -- It can be infinite, as far as 'TypeRep's involved are finite.
131 -- (e.g. polymorphic recursion might cause troubles).
133 = -- | nominal, yet can be parametrised by other structures.
134 Nominal
!TypeRep
!TypeVersion TypeName
[Structure
]
135 |
-- | a newtype wrapper
136 Newtype
!TypeRep
!TypeVersion TypeName Structure
137 |
-- | sum-of-products structure
138 Structure
!TypeRep
!TypeVersion TypeName SopStructure
139 deriving (Eq
, Ord
, Show, Generic
)
141 type SopStructure
= [(ConstructorName
, [Structure
])]
143 -- | A MD5 hash digest of 'Structure'.
144 hashStructure
:: Structure
-> MD5
145 hashStructure
= md5
. LBS
.toStrict
. Builder
.toLazyByteString
. structureBuilder
147 -- | A van-Laarhoven lens into 'TypeVersion' of 'Structure'
150 -- 'typeVersion' :: Lens' 'Structure' 'TypeVersion'
152 typeVersion
:: Functor f
=> (TypeVersion
-> f TypeVersion
) -> Structure
-> f Structure
153 typeVersion f
(Nominal t v n s
) = fmap (\v' -> Nominal t v
' n s
) (f v
)
154 typeVersion f
(Newtype t v n s
) = fmap (\v' -> Newtype t v
' n s
) (f v
)
155 typeVersion f
(Structure t v n s
) = fmap (\v' -> Structure t v
' n s
) (f v
)
157 -- | A van-Laarhoven lens into 'TypeName' of 'Structure'
160 -- 'typeName' :: Lens' 'Structure' 'TypeName'
162 typeName
:: Functor f
=> (TypeName
-> f TypeName
) -> Structure
-> f Structure
163 typeName f
(Nominal t v n s
) = fmap (\n' -> Nominal t v n
' s
) (f n
)
164 typeName f
(Newtype t v n s
) = fmap (\n' -> Newtype t v n
' s
) (f n
)
165 typeName f
(Structure t v n s
) = fmap (\n' -> Structure t v n
' s
) (f n
)
167 -------------------------------------------------------------------------------
169 -------------------------------------------------------------------------------
171 -- | Flatten 'Structure' into something we can calculate hash of.
173 -- As 'Structure' can be potentially infinite. For mutually recursive types,
174 -- we keep track of 'TypeRep's, and put just 'TypeRep' name when it's occurred
176 structureBuilder
:: Structure
-> Builder
.Builder
177 structureBuilder s0
= State
.evalState
(go s0
) Map
.empty
179 go
:: Structure
-> State
.State
(Map
.Map
String (NonEmpty TypeRep
)) Builder
.Builder
180 go
(Nominal t v n s
) = withTypeRep t
$ do
182 return $ mconcat
$ Builder
.word8
1 : Builder
.word32LE v
: Builder
.stringUtf8 n
: s
'
183 go
(Newtype t v n s
) = withTypeRep t
$ do
185 return $ mconcat
[Builder
.word8
2, Builder
.word32LE v
, Builder
.stringUtf8 n
, s
']
186 go
(Structure t v n s
) = withTypeRep t
$ do
188 return $ mconcat
[Builder
.word8
3, Builder
.word32LE v
, Builder
.stringUtf8 n
, s
']
193 Nothing
-> return $ mconcat
[Builder
.word8
0, Builder
.stringUtf8
(show t
)]
198 goSop
:: SopStructure
-> State
.State
(Map
.Map
String (NonEmpty TypeRep
)) Builder
.Builder
200 parts
<- traverse part sop
201 return $ mconcat parts
205 return $ Data
.Monoid
.mconcat
[Builder
.stringUtf8 cn
, mconcat s
']
207 insert :: TypeRep
-> Map
.Map
String (NonEmpty TypeRep
) -> Maybe (Map
.Map
String (NonEmpty TypeRep
))
208 insert tr m
= case Map
.lookup trShown m
of
211 | tr `Data
.Foldable
.elem` ne
-> Nothing
212 |
otherwise -> inserted
214 inserted
= Just
(Map
.insertWith
(Data
.Semigroup
.<>) trShown
(pure tr
) m
)
217 -------------------------------------------------------------------------------
219 -------------------------------------------------------------------------------
221 -- | Class of types with a known 'Structure'.
223 -- For regular data types 'Structured' can be derived generically.
226 -- data Record = Record { a :: Int, b :: Bool, c :: [Char] } deriving ('Generic')
227 -- instance 'Structured' Record
231 class Typeable a
=> Structured a
where
232 structure
:: Proxy a
-> Structure
233 default structure
:: (Generic a
, GStructured
(Rep a
)) => Proxy a
-> Structure
234 structure
= genericStructure
236 -- This member is hidden. It's there to precalc
237 structureHash
' :: Tagged a MD5
238 structureHash
' = Tagged
(hashStructure
(structure
(Proxy
:: Proxy a
)))
241 newtype Tagged a b
= Tagged
{untag
:: b
}
243 -- | Semantically @'hashStructure' . 'structure'@.
244 structureHash
:: forall a
. Structured a
=> Proxy a
-> MD5
245 structureHash _
= untag
(structureHash
' :: Tagged a MD5
)
247 -------------------------------------------------------------------------------
249 -------------------------------------------------------------------------------
251 -- | Structured 'Binary.encode'.
252 -- Encode a value to using binary serialisation to a lazy 'LBS.ByteString'.
253 -- Encoding starts with 16 byte large structure hash.
256 . (Binary
.Binary a
, Structured a
)
259 structuredEncode x
= Binary
.encode
(Tag
:: Tag a
, x
)
261 -- | Lazily serialise a value to a file
262 structuredEncodeFile
:: (Binary
.Binary a
, Structured a
) => FilePath -> a
-> IO ()
263 structuredEncodeFile f
= LBS
.writeFile f
. structuredEncode
265 -- | Structured 'Binary.decode'.
266 -- Decode a value from a lazy 'LBS.ByteString', reconstructing the original structure.
267 -- Throws pure exception on invalid inputs.
270 . (Binary
.Binary a
, Structured a
)
273 structuredDecode lbs
= snd (Binary
.decode lbs
:: (Tag a
, a
))
275 structuredDecodeOrFailIO
:: (Binary
.Binary a
, Structured a
) => LBS
.ByteString
-> IO (Either String a
)
276 structuredDecodeOrFailIO bs
=
277 catch (evaluate
(structuredDecode bs
) >>= return . Right
) handler
279 handler
(ErrorCall str
) = return $ Left str
281 -- | Lazily reconstruct a value previously written to a file.
282 structuredDecodeFileOrFail
:: (Binary
.Binary a
, Structured a
) => FilePath -> IO (Either String a
)
283 structuredDecodeFileOrFail f
= structuredDecodeOrFailIO
=<< LBS
.readFile f
285 -------------------------------------------------------------------------------
287 -------------------------------------------------------------------------------
291 instance Structured a
=> Binary
.Binary
(Tag a
) where
293 actual
<- binaryGetMD5
294 if actual
== expected
299 [ "Non-matching structured hashes: "
305 expected
= untag
(structureHash
' :: Tagged a MD5
)
307 put _
= binaryPutMD5 expected
309 expected
= untag
(structureHash
' :: Tagged a MD5
)
311 -------------------------------------------------------------------------------
312 -- Smart constructors
313 -------------------------------------------------------------------------------
315 -- | Use 'Typeable' to infer name
316 nominalStructure
:: Typeable a
=> Proxy a
-> Structure
317 nominalStructure p
= Nominal tr
0 (show tr
) []
321 containerStructure
:: forall f a
. (Typeable f
, Structured a
) => Proxy
(f a
) -> Structure
322 containerStructure _
=
327 [ structure
(Proxy
:: Proxy a
)
330 fTypeRep
= typeRep
(Proxy
:: Proxy f
)
331 faTypeRep
= typeRep
(Proxy
:: Proxy
(f a
))
333 -------------------------------------------------------------------------------
335 -------------------------------------------------------------------------------
337 -- | Derive 'structure' generically.
338 genericStructure
:: forall a
. (Typeable a
, Generic a
, GStructured
(Rep a
)) => Proxy a
-> Structure
339 genericStructure _
= gstructured
(typeRep
(Proxy
:: Proxy a
)) (Proxy
:: Proxy
(Rep a
)) 0
341 -- | Used to implement 'genericStructure'.
342 class GStructured
(f
:: Type
-> Type
) where
343 gstructured
:: TypeRep
-> Proxy f
-> TypeVersion
-> Structure
345 instance (i ~ D
, Datatype c
, GStructuredSum f
) => GStructured
(M1 i c f
) where
346 gstructured tr _ v
= case sop
of
347 [(_
, [s
])] | isNewtype p
-> Newtype tr v name s
348 _
-> Structure tr v name sop
350 p
= undefined :: M1 i c f
()
351 name
= datatypeName p
352 sop
= gstructuredSum
(Proxy
:: Proxy f
) []
354 class GStructuredSum
(f
:: Type
-> Type
) where
355 gstructuredSum
:: Proxy f
-> SopStructure
-> SopStructure
357 instance (i ~ C
, Constructor c
, GStructuredProd f
) => GStructuredSum
(M1 i c f
) where
358 gstructuredSum _ xs
= (name
, prod
) : xs
360 name
= conName
(undefined :: M1 i c f
())
361 prod
= gstructuredProd
(Proxy
:: Proxy f
) []
363 instance (GStructuredSum f
, GStructuredSum g
) => GStructuredSum
(f
:+: g
) where
364 gstructuredSum _ xs
=
365 gstructuredSum
(Proxy
:: Proxy f
) $
366 gstructuredSum
(Proxy
:: Proxy g
) xs
368 instance GStructuredSum V1
where
369 gstructuredSum _
= id
371 class GStructuredProd
(f
:: Type
-> Type
) where
372 gstructuredProd
:: Proxy f
-> [Structure
] -> [Structure
]
374 instance (i ~ S
, GStructuredProd f
) => GStructuredProd
(M1 i c f
) where
375 gstructuredProd _
= gstructuredProd
(Proxy
:: Proxy f
)
377 instance Structured c
=> GStructuredProd
(K1 i c
) where
378 gstructuredProd _ xs
= structure
(Proxy
:: Proxy c
) : xs
380 instance GStructuredProd U1
where
381 gstructuredProd _
= id
383 instance (GStructuredProd f
, GStructuredProd g
) => GStructuredProd
(f
:*: g
) where
384 gstructuredProd _ xs
=
385 gstructuredProd
(Proxy
:: Proxy f
) $
386 gstructuredProd
(Proxy
:: Proxy g
) xs
388 -------------------------------------------------------------------------------
390 -------------------------------------------------------------------------------
392 instance Structured
()
393 instance Structured
Bool
394 instance Structured
Ordering
396 instance Structured
Char where structure
= nominalStructure
397 instance Structured
Int where structure
= nominalStructure
398 instance Structured
Integer where structure
= nominalStructure
400 instance Structured Data
.Word
.Word
where structure
= nominalStructure
402 instance Structured Int8
where structure
= nominalStructure
403 instance Structured Int16
where structure
= nominalStructure
404 instance Structured Int32
where structure
= nominalStructure
405 instance Structured Int64
where structure
= nominalStructure
407 instance Structured Word8
where structure
= nominalStructure
408 instance Structured Word16
where structure
= nominalStructure
409 instance Structured Word32
where structure
= nominalStructure
410 instance Structured Word64
where structure
= nominalStructure
412 instance Structured
Float where structure
= nominalStructure
413 instance Structured
Double where structure
= nominalStructure
415 instance Structured a
=> Structured
(Maybe a
)
416 instance (Structured a
, Structured b
) => Structured
(Either a b
)
417 instance Structured a
=> Structured
(Ratio a
) where structure
= containerStructure
418 instance Structured a
=> Structured
[a
] where structure
= containerStructure
419 instance Structured a
=> Structured
(NonEmpty a
) where structure
= containerStructure
421 -- These instances are defined directly because the generic names for tuples changed
422 -- in 9.6 (https://gitlab.haskell.org/ghc/ghc/-/issues/24291).
424 -- By defining our own instances the STuple2 identifier will be used in the hash and
425 -- hence the same on all GHC versions.
427 data STuple2 a b
= STuple2 a b
deriving (Generic
)
428 data STuple3 a b c
= STuple3 a b c
deriving (Generic
)
429 data STuple4 a b c d
= STuple4 a b c d
deriving (Generic
)
430 data STuple5 a b c d e
= STuple5 a b c d e
deriving (Generic
)
431 data STuple6 a b c d e f
= STuple6 a b c d e f
deriving (Generic
)
432 data STuple7 a b c d e f g
= STuple7 a b c d e f g
deriving (Generic
)
434 instance (Structured a1
, Structured a2
) => Structured
(STuple2 a1 a2
)
435 instance (Structured a1
, Structured a2
) => Structured
(a1
, a2
) where
436 structure Proxy
= structure
@(STuple2 a1 a2
) Proxy
438 instance (Structured a1
, Structured a2
, Structured a3
) => Structured
(STuple3 a1 a2 a3
)
439 instance (Structured a1
, Structured a2
, Structured a3
) => Structured
(a1
, a2
, a3
) where
440 structure Proxy
= structure
@(STuple3 a1 a2 a3
) Proxy
442 instance (Structured a1
, Structured a2
, Structured a3
, Structured a4
) => Structured
(STuple4 a1 a2 a3 a4
)
443 instance (Structured a1
, Structured a2
, Structured a3
, Structured a4
) => Structured
(a1
, a2
, a3
, a4
) where
444 structure Proxy
= structure
@(STuple4 a1 a2 a3 a4
) Proxy
446 instance (Structured a1
, Structured a2
, Structured a3
, Structured a4
, Structured a5
) => Structured
(STuple5 a1 a2 a3 a4 a5
)
447 instance (Structured a1
, Structured a2
, Structured a3
, Structured a4
, Structured a5
) => Structured
(a1
, a2
, a3
, a4
, a5
) where
448 structure Proxy
= structure
@(STuple5 a1 a2 a3 a4 a5
) Proxy
450 instance (Structured a1
, Structured a2
, Structured a3
, Structured a4
, Structured a5
, Structured a6
) => Structured
(STuple6 a1 a2 a3 a4 a5 a6
)
451 instance (Structured a1
, Structured a2
, Structured a3
, Structured a4
, Structured a5
, Structured a6
) => Structured
(a1
, a2
, a3
, a4
, a5
, a6
) where
452 structure Proxy
= structure
@(STuple6 a1 a2 a3 a4 a5 a6
) Proxy
454 instance (Structured a1
, Structured a2
, Structured a3
, Structured a4
, Structured a5
, Structured a6
, Structured a7
) => Structured
(STuple7 a1 a2 a3 a4 a5 a6 a7
)
455 instance (Structured a1
, Structured a2
, Structured a3
, Structured a4
, Structured a5
, Structured a6
, Structured a7
) => Structured
(a1
, a2
, a3
, a4
, a5
, a6
, a7
) where
456 structure Proxy
= structure
@(STuple7 a1 a2 a3 a4 a5 a6 a7
) Proxy
458 instance Structured BS
.ByteString
where structure
= nominalStructure
459 instance Structured LBS
.ByteString
where structure
= nominalStructure
461 instance Structured T
.Text
where structure
= nominalStructure
462 instance Structured LT
.Text
where structure
= nominalStructure
464 instance (Structured k
, Structured v
) => Structured
(Map
.Map k v
) where structure _
= Nominal
(typeRep
(Proxy
:: Proxy
(Map
.Map k v
))) 0 "Map" [structure
(Proxy
:: Proxy k
), structure
(Proxy
:: Proxy v
)]
465 instance Structured k
=> Structured
(Set
.Set k
) where structure
= containerStructure
466 instance Structured v
=> Structured
(IM
.IntMap v
) where structure
= containerStructure
467 instance Structured IS
.IntSet
where structure
= nominalStructure
468 instance Structured v
=> Structured
(Seq
.Seq v
) where structure
= containerStructure
470 instance Structured Time
.UTCTime
where structure
= nominalStructure
471 instance Structured Time
.DiffTime
where structure
= nominalStructure
472 instance Structured Time
.UniversalTime
where structure
= nominalStructure
473 instance Structured Time
.NominalDiffTime
where structure
= nominalStructure
474 instance Structured Time
.Day where structure
= nominalStructure
475 instance Structured Time
.TimeZone
where structure
= nominalStructure
476 instance Structured Time
.TimeOfDay
where structure
= nominalStructure
477 instance Structured Time
.LocalTime
where structure
= nominalStructure