swagger2-2.1.3: test/Data/Swagger/Schema/ValidationSpec.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE PackageImports #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Data.Swagger.Schema.ValidationSpec where
import Control.Applicative
import Data.Aeson
import Data.Aeson.Types
import Data.Int
import Data.IntMap (IntMap)
import Data.Hashable (Hashable)
import "unordered-containers" Data.HashSet (HashSet)
import qualified "unordered-containers" Data.HashSet as HashSet
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.Map (Map)
import Data.Proxy
import Data.Time
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Set (Set)
import Data.Word
import GHC.Generics
import Data.Swagger
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
shouldValidate :: (ToJSON a, ToSchema a) => Proxy a -> a -> Bool
shouldValidate _ x = validateToJSON x == []
spec :: Spec
spec = do
describe "Validation" $ do
prop "Bool" $ shouldValidate (Proxy :: Proxy Bool)
prop "Char" $ shouldValidate (Proxy :: Proxy Char)
prop "Double" $ shouldValidate (Proxy :: Proxy Double)
prop "Float" $ shouldValidate (Proxy :: Proxy Float)
prop "Int" $ shouldValidate (Proxy :: Proxy Int)
prop "Int8" $ shouldValidate (Proxy :: Proxy Int8)
prop "Int16" $ shouldValidate (Proxy :: Proxy Int16)
prop "Int32" $ shouldValidate (Proxy :: Proxy Int32)
prop "Int64" $ shouldValidate (Proxy :: Proxy Int64)
prop "Integer" $ shouldValidate (Proxy :: Proxy Integer)
prop "Word" $ shouldValidate (Proxy :: Proxy Word)
prop "Word8" $ shouldValidate (Proxy :: Proxy Word8)
prop "Word16" $ shouldValidate (Proxy :: Proxy Word16)
prop "Word32" $ shouldValidate (Proxy :: Proxy Word32)
prop "Word64" $ shouldValidate (Proxy :: Proxy Word64)
prop "String" $ shouldValidate (Proxy :: Proxy String)
prop "()" $ shouldValidate (Proxy :: Proxy ())
prop "ZonedTime" $ shouldValidate (Proxy :: Proxy ZonedTime)
prop "UTCTime" $ shouldValidate (Proxy :: Proxy UTCTime)
prop "T.Text" $ shouldValidate (Proxy :: Proxy T.Text)
prop "TL.Text" $ shouldValidate (Proxy :: Proxy TL.Text)
prop "[String]" $ shouldValidate (Proxy :: Proxy [String])
-- prop "(Maybe [Int])" $ shouldValidate (Proxy :: Proxy (Maybe [Int]))
prop "(IntMap String)" $ shouldValidate (Proxy :: Proxy (IntMap String))
prop "(Set Bool)" $ shouldValidate (Proxy :: Proxy (Set Bool))
prop "(HashSet Bool)" $ shouldValidate (Proxy :: Proxy (HashSet Bool))
prop "(Either Int String)" $ shouldValidate (Proxy :: Proxy (Either Int String))
prop "(Int, String)" $ shouldValidate (Proxy :: Proxy (Int, String))
prop "(Map String Int)" $ shouldValidate (Proxy :: Proxy (Map String Int))
prop "(Map T.Text Int)" $ shouldValidate (Proxy :: Proxy (Map T.Text Int))
prop "(Map TL.Text Bool)" $ shouldValidate (Proxy :: Proxy (Map TL.Text Bool))
prop "(HashMap String Int)" $ shouldValidate (Proxy :: Proxy (HashMap String Int))
prop "(HashMap T.Text Int)" $ shouldValidate (Proxy :: Proxy (HashMap T.Text Int))
prop "(HashMap TL.Text Bool)" $ shouldValidate (Proxy :: Proxy (HashMap TL.Text Bool))
prop "(Int, String, Double)" $ shouldValidate (Proxy :: Proxy (Int, String, Double))
prop "(Int, String, Double, [Int])" $ shouldValidate (Proxy :: Proxy (Int, String, Double, [Int]))
prop "(Int, String, Double, [Int], Int)" $ shouldValidate (Proxy :: Proxy (Int, String, Double, [Int], Int))
prop "Person" $ shouldValidate (Proxy :: Proxy Person)
prop "Color" $ shouldValidate (Proxy :: Proxy Color)
prop "Paint" $ shouldValidate (Proxy :: Proxy Paint)
prop "MyRoseTree" $ shouldValidate (Proxy :: Proxy MyRoseTree)
prop "Light" $ shouldValidate (Proxy :: Proxy Light)
main :: IO ()
main = hspec spec
-- ========================================================================
-- Person (simple record with optional fields)
-- ========================================================================
data Person = Person
{ name :: String
, phone :: Integer
, email :: Maybe String
} deriving (Show, Generic)
instance ToJSON Person
instance ToSchema Person
instance Arbitrary Person where
arbitrary = Person <$> arbitrary <*> arbitrary <*> arbitrary
-- ========================================================================
-- Color (enum)
-- ========================================================================
data Color = Red | Green | Blue deriving (Show, Generic, Bounded, Enum)
instance ToJSON Color
instance ToSchema Color
instance Arbitrary Color where
arbitrary = arbitraryBoundedEnum
-- ========================================================================
-- Paint (record with bounded enum property)
-- ========================================================================
newtype Paint = Paint { color :: Color }
deriving (Show, Generic)
instance ToJSON Paint
instance ToSchema Paint
instance Arbitrary Paint where
arbitrary = Paint <$> arbitrary
-- ========================================================================
-- MyRoseTree (custom datatypeNameModifier)
-- ========================================================================
data MyRoseTree = MyRoseTree
{ root :: String
, trees :: [MyRoseTree]
} deriving (Show, Generic)
instance ToJSON MyRoseTree
instance ToSchema MyRoseTree where
declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
{ datatypeNameModifier = drop (length "My") }
instance Arbitrary MyRoseTree where
arbitrary = fmap (cut limit) $ MyRoseTree <$> arbitrary <*> (take limit <$> arbitrary)
where
limit = 4
cut 0 (MyRoseTree x _ ) = MyRoseTree x []
cut n (MyRoseTree x xs) = MyRoseTree x (map (cut (n - 1)) xs)
-- ========================================================================
-- Light (sum type)
-- ========================================================================
data Light = NoLight | LightFreq Double | LightColor Color deriving (Show, Generic)
instance ToSchema Light
instance ToJSON Light where
toJSON = genericToJSON defaultOptions { sumEncoding = ObjectWithSingleField }
instance Arbitrary Light where
arbitrary = oneof
[ return NoLight
, LightFreq <$> arbitrary
, LightColor <$> arbitrary
]
-- Arbitrary instances for common types
instance (Eq k, Hashable k, Arbitrary k, Arbitrary v) => Arbitrary (HashMap k v) where
arbitrary = HashMap.fromList <$> arbitrary
instance (Eq a, Hashable a, Arbitrary a) => Arbitrary (HashSet a) where
arbitrary = HashSet.fromList <$> arbitrary
instance Arbitrary T.Text where
arbitrary = T.pack <$> arbitrary
instance Arbitrary TL.Text where
arbitrary = TL.pack <$> arbitrary
instance Arbitrary Day where
arbitrary = liftA3 fromGregorian (fmap ((+ 1) . abs) arbitrary) arbitrary arbitrary
instance Arbitrary LocalTime where
arbitrary = LocalTime
<$> arbitrary
<*> liftA3 TimeOfDay (choose (0, 23)) (choose (0, 59)) (fromInteger <$> choose (0, 60))
instance Eq ZonedTime where
ZonedTime t (TimeZone x _ _) == ZonedTime t' (TimeZone y _ _) = t == t' && x == y
instance Arbitrary ZonedTime where
arbitrary = ZonedTime
<$> arbitrary
<*> liftA3 TimeZone arbitrary arbitrary (vectorOf 3 (elements ['A'..'Z']))
instance Arbitrary UTCTime where
arbitrary = UTCTime <$> arbitrary <*> fmap fromInteger (choose (0, 86400))