@@ -39,11 +39,12 @@ import qualified Data.Set as S
3939import Data.Text (Text)
4040import qualified Data.Text as T hiding (singleton)
4141import Data.Typeable
42+import OpenAPI.Checker.Behavior
4243import OpenAPI.Checker.Orphans ()
4344import OpenAPI.Checker.References
45+import OpenAPI.Checker.Paths
46+import qualified OpenAPI.Checker.PathsPrefixTree as P
4447import OpenAPI.Checker.Subtree
45−import OpenAPI.Checker.Trace
46−import qualified OpenAPI.Checker.TracePrefixTree as T
4748
4849-- | Type of a JSON value
4950data JsonType
@@ -53,7 +54,7 @@ data JsonType
5354 | String
5455 | Array
5556 | Object
56− deriving (Eq, Show)
57+ deriving stock (Eq, Ord, Show)
5758
5859-- | A 'A.Value' whose type we know
5960data TypedValue :: JsonType -> Type where
@@ -88,8 +89,8 @@ instance Ord a => Ord (Bound a) where
8889
8990data Property = Property
9091 { propRequired :: Bool
91− , propFormula :: ForeachType (JsonFormula OpenApi)
92− , propRefSchema :: Traced OpenApi (Referenced Schema)
92+ , propFormula :: ForeachType JsonFormula
93+ , propRefSchema :: Traced (Referenced Schema)
9394 }
9495 deriving stock (Eq, Ord, Show)
9596
@@ -106,17 +107,17 @@ data Condition :: JsonType -> Type where
106107 Pattern :: !Pattern -> Condition 'String
107108 StringFormat :: !Format -> Condition 'String
108109 Items
109− :: !(ForeachType (JsonFormula OpenApi))
110− -> !(Traced OpenApi (Referenced Schema))
110+ :: !(ForeachType JsonFormula)
111+ -> !(Traced (Referenced Schema))
111112 -> Condition 'Array
112113 MaxItems :: !Integer -> Condition 'Array
113114 MinItems :: !Integer -> Condition 'Array
114115 UniqueItems :: Condition 'Array
115116 Properties
116117 :: !(M.Map Text Property)
117− -> !(ForeachType (JsonFormula OpenApi))
118+ -> !(ForeachType JsonFormula)
118119 -- ^ formula for additional properties
119− -> !(Maybe (Traced OpenApi (Referenced Schema)))
120+ -> !(Maybe (Traced (Referenced Schema)))
120121 -- ^ schema for additional properties, Nothing means bottom
121122 -> Condition 'Object
122123 MaxProperties :: !Integer -> Condition 'Object
@@ -158,7 +159,7 @@ deriving stock instance Ord (Condition t)
158159deriving stock instance Show (Condition t)
159160
160161data SomeCondition where
161− SomeCondition :: Typeable t => Traced OpenApi (Condition t) -> SomeCondition
162+ SomeCondition :: Typeable t => Condition t -> SomeCondition
162163
163164instance Eq SomeCondition where
164165 SomeCondition x == SomeCondition y = case cast x of
@@ -175,26 +176,24 @@ deriving stock instance Show SomeCondition
175176-- | A boolean formula (without "not") of 'Condition's. Represented as a
176177-- Disjunctive Normal Form: the formula is a disjunction of a set of conjuncts,
177178-- each of which is a conjunction of a set of 'Condition's.
178−newtype JsonFormula r t
179− = DNF (S.Set (M.Map (Condition t) (Trace r (Condition t))))
179+newtype JsonFormula t
180+ = DNF (S.Set (S.Set (Condition t)))
180181 deriving stock (Eq, Ord, Show)
181182
182183disjAdd
183− :: JsonFormula r t
184− -> M.Map (Condition t) (Trace r (Condition t))
185− -> JsonFormula r t
184+ :: JsonFormula t
185+ -> S.Set (Condition t)
186+ -> JsonFormula t
186187disjAdd (DNF yss) xs
187− | any (`isMapSubsetOf` xs) yss = DNF yss
188− | otherwise = DNF $ S.insert xs $ S.filter (not . isMapSubsetOf xs) yss
189− where
190− isMapSubsetOf a b = M.keysSet a `S.isSubsetOf` M.keysSet b
188+ | any (`S.isSubsetOf` xs) yss = DNF yss
189+ | otherwise = DNF $ S.insert xs $ S.filter (not . S.isSubsetOf xs) yss
191190
192−instance Lattice (JsonFormula r t) where
191+instance Lattice (JsonFormula t) where
193192 xss \/ DNF yss = S.foldl' disjAdd xss yss
194193 DNF xss /\ DNF yss = F.foldl' disjAdd bottom $
195− liftA2 M.union (S.toList xss) (S.toList yss)
194+ liftA2 S.union (S.toList xss) (S.toList yss)
196195
197−pattern BottomFormula :: JsonFormula r t
196+pattern BottomFormula :: JsonFormula t
198197pattern BottomFormula <- DNF (S.null -> True)
199198 where BottomFormula = DNF S.empty
200199
@@ -203,35 +202,35 @@ isSingleton s
203202 | S.size s == 1 = S.lookupMin s
204203 | otherwise = Nothing
205204
206−pattern Conjunct :: [Traced r (Condition t)] -> M.Map (Condition t) (Trace r (Condition t))
207−pattern Conjunct xs <- (map (uncurry $ flip traced) . M.toList -> xs)
208− where Conjunct xs = M.fromList $ (extract &&& ask) <$> xs
205+pattern Conjunct :: [Condition t] -> S.Set (Condition t)
206+pattern Conjunct xs <- (S.toList -> xs)
207+ where Conjunct = S.fromList
209208{-# COMPLETE Conjunct #-}
210209
211−pattern SingleConjunct :: [Traced r (Condition t)] -> JsonFormula r t
210+pattern SingleConjunct :: [Condition t] -> JsonFormula t
212211pattern SingleConjunct xs <- DNF (isSingleton -> Just (Conjunct xs))
213212 where SingleConjunct xs = DNF $ S.singleton $ Conjunct xs
214213
215−pattern TopFormula :: JsonFormula r t
216−pattern TopFormula <- DNF (isSingleton -> Just (M.null -> True))
217− where TopFormula = DNF $ S.singleton M.empty
214+pattern TopFormula :: JsonFormula t
215+pattern TopFormula <- DNF (isSingleton -> Just (S.null -> True))
216+ where TopFormula = DNF $ S.singleton S.empty
218217
219−instance BoundedJoinSemiLattice (JsonFormula r t) where
218+instance BoundedJoinSemiLattice (JsonFormula t) where
220219 bottom = BottomFormula
221220
222−instance BoundedMeetSemiLattice (JsonFormula r t) where
221+instance BoundedMeetSemiLattice (JsonFormula t) where
223222 top = TopFormula
224223
225224foldLattice
226225 :: BoundedLattice l
227− => (Traced r (Condition t) -> l)
228− -> JsonFormula r t
226+ => (Condition t -> l)
227+ -> JsonFormula t
229228 -> l
230229foldLattice f (DNF xss) = S.foldl' (\z w ->
231− z \/ M.foldlWithKey' (\x y t -> x /\ f (traced t y)) top w) bottom xss
230+ z \/ S.foldl' (\x y -> x /\ f y) top w) bottom xss
232231
233−satisfiesFormula :: TypedValue t -> JsonFormula r t -> Bool
234−satisfiesFormula val = foldLattice (satisfiesTyped val . extract)
232+satisfiesFormula :: TypedValue t -> JsonFormula t -> Bool
233+satisfiesFormula val = foldLattice (satisfiesTyped val)
235234
236235data ForeachType (f :: JsonType -> Type) = ForeachType
237236 { forNull :: f 'Null
@@ -242,7 +241,7 @@ data ForeachType (f :: JsonType -> Type) = ForeachType
242241 , forObject :: f 'Object
243242 }
244243
245−satisfies :: A.Value -> ForeachType (JsonFormula r) -> Bool
244+satisfies :: A.Value -> ForeachType JsonFormula -> Bool
246245satisfies val p = case val of
247246 A.Null -> satisfiesFormula TNull $ forNull p
248247 A.Bool b -> satisfiesFormula (TBool b) $ forBoolean p
@@ -257,27 +256,27 @@ deriving stock instance (forall x. Typeable x => Show (f x)) => Show (ForeachTyp
257256
258257foldType
259258 :: Monoid m
260− => (forall x. Typeable x => (ForeachType f -> f x) -> m)
259+ => (forall x. Typeable x => JsonType -> (ForeachType f -> f x) -> m)
261260 -> m
262261foldType k =
263− k forNull <>
264− k forBoolean <>
265− k forNumber <>
266− k forString <>
267− k forArray <>
268− k forObject
262+ k Null forNull <>
263+ k Boolean forBoolean <>
264+ k Number forNumber <>
265+ k String forString <>
266+ k Array forArray <>
267+ k Object forObject
269268
270269forType_
271270 :: Applicative m
272− => (forall x. Typeable x => (ForeachType f -> f x) -> m ())
271+ => (forall x. Typeable x => JsonType -> (ForeachType f -> f x) -> m ())
273272 -> m ()
274273forType_ k = do
275− k forNull
276− k forBoolean
277− k forNumber
278− k forString
279− k forArray
280− k forObject
274+ k Null forNull
275+ k Boolean forBoolean
276+ k Number forNumber
277+ k String forString
278+ k Array forArray
279+ k Object forObject
281280 pure ()
282281
283282instance (forall x. Lattice (f x)) => Lattice (ForeachType f) where
@@ -320,6 +319,7 @@ instance (forall x. BoundedMeetSemiLattice (f x))
320319 , forObject = top
321320 }
322321
322+{- TODO: remove
323323instance Typeable t => Steppable Schema (Condition t) where
324324 data Step Schema (Condition t)
325325 = EnumField
@@ -340,6 +340,11 @@ instance Typeable t => Steppable Schema (Condition t) where
340340 | NullableField
341341 | IntegerType -- type=integer
342342 deriving (Eq, Ord, Show)
343+-}
344+
345+instance Behavable 'SchemaLevel 'SchemaLevel where
346+ data Behave 'SchemaLevel 'SchemaLevel
347+ deriving (Eq, Ord, Show)
343348
344349instance Steppable Schema (Referenced Schema) where
345350 data Step Schema (Referenced Schema)
@@ -352,44 +357,42 @@ instance Steppable Schema (Referenced Schema) where
352357 | PropertiesStep Text
353358 deriving (Eq, Ord, Show)
354359
355−type ProcessM = ReaderT (Definitions Schema) (Writer (T.TracePrefixTree SubtreeCheckIssue OpenApi))
360+type ProcessM = ReaderT (Traced (Definitions Schema)) (Writer (P.PathsPrefixTree Behave AnIssue 'SchemaLevel))
356361
357−warn
358− :: (Subtree t, ComonadEnv (Trace OpenApi t) w)
359− => w x -> CheckIssue t -> ProcessM ()
360−warn t issue = tell $ T.singleton $ AnItem (ask t) $ SubtreeCheckIssue issue
362+warn :: Issue 'SchemaLevel -> ProcessM ()
363+warn issue = tell $ P.singleton $ AnItem Root $ AnIssue issue
361364
362365processRefSchema
363− :: Traced OpenApi (Referenced Schema)
364− -> ProcessM (ForeachType (JsonFormula OpenApi))
366+ :: Traced (Referenced Schema)
367+ -> ProcessM (ForeachType JsonFormula)
365368processRefSchema x = do
366369 defs <- R.ask
367370 processSchema $ dereference defs x
368371
369−tracedAllOf :: Traced r Schema -> Maybe [Traced r (Referenced Schema)]
372+tracedAllOf :: Traced Schema -> Maybe [Traced (Referenced Schema)]
370373tracedAllOf sch = _schemaAllOf (extract sch) <&> \xs ->
371374 [ traced (ask sch >>> step (AllOfStep i)) x | (i, x) <- zip [0..] xs ]
372375
373−tracedAnyOf :: Traced r Schema -> Maybe [Traced r (Referenced Schema)]
376+tracedAnyOf :: Traced Schema -> Maybe [Traced (Referenced Schema)]
374377tracedAnyOf sch = _schemaAnyOf (extract sch) <&> \xs ->
375378 [ traced (ask sch >>> step (AnyOfStep i)) x | (i, x) <- zip [0..] xs ]
376379
377−tracedOneOf :: Traced r Schema -> Maybe [Traced r (Referenced Schema)]
380+tracedOneOf :: Traced Schema -> Maybe [Traced (Referenced Schema)]
378381tracedOneOf sch = _schemaOneOf (extract sch) <&> \xs ->
379382 [ traced (ask sch >>> step (OneOfStep i)) x | (i, x) <- zip [0..] xs ]
380383
381−tracedItems :: Traced r Schema -> Maybe (Either (Traced r (Referenced Schema)) [Traced r (Referenced Schema)])
384+tracedItems :: Traced Schema -> Maybe (Either (Traced (Referenced Schema)) [Traced (Referenced Schema)])
382385tracedItems sch = _schemaItems (extract sch) <&> \case
383386 OpenApiItemsObject x -> Left $ traced (ask sch >>> step ItemsObjectStep) x
384387 OpenApiItemsArray xs -> Right
385388 [ traced (ask sch >>> step (ItemsArrayStep i)) x | (i, x) <- zip [0..] xs ]
386389
387−tracedAdditionalProperties :: Traced r Schema -> Maybe (Either Bool (Traced r (Referenced Schema)))
390+tracedAdditionalProperties :: Traced Schema -> Maybe (Either Bool (Traced (Referenced Schema)))
388391tracedAdditionalProperties sch = _schemaAdditionalProperties (extract sch) <&> \case
389392 AdditionalPropertiesAllowed b -> Left b
390393 AdditionalPropertiesSchema x -> Right $ traced (ask sch >>> step AdditionalPropertiesStep) x
391394
392−tracedProperties :: Traced r Schema -> IOHM.InsOrdHashMap Text (Traced r (Referenced Schema))
395+tracedProperties :: Traced Schema -> IOHM.InsOrdHashMap Text (Traced (Referenced Schema))
393396tracedProperties sch = IOHM.mapWithKey (\k -> traced (ask sch >>> step (PropertiesStep k)))
394397 $ _schemaProperties $ extract sch
395398
@@ -397,35 +400,35 @@ tracedProperties sch = IOHM.mapWithKey (\k -> traced (ask sch >>> step (Properti
397400-- for every possible type of a JSON value. The conditions are independent, and
398401-- are thus checked independently.
399402processSchema
400− :: Traced OpenApi Schema
401− -> ProcessM (ForeachType (JsonFormula OpenApi))
403+ :: Traced Schema
404+ -> ProcessM (ForeachType JsonFormula)
402405processSchema sch@(extract -> Schema{..}) = do
403406 let
404− singletonFormula :: Typeable t => Step Schema (Condition t) -> Condition t -> JsonFormula OpenApi t
405− singletonFormula t f = SingleConjunct [traced (ask sch >>> step t) f]
407+ singletonFormula :: Condition t -> JsonFormula t
408+ singletonFormula f = SingleConjunct [f]
406409
407410 allClauses <- case tracedAllOf sch of
408411 Nothing -> pure []
409− Just [] -> [] <$ warn sch (InvalidSchema "no items in allOf")
412+ Just [] -> [] <$ warn (InvalidSchema "no items in allOf")
410413 Just xs -> mapM processRefSchema xs
411414
412415 anyClause <- case tracedAnyOf sch of
413416 Nothing -> pure top
414− Just [] -> bottom <$ warn sch (InvalidSchema "no items in anyOf")
417+ Just [] -> bottom <$ warn (InvalidSchema "no items in anyOf")
415418 Just xs -> joins <$> mapM processRefSchema xs
416419
417420 oneClause <- case tracedOneOf sch of
418421 Nothing -> pure top
419− Just [] -> bottom <$ warn sch (InvalidSchema "no items in oneOf")
422+ Just [] -> bottom <$ warn (InvalidSchema "no items in oneOf")
420423 Just xs -> do
421424 checkOneOfDisjoint xs >>= \case
422425 True -> pure ()
423− False -> warn sch (NotSupported "Could not determine that oneOf branches are disjoint")
426+ False -> warn (NotSupported "Could not determine that oneOf branches are disjoint")
424427 joins <$> mapM processRefSchema xs
425428
426429 case _schemaNot of
427430 Nothing -> pure ()
428− Just _ -> warn sch (NotSupported "not clause is unsupported")
431+ Just _ -> warn (NotSupported "not clause is unsupported")
429432
430433 let
431434 typeClause = case _schemaType of
@@ -437,7 +440,7 @@ processSchema sch@(extract -> Schema{..}) = do
437440 Just OpenApiNumber -> bottom
438441 { forBoolean = top }
439442 Just OpenApiInteger -> bottom
440− { forNumber = singletonFormula IntegerType $ MultipleOf 1 }
443+ { forNumber = singletonFormula $ MultipleOf 1 }
441444 Just OpenApiString -> bottom
442445 { forString = top }
443446 Just OpenApiArray -> bottom
@@ -447,27 +450,27 @@ processSchema sch@(extract -> Schema{..}) = do
447450
448451 let
449452 valueEnum A.Null = bottom
450− { forNull = singletonFormula EnumField $ Exactly TNull }
453+ { forNull = singletonFormula $ Exactly TNull }
451454 valueEnum (A.Bool b) = bottom
452− { forBoolean = singletonFormula EnumField $ Exactly $ TBool b }
455+ { forBoolean = singletonFormula $ Exactly $ TBool b }
453456 valueEnum (A.Number n) = bottom
454− { forNumber = singletonFormula EnumField $ Exactly $ TNumber n }
457+ { forNumber = singletonFormula $ Exactly $ TNumber n }
455458 valueEnum (A.String s) = bottom
456− { forString = singletonFormula EnumField $ Exactly $ TString s }
459+ { forString = singletonFormula $ Exactly $ TString s }
457460 valueEnum (A.Array a) = bottom
458− { forArray = singletonFormula EnumField $ Exactly $ TArray a }
461+ { forArray = singletonFormula $ Exactly $ TArray a }
459462 valueEnum (A.Object o) = bottom
460− { forObject = singletonFormula EnumField $ Exactly $ TObject o }
463+ { forObject = singletonFormula $ Exactly $ TObject o }
461464 enumClause <- case _schemaEnum of
462465 Nothing -> pure top
463− Just [] -> bottom <$ warn sch (InvalidSchema "no items in enum")
466+ Just [] -> bottom <$ warn (InvalidSchema "no items in enum")
464467 Just xs -> pure $ joins (valueEnum <$> xs)
465468
466469 let
467470 maximumClause = case _schemaMaximum of
468471 Nothing -> top
469472 Just n -> top
470− { forNumber = singletonFormula MaximumFields $ Maximum $
473+ { forNumber = singletonFormula $ Maximum $
471474 case _schemaExclusiveMaximum of
472475 Just True -> Exclusive n
473476 _ -> Inclusive n }
@@ -475,7 +478,7 @@ processSchema sch@(extract -> Schema{..}) = do
475478 minimumClause = case _schemaMinimum of
476479 Nothing -> top
477480 Just n -> top
478− { forNumber = singletonFormula MinimumFields $ Minimum $ Down $
481+ { forNumber = singletonFormula $ Minimum $ Down $
479482 case _schemaExclusiveMinimum of
480483 Just True -> Exclusive $ Down n
481484 _ -> Inclusive $ Down n }
@@ -483,53 +486,53 @@ processSchema sch@(extract -> Schema{..}) = do
483486 multipleOfClause = case _schemaMultipleOf of
484487 Nothing -> top
485488 Just n -> top
486− { forNumber = singletonFormula MultipleOfField $ MultipleOf n }
489+ { forNumber = singletonFormula $ MultipleOf n }
487490
488491 formatClause <- case _schemaFormat of
489492 Nothing -> pure top
490493 Just f | f `elem` ["int32", "int64", "float", "double"] -> pure top
491− { forNumber = singletonFormula FormatField $ NumberFormat f }
494+ { forNumber = singletonFormula $ NumberFormat f }
492495 Just f | f `elem` ["byte", "binary", "date", "date-time", "password"] -> pure top
493− { forString = singletonFormula FormatField $ StringFormat f }
494− Just f -> top <$ warn sch (NotSupported $ "Unknown format: " <> f)
496+ { forString = singletonFormula $ StringFormat f }
497+ Just f -> top <$ warn (NotSupported $ "Unknown format: " <> f)
495498
496499 let
497500 maxLengthClause = case _schemaMaxLength of
498501 Nothing -> top
499502 Just n -> top
500− { forString = singletonFormula MaxLengthField $ MaxLength n }
503+ { forString = singletonFormula $ MaxLength n }
501504
502505 minLengthClause = case _schemaMinLength of
503506 Nothing -> top
504507 Just n -> top
505− { forString = singletonFormula MinLengthField $ MinLength n }
508+ { forString = singletonFormula $ MinLength n }
506509
507510 patternClause = case _schemaPattern of
508511 Nothing -> top
509512 Just p -> top
510− { forString = singletonFormula PatternField $ Pattern p }
513+ { forString = singletonFormula $ Pattern p }
511514
512515 itemsClause <- case tracedItems sch of
513516 Nothing -> pure top
514517 Just (Left rs) -> do
515518 f <- processRefSchema rs
516− pure top { forArray = singletonFormula ItemsField $ Items f rs }
517− Just (Right _) -> top <$ warn sch (NotSupported "array in items is not supported")
519+ pure top { forArray = singletonFormula $ Items f rs }
520+ Just (Right _) -> top <$ warn (NotSupported "array in items is not supported")
518521
519522 let
520523 maxItemsClause = case _schemaMaxItems of
521524 Nothing -> top
522525 Just n -> top
523− { forArray = singletonFormula MaxItemsField $ MaxItems n }
526+ { forArray = singletonFormula $ MaxItems n }
524527
525528 minItemsClause = case _schemaMinItems of
526529 Nothing -> top
527530 Just n -> top
528− { forArray = singletonFormula MinItemsField $ MinItems n }
531+ { forArray = singletonFormula $ MinItems n }
529532
530533 uniqueItemsClause = case _schemaUniqueItems of
531534 Just True -> top
532− { forArray = singletonFormula UniqueItemsField UniqueItems }
535+ { forArray = singletonFormula UniqueItems }
533536 _ -> top
534537
535538 (addProps, addPropSchema) <- case tracedAdditionalProperties sch of
@@ -546,10 +549,10 @@ processSchema sch@(extract -> Schema{..}) = do
546549 in pure (addProps, fromMaybe fakeSchema addPropSchema)
547550 pure (k, Property (k `elem` _schemaRequired) f psch)
548551 let
549− allBottom f = getAll $ foldType $ \ty -> case ty f of
552+ allBottom f = getAll $ foldType $ \_ ty -> case ty f of
550553 BottomFormula -> All True
551554 _ -> All False
552− allTop f = getAll $ foldType $ \ty -> case ty f of
555+ allTop f = getAll $ foldType $ \_ ty -> case ty f of
553556 TopFormula -> All True
554557 _ -> All False
555558 -- remove optional fields whose schemata match that of additional props
@@ -561,21 +564,21 @@ processSchema sch@(extract -> Schema{..}) = do
561564 = top -- if all fields are optional and have trivial schemata
562565 | otherwise
563566 = top
564− { forObject = singletonFormula PropertiesFields $ Properties propMap addProps addPropSchema }
567+ { forObject = singletonFormula $ Properties propMap addProps addPropSchema }
565568
566569 maxPropertiesClause = case _schemaMaxProperties of
567570 Nothing -> top
568571 Just n -> top
569− { forObject = singletonFormula MaxPropertiesField $ MaxProperties n }
572+ { forObject = singletonFormula $ MaxProperties n }
570573
571574 minPropertiesClause = case _schemaMinProperties of
572575 Nothing -> top
573576 Just n -> top
574− { forObject = singletonFormula MinPropertiesField $ MinProperties n }
577+ { forObject = singletonFormula $ MinProperties n }
575578
576579 nullableClause
577580 | Just True <- _schemaNullable = bottom
578− { forNull = singletonFormula NullableField $ Exactly TNull }
581+ { forNull = singletonFormula $ Exactly TNull }
579582 | otherwise = bottom
580583
581584 pure $ nullableClause \/ meets (allClauses <>
@@ -585,24 +588,24 @@ processSchema sch@(extract -> Schema{..}) = do
585588 , uniqueItemsClause, propertiesClause, maxPropertiesClause, minPropertiesClause])
586589{- TODO: ReadOnly/WriteOnly -}
587590
588−checkOneOfDisjoint :: [Traced OpenApi (Referenced Schema)] -> ProcessM Bool
591+checkOneOfDisjoint :: [Traced (Referenced Schema)] -> ProcessM Bool
589592checkOneOfDisjoint = const $ pure True -- TODO
590593
591594schemaToFormula
592− :: Definitions Schema
593− -> Traced OpenApi Schema
594− -> (ForeachType (JsonFormula OpenApi), T.TracePrefixTree SubtreeCheckIssue OpenApi)
595+ :: Traced (Definitions Schema)
596+ -> Traced Schema
597+ -> (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel)
595598schemaToFormula defs rs = runWriter . (`runReaderT` defs) $ processSchema rs
596599
597600checkFormulas
598601 :: HasAll (CheckEnv Schema) xs
599602 => HList xs
600− -> Trace OpenApi Schema
601− -> ProdCons (ForeachType (JsonFormula OpenApi), T.TracePrefixTree SubtreeCheckIssue OpenApi)
603+ -> Behavior 'SchemaLevel
604+ -> ProdCons (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel)
602605 -> CompatFormula ()
603−checkFormulas env tr (ProdCons (fp, ep) (fc, ec)) =
604− case T.toList ep ++ T.toList ec of
605− issues@(_:_) -> F.for_ issues $ \(AnItem t (SubtreeCheckIssue e)) -> issueAtTrace t e
606+checkFormulas env beh (ProdCons (fp, ep) (fc, ec)) =
607+ case P.toList ep ++ P.toList ec of
608+ issues@(_:_) -> F.for_ issues $ \(AnItem t (AnIssue e)) -> issueAt (beh >>> t) e
606609 [] -> do
607610 -- We have the following isomorphisms:
608611 -- (A ⊂ X ∪ Y) = (A ⊂ X) \/ (A ⊂ Y)
@@ -631,120 +634,122 @@ checkFormulas env tr (ProdCons (fp, ep) (fc, ec)) =
631634 -- (⋃_i ⋂_j A[i,j]) ⊂ ∅ = /\_i (⋂_j A[i,j]) ⊂ ∅
632635 -- where we again delegate (⋂_j A[j]) ⊂ ∅ to a heuristic, though here the
633636 -- shortcut of \/_j A[j] ⊂ ∅ hardly helps.
634− forType_ $ \ty ->
637+ forType_ $ \tyName ty -> do
638+ let beh' = beh >>> step (OfType tyName)
635639 case (ty fp, ty fc) of
636− (DNF pss, BottomFormula) -> F.for_ pss $ \(Conjunct ps) -> checkContradiction tr ps
640+ (DNF pss, BottomFormula) -> F.for_ pss $ \(Conjunct ps) -> checkContradiction beh' ps
637641 (DNF pss, SingleConjunct cs) -> F.for_ pss $ \(Conjunct ps) -> do
638− F.for_ cs $ checkImplication env ps -- avoid disjuntion if there's only one conjunct
642+ F.for_ cs $ checkImplication env beh' ps -- avoid disjuntion if there's only one conjunct
639643 (DNF pss, DNF css) -> F.for_ pss $ \(Conjunct ps) -> do
640− anyOfM tr (NoMatchingCondition $ SomeCondition <$> ps)
641− [F.for_ cs $ checkImplication env ps | Conjunct cs <- S.toList css]
644+ anyOfAt beh' (NoMatchingCondition $ SomeCondition <$> ps)
645+ [F.for_ cs $ checkImplication env beh' ps | Conjunct cs <- S.toList css]
642646
643647checkContradiction
644− :: Trace OpenApi Schema
645− -> [Traced OpenApi (Condition t)]
648+ :: Behavior 'TypedSchemaLevel
649+ -> [Condition t]
646650 -> CompatFormula ()
647−checkContradiction tr _ = issueAtTrace tr NoContradiction -- TODO
651+checkContradiction beh _ = issueAt beh NoContradiction -- TODO
648652
649653checkImplication
650− :: (HasAll (CheckEnv Schema) xs, Typeable t)
654+ :: (HasAll (CheckEnv Schema) xs)
651655 => HList xs
652− -> [Traced OpenApi (Condition t)]
653− -> Traced OpenApi (Condition t)
656+ -> Behavior 'TypedSchemaLevel
657+ -> [Condition t]
658+ -> Condition t
654659 -> CompatFormula ()
655−checkImplication env prods cons = case findExactly prods of
660+checkImplication env beh prods cons = case findExactly prods of
656661 Just e
657− | all (satisfiesTyped e) (extract <$> prods) ->
658− if satisfiesTyped e $ extract cons then pure ()
659− else issueAt cons (EnumDoesntSatisfy e)
662+ | all (satisfiesTyped e) prods ->
663+ if satisfiesTyped e cons then pure ()
664+ else issueAt beh (EnumDoesntSatisfy $ untypeValue e)
660665 | otherwise -> pure () -- vacuously true
661− Nothing -> case extract cons of
666+ Nothing -> case cons of
662667 -- the above code didn't catch it, so there's no Exactly condition on the lhs
663− Exactly e -> issueAt cons (NoMatchingEnum e)
668+ Exactly e -> issueAt beh (NoMatchingEnum $ untypeValue e)
664669 Maximum m -> case findRelevant min (\case Maximum m' -> Just m'; _ -> Nothing) prods of
665670 Just m' -> if m' <= m then pure ()
666− else issueAt cons (MatchingMaximumWeak m m')
667− Nothing -> issueAt cons (NoMatchingMaximum m)
671+ else issueAt beh (MatchingMaximumWeak m m')
672+ Nothing -> issueAt beh (NoMatchingMaximum m)
668673 Minimum m -> case findRelevant max (\case Minimum m' -> Just m'; _ -> Nothing) prods of
669674 Just m' -> if m' >= m then pure ()
670− else issueAt cons (MatchingMinimumWeak (coerce m) (coerce m'))
671− Nothing -> issueAt cons (NoMatchingMinimum (coerce m))
675+ else issueAt beh (MatchingMinimumWeak (coerce m) (coerce m'))
676+ Nothing -> issueAt beh (NoMatchingMinimum (coerce m))
672677 MultipleOf m -> case findRelevant lcmScientific (\case MultipleOf m' -> Just m'; _ -> Nothing) prods of
673678 Just m' -> if lcmScientific m m' == m' then pure ()
674− else issueAt cons (MatchingMultipleOfWeak m m')
675− Nothing -> issueAt cons (NoMatchingMultipleOf m)
676− NumberFormat f -> if any (\case NumberFormat f' -> f == f'; _ -> False) $ extract <$> prods
677− then pure () else issueAt cons (NoMatchingFormat f)
679+ else issueAt beh (MatchingMultipleOfWeak m m')
680+ Nothing -> issueAt beh (NoMatchingMultipleOf m)
681+ NumberFormat f -> if any (\case NumberFormat f' -> f == f'; _ -> False) prods
682+ then pure () else issueAt beh (NoMatchingFormat f)
678683 MaxLength m -> case findRelevant min (\case MaxLength m' -> Just m'; _ -> Nothing) prods of
679684 Just m' -> if m' <= m then pure ()
680− else issueAt cons (MatchingMaxLengthWeak m m')
681− Nothing -> issueAt cons (NoMatchingMaxLength m)
685+ else issueAt beh (MatchingMaxLengthWeak m m')
686+ Nothing -> issueAt beh (NoMatchingMaxLength m)
682687 MinLength m -> case findRelevant max (\case MinLength m' -> Just m'; _ -> Nothing) prods of
683688 Just m' -> if m' >= m then pure ()
684− else issueAt cons (MatchingMinLengthWeak m m')
685− Nothing -> issueAt cons (NoMatchingMinLength m)
686− Pattern p -> if any (\case Pattern p' -> p == p'; _ -> False) $ extract <$> prods
687− then pure () else issueAt cons (NoMatchingPattern p)
688− StringFormat f -> if any (\case StringFormat f' -> f == f'; _ -> False) $ extract <$> prods
689− then pure () else issueAt cons (NoMatchingFormat f)
689+ else issueAt beh (MatchingMinLengthWeak m m')
690+ Nothing -> issueAt beh (NoMatchingMinLength m)
691+ Pattern p -> if any (\case Pattern p' -> p == p'; _ -> False) prods
692+ then pure () else issueAt beh (NoMatchingPattern p) -- TODO: regex comparison
693+ StringFormat f -> if any (\case StringFormat f' -> f == f'; _ -> False) prods
694+ then pure () else issueAt beh (NoMatchingFormat f)
690695 Items _ cons' -> case findRelevant (<>) (\case Items _ rs -> Just (rs NE.:| []); _ -> Nothing) prods of
691− Just (rs NE.:| []) -> checkCompatibility env $ ProdCons rs cons'
696+ Just (rs NE.:| []) -> checkCompatibility env (beh >>> step InItems) $ ProdCons rs cons'
692697 Just rs -> do
693698 let sch = Inline mempty { _schemaAllOf = Just . NE.toList $ extract <$> rs }
694− checkCompatibility env $ ProdCons (traced (ask $ NE.head rs) sch) cons' -- TODO: bad trace
695− Nothing -> issueAt cons NoMatchingItems
699+ checkCompatibility env (beh >>> step InItems) $ ProdCons (traced (ask $ NE.head rs) sch) cons' -- TODO: bad trace
700+ Nothing -> issueAt beh NoMatchingItems
696701 MaxItems m -> case findRelevant min (\case MaxItems m' -> Just m'; _ -> Nothing) prods of
697702 Just m' -> if m' <= m then pure ()
698− else issueAt cons (MatchingMaxItemsWeak m m')
699− Nothing -> issueAt cons (NoMatchingMaxItems m)
703+ else issueAt beh (MatchingMaxItemsWeak m m')
704+ Nothing -> issueAt beh (NoMatchingMaxItems m)
700705 MinItems m -> case findRelevant max (\case MinItems m' -> Just m'; _ -> Nothing) prods of
701706 Just m' -> if m' >= m then pure ()
702− else issueAt cons (MatchingMinItemsWeak m m')
703− Nothing -> issueAt cons (NoMatchingMinItems m)
704− UniqueItems -> if any (== UniqueItems) $ extract <$> prods then pure ()
705− else issueAt cons NoMatchingUniqueItems
707+ else issueAt beh (MatchingMinItemsWeak m m')
708+ Nothing -> issueAt beh (NoMatchingMinItems m)
709+ UniqueItems -> if any (== UniqueItems) $ prods then pure ()
710+ else issueAt beh NoMatchingUniqueItems
706711 Properties props _ madd -> case findRelevant (<>) (\case Properties props' _ madd' -> Just $ (props', madd') NE.:| []; _ -> Nothing) prods of
707712 Just ((props', madd') NE.:| []) -> do
708713 F.for_ (S.fromList $ M.keys props <> M.keys props') $ \k -> do
709− let go sch sch' = checkCompatibility env (ProdCons sch sch')
714+ let go sch sch' = checkCompatibility env (beh >>> step (InProperty k)) (ProdCons sch sch')
710715 case (M.lookup k props', madd', M.lookup k props, madd) of
711716 (Nothing, Nothing, _, _) -> pure () -- vacuously
712− (_, _, Nothing, Nothing) -> issueAt cons (UnexpectedProperty k)
717+ (_, _, Nothing, Nothing) -> issueAt beh (UnexpectedProperty k)
713718 (Just p', _, Just p, _) -> go (propRefSchema p') (propRefSchema p)
714719 (Nothing, Just add', Just p, _) -> go add' (propRefSchema p)
715720 (Just p', _, Nothing, Just add) -> go (propRefSchema p') add
716721 (Nothing, Just _, Nothing, Just _) -> pure ()
717722 case (maybe False propRequired $ M.lookup k props', maybe False propRequired $ M.lookup k props) of
718− (False, True) -> issueAt cons (PropertyNowRequired k)
723+ (False, True) -> issueAt beh (PropertyNowRequired k)
719724 _ -> pure ()
720725 pure ()
721726 case (madd', madd) of
722727 (Nothing, _) -> pure () -- vacuously
723− (_, Nothing) -> issueAt cons NoAdditionalProperties
724− (Just add', Just add) -> checkCompatibility env (ProdCons add' add)
728+ (_, Nothing) -> issueAt beh NoAdditionalProperties
729+ (Just add', Just add) -> checkCompatibility env (beh >>> step InAdditionalProperty) (ProdCons add' add)
725730 pure ()
726− Nothing -> issueAt cons NoMatchingProperties
731+ Nothing -> issueAt beh NoMatchingProperties
727732 MaxProperties m -> case findRelevant min (\case MaxProperties m' -> Just m'; _ -> Nothing) prods of
728733 Just m' -> if m' <= m then pure ()
729− else issueAt cons (MatchingMaxPropertiesWeak m m')
730− Nothing -> issueAt cons (NoMatchingMaxProperties m)
734+ else issueAt beh (MatchingMaxPropertiesWeak m m')
735+ Nothing -> issueAt beh (NoMatchingMaxProperties m)
731736 MinProperties m -> case findRelevant max (\case MinProperties m' -> Just m'; _ -> Nothing) prods of
732737 Just m' -> if m' >= m then pure ()
733− else issueAt cons (MatchingMinPropertiesWeak m m')
734− Nothing -> issueAt cons (NoMatchingMinProperties m)
738+ else issueAt beh (MatchingMinPropertiesWeak m m')
739+ Nothing -> issueAt beh (NoMatchingMinProperties m)
735740 where
736− findExactly ((extract -> Exactly x):_) = Just x
741+ findExactly (Exactly x:_) = Just x
737742 findExactly (_:xs) = findExactly xs
738743 findExactly [] = Nothing
739744 findRelevant combine extr
740− = fmap (foldr1 combine) . NE.nonEmpty . mapMaybe (extr . extract)
745+ = fmap (foldr1 combine) . NE.nonEmpty . mapMaybe extr
741746 lcmScientific (toRational -> a) (toRational -> b)
742747 = fromRational $ lcm (numerator a) (numerator b) % gcd (denominator a) (denominator b)
743748
744−instance Typeable t => Subtree (Condition t) where
745− data CheckIssue (Condition t)
746− = EnumDoesntSatisfy (TypedValue t)
747− | NoMatchingEnum (TypedValue t)
749+instance Issuable 'TypedSchemaLevel where
750+ data Issue 'TypedSchemaLevel
751+ = EnumDoesntSatisfy A.Value
752+ | NoMatchingEnum A.Value
748753 | NoMatchingMaximum (Bound Scientific)
749754 | MatchingMaximumWeak (Bound Scientific) (Bound Scientific)
750755 | NoMatchingMinimum (Bound Scientific)
@@ -771,29 +776,42 @@ instance Typeable t => Subtree (Condition t) where
771776 | MatchingMaxPropertiesWeak Integer Integer
772777 | NoMatchingMinProperties Integer
773778 | MatchingMinPropertiesWeak Integer Integer
779+ | NoMatchingCondition [SomeCondition]
780+ | NoContradiction
774781 deriving stock (Eq, Ord, Show)
775− type CheckEnv (Condition t) = CheckEnv Schema
776− normalizeTrace = undefined
777− checkCompatibility env pc = checkImplication env [producer pc] (consumer pc)
782+ issueIsUnsupported _ = False
778783
779−instance Subtree Schema where
780− data CheckIssue Schema
784+instance Issuable 'SchemaLevel where
785+ data Issue 'SchemaLevel
781786 = NotSupported Text
782787 | InvalidSchema Text
783− | NoMatchingCondition [SomeCondition]
784− | NoContradiction
785788 deriving stock (Eq, Ord, Show)
786− type CheckEnv Schema = '[ProdCons (Definitions Schema)]
787− checkCompatibility env schs = do
789+ issueIsUnsupported _ = True
790+
791+instance Behavable 'SchemaLevel 'TypedSchemaLevel where
792+ data Behave 'SchemaLevel 'TypedSchemaLevel
793+ = OfType JsonType
794+ deriving stock (Eq, Ord, Show)
795+
796+instance Behavable 'TypedSchemaLevel 'SchemaLevel where
797+ data Behave 'TypedSchemaLevel 'SchemaLevel
798+ = InItems
799+ | InProperty Text
800+ | InAdditionalProperty
801+ deriving stock (Eq, Ord, Show)
802+
803+instance Subtree Schema where
804+ type SubtreeLevel Schema = 'SchemaLevel
805+ type CheckEnv Schema = '[ProdCons (Traced (Definitions Schema))]
806+ checkCompatibility env beh schs = do
788807 let defs = getH env
789− checkFormulas env (ask $ producer schs) $ schemaToFormula <$> defs <*> schs
808+ checkFormulas env beh $ schemaToFormula <$> defs <*> schs
790809
791810instance Subtree (Referenced Schema) where
792− data CheckIssue (Referenced Schema)
793− deriving stock (Eq, Ord, Show)
811+ type SubtreeLevel (Referenced Schema) = 'SchemaLevel
794812 type CheckEnv (Referenced Schema) = CheckEnv Schema
795− checkCompatibility env refs = do
813+ checkCompatibility env beh refs = do
796814 let
797815 defs = getH env
798816 schs = dereference <$> defs <*> refs
799− checkFormulas env (ask $ producer schs) $ schemaToFormula <$> defs <*> schs
817+ checkFormulas env beh $ schemaToFormula <$> defs <*> schs