@@ -40,6 +40,7 @@ import qualified Data.Set as S
40import Data.Text (Text)
40import Data.Text (Text)
41import qualified Data.Text as T hiding (singleton)
41import qualified Data.Text as T hiding (singleton)
42import Data.Typeable
42import Data.Typeable
43+import Text.Regex.Pcre2
43import OpenAPI.Checker.Behavior
44import OpenAPI.Checker.Behavior
44import OpenAPI.Checker.Orphans ()
45import OpenAPI.Checker.Orphans ()
45import OpenAPI.Checker.Paths
46import OpenAPI.Checker.Paths
@@ -131,16 +132,16 @@ satisfiesTyped (TNumber n) (Maximum (Exclusive m)) = n < m
131satisfiesTyped (TNumber n) (Maximum (Inclusive m)) = n <= m
132satisfiesTyped (TNumber n) (Maximum (Inclusive m)) = n <= m
132satisfiesTyped (TNumber n) (Minimum (Down (Exclusive (Down m)))) = n > m
133satisfiesTyped (TNumber n) (Minimum (Down (Exclusive (Down m)))) = n > m
133satisfiesTyped (TNumber n) (Minimum (Down (Inclusive (Down m)))) = n >= m
134satisfiesTyped (TNumber n) (Minimum (Down (Inclusive (Down m)))) = n >= m
134−satisfiesTyped (TNumber n) (MultipleOf m) = denominator (toRational n / toRational m) == 1 -- TODO: could be better
135+satisfiesTyped (TNumber n) (MultipleOf m) = denominator (toRational n / toRational m) == 1 -- TODO: could be better #36
135satisfiesTyped (TNumber n) (NumberFormat f) = checkNumberFormat f n
136satisfiesTyped (TNumber n) (NumberFormat f) = checkNumberFormat f n
136satisfiesTyped (TString s) (MaxLength m) = fromIntegral (T.length s) <= m
137satisfiesTyped (TString s) (MaxLength m) = fromIntegral (T.length s) <= m
137satisfiesTyped (TString s) (MinLength m) = fromIntegral (T.length s) >= m
138satisfiesTyped (TString s) (MinLength m) = fromIntegral (T.length s) >= m
138−satisfiesTyped (TString s) (Pattern p) = undefined s p -- TODO: regex stuff
139+satisfiesTyped (TString s) (Pattern p) = isJust $ match p s -- TODO: regex stuff #32
139−satisfiesTyped (TString s) (StringFormat f) = undefined s f -- TODO: string format
140+satisfiesTyped (TString s) (StringFormat f) = checkStringFormat f s
140satisfiesTyped (TArray a) (Items f _) = all (`satisfies` f) a
141satisfiesTyped (TArray a) (Items f _) = all (`satisfies` f) a
141satisfiesTyped (TArray a) (MaxItems m) = fromIntegral (F.length a) <= m
142satisfiesTyped (TArray a) (MaxItems m) = fromIntegral (F.length a) <= m
142satisfiesTyped (TArray a) (MinItems m) = fromIntegral (F.length a) >= m
143satisfiesTyped (TArray a) (MinItems m) = fromIntegral (F.length a) >= m
143−satisfiesTyped (TArray a) UniqueItems = S.size (S.fromList $ F.toList a) == F.length a -- TODO: could be better
144+satisfiesTyped (TArray a) UniqueItems = S.size (S.fromList $ F.toList a) == F.length a -- TODO: could be better #36
144satisfiesTyped (TObject o) (Properties props additional _) =
145satisfiesTyped (TObject o) (Properties props additional _) =
145 all (`HM.member` o) (M.keys (M.filter propRequired props))
146 all (`HM.member` o) (M.keys (M.filter propRequired props))
146 && all (\(k, v) -> satisfies v $ maybe additional propFormula $ M.lookup k props) (HM.toList o)
147 && all (\(k, v) -> satisfies v $ maybe additional propFormula $ M.lookup k props) (HM.toList o)
@@ -160,6 +161,15 @@ checkNumberFormat "float" _n = True
160checkNumberFormat "double" _n = True
161checkNumberFormat "double" _n = True
161checkNumberFormat f _n = error $ "Invalid number format: " <> T.unpack f
162checkNumberFormat f _n = error $ "Invalid number format: " <> T.unpack f
164+checkStringFormat :: Format -> Text -> Bool
165+checkStringFormat "byte" _s = True -- TODO: regex stuff #32
166+checkStringFormat "binary" _s = True
167+checkStringFormat "date" _s = True
168+checkStringFormat "date-time" _s = True
169+checkStringFormat "password" _s = True
170+checkStringFormat "uuid" _s = True
171+checkStringFormat f _s = error $ "Invalid string format: " <> T.unpack f
163deriving stock instance Eq (Condition t)
173deriving stock instance Eq (Condition t)
165deriving stock instance Ord (Condition t)
175deriving stock instance Ord (Condition t)
@@ -351,29 +361,6 @@ instance
351 , forObject = top
361 , forObject = top
355−instance Typeable t => Steppable Schema (Condition t) where
356− data Step Schema (Condition t)
358− | MaximumFields -- maximum & exclusiveMaximum
359− | MinimumFields -- minimum & exclusiveMinimum
369− | PropertiesFields -- properties, additionalProperties & required
370− | MaxPropertiesField
371− | MinPropertiesField
373− | IntegerType -- type=integer
374− deriving (Eq, Ord, Show)
377instance Behavable 'SchemaLevel 'SchemaLevel where
364instance Behavable 'SchemaLevel 'SchemaLevel where
378 data Behave 'SchemaLevel 'SchemaLevel
365 data Behave 'SchemaLevel 'SchemaLevel
379 deriving (Eq, Ord, Show)
366 deriving (Eq, Ord, Show)
@@ -724,10 +711,10 @@ processSchema sch@(extract -> Schema {..}) = do
724 , minPropertiesClause
711 , minPropertiesClause
727−{- TODO: ReadOnly/WriteOnly -}
714+{- TODO: ReadOnly/WriteOnly #68 -}
729checkOneOfDisjoint :: [Traced (Referenced Schema)] -> ProcessM Bool
716checkOneOfDisjoint :: [Traced (Referenced Schema)] -> ProcessM Bool
730−checkOneOfDisjoint = const $ pure True -- TODO
717+checkOneOfDisjoint = const $ pure True -- TODO #69
732schemaToFormula
719schemaToFormula
733 :: Traced (Definitions Schema)
720 :: Traced (Definitions Schema)
@@ -788,7 +775,7 @@ checkContradiction
788 :: Behavior 'TypedSchemaLevel
775 :: Behavior 'TypedSchemaLevel
789 -> [Condition t]
776 -> [Condition t]
790 -> SemanticCompatFormula ()
777 -> SemanticCompatFormula ()
791−checkContradiction beh _ = issueAt beh NoContradiction -- TODO
778+checkContradiction beh _ = issueAt beh NoContradiction -- TODO #70
793checkImplication
780checkImplication
794 :: (HasAll (CheckEnv Schema) xs)
781 :: (HasAll (CheckEnv Schema) xs)
@@ -844,7 +831,7 @@ checkImplication env beh prods cons = case findExactly prods of
844 Pattern p ->
831 Pattern p ->
845 if any (\case Pattern p' -> p == p'; _ -> False) prods
832 if any (\case Pattern p' -> p == p'; _ -> False) prods
846 then pure ()
833 then pure ()
847− else issueAt beh (NoMatchingPattern p) -- TODO: regex comparison
834+ else issueAt beh (NoMatchingPattern p) -- TODO: regex comparison #32
848 StringFormat f ->
835 StringFormat f ->
849 if any (\case StringFormat f' -> f == f'; _ -> False) prods
836 if any (\case StringFormat f' -> f == f'; _ -> False) prods
850 then pure ()
837 then pure ()
@@ -872,7 +859,7 @@ checkImplication env beh prods cons = case findExactly prods of
872 then pure ()
859 then pure ()
873 else issueAt beh NoMatchingUniqueItems
860 else issueAt beh NoMatchingUniqueItems
874 Properties props _ madd -> case findRelevant (<>) (\case Properties props' _ madd' -> Just $ (props', madd') NE.:| []; _ -> Nothing) prods of
861 Properties props _ madd -> case findRelevant (<>) (\case Properties props' _ madd' -> Just $ (props', madd') NE.:| []; _ -> Nothing) prods of
875− Just ((props', madd') NE.:| []) -> do
862+ Just pm -> anyOfAt beh NoMatchingProperties $ NE.toList pm <&> \(props', madd') -> do
876 F.for_ (S.fromList $ M.keys props <> M.keys props') $ \k -> do
863 F.for_ (S.fromList $ M.keys props <> M.keys props') $ \k -> do
877 let go sch sch' = checkCompatibility env (beh >>> step (InProperty k)) (ProdCons sch sch')
864 let go sch sch' = checkCompatibility env (beh >>> step (InProperty k)) (ProdCons sch sch')
878 case (M.lookup k props', madd', M.lookup k props, madd) of
865 case (M.lookup k props', madd', M.lookup k props, madd) of
@@ -916,42 +903,74 @@ checkImplication env beh prods cons = case findExactly prods of
916instance Issuable 'TypedSchemaLevel where
903instance Issuable 'TypedSchemaLevel where
917 data Issue 'TypedSchemaLevel
904 data Issue 'TypedSchemaLevel
918 = EnumDoesntSatisfy A.Value
905 = EnumDoesntSatisfy A.Value
906+ -- ^ producer produces a specific value ($1), consumer has a condition that is not satisfied by said value
919 | NoMatchingEnum A.Value
907 | NoMatchingEnum A.Value
908+ -- ^ producer produces a specific value ($1) which is not listed in the consumer's list of specific values
920 | NoMatchingMaximum (Bound Scientific)
909 | NoMatchingMaximum (Bound Scientific)
910+ -- ^ producer declares a maximum numeric value ($1), consumer doesnt
921 | MatchingMaximumWeak (Bound Scientific) (Bound Scientific)
911 | MatchingMaximumWeak (Bound Scientific) (Bound Scientific)
912+ -- ^ producer declares a maximum numeric value ($1), consumer declares a weaker (higher) limit ($2)
922 | NoMatchingMinimum (Bound Scientific)
913 | NoMatchingMinimum (Bound Scientific)
914+ -- ^ producer declares a minimum numeric value, consumer doesnt
923 | MatchingMinimumWeak (Bound Scientific) (Bound Scientific)
915 | MatchingMinimumWeak (Bound Scientific) (Bound Scientific)
916+ -- ^ producer declares a minimum numeric value ($1), consumer declares a weaker (lower) limit ($2)
924 | NoMatchingMultipleOf Scientific
917 | NoMatchingMultipleOf Scientific
918+ -- ^ producer declares that the numeric value must be a multiple of $1, consumer doesn't
925 | MatchingMultipleOfWeak Scientific Scientific
919 | MatchingMultipleOfWeak Scientific Scientific
920+ -- ^ producer declares that the numeric value must be a multiple of $1, consumer declares a weaker condition (multiple of $2)
926 | NoMatchingFormat Format
921 | NoMatchingFormat Format
922+ -- ^ producer declares a string/number format, consumer declares none or a different format (TODO: improve via regex #32)
927 | NoMatchingMaxLength Integer
923 | NoMatchingMaxLength Integer
924+ -- ^ producer declares a maximum length of the string ($1), consumer doesn't.
928 | MatchingMaxLengthWeak Integer Integer
925 | MatchingMaxLengthWeak Integer Integer
926+ -- ^ producer declares a maximum length of the string ($1), consumer declares a weaker (higher) limit ($2)
929 | NoMatchingMinLength Integer
927 | NoMatchingMinLength Integer
928+ -- ^ producer declares a minimum length of the string ($1), consumer doesn't.
930 | MatchingMinLengthWeak Integer Integer
929 | MatchingMinLengthWeak Integer Integer
930+ -- ^ producer declares a minimum length of the string ($1), consumer declares a weaker (lower) limit ($2)
931 | NoMatchingPattern Pattern
931 | NoMatchingPattern Pattern
932+ -- ^ producer declares the string value must matrix a regex ($1), consumer doesn't declare or declares different regex (TODO: #32)
932 | NoMatchingItems
933 | NoMatchingItems
934+ -- ^ producer declares the items of an array must satisfy some condition, consumer doesn't
933 | NoMatchingMaxItems Integer
935 | NoMatchingMaxItems Integer
936+ -- ^ producer declares a maximum length of the array ($1), consumer doesn't.
934 | MatchingMaxItemsWeak Integer Integer
937 | MatchingMaxItemsWeak Integer Integer
938+ -- ^ producer declares a maximum length of the array ($1), consumer declares a weaker (higher) limit ($2)
935 | NoMatchingMinItems Integer
939 | NoMatchingMinItems Integer
940+ -- ^ producer declares a minimum length of the array ($1), consumer doesn't.
936 | MatchingMinItemsWeak Integer Integer
941 | MatchingMinItemsWeak Integer Integer
942+ -- ^ producer declares a minimum length of the array ($1), consumer declares a weaker (lower) limit ($2)
937 | NoMatchingUniqueItems
943 | NoMatchingUniqueItems
944+ -- ^ producer declares that items must be unique, consumer doesn't
938 | NoMatchingProperties
945 | NoMatchingProperties
946+ -- ^ producer declares the properties of an object must satisfy some condition, consumer doesn't
939 | UnexpectedProperty Text
947 | UnexpectedProperty Text
948+ -- ^ producer allows a property that is not allowed in the consumer
940 | PropertyNowRequired Text
949 | PropertyNowRequired Text
950+ -- ^ consumer requires a property that is not required/allowed in the consumer
941 | NoAdditionalProperties
951 | NoAdditionalProperties
952+ -- ^ producer allows additional properties, consumer doesn't
942 | NoMatchingMaxProperties Integer
953 | NoMatchingMaxProperties Integer
954+ -- ^ producer declares a maximum number of properties in the object ($1), consumer doesn't.
943 | MatchingMaxPropertiesWeak Integer Integer
955 | MatchingMaxPropertiesWeak Integer Integer
956+ -- ^ producer declares a maximum number of properties in the object ($1), consumer declares a weaker (higher) limit ($2)
944 | NoMatchingMinProperties Integer
957 | NoMatchingMinProperties Integer
958+ -- ^ producer declares a minimum number of properties in the object ($1), consumer doesn't.
945 | MatchingMinPropertiesWeak Integer Integer
959 | MatchingMinPropertiesWeak Integer Integer
960+ -- ^ producer declares a minimum number of properties in the object ($1), consumer declares a weaker (lower) limit ($2)
946 | NoMatchingCondition [SomeCondition]
961 | NoMatchingCondition [SomeCondition]
962+ -- ^ 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)
947 | NoContradiction
963 | NoContradiction
964+ -- ^ 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)
948 deriving stock (Eq, Ord, Show)
965 deriving stock (Eq, Ord, Show)
949 issueIsUnsupported _ = False
966 issueIsUnsupported _ = False
951instance Issuable 'SchemaLevel where
968instance Issuable 'SchemaLevel where
952 data Issue 'SchemaLevel
969 data Issue 'SchemaLevel
953 = NotSupported Text
970 = NotSupported Text
971+ -- ^ Some (openapi-supported) feature that we do not support was encountered in the schema
954 | InvalidSchema Text
972 | InvalidSchema Text
973+ -- ^ The schema is actually invalid
955 deriving stock (Eq, Ord, Show)
974 deriving stock (Eq, Ord, Show)
956 issueIsUnsupported _ = True
975 issueIsUnsupported _ = True