packages feed

tomland-1.0.0: test/Test/Toml/BiCode/Property.hs

module Test.Toml.BiCode.Property where

import Control.Applicative ((<|>))
import Control.Category ((>>>))
import Data.ByteString (ByteString)
import Data.HashSet (HashSet)
import Data.IntSet (IntSet)
import Data.List.NonEmpty (NonEmpty)
import Data.Set (Set)
import Data.Text (Text)
import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime, zonedTimeToUTC)
import GHC.Exts (fromList)
import Hedgehog (Gen, forAll, tripping)
import Numeric.Natural (Natural)

import Toml (TomlBiMap, TomlCodec, (.=))
import Toml.Bi.Code (decode, encode)

import Test.Toml.Gen (PropertyTest, genBool, genByteString, genDay, genDouble, genFloat, genHashSet,
                      genHours, genInt, genIntSet, genInteger, genLByteString, genLocal, genNatural,
                      genNonEmpty, genString, genText, genWord, genZoned, prop)

import qualified Data.ByteString.Lazy as L
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import qualified Toml

test_encodeDecodeProp :: PropertyTest
test_encodeDecodeProp = prop "decode . encode == id" $ do
    bigType <- forAll genBigType
    tripping bigType (encode bigTypeCodec) (decode bigTypeCodec)

data BigType = BigType
    { btBool          :: Bool
    , btInteger       :: Integer
    , btNatural       :: Natural
    , btInt           :: Int
    , btWord          :: Word
    , btDouble        :: Batman Double
    , btFloat         :: Batman Float
    , btText          :: Text
    , btString        :: String
    , btBS            :: ByteString
    , btLazyBS        :: L.ByteString
    , btLocalTime     :: LocalTime
    , btDay           :: Day
    , btTimeOfDay     :: TimeOfDay
    , btArray         :: [Int]
    , btArraySet      :: Set Word
    , btArrayIntSet   :: IntSet
    , btArrayHashSet  :: HashSet Natural
    , btArrayNonEmpty :: NonEmpty Text
    , btNonEmpty      :: NonEmpty ByteString
    , btList          :: [Bool]
    , btNewtype       :: BigTypeNewtype
    , btSum           :: BigTypeSum
    , btRecord        :: BigTypeRecord
    } deriving (Show, Eq)

-- | Wrapper over 'Double' and 'Float' to be equal on @NaN@ values.
newtype Batman a = Batman a
    deriving (Show)

instance RealFloat a => Eq (Batman a) where
    Batman a == Batman b =
        if isNaN a
            then isNaN b
            else a == b

newtype BigTypeNewtype = BigTypeNewtype ZonedTime
    deriving (Show)

instance Eq BigTypeNewtype where
    (BigTypeNewtype a) == (BigTypeNewtype b) = zonedTimeToUTC a == zonedTimeToUTC b

data BigTypeSum = BigTypeSumA Integer | BigTypeSumB Text
    deriving (Show, Eq)

data BigTypeRecord = BigTypeRecord
    { btrBoolSet     :: Set Bool
    , btrNewtypeList :: [BigTypeSum]
    } deriving (Show, Eq)

