yamlet-1.0.0.0: tests/Yamlet/Test/TypeError.hs
{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}
-- | The type errors of the generic instances. The module defers type errors,
-- so an instance with a type error compiles, and using it throws the error.
module Yamlet.Test.TypeError (typeErrorTests) where
import Control.Exception
import Data.List qualified as L
import Data.Text qualified as T
import Test.Tasty
import Test.Tasty.HUnit
import Yamlet
typeErrorTests :: TestTree
typeErrorTests =
testGroup
"type errors"
[ testCase "several fields without names" $ do
rejects
"The constructor Pair has several fields without names."
(encodeText (Pair 1 "a"))
rejects
"The constructor Pair has several fields without names."
(decodeText @Pair "[1, a]")
rejects "Give the fields names." (encodeText (Pair 1 "a"))
, testCase "several fields without names in a sum" $
rejects
"The constructor Line has several fields without names."
(encodeText (Line 1 2))
, testCase "named fields and a field without a name" $ do
rejects
"The constructor Circle has named fields and the constructor Label has one field without a name."
(encodeText (Label "x"))
rejects "use the sum encoding SingleField" (encodeText (Label "x"))
, testCase "flat named fields" $ do
rejects
"TaggedFlat needs constructors with one field without a name, but the constructor Jump has named fields."
(encodeText (Jump 1))
rejects flatFieldsFix (encodeText (Jump 1))
, testCase "flat several fields without names" $ do
rejects
"The constructor Leap has several fields without names."
(encodeText (Leap 1 2))
rejects flatFieldsFix (encodeText (Leap 1 2))
, testCase "flat named fields and a field without a name" $ do
rejects
"TaggedFlat needs constructors with one field without a name, but the constructor Run has named fields."
(encodeText (Wait 1))
rejects flatFieldsFix (encodeText (Wait 1))
, testCase "several fields without names in a single field" $
rejects
"The constructor Coords has several fields without names."
(encodeText (Coords 1 2))
, testCase "no constructors" $
rejects
"A type without constructors cannot derive FromYaml or ToYaml"
(decodeText @Empty "null")
]
data Pair = Pair Int T.Text
deriving stock (Generic)
deriving anyclass (GenericYamlOptions)
deriving (FromYaml, ToYaml) via GenericYaml Pair
data Segment = Line Double Double | Dot
deriving stock (Generic)
deriving anyclass (GenericYamlOptions)
deriving (FromYaml, ToYaml) via GenericYaml Segment
data Mixed = Circle {radius :: Double} | Label T.Text
deriving stock (Generic)
deriving anyclass (GenericYamlOptions)
deriving (FromYaml, ToYaml) via GenericYaml Mixed
data FlatNamed = Jump {height :: Int} | Halt
deriving stock (Generic)
deriving (FromYaml, ToYaml) via GenericYaml FlatNamed
instance GenericYamlOptions FlatNamed where
type SumEncoding FlatNamed = TaggedFlat
data FlatPair = Leap Int Int | Rest
deriving stock (Generic)
deriving (FromYaml, ToYaml) via GenericYaml FlatPair
instance GenericYamlOptions FlatPair where
type SumEncoding FlatPair = TaggedFlat
data FlatMixed = Run {speed :: Int} | Wait Int | Idle
deriving stock (Generic)
deriving (FromYaml, ToYaml) via GenericYaml FlatMixed
instance GenericYamlOptions FlatMixed where
type SumEncoding FlatMixed = TaggedFlat
flatFieldsFix :: String
flatFieldsFix =
"Put the fields in a record type, and make it the one field of the constructor."
data Place = Coords Double Double | Nowhere
deriving stock (Generic)
deriving (FromYaml, ToYaml) via GenericYaml Place
instance GenericYamlOptions Place where
type SumEncoding Place = SingleField
data Empty
deriving stock (Generic)
deriving anyclass (GenericYamlOptions)
deriving (FromYaml, ToYaml) via GenericYaml Empty
-- | Using the value throws a deferred type error with the message.
rejects :: String -> a -> Assertion
rejects expected x =
try (evaluate x) >>= \case
Left (TypeError msg) ->
assertBool
("the message contains " ++ show expected ++ ":\n" ++ msg)
(expected `L.isInfixOf` msg)
Right _ -> assertFailure "expected a type error"