aeson-2.2.1.0: tests/RFC8785.hs
{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
module RFC8785 (rfc8785Tests) where
import Data.Text (Text)
import Data.Word (Word64)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit ((@?=), testCase)
import Test.Tasty.QuickCheck (testProperty, (===))
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Lazy.Char8 as LBS8
import qualified Data.Scientific as Sci
import Data.Aeson
import Data.Aeson.Decoding.ByteString.Lazy
import Data.Aeson.Decoding.Tokens
import Data.Aeson.QQ.Simple
import Data.Aeson.RFC8785
import CastFloat
import DoubleToScientific
rfc8785Tests :: TestTree
rfc8785Tests = testGroup "RFC8785"
[ testCase "example" $ do
encodeCanonical exampleValue @?= exampleBS
, testCase "key-sort" $ do
recordTextValues (encodeCanonical sortingValue) @?= sortingStrings
, numberCases
, testProperty "roundtrip" $ \v ->
decode (encodeCanonical v) === Just (v :: Value)
, castFloatTests
, doubleToScientificTests
]
-------------------------------------------------------------------------------
-- Example
-------------------------------------------------------------------------------
exampleValue :: Value
exampleValue = mapNumbers (doubleToScientific . Sci.toRealFloat) [aesonQQ| {
"numbers": [333333333.33333329, 1E30, 4.50,
2e-3, 0.000000000000000000000000001],
"string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
"literals": [null, true, false]
} |]
mapNumbers :: (Sci.Scientific -> Sci.Scientific) -> Value -> Value
mapNumbers _ v@Null = v
mapNumbers _ v@(String _) = v
mapNumbers _ v@(Bool _) = v
mapNumbers f (Number x) = Number (f x)
mapNumbers f (Object xs) = Object (fmap (mapNumbers f) xs)
mapNumbers f (Array xs) = Array (fmap (mapNumbers f) xs)
exampleBS :: LBS.ByteString
exampleBS = LBS.pack
[ 0x7b, 0x22, 0x6c, 0x69, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x73, 0x22, 0x3a, 0x5b, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x74, 0x72
, 0x75, 0x65, 0x2c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x5d, 0x2c, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x22, 0x3a
, 0x5b, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x2e, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x2c, 0x31
, 0x65, 0x2b, 0x33, 0x30, 0x2c, 0x34, 0x2e, 0x35, 0x2c, 0x30, 0x2e, 0x30, 0x30, 0x32, 0x2c, 0x31, 0x65, 0x2d, 0x32, 0x37
, 0x5d, 0x2c, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x22, 0xe2, 0x82, 0xac, 0x24, 0x5c, 0x75, 0x30, 0x30
, 0x30, 0x66, 0x5c, 0x6e, 0x41, 0x27, 0x42, 0x5c, 0x22, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x22, 0x2f, 0x22, 0x7d
]
-------------------------------------------------------------------------------
-- Key sorting
-------------------------------------------------------------------------------
sortingValue :: Value
sortingValue = [aesonQQ| {
"\u20ac": "Euro Sign",
"\r": "Carriage Return",
"\ufb33": "Hebrew Letter Dalet With Dagesh",
"1": "One",
"\ud83d\ude00": "Emoji: Grinning Face",
"\u0080": "Control",
"\u00f6": "Latin Small Letter O With Diaeresis"
} |]
sortingStrings :: [Text]
sortingStrings =
[ "Carriage Return"
, "One"
, "Control"
, "Latin Small Letter O With Diaeresis"
, "Euro Sign"
, "Emoji: Grinning Face"
, "Hebrew Letter Dalet With Dagesh"
]
recordTextValues :: LBS.ByteString -> [Text]
recordTextValues lbs = case lbsToTokens lbs of
TkRecordOpen r -> go r
_ -> []
where
go (TkPair _ (TkText s ts)) = s : go ts
go _ = []
-------------------------------------------------------------------------------
-- Numbers
-------------------------------------------------------------------------------
-- Appendix B. Number Serialization Samples
numbers :: [(Word64, LBS.ByteString)]
numbers =
[ (0x0000000000000000, "0") -- Zero
, (0x8000000000000000, "0") -- Minus zero
, (0x0000000000000001, "5e-324") -- Min pos number
, (0x8000000000000001, "-5e-324") -- Min neg number
, (0x7fefffffffffffff, "1.7976931348623157e+308") -- Max pos number
, (0xffefffffffffffff, "-1.7976931348623157e+308") -- Max neg number
, (0x4340000000000000, "9007199254740992") -- Max pos int (1)
, (0xc340000000000000, "-9007199254740992") -- Max neg int (1)
, (0x4430000000000000, "295147905179352830000") -- ~2**68 (2)
, (0x44b52d02c7e14af5, "9.999999999999997e+22")
, (0x44b52d02c7e14af6, "1e+23")
, (0x44b52d02c7e14af7, "1.0000000000000001e+23")
, (0x444b1ae4d6e2ef4e, "999999999999999700000")
, (0x444b1ae4d6e2ef4f, "999999999999999900000")
, (0x444b1ae4d6e2ef50, "1e+21")
, (0x3eb0c6f7a0b5ed8c, "9.999999999999997e-7")
, (0x3eb0c6f7a0b5ed8d, "0.000001")
, (0x41b3de4355555553, "333333333.3333332")
, (0x41b3de4355555554, "333333333.33333325")
, (0x41b3de4355555555, "333333333.3333333")
, (0x41b3de4355555556, "333333333.3333334")
, (0x41b3de4355555557, "333333333.33333343")
, (0xbecbf647612f3696, "-0.0000033333333333333333")
, (0x43143ff3c1cb0959, "1424953923781206.2") -- Round to even (4)
]
numberCases :: TestTree
numberCases = testGroup "numbers"
[ testCase (LBS8.unpack lbs) $ do
encodeCanonical (doubleToScientific d) @?= lbs
| (w64, lbs) <- numbers
, let d = castWord64ToDouble w64 :: Double
]