jordan-0.1.0.0: test/Jordan/RoundTripSpec.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Jordan.RoundTripSpec
where
import Data.ByteString.Lazy (fromStrict, toStrict)
import Data.Functor.Contravariant (Contravariant(..))
import Data.Proxy (Proxy(..))
import Data.Text (Text, unpack)
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import GHC.Generics
import Jordan (parseViaAttoparsec, parseViaMegaparsec, toJSONText, toJSONViaBuilder)
import Jordan.FromJSON.Class (FromJSON(..), JSONParser(..))
import Jordan.ToJSON.Class (JSONSerializer(..), ToJSON(..))
import Test.Hspec (Spec, describe)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck
import Test.QuickCheck.Utf8
makeResult
:: (ToJSON a, FromJSON a, Arbitrary a, Show a, Eq a)
=> Proxy a
-> (a -> b)
-> (b -> String)
-> (b -> Either String a)
-> Property
makeResult (Proxy :: Proxy a) convForward convString convBack =
forAllShow (arbitrary @a) showResult convert
where
showResult a
= show a
<> "\n"
<> convString (convForward a)
<> "\n"
<> showError (convBack $ convForward a)
convert a = convBack (convForward a) == pure a
showError :: Either String a -> String
showError (Left err) = "Error\n" <> err
showError (Right a) = "Success: " <> show a
newtype ExtremelyBasic
= ExtremelyBasic { getExtremelyBasic :: () }
deriving (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)
instance Arbitrary ExtremelyBasic where
arbitrary = ExtremelyBasic <$> arbitrary
data TwoFieldsRec
= TwoFieldsRec { firstField :: Int, secondField :: Int }
deriving (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)
instance Arbitrary TwoFieldsRec where
arbitrary = TwoFieldsRec <$> arbitrary <*> arbitrary
data RLMSum
= Mike
| Jay
| Rich
| Jack
| Josh
deriving (Eq, Show, Generic, Bounded, Enum)
deriving anyclass (ToJSON, FromJSON)
instance Arbitrary RLMSum where
arbitrary = arbitraryBoundedEnum
data ManyChoices
= ChoseFirst { getFirst :: Int }
| ChoseSecond { getSecondA :: Int, getSecondB :: Int }
deriving (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)
instance Arbitrary ManyChoices where
arbitrary
= oneof
[ ChoseFirst <$> arbitrary
, ChoseSecond <$> arbitrary <*> arbitrary
]
data FakePerson
= FakePerson
{ age :: Int
, name :: String
, cool :: Bool
} deriving (Eq, Show, Generic)
deriving anyclass (FromJSON, ToJSON)
instance Arbitrary FakePerson where
arbitrary
= FakePerson
<$> arbitrary
<*> fmap unpack genValidUtf8
<*> arbitrary
newtype OnlyText = OnlyText { getText :: Text }
deriving (Show, Eq)
instance ToJSON OnlyText where
toJSON = contramap getText serializeText
instance FromJSON OnlyText where
fromJSON = OnlyText <$> parseText
instance Arbitrary OnlyText where
arbitrary = OnlyText <$> genValidUtf8
data EnumyObject
= EnumA
| EnumB
| EnumC
| EnumObject { enumValue :: Text }
deriving (Show, Eq, Generic)
deriving anyclass (ToJSON, FromJSON)
instance Arbitrary EnumyObject where
arbitrary
= oneof
[ pure EnumA
, pure EnumB
, pure EnumC
, EnumObject <$> genValidUtf8
]
data AllTogether
= AllTogether
{ extremelyBasic :: ExtremelyBasic
, twoFieldsRec :: TwoFieldsRec
, rlmSum :: RLMSum
, fakePerson :: FakePerson
, onlyText :: OnlyText
, enumyObject :: EnumyObject
} deriving (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)
instance Arbitrary AllTogether where
arbitrary
= AllTogether
<$> arbitrary
<*> arbitrary
<*> arbitrary
<*> arbitrary
<*> arbitrary
<*> arbitrary
showViaText :: (ToJSON a) => a -> String
showViaText = unpack . toJSONText
showViaBuilder :: (ToJSON a) => a -> String
showViaBuilder = unpack . decodeUtf8 . toStrict . toJSONViaBuilder
roundtrips
:: (Arbitrary a, Show a, Eq a, ToJSON a, FromJSON a)
=> String
-> Proxy a
-> Spec
roundtrips n p = describe ("round-trippping " <> n) $ do
describe "when serializing via text" $ do
let q = makeResult p toJSONText unpack
prop "roundtrips back via megaparsec" $
q parseViaMegaparsec
prop "roundtrips back via attoparsec" $
q (parseViaAttoparsec . encodeUtf8)
describe "when serializing via a builder" $ do
let q = makeResult p toJSONViaBuilder (unpack . decodeUtf8 . toStrict)
prop "roundtrips back via megaparsec" $
q (parseViaMegaparsec . decodeUtf8 . toStrict)
prop "roundtrips back via attoparsec" $
q (parseViaAttoparsec . toStrict)
spec :: Spec
spec = describe "round-tripping generic values" $ do
roundtrips "a newtype around ()" (Proxy @ExtremelyBasic)
roundtrips "a record with two int fields" (Proxy @TwoFieldsRec)
roundtrips "a sum enum type" (Proxy @RLMSum)
roundtrips "a type with some text" (Proxy @FakePerson)
roundtrips "just text" (Proxy @OnlyText)
roundtrips "an object with some constructors as enums" (Proxy @EnumyObject)
roundtrips "an object composed of all rountrip'd objects" (Proxy @AllTogether)