packages feed

highjson-0.4.0.0: test/Data/JsonSpec.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Data.JsonSpec where

import Data.HighJson

import Control.Lens.TH
import Data.Aeson (eitherDecode, encode)
import Data.Typeable
import Test.Hspec
import Test.QuickCheck
import qualified Data.Text as T

data SomeDummy
   = SomeDummy
   { sd_int :: Int
   , sd_bool :: Bool
   , sd_text :: T.Text
   , sd_either :: Either Bool T.Text
   , sd_maybe :: Maybe Int
   } deriving (Show, Eq)

someDummySpec :: RecordTypeSpec SomeDummy '[Int, Bool, T.Text, Either Bool T.Text, Maybe Int]
someDummySpec =
    recSpec "Some Dummy" Nothing SomeDummy $
    "int" .= sd_int
    :& "bool" .= sd_bool
    :& "text" .= sd_text
    :& "either" .= sd_either
    :& "maybe" .= sd_maybe

instance ToJSON SomeDummy where
    toJSON = jsonSerializer someDummySpec
    toEncoding = jsonEncoder someDummySpec

instance FromJSON SomeDummy where
    parseJSON = jsonParser someDummySpec

newtype SomeText = SomeText { unSomeText :: T.Text }

instance Arbitrary SomeText where
    arbitrary = SomeText . T.pack <$> listOf1 (choose ('A', 'Z'))

instance Arbitrary SomeDummy where
    arbitrary =
        SomeDummy
        <$> arbitrary
        <*> arbitrary
        <*> (unSomeText <$> arbitrary)
        <*> ebt
        <*> arbitrary
        where
          ebt =
              do v <- arbitrary
                 case v of
                   Left b -> pure (Left b)
                   Right (SomeText t) -> pure (Right t)

data SomeNested
   = SomeNested
   { sn_list :: [SomeNested]
   , sn_obj :: Maybe SomeNested
   } deriving (Show, Eq, Typeable)

someNestedSpec :: RecordTypeSpec SomeNested '[[SomeNested], Maybe SomeNested]
someNestedSpec =
    recSpec "Nested" Nothing SomeNested $
    "list" .= sn_list
    :& "obj" .=? sn_obj

instance ToJSON SomeNested where
    toJSON = jsonSerializer someNestedSpec
    toEncoding = jsonEncoder someNestedSpec

instance FromJSON SomeNested where
    parseJSON = jsonParser someNestedSpec

data SomeSumType
   = SomeDummyT SomeDummy
   | SomeInt Int
   | SomeBool Bool
   | SomeEnum
   deriving (Show, Eq, Typeable)

makePrisms ''SomeSumType

someSumSpec :: SumTypeSpec SomeSumType '[SomeDummy, Int, Bool, ()]
someSumSpec =
    sumSpec "SomeSum" Nothing $
    "some_dummy" .-> _SomeDummyT
    :& "some_int" .-> _SomeInt
    :& "some_bool" .-> _SomeBool
    :& "some_enum" .-> _SomeEnum

instance ToJSON SomeSumType where
    toJSON = jsonSerializer someSumSpec
    toEncoding = jsonEncoder someSumSpec

instance FromJSON SomeSumType where
    parseJSON = jsonParser someSumSpec

instance Arbitrary SomeSumType where
    arbitrary =
        oneof
        [ SomeDummyT <$> arbitrary
        , SomeInt <$> arbitrary
        , SomeBool <$> arbitrary
        ]

data ParamType a
   = ParamType
   { pt_key :: a
   , pt_val :: T.Text
   } deriving (Show, Eq, Typeable)

paramTypeSpec :: FromJSON a => RecordTypeSpec (ParamType a) '[a, T.Text]
paramTypeSpec =
    recSpec "Some Param" Nothing ParamType $
    "key" .= pt_key
    :& "val" .= pt_val

instance (ToJSON a, FromJSON a) => ToJSON (ParamType a) where
    toJSON = jsonSerializer paramTypeSpec
    toEncoding = jsonEncoder paramTypeSpec

instance (FromJSON a) => FromJSON (ParamType a) where
    parseJSON = jsonParser paramTypeSpec

instance Arbitrary a => Arbitrary (ParamType a) where
    arbitrary =
        ParamType <$> arbitrary <*> (unSomeText <$> arbitrary)

data SomeEnumType
   = SomeEnumA
   | SomeEnumB
   | SomeEnumC
   | SomeEnumD
   deriving (Show, Eq, Typeable)

makePrisms ''SomeEnumType

someEnumSpec :: EnumTypeSpec SomeEnumType '[(), (), (), ()]
someEnumSpec =
    enumSpec "SomeSum" Nothing $
    "a" @-> _SomeEnumA
    :& "b" @-> _SomeEnumB
    :& "c" @-> _SomeEnumC
    :& "d" @-> _SomeEnumD

instance ToJSON SomeEnumType where
    toJSON = jsonSerializer someEnumSpec
    toEncoding = jsonEncoder someEnumSpec

instance FromJSON SomeEnumType where
    parseJSON = jsonParser someEnumSpec

instance Arbitrary SomeEnumType where
    arbitrary =
        oneof
        [ pure SomeEnumA
        , pure SomeEnumB
        , pure SomeEnumC
        , pure SomeEnumD
        ]

spec :: Spec
spec =
    describe "Parser and Serialiser" $
    do it "Handles custom types correctly" $
            do let t1 = SomeDummy 34 True "Teext" (Left False) Nothing
               eitherDecode (encode t1) `shouldBe` Right t1
               let t2 = SomeDummy 34 True "Teext" (Right "ok") (Just 42)
               eitherDecode (encode t2) `shouldBe` Right t2
       it "Handles arbitrary custom types correctly" $ property $ \t ->
           (eitherDecode . encode) t == Right (t :: SomeDummy)
       it "Handles arbitrary custom sum types correctly" $ property $ \t ->
           (eitherDecode . encode) t == Right (t :: SomeSumType)
       it "Handles arbitrary custom enum types correctly" $ property $ \t ->
           (eitherDecode . encode) t == Right (t :: SomeEnumType)
       it "Handles arbitrary custom parametrized types correctly" $ property $ \t ->
           (eitherDecode . encode) t == Right (t :: ParamType SomeSumType)
       it "Handles arbitrary custom parametrized types correctly 2" $ property $ \t ->
           (eitherDecode . encode) t == Right (t :: ParamType SomeEnumType)
       it "Handles nested types correctly" $
          do let nested = SomeNested [SomeNested [] Nothing] (Just $ SomeNested [] Nothing)
             eitherDecode (encode nested) `shouldBe` Right nested