bigTypeCodec :: TomlCodec BigType
bigTypeCodec = BigType
    <$> Toml.bool                            "bool"                .= btBool
    <*> Toml.integer                         "integer"             .= btInteger
    <*> Toml.natural                         "natural"             .= btNatural
    <*> Toml.int                             "int"                 .= btInt
    <*> Toml.word                            "word"                .= btWord
    <*> Toml.diwrap (Toml.double "double")                         .= btDouble
    <*> Toml.diwrap (Toml.float "float")                           .= btFloat
    <*> Toml.text                            "text"                .= btText
    <*> Toml.string                          "string"              .= btString
    <*> Toml.byteString                      "bs"                  .= btBS
    <*> Toml.lazyByteString                  "lbs"                 .= btLazyBS
    <*> Toml.localTime                       "localTime"           .= btLocalTime
    <*> Toml.day                             "day"                 .= btDay
    <*> Toml.timeOfDay                       "timeOfDay"           .= btTimeOfDay
    <*> Toml.arrayOf Toml._Int               "arrayOfInt"          .= btArray
    <*> Toml.arraySetOf Toml._Word           "arraySetOfWord"      .= btArraySet
    <*> Toml.arrayIntSet                     "arrayIntSet"         .= btArrayIntSet
    <*> Toml.arrayHashSetOf Toml._Natural    "arrayHashSetDouble"  .= btArrayHashSet
    <*> Toml.arrayNonEmptyOf Toml._Text      "arrayNonEmptyOfText" .= btArrayNonEmpty
    <*> Toml.nonEmpty (Toml.byteString "bs") "nonEmptyBS"          .= btNonEmpty
    <*> Toml.list (Toml.bool "bool")         "listBool"            .= btList
    <*> Toml.diwrap (Toml.zonedTime "nt.zonedTime")                .= btNewtype
    <*> bigTypeSumCodec                                            .= btSum
    <*> Toml.table bigTypeRecordCodec        "table-record"        .= btRecord

_BigTypeSumA :: TomlBiMap BigTypeSum Integer
_BigTypeSumA = Toml.prism BigTypeSumA $ \case
    BigTypeSumA i -> Right i
    other   -> Toml.wrongConstructor "BigTypeSumA" other

_BigTypeSumB :: TomlBiMap BigTypeSum Text
_BigTypeSumB = Toml.prism BigTypeSumB $ \case
    BigTypeSumB n -> Right n
    other    -> Toml.wrongConstructor "BigTypeSumB" other

bigTypeSumCodec :: TomlCodec BigTypeSum
bigTypeSumCodec =
        Toml.match (_BigTypeSumA >>> Toml._Integer) "sum.integer"
    <|> Toml.match (_BigTypeSumB >>> Toml._Text)    "sum.text"

bigTypeRecordCodec :: TomlCodec BigTypeRecord
bigTypeRecordCodec = BigTypeRecord
    <$> Toml.arraySetOf Toml._Bool "rboolSet" .= btrBoolSet
    <*> Toml.list bigTypeSumCodec "rnewtype" .= btrNewtypeList

----------------------------------------------------------------------------
-- Generator
----------------------------------------------------------------------------

genBigType :: Gen BigType
genBigType = do
    btBool          <- genBool
    btInteger       <- genInteger
    btNatural       <- genNatural
    btInt           <- genInt
    btWord          <- genWord
    btDouble        <- Batman <$> genDouble
    btFloat         <- Batman <$> genFloat
    btText          <- genText
    btString        <- genString
    btBS            <- genByteString
    btLazyBS        <- genLByteString
    btLocalTime     <- genLocal
    btDay           <- genDay
    btTimeOfDay     <- genHours
    btArray         <- Gen.list (Range.constant 0 5) genInt
    btArraySet      <- Gen.set (Range.constant 0 5) genWord
    btArrayIntSet   <- genIntSet
    btArrayHashSet  <- genHashSet genNatural
    btArrayNonEmpty <- genNonEmpty genText
    btNonEmpty      <- genNonEmpty genByteString
    btList          <- Gen.list (Range.constant 0 5) genBool
    btNewtype       <- genNewType
    btSum           <- genSum
    btRecord        <- genRec
    pure BigType {..}

-- Custom generators

genNewType :: Gen BigTypeNewtype
genNewType = BigTypeNewtype <$> genZoned

genSum :: Gen BigTypeSum
genSum = Gen.choice
    [ BigTypeSumA <$> genInteger
    , BigTypeSumB <$> genText
    ]

genRec :: Gen BigTypeRecord
genRec = do
    btrBoolSet <- fromList <$> Gen.list (Range.constant 0 5) genBool
    btrNewtypeList <- Gen.list (Range.constant 0 5) genSum
    pure BigTypeRecord{..}