@@ -17,12 +17,13 @@ import Algebra.Lattice
17import Control.Applicative
17import Control.Applicative
18import Control.Arrow
18import Control.Arrow
19import Control.Comonad.Env hiding (env)
19import Control.Comonad.Env hiding (env)
20−import Control.Lens hiding (cons)
20+import Control.Lens hiding (cons, para)
21import Control.Monad.Reader hiding (ask)
21import Control.Monad.Reader hiding (ask)
22import qualified Control.Monad.Reader as R
22import qualified Control.Monad.Reader as R
23import Control.Monad.State
23import Control.Monad.State
24import Control.Monad.Writer
24import Control.Monad.Writer
25import qualified Data.Aeson as A
25import qualified Data.Aeson as A
26+import qualified Data.ByteString.Lazy as BSL
26import Data.Coerce
27import Data.Coerce
27import qualified Data.Foldable as F
28import qualified Data.Foldable as F
28import Data.HList
29import Data.HList
@@ -40,8 +41,8 @@ import Data.Scientific
40import qualified Data.Set as S
41import qualified Data.Set as S
41import Data.Text (Text)
42import Data.Text (Text)
42import qualified Data.Text as T hiding (singleton)
43import qualified Data.Text as T hiding (singleton)
44+import qualified Data.Text.Encoding as T
43import Data.Typeable
45import Data.Typeable
44−import Text.Regex.Pcre2
45import OpenAPI.Checker.Behavior
46import OpenAPI.Checker.Behavior
46import OpenAPI.Checker.Memo
47import OpenAPI.Checker.Memo
47import OpenAPI.Checker.Orphans ()
48import OpenAPI.Checker.Orphans ()
@@ -49,6 +50,8 @@ import OpenAPI.Checker.Paths
49import qualified OpenAPI.Checker.PathsPrefixTree as P
50import qualified OpenAPI.Checker.PathsPrefixTree as P
50import OpenAPI.Checker.References
51import OpenAPI.Checker.References
51import OpenAPI.Checker.Subtree
52import OpenAPI.Checker.Subtree
53+import Text.Pandoc.Builder hiding (Format, Null)
54+import Text.Regex.Pcre2
53-- | Type of a JSON value
56-- | Type of a JSON value
54data JsonType
57data JsonType
@@ -84,7 +87,7 @@ untypeValue (TArray a) = A.Array a
84untypeValue (TObject o) = A.Object o
87untypeValue (TObject o) = A.Object o
86data Bound a = Exclusive !a | Inclusive !a
89data Bound a = Exclusive !a | Inclusive !a
87− deriving (Eq, Show)
90+ deriving (Eq, Show, Functor)
89-- | The order is lexicographical on @a * Bool@.
92-- | The order is lexicographical on @a * Bool@.
90instance Ord a => Ord (Bound a) where
93instance Ord a => Ord (Bound a) where
@@ -128,6 +131,45 @@ data Condition :: JsonType -> Type where
128 MaxProperties :: !Integer -> Condition 'Object
131 MaxProperties :: !Integer -> Condition 'Object
129 MinProperties :: !Integer -> Condition 'Object
132 MinProperties :: !Integer -> Condition 'Object
134+showCondition :: Condition a -> Blocks
135+showCondition = \case
136+ (Exactly v) -> para "The value should be:" <> showJSONValue (untypeValue v)
137+ (Maximum b) -> para $ "The value should be less than " <> showBound b <> "."
138+ (Minimum (Down b)) -> para $ "The value should be more than " <> showBound (getDown <$> b) <> "."
139+ (MultipleOf n) -> para $ "The value should be a multiple of " <> show' n <> "."
140+ (NumberFormat p) -> para $ "The number should have the following format:" <> code p <> "."
141+ (Pattern p) -> para "The value should satisfy the following pattern (regular expression):" <> codeBlock p
142+ (StringFormat p) -> para $ "The string should have the following format:" <> code p <> "."
143+ (MaxLength p) -> para $ "The length of the string should be less than or equal to " <> show' p <> "."
144+ (MinLength p) -> para $ "The length of the string should be more than or equal to " <> show' p <> "."
145+ (Items i _) -> para "The items of the array should satisfy:" <> showForEachJsonFormula i
146+ (MaxItems n) -> para $ "The length of the array should be less than or equal to " <> show' n <> "."
147+ (MinItems n) -> para $ "The length of the array should be more than or equal to " <> show' n <> "."
148+ UniqueItems -> para "The elements in the array should be unique."
149+ (Properties props additional _) ->
154+ <> para (strong $ if propRequired p then "Required" else "Optional")
155+ <> showForEachJsonFormula (propFormula p)))
156+ <> [ para (emph "Additional properties")
157+ <> showForEachJsonFormula additional
159+ (MaxProperties n) -> para $ "The maximum number of fields should be " <> show' n <> "."
160+ (MinProperties n) -> para $ "The minimum number of fields should be " <> show' n <> "."
162+ showForEachJsonFormula :: ForeachType JsonFormula -> Blocks
163+ showForEachJsonFormula i =
167+ let (DNF conds') = f i
168+ conds = S.toList <$> S.toList conds'
169+ in [ para (describeJSONType t)
170+ <> bulletList (conds <&> \cond -> bulletList (showCondition <$> cond))
131satisfiesTyped :: TypedValue t -> Condition t -> Bool
173satisfiesTyped :: TypedValue t -> Condition t -> Bool
132satisfiesTyped e (Exactly e') = e == e'
174satisfiesTyped e (Exactly e') = e == e'
133satisfiesTyped (TNumber n) (Maximum (Exclusive m)) = n < m
175satisfiesTyped (TNumber n) (Maximum (Exclusive m)) = n < m
@@ -363,10 +405,6 @@ instance
363 , forObject = top
405 , forObject = top
366−instance Behavable 'SchemaLevel 'SchemaLevel where
367− data Behave 'SchemaLevel 'SchemaLevel
368− deriving (Eq, Ord, Show)
370instance Steppable Schema (Referenced Schema) where
408instance Steppable Schema (Referenced Schema) where
371 data Step Schema (Referenced Schema)
409 data Step Schema (Referenced Schema)
372 = AllOfStep Int
410 = AllOfStep Int
@@ -396,7 +434,7 @@ parseDiscriminatorValue v = case A.fromJSON @(Referenced Schema) $ A.object ["$r
396 A.Error _ -> Ref $ Reference v
434 A.Error _ -> Ref $ Reference v
398-- | A fake writer monad that doesn't actually record anything and allows lazy recursion.
436-- | A fake writer monad that doesn't actually record anything and allows lazy recursion.
399−newtype Silent q f r a = Silent { runSilent :: a }
437+newtype Silent q f r a = Silent {runSilent :: a}
400 deriving stock (Functor)
438 deriving stock (Functor)
401 deriving (Applicative, Monad) via Identity
439 deriving (Applicative, Monad) via Identity
@@ -427,11 +465,12 @@ silently m = do
427 pure . runSilent $ runReaderT m defs
465 pure . runSilent $ runReaderT m defs
429warnKnot :: MonadProcess m => KnotTier (ForeachType JsonFormula) () m
467warnKnot :: MonadProcess m => KnotTier (ForeachType JsonFormula) () m
430−warnKnot = KnotTier
468+warnKnot =
431− { onKnotFound = warn UnguardedRecursion
470+ { onKnotFound = warn UnguardedRecursion
432− , onKnotUsed = \_ -> pure bottom
471+ , onKnotUsed = \_ -> pure bottom
433− , tieKnot = \_ -> pure
472+ , tieKnot = \_ -> pure
436processRefSchema
475processRefSchema
437 :: MonadProcess m
476 :: MonadProcess m
@@ -835,19 +874,19 @@ checkImplication env beh prods cons = case findExactly prods of
835 Just m' ->
874 Just m' ->
836 if m' <= m
875 if m' <= m
837 then pure ()
876 then pure ()
838− else issueAt beh (MatchingMaximumWeak m m')
877+ else issueAt beh (MatchingMaximumWeak $ ProdCons {producer = m', consumer = m})
839 Nothing -> issueAt beh (NoMatchingMaximum m)
878 Nothing -> issueAt beh (NoMatchingMaximum m)
840 Minimum m -> case findRelevant max (\case Minimum m' -> Just m'; _ -> Nothing) prods of
879 Minimum m -> case findRelevant max (\case Minimum m' -> Just m'; _ -> Nothing) prods of
841 Just m' ->
880 Just m' ->
842 if m' >= m
881 if m' >= m
843 then pure ()
882 then pure ()
844− else issueAt beh (MatchingMinimumWeak (coerce m) (coerce m'))
883+ else issueAt beh (MatchingMinimumWeak ProdCons {producer = coerce m', consumer = coerce m})
845 Nothing -> issueAt beh (NoMatchingMinimum (coerce m))
884 Nothing -> issueAt beh (NoMatchingMinimum (coerce m))
846 MultipleOf m -> case findRelevant lcmScientific (\case MultipleOf m' -> Just m'; _ -> Nothing) prods of
885 MultipleOf m -> case findRelevant lcmScientific (\case MultipleOf m' -> Just m'; _ -> Nothing) prods of
847 Just m' ->
886 Just m' ->
848 if lcmScientific m m' == m'
887 if lcmScientific m m' == m'
849 then pure ()
888 then pure ()
850− else issueAt beh (MatchingMultipleOfWeak m m')
889+ else issueAt beh (MatchingMultipleOfWeak $ ProdCons {producer = m', consumer = m})
851 Nothing -> issueAt beh (NoMatchingMultipleOf m)
890 Nothing -> issueAt beh (NoMatchingMultipleOf m)
852 NumberFormat f ->
891 NumberFormat f ->
853 if any (\case NumberFormat f' -> f == f'; _ -> False) prods
892 if any (\case NumberFormat f' -> f == f'; _ -> False) prods
@@ -857,13 +896,13 @@ checkImplication env beh prods cons = case findExactly prods of
857 Just m' ->
896 Just m' ->
858 if m' <= m
897 if m' <= m
859 then pure ()
898 then pure ()
860− else issueAt beh (MatchingMaxLengthWeak m m')
899+ else issueAt beh (MatchingMaxLengthWeak $ ProdCons {producer = m', consumer = m})
861 Nothing -> issueAt beh (NoMatchingMaxLength m)
900 Nothing -> issueAt beh (NoMatchingMaxLength m)
862 MinLength m -> case findRelevant max (\case MinLength m' -> Just m'; _ -> Nothing) prods of
901 MinLength m -> case findRelevant max (\case MinLength m' -> Just m'; _ -> Nothing) prods of
863 Just m' ->
902 Just m' ->
864 if m' >= m
903 if m' >= m
865 then pure ()
904 then pure ()
866− else issueAt beh (MatchingMinLengthWeak m m')
905+ else issueAt beh (MatchingMinLengthWeak $ ProdCons {producer = m', consumer = m})
867 Nothing -> issueAt beh (NoMatchingMinLength m)
906 Nothing -> issueAt beh (NoMatchingMinLength m)
868 Pattern p ->
907 Pattern p ->
869 if any (\case Pattern p' -> p == p'; _ -> False) prods
908 if any (\case Pattern p' -> p == p'; _ -> False) prods
@@ -883,50 +922,52 @@ checkImplication env beh prods cons = case findExactly prods of
883 Just m' ->
922 Just m' ->
884 if m' <= m
923 if m' <= m
885 then pure ()
924 then pure ()
886− else issueAt beh (MatchingMaxItemsWeak m m')
925+ else issueAt beh (MatchingMaxItemsWeak ProdCons {producer = m', consumer = m})
887 Nothing -> issueAt beh (NoMatchingMaxItems m)
926 Nothing -> issueAt beh (NoMatchingMaxItems m)
888 MinItems m -> case findRelevant max (\case MinItems m' -> Just m'; _ -> Nothing) prods of
927 MinItems m -> case findRelevant max (\case MinItems m' -> Just m'; _ -> Nothing) prods of
889 Just m' ->
928 Just m' ->
890 if m' >= m
929 if m' >= m
891 then pure ()
930 then pure ()
892− else issueAt beh (MatchingMinItemsWeak m m')
931+ else issueAt beh (MatchingMinItemsWeak ProdCons {producer = m', consumer = m})
893 Nothing -> issueAt beh (NoMatchingMinItems m)
932 Nothing -> issueAt beh (NoMatchingMinItems m)
894 UniqueItems ->
933 UniqueItems ->
895 if any (== UniqueItems) $ prods
934 if any (== UniqueItems) $ prods
896 then pure ()
935 then pure ()
897 else issueAt beh NoMatchingUniqueItems
936 else issueAt beh NoMatchingUniqueItems
898 Properties props _ madd -> case findRelevant (<>) (\case Properties props' _ madd' -> Just $ (props', madd') NE.:| []; _ -> Nothing) prods of
937 Properties props _ madd -> case findRelevant (<>) (\case Properties props' _ madd' -> Just $ (props', madd') NE.:| []; _ -> Nothing) prods of
899− Just pm -> anyOfAt beh NoMatchingProperties $ NE.toList pm <&> \(props', madd') -> do
939+ anyOfAt beh NoMatchingProperties $
940+ NE.toList pm <&> \(props', madd') -> do
900− F.for_ (S.fromList $ M.keys props <> M.keys props') $ \k -> do
941+ F.for_ (S.fromList $ M.keys props <> M.keys props') $ \k -> do
901− let go sch sch' = checkCompatibility env (beh >>> step (InProperty k)) (ProdCons sch sch')
942+ let go sch sch' = checkCompatibility env (beh >>> step (InProperty k)) (ProdCons sch sch')
902− case (M.lookup k props', madd', M.lookup k props, madd) of
943+ case (M.lookup k props', madd', M.lookup k props, madd) of
903− (Nothing, Nothing, _, _) -> pure () -- vacuously
944+ (Nothing, Nothing, _, _) -> pure () -- vacuously
904− (_, _, Nothing, Nothing) -> issueAt beh (UnexpectedProperty k)
945+ (_, _, Nothing, Nothing) -> issueAt beh (UnexpectedProperty k)
905− (Just p', _, Just p, _) -> go (propRefSchema p') (propRefSchema p)
946+ (Just p', _, Just p, _) -> go (propRefSchema p') (propRefSchema p)
906− (Nothing, Just add', Just p, _) -> go add' (propRefSchema p)
947+ (Nothing, Just add', Just p, _) -> go add' (propRefSchema p)
907− (Just p', _, Nothing, Just add) -> go (propRefSchema p') add
948+ (Just p', _, Nothing, Just add) -> go (propRefSchema p') add
908− (Nothing, Just _, Nothing, Just _) -> pure ()
949+ (Nothing, Just _, Nothing, Just _) -> pure ()
909− case (maybe False propRequired $ M.lookup k props', maybe False propRequired $ M.lookup k props) of
950+ case (maybe False propRequired $ M.lookup k props', maybe False propRequired $ M.lookup k props) of
910− (False, True) -> issueAt beh (PropertyNowRequired k)
951+ (False, True) -> issueAt beh (PropertyNowRequired k)
911− _ -> pure ()
952+ _ -> pure ()
913− case (madd', madd) of
954+ case (madd', madd) of
914− (Nothing, _) -> pure () -- vacuously
955+ (Nothing, _) -> pure () -- vacuously
915− (_, Nothing) -> issueAt beh NoAdditionalProperties
956+ (_, Nothing) -> issueAt beh NoAdditionalProperties
916− (Just add', Just add) -> checkCompatibility env (beh >>> step InAdditionalProperty) (ProdCons add' add)
957+ (Just add', Just add) -> checkCompatibility env (beh >>> step InAdditionalProperty) (ProdCons add' add)
918 Nothing -> issueAt beh NoMatchingProperties
959 Nothing -> issueAt beh NoMatchingProperties
919 MaxProperties m -> case findRelevant min (\case MaxProperties m' -> Just m'; _ -> Nothing) prods of
960 MaxProperties m -> case findRelevant min (\case MaxProperties m' -> Just m'; _ -> Nothing) prods of
920 Just m' ->
961 Just m' ->
921 if m' <= m
962 if m' <= m
922 then pure ()
963 then pure ()
923− else issueAt beh (MatchingMaxPropertiesWeak m m')
964+ else issueAt beh (MatchingMaxPropertiesWeak ProdCons {producer = m', consumer = m})
924 Nothing -> issueAt beh (NoMatchingMaxProperties m)
965 Nothing -> issueAt beh (NoMatchingMaxProperties m)
925 MinProperties m -> case findRelevant max (\case MinProperties m' -> Just m'; _ -> Nothing) prods of
966 MinProperties m -> case findRelevant max (\case MinProperties m' -> Just m'; _ -> Nothing) prods of
926 Just m' ->
967 Just m' ->
927 if m' >= m
968 if m' >= m
928 then pure ()
969 then pure ()
929− else issueAt beh (MatchingMinPropertiesWeak m m')
970+ else issueAt beh (MatchingMinPropertiesWeak ProdCons {producer = m', consumer = m})
930 Nothing -> issueAt beh (NoMatchingMinProperties m)
971 Nothing -> issueAt beh (NoMatchingMinProperties m)
932 findExactly (Exactly x : _) = Just x
973 findExactly (Exactly x : _) = Just x
@@ -939,85 +980,145 @@ checkImplication env beh prods cons = case findExactly prods of
940instance Issuable 'TypedSchemaLevel where
981instance Issuable 'TypedSchemaLevel where
941 data Issue 'TypedSchemaLevel
982 data Issue 'TypedSchemaLevel
942− = EnumDoesntSatisfy A.Value
943− -- ^ producer produces a specific value ($1), consumer has a condition that is not satisfied by said value
983+ = -- | producer produces a specific value ($1), consumer has a condition that is not satisfied by said value
984+ EnumDoesntSatisfy A.Value
985+ | -- | consumer only expects a specific value which the producer does not produce.
944− | NoMatchingEnum A.Value
986+ NoMatchingEnum A.Value
945− -- ^ producer produces a specific value ($1) which is not listed in the consumer's list of specific values
987+ | -- | consumer declares a maximum numeric value ($1), producer doesn't
946− | NoMatchingMaximum (Bound Scientific)
988+ NoMatchingMaximum (Bound Scientific)
947− -- ^ producer declares a maximum numeric value ($1), consumer doesnt
989+ | -- | consumer declares a maximum numeric value ($1), producer declares a weaker (higher) limit ($2)
948− | MatchingMaximumWeak (Bound Scientific) (Bound Scientific)
990+ MatchingMaximumWeak (ProdCons (Bound Scientific))
949− -- ^ producer declares a maximum numeric value ($1), consumer declares a weaker (higher) limit ($2)
991+ | -- | consumer declares a minimum numeric value, producer doesn't
950− | NoMatchingMinimum (Bound Scientific)
992+ NoMatchingMinimum (Bound Scientific)
951− -- ^ producer declares a minimum numeric value, consumer doesnt
993+ | -- | consumer declares a minimum numeric value ($1), producer declares a weaker (lower) limit ($2)
952− | MatchingMinimumWeak (Bound Scientific) (Bound Scientific)
994+ MatchingMinimumWeak (ProdCons (Bound Scientific))
953− -- ^ producer declares a minimum numeric value ($1), consumer declares a weaker (lower) limit ($2)
995+ | -- | consumer declares that the numeric value must be a multiple of $1, producer doesn't
954− | NoMatchingMultipleOf Scientific
996+ NoMatchingMultipleOf Scientific
955− -- ^ producer declares that the numeric value must be a multiple of $1, consumer doesn't
997+ | -- | consumer declares that the numeric value must be a multiple of $1, producer declares a weaker condition (multiple of $2)
956− | MatchingMultipleOfWeak Scientific Scientific
998+ MatchingMultipleOfWeak (ProdCons Scientific)
957− -- ^ producer declares that the numeric value must be a multiple of $1, consumer declares a weaker condition (multiple of $2)
958− | NoMatchingFormat Format
959− -- ^ producer declares a string/number format, consumer declares none or a different format (TODO: improve via regex #32)
999+ | -- | consumer declares a string/number format, producer declares none or a different format (TODO: improve via regex #32)
1000+ NoMatchingFormat Format
1001+ | -- | consumer declares a maximum length of the string ($1), producer doesn't.
960− | NoMatchingMaxLength Integer
1002+ NoMatchingMaxLength Integer
961− -- ^ producer declares a maximum length of the string ($1), consumer doesn't.
1003+ | -- | consumer declares a maximum length of the string ($1), producer declares a weaker (higher) limit ($2)
962− | MatchingMaxLengthWeak Integer Integer
1004+ MatchingMaxLengthWeak (ProdCons Integer)
963− -- ^ producer declares a maximum length of the string ($1), consumer declares a weaker (higher) limit ($2)
1005+ | -- | consumer declares a minimum length of the string ($1), producer doesn't.
964− | NoMatchingMinLength Integer
1006+ NoMatchingMinLength Integer
965− -- ^ producer declares a minimum length of the string ($1), consumer doesn't.
1007+ | -- | consumer declares a minimum length of the string ($1), producer declares a weaker (lower) limit ($2)
966− | MatchingMinLengthWeak Integer Integer
1008+ MatchingMinLengthWeak (ProdCons Integer)
967− -- ^ producer declares a minimum length of the string ($1), consumer declares a weaker (lower) limit ($2)
968− | NoMatchingPattern Pattern
969− -- ^ producer declares the string value must matrix a regex ($1), consumer doesn't declare or declares different regex (TODO: #32)
1009+ | -- | consumer declares the string value must matrix a regex ($1), producer doesn't declare or declares different regex (TODO: #32)
1010+ NoMatchingPattern Pattern
1011+ | -- | consumer declares the items of an array must satisfy some condition, producer doesn't
970− | NoMatchingItems
1012+ NoMatchingItems
971− -- ^ producer declares the items of an array must satisfy some condition, consumer doesn't
1013+ | -- | consumer declares a maximum length of the array ($1), producer doesn't.
972− | NoMatchingMaxItems Integer
1014+ NoMatchingMaxItems Integer
973− -- ^ producer declares a maximum length of the array ($1), consumer doesn't.
1015+ | -- | consumer declares a maximum length of the array ($1), producer declares a weaker (higher) limit ($2)
974− | MatchingMaxItemsWeak Integer Integer
1016+ MatchingMaxItemsWeak (ProdCons Integer)
975− -- ^ producer declares a maximum length of the array ($1), consumer declares a weaker (higher) limit ($2)
1017+ | -- | consumer declares a minimum length of the array ($1), producer doesn't.
976− | NoMatchingMinItems Integer
1018+ NoMatchingMinItems Integer
977− -- ^ producer declares a minimum length of the array ($1), consumer doesn't.
1019+ | -- | consumer declares a minimum length of the array ($1), producer declares a weaker (lower) limit ($2)
978− | MatchingMinItemsWeak Integer Integer
1020+ MatchingMinItemsWeak (ProdCons Integer)
979− -- ^ producer declares a minimum length of the array ($1), consumer declares a weaker (lower) limit ($2)
1021+ | -- | consumer declares that items must be unique, producer doesn't
980− | NoMatchingUniqueItems
1022+ NoMatchingUniqueItems
981− -- ^ producer declares that items must be unique, consumer doesn't
1023+ | -- | consumer declares the properties of an object must satisfy some condition, producer doesn't
982− | NoMatchingProperties
1024+ NoMatchingProperties
983− -- ^ producer declares the properties of an object must satisfy some condition, consumer doesn't
1025+ | -- | producer allows a property that is not allowed in the consumer
984− | UnexpectedProperty Text
1026+ UnexpectedProperty Text
985− -- ^ producer allows a property that is not allowed in the consumer
986− | PropertyNowRequired Text
987− -- ^ consumer requires a property that is not required/allowed in the consumer
1027+ | -- | consumer requires a property that is not required/allowed in the producer
988− | NoAdditionalProperties
1028+ PropertyNowRequired Text
989− -- ^ producer allows additional properties, consumer doesn't
1029+ | -- | producer allows additional properties, consumer doesn't
1030+ NoAdditionalProperties
1031+ | -- | consumer declares a maximum number of properties in the object ($1), producer doesn't.
990− | NoMatchingMaxProperties Integer
1032+ NoMatchingMaxProperties Integer
991− -- ^ producer declares a maximum number of properties in the object ($1), consumer doesn't.
1033+ | -- | consumer declares a maximum number of properties in the object ($1), producer declares a weaker (higher) limit ($2)
992− | MatchingMaxPropertiesWeak Integer Integer
1034+ MatchingMaxPropertiesWeak (ProdCons Integer)
993− -- ^ producer declares a maximum number of properties in the object ($1), consumer declares a weaker (higher) limit ($2)
1035+ | -- | consumer declares a minimum number of properties in the object ($1), producer doesn't.
994− | NoMatchingMinProperties Integer
1036+ NoMatchingMinProperties Integer
995− -- ^ producer declares a minimum number of properties in the object ($1), consumer doesn't.
1037+ | -- | consumer declares a minimum number of properties in the object ($1), producer declares a weaker (lower) limit ($2)
996− | MatchingMinPropertiesWeak Integer Integer
1038+ MatchingMinPropertiesWeak (ProdCons Integer)
997− -- ^ producer declares a minimum number of properties in the object ($1), consumer declares a weaker (lower) limit ($2)
998− | NoMatchingCondition [SomeCondition]
999− -- ^ consumer declares that the value must satisfy a disjunction of some conditions, but producer's requirements couldn't be matched against any single one of them (TODO: split heuristic #71)
1039+ | -- | consumer declares that the value must satisfy a disjunction of some conditions, but producer's requirements couldn't be matched against any single one of them (TODO: split heuristic #71)
1000− | NoContradiction
1040+ NoMatchingCondition [SomeCondition]
1001− -- ^ producer indicates that values of this type are now allowed, but the consumer does not do so (currently we only check immediate contradictions, c.f. #70)
1041+ | -- | consumer indicates that values of this type are now allowed, but the producer does not do so (currently we only check immediate contradictions, c.f. #70)
1002 deriving stock (Eq, Ord, Show)
1043 deriving stock (Eq, Ord, Show)
1003 issueIsUnsupported _ = False
1044 issueIsUnsupported _ = False
1045+ describeIssue (EnumDoesntSatisfy v) = para "The following enum value will yield an error:" <> showJSONValue v
1046+ describeIssue (NoMatchingEnum v) = para "The following enum value is not supported:" <> showJSONValue v
1047+ describeIssue (NoMatchingMaximum b) = para $ "Unexpected upper bound " <> showBound b <> "."
1048+ describeIssue (MatchingMaximumWeak (ProdCons p c)) = para $ "Expected upper bound " <> showBound p <> " but but found " <> showBound c <> "."
1049+ describeIssue (NoMatchingMinimum b) = para $ "Unexpected lower bound " <> showBound b <> "."
1050+ describeIssue (MatchingMinimumWeak (ProdCons p c)) = para $ "Expected lower bound " <> showBound p <> " but but found " <> showBound c <> "."
1051+ describeIssue (NoMatchingMultipleOf n) = para $ "Didn't expect the value to be a multiple of " <> show' n <> " but it was."
1052+ describeIssue (MatchingMultipleOfWeak (ProdCons p c)) = para $ "Expected the value to be a multiple of " <> show' p <> " but found a multiple of " <> show' c <> "."
1053+ describeIssue (NoMatchingFormat f) = para $ "Unexpected format: " <> code f <> "."
1054+ describeIssue (NoMatchingMaxLength n) = para $ "Unexpected maximum length of the string " <> show' n <> "."
1055+ describeIssue (MatchingMaxLengthWeak (ProdCons p c)) = para $ "Expected the maximum length of the string to be " <> show' p <> "but it was " <> show' c <> "."
1056+ describeIssue (NoMatchingMinLength n) = para $ "Unexpected minimum length of the string " <> show' n <> "."
1057+ describeIssue (MatchingMinLengthWeak (ProdCons p c)) = para $ "Expected the minimum length of the string to be " <> show' p <> "but it was " <> show' c <> "."
1058+ describeIssue (NoMatchingPattern p) = para "Unexpected pattern (regular expression): " <> codeBlock p
1059+ describeIssue NoMatchingItems = para "Couldn't find any matching items."
1060+ describeIssue (NoMatchingMaxItems n) = para $ "Unexpected maximum length of the array " <> show' n <> "."
1061+ describeIssue (MatchingMaxItemsWeak (ProdCons p c)) = para $ "Expected the maximum length of the array to be " <> show' p <> "but it was " <> show' c <> "."
1062+ describeIssue (NoMatchingMinItems n) = para $ "Unexpected minimum length of the array " <> show' n <> "."
1063+ describeIssue (MatchingMinItemsWeak (ProdCons p c)) = para $ "Expected the minimum length of the array to be " <> show' p <> "but it was " <> show' c <> "."
1064+ describeIssue NoMatchingUniqueItems = para "Didn't expect the items to be unique, but they were."
1065+ describeIssue NoMatchingProperties = para "Couldn't find matching properties."
1066+ describeIssue (UnexpectedProperty p) = para $ "Expected the property " <> code p <> " to be allowed, but it wasn't."
1067+ describeIssue (PropertyNowRequired p) = para $ "Don't have a required property " <> code p <> "."
1068+ describeIssue NoAdditionalProperties = para "Expected additional properties to be allowed, but they weren't."
1069+ describeIssue (NoMatchingMaxProperties n) = para $ "Unexpected maximum number of properties " <> show' n <> "."
1070+ describeIssue (MatchingMaxPropertiesWeak (ProdCons p c)) = para $ "Expected the maximum number of properties to be " <> show' p <> "but it was " <> show' c <> "."
1071+ describeIssue (NoMatchingMinProperties n) = para $ "Unexpected minimum number of properties " <> show' n <> "."
1072+ describeIssue (MatchingMinPropertiesWeak (ProdCons p c)) = para $ "Expected the minimum number of properties to be " <> show' p <> "but it was " <> show' c <> "."
1073+ describeIssue (NoMatchingCondition conds) =
1074+ para "Expected the following conditions to hold, but they didn't:"
1075+ <> bulletList ((\(SomeCondition c) -> showCondition c) <$> conds)
1076+ describeIssue NoContradiction = para "Expected the type to be allowed, but it wasn't."
1078+showJSONValue :: A.Value -> Blocks
1079+showJSONValue v = codeBlockWith ("", ["json"], mempty) (T.decodeUtf8 . BSL.toStrict . A.encode $ v)
1081+showBound :: Show a => Bound a -> Inlines
1082+showBound (Inclusive x) = show' x <> " inclusive"
1083+showBound (Exclusive x) = show' x <> " exclusive"
1085+show' :: Show x => x -> Inlines
1086+show' = str . T.pack . show
1005instance Issuable 'SchemaLevel where
1088instance Issuable 'SchemaLevel where
1006 data Issue 'SchemaLevel
1089 data Issue 'SchemaLevel
1007− = NotSupported Text
1008− -- ^ Some (openapi-supported) feature that we do not support was encountered in the schema
1090+ = -- | Some (openapi-supported) feature that we do not support was encountered in the schema
1009− | InvalidSchema Text
1091+ NotSupported Text
1010− -- ^ The schema is actually invalid
1092+ | -- | The schema is actually invalid
1011− | UnguardedRecursion
1093+ InvalidSchema Text
1012− -- ^ The schema contains a reference loop along "anyOf"/"allOf"/"oneOf".
1094+ | -- | The schema contains a reference loop along "anyOf"/"allOf"/"oneOf".
1013 deriving stock (Eq, Ord, Show)
1096 deriving stock (Eq, Ord, Show)
1014 issueIsUnsupported _ = True
1097 issueIsUnsupported _ = True
1099+ describeIssue (NotSupported i) =
1100+ para (emph "Encountered a feature that OpenApi Diff does not support: " <> text i <> ".")
1101+ describeIssue (InvalidSchema i) =
1102+ para (emph "The schema is invalid: " <> text i <> ".")
1103+ describeIssue UnguardedRecursion =
1104+ para "Encountered recursion that is too complex for OpenApi Diff to untangle."
1016instance Behavable 'SchemaLevel 'TypedSchemaLevel where
1106instance Behavable 'SchemaLevel 'TypedSchemaLevel where
1017 data Behave 'SchemaLevel 'TypedSchemaLevel
1107 data Behave 'SchemaLevel 'TypedSchemaLevel
1018 = OfType JsonType
1108 = OfType JsonType
1019 deriving stock (Eq, Ord, Show)
1109 deriving stock (Eq, Ord, Show)
1111+ describeBehaviour (OfType t) = describeJSONType t
1113+describeJSONType :: JsonType -> Inlines
1114+describeJSONType = \case
1116+ Boolean -> "Boolean"
1021instance Behavable 'TypedSchemaLevel 'SchemaLevel where
1122instance Behavable 'TypedSchemaLevel 'SchemaLevel where
1022 data Behave 'TypedSchemaLevel 'SchemaLevel
1123 data Behave 'TypedSchemaLevel 'SchemaLevel
1023 = InItems
1124 = InItems
@@ -1025,6 +1126,10 @@ instance Behavable 'TypedSchemaLevel 'SchemaLevel where
1025 | InAdditionalProperty
1126 | InAdditionalProperty
1026 deriving stock (Eq, Ord, Show)
1127 deriving stock (Eq, Ord, Show)
1129+ describeBehaviour InItems = "Items"
1130+ describeBehaviour (InProperty p) = "Property " <> code p
1131+ describeBehaviour InAdditionalProperty = "Additional properties"
1028instance Subtree Schema where
1133instance Subtree Schema where
1029 type SubtreeLevel Schema = 'SchemaLevel
1134 type SubtreeLevel Schema = 'SchemaLevel
1030 type CheckEnv Schema = '[ProdCons (Traced (Definitions Schema))]
1135 type CheckEnv Schema = '[ProdCons (Traced (Definitions Schema))]