on v1420 help sign in
typeable/aeson/tests/SerializationFormatSpec.hs
270 lines 13 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE GADTs #-}
4{-# LANGUAGE NoImplicitPrelude #-}
5{-# LANGUAGE OverloadedStrings #-}
6#if __GLASGOW_HASKELL__ >= 708
7{-# LANGUAGE DataKinds #-}
8#endif
10------------------------------------------------------------------------------
11-- These tests assert that the JSON serialization doesn't change by accident.
12-----------------------------------------------------------------------------
14module SerializationFormatSpec
15 (
16 tests
17 ) where
19import Prelude.Compat
21import Control.Applicative (Const(..))
22import Data.Aeson (FromJSON(..), decode, encode, genericParseJSON, genericToEncoding, genericToJSON)
23import Data.Aeson.Types (Options(..), SumEncoding(..), ToJSON(..), defaultOptions)
24import Data.Fixed (Pico)
25import Data.Foldable (for_, toList)
26import Data.Functor.Compose (Compose(..))
27import Data.Functor.Identity (Identity(..))
28import Data.Functor.Product (Product(..))
29import Data.Functor.Sum (Sum(..))
30import Data.List.NonEmpty (NonEmpty(..))
31import Data.Proxy (Proxy(..))
32import Data.Scientific (Scientific)
33import Data.Tagged (Tagged(..))
34import Data.Time (fromGregorian)
35import Data.Word (Word8)
36import GHC.Generics (Generic)
37import Instances ()
38import Test.Tasty (TestTree, testGroup)
39import Test.Tasty.HUnit (assertFailure, assertEqual, testCase)
40import Types (Approx(..), Compose3, Compose3', I)
41import qualified Data.ByteString.Lazy.Char8 as L
42import qualified Data.DList as DList
43import qualified Data.HashMap.Strict as HM
44import qualified Data.HashSet as HashSet
45import qualified Data.IntMap as IntMap
46import qualified Data.IntSet as IntSet
47import qualified Data.Map as M
48import qualified Data.Monoid as Monoid
49import qualified Data.Semigroup as Semigroup
50import qualified Data.Sequence as Seq
51import qualified Data.Set as Set
52import qualified Data.Tree as Tree
53import qualified Data.UUID.Types as UUID
54import qualified Data.Vector as Vector
56tests :: [TestTree]
57tests =
58 [
59 testGroup "To JSON representation" $ fmap assertJsonEncodingExample jsonEncodingExamples
60 , testGroup "From JSON representation" $ fmap assertJsonExample jsonDecodingExamples
61 , testGroup "To/From JSON representation" $ fmap assertJsonExample jsonExamples
63 ]
65jsonExamples :: [Example]
66jsonExamples =
67 [
68 example "Either Left" "{\"Left\":1}" (Left 1 :: Either Int Int)
69 , example "Either Right" "{\"Right\":1}" (Right 1 :: Either Int Int)
70 , example "Nothing" "null" (Nothing :: Maybe Int)
71 , example "Just" "1" (Just 1 :: Maybe Int)
72 , example "Proxy Int" "null" (Proxy :: Proxy Int)
73 , example "Tagged Char Int" "1" (Tagged 1 :: Tagged Char Int)
74#if __GLASGOW_HASKELL__ >= 708
75 -- Test Tagged instance is polykinded
76 , example "Tagged 123 Int" "1" (Tagged 1 :: Tagged 123 Int)
77#endif
78 , example "Const Char Int" "\"c\"" (Const 'c' :: Const Char Int)
79 , example "Tuple" "[1,2]" ((1, 2) :: (Int, Int))
80 , example "NonEmpty" "[1,2,3]" (1 :| [2, 3] :: NonEmpty Int)
81 , example "Seq" "[1,2,3]" (Seq.fromList [1, 2, 3] :: Seq.Seq Int)
82 , example "DList" "[1,2,3]" (DList.fromList [1, 2, 3] :: DList.DList Int)
83 , example "()" "[]" ()
85 , Example "HashMap Int Int"
86 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
87 (HM.fromList [(0,1),(2,3)] :: HM.HashMap Int Int)
88 , Example "Map Int Int"
89 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
90 (M.fromList [(0,1),(2,3)] :: M.Map Int Int)
91 , Example "Map (Tagged Int Int) Int"
92 [ "{\"0\":1,\"2\":3}", "{\"2\":3,\"0\":1}"]
93 (M.fromList [(Tagged 0,1),(Tagged 2,3)] :: M.Map (Tagged Int Int) Int)
94 , example "Map [Int] Int"
95 "[[[0],1],[[2],3]]"
96 (M.fromList [([0],1),([2],3)] :: M.Map [Int] Int)
97 , Example "Map [Char] Int"
98 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
99 (M.fromList [("ab",1),("cd",3)] :: M.Map String Int)
100 , Example "Map [I Char] Int"
101 [ "{\"ab\":1,\"cd\":3}", "{\"cd\":3,\"ab\":1}" ]
102 (M.fromList [(map pure "ab",1),(map pure "cd",3)] :: M.Map [I Char] Int)
104 , example "nan :: Double" "null" (Approx $ 0/0 :: Approx Double)
106 , example "Ordering LT" "\"LT\"" LT
107 , example "Ordering EQ" "\"EQ\"" EQ
108 , example "Ordering GT" "\"GT\"" GT
110 , example "Float" "3.14" (3.14 :: Float)
111 , example "Pico" "3.14" (3.14 :: Pico)
112 , example "Scientific" "3.14" (3.14 :: Scientific)
114 , example "UUID" "\"c2cc10e1-57d6-4b6f-9899-38d972112d8c\"" $ UUID.fromWords
115 0xc2cc10e1 0x57d64b6f 0x989938d9 0x72112d8c
117 , example "Set Int" "[1,2,3]" (Set.fromList [3, 2, 1] :: Set.Set Int)
118 , example "IntSet" "[1,2,3]" (IntSet.fromList [3, 2, 1])
119 , example "IntMap" "[[1,2],[3,4]]" (IntMap.fromList [(3,4), (1,2)] :: IntMap.IntMap Int)
120 , example "Vector" "[1,2,3]" (Vector.fromList [1, 2, 3] :: Vector.Vector Int)
121 , example "HashSet Int" "[1,2,3]" (HashSet.fromList [3, 2, 1] :: HashSet.HashSet Int)
122 , example "Tree Int" "[1,[[2,[[3,[]],[4,[]]]],[5,[]]]]" (let n = Tree.Node in n 1 [n 2 [n 3 [], n 4 []], n 5 []] :: Tree.Tree Int)
124 -- Three separate cases, as ordering in HashMap is not defined
125 , example "HashMap Float Int, NaN" "{\"NaN\":1}" (Approx $ HM.singleton (0/0) 1 :: Approx (HM.HashMap Float Int))
126 , example "HashMap Float Int, Infinity" "{\"Infinity\":1}" (HM.singleton (1/0) 1 :: HM.HashMap Float Int)
127 , example "HashMap Float Int, +Infinity" "{\"-Infinity\":1}" (HM.singleton (negate 1/0) 1 :: HM.HashMap Float Int)
129 -- Functors
130 , example "Identity Int" "1" (pure 1 :: Identity Int)
132 , example "Identity Char" "\"x\"" (pure 'x' :: Identity Char)
133 , example "Identity String" "\"foo\"" (pure "foo" :: Identity String)
134 , example "[Identity Char]" "\"xy\"" ([pure 'x', pure 'y'] :: [Identity Char])
136 , example "Maybe Char" "\"x\"" (pure 'x' :: Maybe Char)
137 , example "Maybe String" "\"foo\"" (pure "foo" :: Maybe String)
138 , example "Maybe [Identity Char]" "\"xy\"" (pure [pure 'x', pure 'y'] :: Maybe [Identity Char])
140 , example "Day; year >= 1000" "\"1999-10-12\"" (fromGregorian 1999 10 12)
141 , example "Day; year > 0 && < 1000" "\"0500-03-04\"" (fromGregorian 500 3 4)
142 , example "Day; year == 0" "\"0000-02-20\"" (fromGregorian 0 2 20)
143 , example "Day; year < 0" "\"-0234-01-01\"" (fromGregorian (-234) 1 1)
144 , example "Day; year < -1000" "\"-1234-01-01\"" (fromGregorian (-1234) 1 1)
146 , example "Product I Maybe Int" "[1,2]" (Pair (pure 1) (pure 2) :: Product I Maybe Int)
147 , example "Product I Maybe Int" "[1,null]" (Pair (pure 1) Nothing :: Product I Maybe Int)
148 , example "Product I [] Char" "[\"a\",\"foo\"]" (Pair (pure 'a') "foo" :: Product I [] Char)
150 , example "Sum I [] Int: InL" "{\"InL\":1}" (InL (pure 1) :: Sum I [] Int)
151 , example "Sum I [] Int: InR" "{\"InR\":[1,2]}" (InR [1, 2] :: Sum I [] Int)
152 , example "Sum I [] Char: InR" "{\"InR\":\"foo\"}" (InR "foo" :: Sum I [] Char)
154 , example "Compose I I Int" "1" (pure 1 :: Compose I I Int)
155 , example "Compose I [] Int" "[1]" (pure 1 :: Compose I [] Int)
156 , example "Compose [] I Int" "[1]" (pure 1 :: Compose [] I Int)
157 , example "Compose [] [] Int" "[[1]]" (pure 1 :: Compose [] [] Int)
159 , example "Compose I I Char" "\"x\"" (pure 'x' :: Compose I I Char)
160 , example "Compose I [] Char" "\"x\"" (pure 'x' :: Compose I [] Char)
161 , example "Compose [] I Char" "\"x\"" (pure 'x' :: Compose [] I Char)
162 , example "Compose [] [] Char" "[\"x\"]" (pure 'x' :: Compose [] [] Char)
164 , example "Compose3 I I I Char" "\"x\"" (pure 'x' :: Compose3 I I I Char)
165 , example "Compose3 I I [] Char" "\"x\"" (pure 'x' :: Compose3 I I [] Char)
166 , example "Compose3 I [] I Char" "\"x\"" (pure 'x' :: Compose3 I [] I Char)
167 , example "Compose3 I [] [] Char" "[\"x\"]" (pure 'x' :: Compose3 I [] [] Char)
168 , example "Compose3 [] I I Char" "\"x\"" (pure 'x' :: Compose3 [] I I Char)
169 , example "Compose3 [] I [] Char" "[\"x\"]" (pure 'x' :: Compose3 [] I [] Char)
170 , example "Compose3 [] [] I Char" "[\"x\"]" (pure 'x' :: Compose3 [] [] I Char)
171 , example "Compose3 [] [] [] Char" "[[\"x\"]]" (pure 'x' :: Compose3 [] [] [] Char)
173 , example "Compose3' I I I Char" "\"x\"" (pure 'x' :: Compose3' I I I Char)
174 , example "Compose3' I I [] Char" "\"x\"" (pure 'x' :: Compose3' I I [] Char)
175 , example "Compose3' I [] I Char" "\"x\"" (pure 'x' :: Compose3' I [] I Char)
176 , example "Compose3' I [] [] Char" "[\"x\"]" (pure 'x' :: Compose3' I [] [] Char)
177 , example "Compose3' [] I I Char" "\"x\"" (pure 'x' :: Compose3' [] I I Char)
178 , example "Compose3' [] I [] Char" "[\"x\"]" (pure 'x' :: Compose3' [] I [] Char)
179 , example "Compose3' [] [] I Char" "[\"x\"]" (pure 'x' :: Compose3' [] [] I Char)
180 , example "Compose3' [] [] [] Char" "[[\"x\"]]" (pure 'x' :: Compose3' [] [] [] Char)
182 , example "MyEither Int String: Left" "42" (MyLeft 42 :: MyEither Int String)
183 , example "MyEither Int String: Right" "\"foo\"" (MyRight "foo" :: MyEither Int String)
185 -- newtypes from Monoid/Semigroup
186 , example "Monoid.Dual Int" "2" (pure 2 :: Monoid.Dual Int)
187 , example "Monoid.First Int" "2" (pure 2 :: Monoid.First Int)
188 , example "Monoid.Last Int" "2" (pure 2 :: Monoid.Last Int)
189 , example "Semigroup.Min Int" "2" (pure 2 :: Semigroup.Min Int)
190 , example "Semigroup.Max Int" "2" (pure 2 :: Semigroup.Max Int)
191 , example "Semigroup.First Int" "2" (pure 2 :: Semigroup.First Int)
192 , example "Semigroup.Last Int" "2" (pure 2 :: Semigroup.Last Int)
193 , example "Semigroup.WrappedMonoid Int" "2" (Semigroup.WrapMonoid 2 :: Semigroup.WrappedMonoid Int)
194 , example "Semigroup.Option Just" "2" (pure 2 :: Semigroup.Option Int)
195 , example "Semigroup.Option Nothing" "null" (Semigroup.Option (Nothing :: Maybe Bool))
196 ]
198jsonEncodingExamples :: [Example]
199jsonEncodingExamples =
200 [
201 -- Maybe serialising is lossy
202 -- https://github.com/bos/aeson/issues/376
203 example "Just Nothing" "null" (Just Nothing :: Maybe (Maybe Int))
204 -- infinities cannot be recovered, null is decoded as NaN
205 , example "inf :: Double" "null" (Approx $ 1/0 :: Approx Double)
206 ]
208jsonDecodingExamples :: [Example]
209jsonDecodingExamples = [
210 -- Maybe serialising is lossy
211 -- https://github.com/bos/aeson/issues/376
212 MaybeExample "Nothing" "null" (Just Nothing :: Maybe (Maybe Int))
213 , MaybeExample "Just" "1" (Just $ Just 1 :: Maybe (Maybe Int))
214 , MaybeExample "Just Nothing" "null" (Just Nothing :: Maybe (Maybe (Maybe Int)))
215 -- Integral values are truncated, and overflowed
216 -- https://github.com/bos/aeson/issues/317
217 , MaybeExample "Word8 3" "3" (Just 3 :: Maybe Word8)
218 , MaybeExample "Word8 3.00" "3.00" (Just 3 :: Maybe Word8)
219 , MaybeExample "Word8 3.14" "3.14" (Nothing :: Maybe Word8)
220 , MaybeExample "Word8 -1" "-1" (Nothing :: Maybe Word8)
221 , MaybeExample "Word8 300" "300" (Nothing :: Maybe Word8)
222 -- Negative zero year, encoding never produces such:
223 , MaybeExample "Day -0000-02-03" "\"-0000-02-03\"" (Just (fromGregorian 0 2 3))
224 ]
226data Example where
227 Example
228 :: (Eq a, Show a, ToJSON a, FromJSON a)
229 => String -> [L.ByteString] -> a -> Example -- empty bytestring will fail, any p [] == False
230 MaybeExample
231 :: (Eq a, Show a, FromJSON a)
232 => String -> L.ByteString -> Maybe a -> Example
234example :: (Eq a, Show a, ToJSON a, FromJSON a)
235 => String -> L.ByteString -> a -> Example
236example n bs x = Example n [bs] x
238data MyEither a b = MyLeft a | MyRight b
239 deriving (Generic, Show, Eq)
241instance (ToJSON a, ToJSON b) => ToJSON (MyEither a b) where
242 toJSON = genericToJSON defaultOptions { sumEncoding = UntaggedValue }
243 toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }
245instance (FromJSON a, FromJSON b) => FromJSON (MyEither a b) where
246 parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }
248assertJsonExample :: Example -> TestTree
249assertJsonExample (Example name bss val) = testCase name $ do
250 assertSomeEqual "encode" bss (encode val)
251 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
252 for_ bss $ \bs ->
253 assertEqual "decode" (Just val) (decode bs)
254assertJsonExample (MaybeExample name bs mval) = testCase name $
255 assertEqual "decode" mval (decode bs)
257assertJsonEncodingExample :: Example -> TestTree
258assertJsonEncodingExample (Example name bss val) = testCase name $ do
259 assertSomeEqual "encode" bss (encode val)
260 assertSomeEqual "encode/via value" bss (encode $ toJSON val)
261assertJsonEncodingExample (MaybeExample name _ _) = testCase name $
262 assertFailure "cannot encode MaybeExample"
264assertSomeEqual :: (Eq a, Show a, Foldable f) => String -> f a -> a -> IO ()
265assertSomeEqual preface expected actual
266 | actual `elem` expected = return ()
267 | otherwise = assertFailure $ preface
268 ++ ": expecting one of " ++ show (toList expected)
269 ++ ", got " ++ show actual