@@ -1,19 +1,18 @@
1{-# OPTIONS_GHC -Wno-orphans #-}
1{-# OPTIONS_GHC -Wno-orphans #-}
3module Data.OpenApi.Compare.Validate.Schema
2module Data.OpenApi.Compare.Validate.Schema
8−import Control.Monad (when,unless)
7+import Control.Monad.Writer
9import qualified Data.Aeson as A
8import qualified Data.Aeson as A
10import Data.Coerce
9import Data.Coerce
11import Data.Foldable (for_, toList)
10import Data.Foldable (for_, toList)
12import Data.Functor
11import Data.Functor
13import Data.HList
12import Data.HList
14−import Data.List (genericIndex, genericLength, group)
15import qualified Data.List.NonEmpty as NE
13import qualified Data.List.NonEmpty as NE
16import qualified Data.Map as M
14import qualified Data.Map as M
15+import Data.List (group, genericLength, genericIndex)
17import Data.Maybe
16import Data.Maybe
18import Data.OpenApi
17import Data.OpenApi
19import Data.OpenApi.Compare.Behavior
18import Data.OpenApi.Compare.Behavior
@@ -23,24 +22,24 @@ import Data.OpenApi.Compare.Subtree
23import Data.OpenApi.Compare.Validate.Schema.DNF
22import Data.OpenApi.Compare.Validate.Schema.DNF
24import Data.OpenApi.Compare.Validate.Schema.Issues
23import Data.OpenApi.Compare.Validate.Schema.Issues
25import Data.OpenApi.Compare.Validate.Schema.JsonFormula
24import Data.OpenApi.Compare.Validate.Schema.JsonFormula
26−import Data.OpenApi.Compare.Validate.Schema.Partition
27−import Data.OpenApi.Compare.Validate.Schema.Process
28import Data.OpenApi.Compare.Validate.Schema.Traced
25import Data.OpenApi.Compare.Validate.Schema.Traced
29import Data.OpenApi.Compare.Validate.Schema.TypedJson
26import Data.OpenApi.Compare.Validate.Schema.TypedJson
27+import Data.OpenApi.Compare.Validate.Schema.Partition
28+import Data.OpenApi.Compare.Validate.Schema.Process
30import Data.Ord
29import Data.Ord
31import Data.Ratio
30import Data.Ratio
32import Data.Semigroup
31import Data.Semigroup
33import qualified Data.Set as S
32import qualified Data.Set as S
34import Data.Text (Text)
33import Data.Text (Text)
36−checkFormulas ::
35+checkFormulas
37− (ReassembleHList xs (CheckEnv (Referenced Schema))) =>
36+ :: (ReassembleHList xs (CheckEnv (Referenced Schema)))
38− HList xs ->
37+ => HList xs
39− Behavior 'SchemaLevel ->
38+ -> Behavior 'SchemaLevel
40− ProdCons (Trace Schema) ->
39+ -> ProdCons (Trace Schema)
41− ProdCons (Traced (Definitions Schema)) ->
40+ -> ProdCons (Traced (Definitions Schema))
42− ProdCons (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel) ->
41+ -> ProdCons (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel)
43− SemanticCompatFormula ()
42+ -> SemanticCompatFormula ()
44checkFormulas env beh trs defs (ProdCons (fp, ep) (fc, ec)) =
43checkFormulas env beh trs defs (ProdCons (fp, ep) (fc, ec)) =
45 case P.toList ep ++ P.toList ec of
44 case P.toList ep ++ P.toList ec of
46 issues@(_ : _) -> for_ issues $ embedFormula beh . anItem
45 issues@(_ : _) -> for_ issues $ embedFormula beh . anItem
@@ -145,21 +144,21 @@ checkFormulas env beh trs defs (ProdCons (fp, ep) (fc, ec)) =
145 EnumDoesntSatisfy $ untypeValue e -- what does this look like when partitioned?
144 EnumDoesntSatisfy $ untypeValue e -- what does this look like when partitioned?
146 issueFromDisjunct mPart ps = NoMatchingCondition mPart $ SomeCondition <$> S.toList ps
145 issueFromDisjunct mPart ps = NoMatchingCondition mPart $ SomeCondition <$> S.toList ps
148−checkContradiction ::
147+checkContradiction
149− Behavior 'TypedSchemaLevel ->
148+ :: Behavior 'TypedSchemaLevel
150− Maybe Partition ->
149+ -> Maybe Partition
151− S.Set (Condition t) ->
150+ -> S.Set (Condition t)
152− SemanticCompatFormula ()
151+ -> SemanticCompatFormula ()
153checkContradiction beh mPart _ = issueAt beh $ maybe TypeBecomesEmpty PartitionBecomesEmpty mPart -- TODO #70
152checkContradiction beh mPart _ = issueAt beh $ maybe TypeBecomesEmpty PartitionBecomesEmpty mPart -- TODO #70
155−checkImplication ::
154+checkImplication
156− (ReassembleHList xs (CheckEnv (Referenced Schema))) =>
155+ :: (ReassembleHList xs (CheckEnv (Referenced Schema)))
157− HList xs ->
156+ => HList xs
158− Behavior 'TypedSchemaLevel ->
157+ -> Behavior 'TypedSchemaLevel
159− ProdCons (Trace Schema) -> -- the traces of the root schemas used in this comparison
158+ -> ProdCons (Trace Schema) -- the traces of the root schemas used in this comparison
160− S.Set (Condition t) ->
159+ -> S.Set (Condition t)
161− Condition t ->
160+ -> Condition t
162− SemanticCompatFormula ()
161+ -> SemanticCompatFormula ()
163checkImplication env beh trs prods cons = case findExactly prods of
162checkImplication env beh trs prods cons = case findExactly prods of
165 | all (satisfiesTyped e) prods ->
164 | all (satisfiesTyped e) prods ->
@@ -167,104 +166,124 @@ checkImplication env beh trs prods cons = case findExactly prods of
167 then pure ()
166 then pure ()
168 else issueAt beh (EnumDoesntSatisfy $ untypeValue e)
167 else issueAt beh (EnumDoesntSatisfy $ untypeValue e)
169 | otherwise -> pure () -- vacuously true
168 | otherwise -> pure () -- vacuously true
170 Nothing -> case cons of
170 Nothing -> case cons of
171 -- the above code didn't catch it, so there's no Exactly condition on the lhs
171 -- the above code didn't catch it, so there's no Exactly condition on the lhs
172 Exactly e -> issueAt beh (NoMatchingEnum $ untypeValue e)
172 Exactly e -> issueAt beh (NoMatchingEnum $ untypeValue e)
173 Maximum m -> foldCheck min m NoMatchingMaximum MatchingMaximumWeak $ \case
174 Maximum m -> foldCheck min m NoMatchingMaximum MatchingMaximumWeak $ \case
174 Maximum m' -> Just m'
175 Maximum m' -> Just m'
175 _ -> Nothing
176 _ -> Nothing
176 Minimum m -> foldCheck max m (NoMatchingMinimum . coerce) (MatchingMinimumWeak . coerce) $ \case
178 Minimum m -> foldCheck max m (NoMatchingMinimum . coerce) (MatchingMinimumWeak . coerce) $ \case
177 Minimum m' -> Just m'
179 Minimum m' -> Just m'
178 _ -> Nothing
180 _ -> Nothing
179 MultipleOf m -> foldCheck lcmScientific m NoMatchingMultipleOf MatchingMultipleOfWeak $ \case
182 MultipleOf m -> foldCheck lcmScientific m NoMatchingMultipleOf MatchingMultipleOfWeak $ \case
180 MultipleOf m' -> Just m'
183 MultipleOf m' -> Just m'
181 _ -> Nothing
184 _ -> Nothing
182 NumberFormat f -> case flip any prods $ \case
186 NumberFormat f -> case flip any prods $ \case
183− NumberFormat f' -> f == f'
187+ NumberFormat f' -> f == f'
184− _ -> False of
188+ _ -> False
185 True -> pure ()
190 True -> pure ()
186 False -> issueAt beh (NoMatchingFormat f)
191 False -> issueAt beh (NoMatchingFormat f)
187 MaxLength m -> foldCheck min m NoMatchingMaxLength MatchingMaxLengthWeak $ \case
193 MaxLength m -> foldCheck min m NoMatchingMaxLength MatchingMaxLengthWeak $ \case
188 MaxLength m' -> Just m'
194 MaxLength m' -> Just m'
189 _ -> Nothing
195 _ -> Nothing
190 MinLength m -> foldCheck max m NoMatchingMinLength MatchingMinLengthWeak $ \case
197 MinLength m -> foldCheck max m NoMatchingMinLength MatchingMinLengthWeak $ \case
191 MinLength m' -> Just m'
198 MinLength m' -> Just m'
192 _ -> Nothing
199 _ -> Nothing
193 Pattern p -> case flip any prods $ \case
201 Pattern p -> case flip any prods $ \case
194− Pattern p' -> p == p'
202+ Pattern p' -> p == p'
195− _ -> False of
203+ _ -> False
196 True -> pure ()
205 True -> pure ()
197 False -> issueAt beh (NoMatchingPattern p) -- TODO: regex comparison #32
206 False -> issueAt beh (NoMatchingPattern p) -- TODO: regex comparison #32
198 StringFormat f -> case flip any prods $ \case
208 StringFormat f -> case flip any prods $ \case
199− StringFormat f' -> f == f'
209+ StringFormat f' -> f == f'
200− _ -> False of
210+ _ -> False
201 True -> pure ()
212 True -> pure ()
202 False -> issueAt beh (NoMatchingFormat f)
213 False -> issueAt beh (NoMatchingFormat f)
203 Items _ cons' -> case foldSome (<>) prods $ \case
215 Items _ cons' -> case foldSome (<>) prods $ \case
204− Items _ rs -> Just (Just (rs NE.:| []), mempty)
216+ Items _ rs -> Just (Just (rs NE.:| []), mempty)
205− TupleItems (map snd -> fs) -> Just (mempty, Just (fs NE.:| []))
217+ TupleItems (map snd -> fs) -> Just (mempty, Just (fs NE.:| []))
206− _ -> Nothing of
218+ _ -> Nothing
207 Just (mItems, Just pfs)
220 Just (mItems, Just pfs)
208 | not $ allSame (length <$> pfs) -> pure () -- vacuously
221 | not $ allSame (length <$> pfs) -> pure () -- vacuously
209− | let plen = genericLength (NE.head pfs) ->
222+ | let plen = genericLength (NE.head pfs)
210− clarifyIssue (AnItem beh (anIssue TupleToArray)) $
223+ -> clarifyIssue (AnItem beh (anIssue TupleToArray)) $ for_ [0 .. plen - 1] $ \i -> do
211− for_ [0 .. plen - 1] $ \i -> do
212− let prod' = tracedConjunct $ case mItems of
224+ let prod' = tracedConjunct $ case mItems of
213− Just prods' -> ((`genericIndex` i) <$> pfs) <> prods'
225+ Just prods' -> ((`genericIndex` i) <$> pfs) <> prods'
214− Nothing -> (`genericIndex` i) <$> pfs
226+ Nothing -> (`genericIndex` i) <$> pfs
215− checkCompatibility (beh >>> step (InItem i)) env $ ProdCons prod' cons'
227+ checkCompatibility (beh >>> step (InItem i)) env $ ProdCons prod' cons'
216 Just (Just prods', Nothing) -> do
228 Just (Just prods', Nothing) -> do
217 let prod' = tracedConjunct prods'
229 let prod' = tracedConjunct prods'
218 checkCompatibility (beh >>> step InItems) env $ ProdCons prod' cons'
230 checkCompatibility (beh >>> step InItems) env $ ProdCons prod' cons'
219 _ -> clarifyIssue (AnItem beh (anIssue NoMatchingItems)) $ do
231 _ -> clarifyIssue (AnItem beh (anIssue NoMatchingItems)) $ do
220 checkCompatibility (beh >>> step InItems) env $ ProdCons prodTopSchema cons'
232 checkCompatibility (beh >>> step InItems) env $ ProdCons prodTopSchema cons'
221 TupleItems (map snd -> fs) -> case foldSome (<>) prods $ \case
234 TupleItems (map snd -> fs) -> case foldSome (<>) prods $ \case
222− TupleItems (map snd -> fs') -> Just (Just $ fs' NE.:| [], Just . Max $ genericLength fs', Just . Min $ genericLength fs', mempty)
235+ TupleItems (map snd -> fs') -> Just (Just $ fs' NE.:| [], Just . Max $ genericLength fs', Just . Min $ genericLength fs', mempty)
223− MinItems m' -> Just (mempty, Just . Max $ m', mempty, mempty)
236+ MinItems m' -> Just (mempty, Just . Max $ m', mempty, mempty)
224− MaxItems m' -> Just (mempty, mempty, Just . Min $ m', mempty)
237+ MaxItems m' -> Just (mempty, mempty, Just . Min $ m', mempty)
225− Items _ rs -> Just (mempty, mempty, mempty, Just (rs NE.:| []))
238+ Items _ rs -> Just (mempty, mempty, mempty, Just (rs NE.:| []))
226− _ -> Nothing of
239+ _ -> Nothing
227− -- if the length constraints in the producer are contradictory:
241+ -- if the length constraints in the producer are contradictory:
228− Just (_, Just (Max lowest), Just (Min highest), _) | lowest > highest -> pure ()
242+ Just (_, Just (Max lowest), Just (Min highest), _) | lowest > highest -> pure ()
229− -- We have an explicit tuple items clause...
243+ -- We have an explicit tuple items clause...
230− Just (Just pfs, Just (Max plen), _, _)
244+ Just (Just pfs, Just (Max plen), _, _)
231− | plen /= genericLength fs -> -- ...of wrong length
245+ | plen /= genericLength fs -- ...of wrong length
232− issueAt beh (TupleItemsLengthChanged ProdCons {producer = plen, consumer = genericLength fs})
246+ -> issueAt beh (TupleItemsLengthChanged ProdCons {producer = plen, consumer = genericLength fs})
233− | otherwise ->
247+ | otherwise
234− for_ [0 .. plen - 1] $ \i -> do
248+ -> for_ [0 .. plen - 1] $ \i -> do
235 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons (tracedConjunct $ (`genericIndex` i) <$> pfs) (fs `genericIndex` i)
249 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons (tracedConjunct $ (`genericIndex` i) <$> pfs) (fs `genericIndex` i)
236− -- We have a fixed length array in the producer...
250+ -- We have a fixed length array in the producer...
237− Just (Nothing, Just (Max plen), Just (Min plen'), mProd)
251+ Just (Nothing, Just (Max plen), Just (Min plen'), mProd)
238− | plen == plen' ->
252+ | plen == plen'
239− clarifyIssue (AnItem beh (anIssue ArrayToTuple)) $ case mProd of
253+ -> clarifyIssue (AnItem beh (anIssue ArrayToTuple)) $ case mProd of
241− | plen /= genericLength fs -> -- ...of wrong length
254+ _ | plen /= genericLength fs -> -- ...of wrong length
242− issueAt beh (TupleItemsLengthChanged ProdCons {producer = plen, consumer = genericLength fs})
255+ issueAt beh (TupleItemsLengthChanged ProdCons {producer = plen, consumer = genericLength fs})
243 Just rs -> for_ [0 .. plen - 1] $ \i -> do
256 Just rs -> for_ [0 .. plen - 1] $ \i -> do
244 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons (tracedConjunct rs) (fs `genericIndex` i)
257 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons (tracedConjunct rs) (fs `genericIndex` i)
245 -- ...and no "items" schema
258 -- ...and no "items" schema
246 Nothing -> clarifyIssue (AnItem beh (anIssue NoMatchingTupleItems)) $ do
259 Nothing -> clarifyIssue (AnItem beh (anIssue NoMatchingTupleItems)) $ do
247 for_ [0 .. plen - 1] $ \i -> do
260 for_ [0 .. plen - 1] $ \i -> do
248 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons prodTopSchema (fs `genericIndex` i)
261 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons prodTopSchema (fs `genericIndex` i)
249− _ -> issueAt beh NoMatchingTupleItems
262+ _ -> issueAt beh NoMatchingTupleItems
250 MaxItems m -> foldCheck min m NoMatchingMaxItems MatchingMaxItemsWeak $ \case
264 MaxItems m -> foldCheck min m NoMatchingMaxItems MatchingMaxItemsWeak $ \case
251 MaxItems m' -> Just m'
265 MaxItems m' -> Just m'
252 TupleItems fs -> Just $ toInteger $ length fs
266 TupleItems fs -> Just $ toInteger $ length fs
253 _ -> Nothing
267 _ -> Nothing
254 MinItems m -> foldCheck max m NoMatchingMinItems MatchingMinItemsWeak $ \case
269 MinItems m -> foldCheck max m NoMatchingMinItems MatchingMinItemsWeak $ \case
255 MinItems m' -> Just m'
270 MinItems m' -> Just m'
256 TupleItems fs -> Just $ toInteger $ length fs
271 TupleItems fs -> Just $ toInteger $ length fs
257 _ -> Nothing
272 _ -> Nothing
258 UniqueItems -> case flip any prods $ \case
274 UniqueItems -> case flip any prods $ \case
259− UniqueItems -> True
275+ UniqueItems -> True
260− MaxItems 1 -> True
276+ MaxItems 1 -> True
261− TupleItems fs | length fs == 1 -> True
277+ TupleItems fs | length fs == 1 -> True
262− _ -> False of
278+ _ -> False
263 True -> pure ()
280 True -> pure ()
264 False -> issueAt beh NoMatchingUniqueItems
281 False -> issueAt beh NoMatchingUniqueItems
265 Properties props _ madd -> case foldSome (<>) prods $ \case
283 Properties props _ madd -> case foldSome (<>) prods $ \case
266− Properties props' _ madd' -> Just $ (props', madd') NE.:| []
284+ Properties props' _ madd' -> Just $ (props', madd') NE.:| []
267− _ -> Nothing of
285+ _ -> Nothing
268 Just pm ->
287 Just pm ->
269 anyOfAt beh NoMatchingProperties $ -- TODO: could first "concat" the lists
288 anyOfAt beh NoMatchingProperties $ -- TODO: could first "concat" the lists
270 NE.toList pm <&> \(props', madd') -> do
289 NE.toList pm <&> \(props', madd') -> do
@@ -295,9 +314,11 @@ checkImplication env beh trs prods cons = case findExactly prods of
295 (Just add', Just add) -> checkCompatibility (beh >>> step InAdditionalProperty) env (ProdCons add' add)
314 (Just add', Just add) -> checkCompatibility (beh >>> step InAdditionalProperty) env (ProdCons add' add)
297 Nothing -> issueAt beh NoMatchingProperties
316 Nothing -> issueAt beh NoMatchingProperties
298 MaxProperties m -> foldCheck min m NoMatchingMaxProperties MatchingMaxPropertiesWeak $ \case
318 MaxProperties m -> foldCheck min m NoMatchingMaxProperties MatchingMaxPropertiesWeak $ \case
299 MaxProperties m' -> Just m'
319 MaxProperties m' -> Just m'
300 _ -> Nothing
320 _ -> Nothing
301 MinProperties m -> foldCheck max m NoMatchingMinProperties MatchingMinPropertiesWeak $ \case
322 MinProperties m -> foldCheck max m NoMatchingMinProperties MatchingMinPropertiesWeak $ \case
302 MinProperties m' -> Just m'
323 MinProperties m' -> Just m'
303 _ -> Nothing
324 _ -> Nothing
@@ -305,14 +326,14 @@ checkImplication env beh trs prods cons = case findExactly prods of
305 lcmScientific (toRational -> a) (toRational -> b) =
326 lcmScientific (toRational -> a) (toRational -> b) =
306 fromRational $ lcm (numerator a) (numerator b) % gcd (denominator a) (denominator b)
327 fromRational $ lcm (numerator a) (numerator b) % gcd (denominator a) (denominator b)
308− foldCheck ::
329+ foldCheck
310− (a -> a -> a) ->
331+ => (a -> a -> a)
312− (a -> Issue 'TypedSchemaLevel) ->
333+ -> (a -> Issue 'TypedSchemaLevel)
313− (ProdCons a -> Issue 'TypedSchemaLevel) ->
334+ -> (ProdCons a -> Issue 'TypedSchemaLevel)
314− (forall t. Condition t -> Maybe a) ->
335+ -> (forall t. Condition t -> Maybe a)
315− SemanticCompatFormula ()
336+ -> SemanticCompatFormula ()
316 foldCheck f m missing weak extr = case foldSome f prods extr of
337 foldCheck f m missing weak extr = case foldSome f prods extr of
318 | f m' m == m' -> pure ()
339 | f m' m == m' -> pure ()