jsons-to-schema-0.1.0.0: test/JSONSchema/Draft4/QuickCheckInstances.hs
module JSONSchema.Draft4.QuickCheckInstances where
import Protolude
import qualified Data.Aeson as AE
import qualified Data.HashMap.Lazy as HM
import qualified Data.Vector as V
import JSONSchema.Draft4
import JSONSchema.Validator.Utils
import Data.Generics.Uniplate.Data
import Test.QuickCheck
import Test.QuickCheck.Instances ()
-- N.B. it is in general not true that the empty schema acts as the identity
-- value with regards to unifySchema because the empty schema represents a
-- more relaxed value for required, which we
-- must respect. To get around this, we create a newtype RestrictedSchema
-- which has a different instance of Arbitrary which always returns Nothing for
-- required, thus avoiding this issue.
newtype RestrictedSchema = RestrictedSchema
{ getSchema :: Schema
} deriving (Show)
instance Arbitrary RestrictedSchema where
arbitrary = sized f
where
maybeGen :: Gen a -> Gen (Maybe a)
maybeGen a = oneof [pure Nothing, Just <$> a]
maybeRecurse :: Int -> Gen a -> Gen (Maybe a)
maybeRecurse n a
| n < 1 = pure Nothing
| otherwise = maybeGen $ resize (n `div` 10) a
f :: Int -> Gen RestrictedSchema
f n = do
a <- maybeGen arbitraryText
b <- maybeGen arbitraryText
c <- maybeGen arbitraryText
d <- pure Nothing
e <- pure mempty
f' <- maybeGen arbitraryPositiveScientific
g <- maybeGen arbitraryScientific
h <- maybeGen arbitrary
i <- maybeGen arbitraryScientific
j <- maybeGen arbitrary
k <- maybeGen (getPositive <$> arbitrary)
l <- maybeGen (getPositive <$> arbitrary)
m <- maybeGen arbitraryText
n' <- maybeGen (getPositive <$> arbitrary)
o <- maybeGen (getPositive <$> arbitrary)
p <- arbitrary
q <- maybeRecurse n arbitrary
r <- maybeRecurse n arbitrary
s <- maybeGen (getPositive <$> arbitrary)
t <- maybeGen (getPositive <$> arbitrary)
u <- pure Nothing
v <- maybeRecurse n arbitraryHashMap
w <- maybeRecurse n arbitraryHashMap
x <- maybeRecurse n arbitraryHashMap
y <- maybeRecurse n arbitrary
z <-
maybeRecurse n (fmap _unArbitraryValue . _unNonEmpty' <$> arbitrary)
a2 <- arbitrary
b2 <- maybeRecurse n (_unNonEmpty' <$> arbitrary)
c2 <- maybeRecurse n (_unNonEmpty' <$> arbitrary)
d2 <- maybeRecurse n (_unNonEmpty' <$> arbitrary)
e2 <- maybeRecurse n arbitrary
pure $
RestrictedSchema
Schema {
_schemaVersion = a
, _schemaId = b
, _schemaRef = c
, _schemaDefinitions = d
, _schemaOther = e
, _schemaMultipleOf = f'
, _schemaMaximum = g
, _schemaExclusiveMaximum = h
, _schemaMinimum = i
, _schemaExclusiveMinimum = j
, _schemaMaxLength = k
, _schemaMinLength = l
, _schemaPattern = m
, _schemaMaxItems = n'
, _schemaMinItems = o
, _schemaUniqueItems = p
, _schemaItems = q
, _schemaAdditionalItems = r
, _schemaMaxProperties = s
, _schemaMinProperties = t
, _schemaRequired = u
, _schemaDependencies = v
, _schemaProperties = w
, _schemaPatternProperties = x
, _schemaAdditionalProperties = y
, _schemaEnum = z
, _schemaType = a2
, _schemaAllOf = b2
, _schemaAnyOf = c2
, _schemaOneOf = d2
, _schemaNot = e2
}
-- For properties that cannot be easily unified we simply choose
-- one (e.g. "version": "1.0" and "version": "2.0"). By excluding
-- these properties, unified with `const`, we can test if a
-- unifySchema is commutative otherwise
newtype CommutativeSchema = CommutativeSchema
{ getCommutativeSchema :: Schema
} deriving (Show)
instance Arbitrary CommutativeSchema where
arbitrary = sized f
where
maybeGen :: Gen a -> Gen (Maybe a)
maybeGen a = oneof [pure Nothing, Just <$> a]
maybeRecurse :: Int -> Gen a -> Gen (Maybe a)
maybeRecurse n a
| n < 1 = pure Nothing
| otherwise = maybeGen $ resize (n `div` 10) a
f :: Int -> Gen CommutativeSchema
f n = do
a <- pure Nothing
b <- pure Nothing
c <- pure Nothing
d <- pure Nothing
e <- pure mempty
f' <- pure Nothing
g <- maybeGen arbitraryScientific
h <- maybeGen arbitrary
i <- maybeGen arbitraryScientific
j <- maybeGen arbitrary
k <- maybeGen (getPositive <$> arbitrary)
l <- maybeGen (getPositive <$> arbitrary)
m <- pure Nothing
n' <- maybeGen (getPositive <$> arbitrary)
o <- maybeGen (getPositive <$> arbitrary)
p <- arbitrary
q <- maybeRecurse n arbitrary
r <- maybeRecurse n arbitrary
s <- maybeGen (getPositive <$> arbitrary)
t <- maybeGen (getPositive <$> arbitrary)
u <- pure Nothing
v <- pure Nothing
w <- maybeRecurse n arbitraryHashMap
x <- maybeRecurse n arbitraryHashMap
y <- maybeRecurse n arbitrary
z <- pure Nothing
a2 <- arbitrary
b2 <- pure Nothing
c2 <- pure Nothing
d2 <- pure Nothing
e2 <- pure Nothing
pure $
CommutativeSchema
Schema {
_schemaVersion = a
, _schemaId = b
, _schemaRef = c
, _schemaDefinitions = d
, _schemaOther = e
, _schemaMultipleOf = f'
, _schemaMaximum = g
, _schemaExclusiveMaximum = h
, _schemaMinimum = i
, _schemaExclusiveMinimum = j
, _schemaMaxLength = k
, _schemaMinLength = l
, _schemaPattern = m
, _schemaMaxItems = n'
, _schemaMinItems = o
, _schemaUniqueItems = p
, _schemaItems = q
, _schemaAdditionalItems = r
, _schemaMaxProperties = s
, _schemaMinProperties = t
, _schemaRequired = u
, _schemaDependencies = v
, _schemaProperties = w
, _schemaPatternProperties = x
, _schemaAdditionalProperties = y
, _schemaEnum = z
, _schemaType = a2
, _schemaAllOf = b2
, _schemaAnyOf = c2
, _schemaOneOf = d2
, _schemaNot = e2
}
-- Arbitrary instance for Value from Reddit.
instance Arbitrary AE.Value where
arbitrary = sized sizedArbitraryValue
-- JSON shrinker borrowed from json-autotype
shrink = valueShrink
sizedArbitraryValue :: Int -> Gen AE.Value
sizedArbitraryValue n
| n <= 0 =
oneof
[ pure AE.Null
, AE.Bool <$> arbitrary
, AE.Number <$> arbitrary
, AE.String <$> arbitrary
]
| otherwise =
resize (div n 2) $
oneof
[ pure AE.Null
, AE.Bool <$> arbitrary
, AE.Number <$> arbitrary
, AE.String <$> arbitrary
, AE.Array <$> arbitrary
, AE.Object <$> arbitrary
]
simpleShrink :: AE.Value -> [AE.Value]
simpleShrink (AE.Array a) = map (AE.Array . V.fromList) $ shrink $ V.toList a
simpleShrink (AE.Object o) =
map (AE.Object . HM.fromList) $ shrink $ HM.toList o
simpleShrink _ = [] -- Nothing for simple objects
valueShrink :: AE.Value -> [AE.Value]
valueShrink = concatMap simpleShrink . universe
sizedJsonProp :: Int -> (AE.Value -> Property) -> Property
sizedJsonProp size = forAllShrink jsonGen valueShrink
where
jsonGen :: Gen AE.Value
jsonGen = resize size arbitrary
sizedJsonsProp :: Int -> ([AE.Value] -> Property) -> Property
sizedJsonsProp size = forAllShrink jsonGen $ shrinkList valueShrink
where
jsonGen :: Gen [AE.Value]
jsonGen = resize size arbitrary