compaREST-0.1.0.0: src/Data/OpenApi/Compare/Validate/Schema/Issues.hs
{-# OPTIONS_GHC -Wno-orphans #-}
module Data.OpenApi.Compare.Validate.Schema.Issues
( Issue (..),
Behave (..),
)
where
import qualified Data.Aeson as A
import Data.OpenApi
import Data.OpenApi.Compare.Behavior
import Data.OpenApi.Compare.Subtree
import Data.OpenApi.Compare.Validate.Schema.JsonFormula
import Data.OpenApi.Compare.Validate.Schema.Partition
import Data.OpenApi.Compare.Validate.Schema.TypedJson
import Data.Scientific
import Data.Text (Text)
import qualified Data.Text as T
import Text.Pandoc.Builder hiding (Format, Null)
instance Issuable 'TypedSchemaLevel where
data Issue 'TypedSchemaLevel
= -- | producer produces a specific value ($1), consumer has a condition that is not satisfied by said value
EnumDoesntSatisfy A.Value
| -- | consumer only expects a specific value which the producer does not produce.
NoMatchingEnum A.Value
| -- | consumer declares a maximum numeric value ($1), producer doesn't
NoMatchingMaximum (Bound Scientific)
| -- | consumer declares a maximum numeric value ($1), producer declares a weaker (higher) limit ($2)
MatchingMaximumWeak (ProdCons (Bound Scientific))
| -- | consumer declares a minimum numeric value, producer doesn't
NoMatchingMinimum (Bound Scientific)
| -- | consumer declares a minimum numeric value ($1), producer declares a weaker (lower) limit ($2)
MatchingMinimumWeak (ProdCons (Bound Scientific))
| -- | consumer declares that the numeric value must be a multiple of $1, producer doesn't
NoMatchingMultipleOf Scientific
| -- | consumer declares that the numeric value must be a multiple of $1, producer declares a weaker condition (multiple of $2)
MatchingMultipleOfWeak (ProdCons Scientific)
| -- | consumer declares a string/number format, producer declares none or a different format (TODO: improve via regex #32)
NoMatchingFormat Format
| -- | consumer declares a maximum length of the string ($1), producer doesn't.
NoMatchingMaxLength Integer
| -- | consumer declares a maximum length of the string ($1), producer declares a weaker (higher) limit ($2)
MatchingMaxLengthWeak (ProdCons Integer)
| -- | consumer declares a minimum length of the string ($1), producer doesn't.
NoMatchingMinLength Integer
| -- | consumer declares a minimum length of the string ($1), producer declares a weaker (lower) limit ($2)
MatchingMinLengthWeak (ProdCons Integer)
| -- | consumer declares the string value must match a regex ($1), producer doesn't declare or declares different regex (TODO: #32)
NoMatchingPattern Pattern
| -- | consumer declares the items of an array must satisfy some condition, producer doesn't
NoMatchingItems
| -- | producer and consumer declare that an array must be a tuple of a fixed length, but the lengths don't match
TupleItemsLengthChanged (ProdCons Integer)
| -- | consumer declares that the array is a tuple, but the producer doesn't, the length constraints match, but there were issues with the components
ArrayToTuple
| -- | producer declares that the array is a tuple, but the consumer doesn't, and there were issues with the components
TupleToArray
| -- | consumer declares that the array is a tuple, but the producer doesn't, and there aren't sufficient length constraints
NoMatchingTupleItems
| -- | consumer declares a maximum length of the array ($1), producer doesn't.
NoMatchingMaxItems Integer
| -- | consumer declares a maximum length of the array ($1), producer declares a weaker (higher) limit ($2)
MatchingMaxItemsWeak (ProdCons Integer)
| -- | consumer declares a minimum length of the array ($1), producer doesn't.
NoMatchingMinItems Integer
| -- | consumer declares a minimum length of the array ($1), producer declares a weaker (lower) limit ($2)
MatchingMinItemsWeak (ProdCons Integer)
| -- | consumer declares that items must be unique, producer doesn't
NoMatchingUniqueItems
| -- | consumer declares the properties of an object must satisfy some condition, producer doesn't
NoMatchingProperties
| -- | producer allows additional properties, consumer doesn't
NoAdditionalProperties
| -- | consumer declares a maximum number of properties in the object ($1), producer doesn't.
NoMatchingMaxProperties Integer
| -- | consumer declares a maximum number of properties in the object ($1), producer declares a weaker (higher) limit ($2)
MatchingMaxPropertiesWeak (ProdCons Integer)
| -- | consumer declares a minimum number of properties in the object ($1), producer doesn't.
NoMatchingMinProperties Integer
| -- | consumer declares a minimum number of properties in the object ($1), producer declares a weaker (lower) limit ($2)
MatchingMinPropertiesWeak (ProdCons Integer)
| -- | producer declares that the value must satisfy a disjunction of some conditions, but consumer's requirements couldn't be matched against any single one of them (TODO: split heuristic #71)
NoMatchingCondition (Maybe Partition) [SomeCondition]
| -- | consumer indicates that no values of this type are allowed, but we weren't able to conclude that in the producer (currently only immediate contradictions are checked, c.f. #70)
TypeBecomesEmpty
| -- | consumer indicates that no values in a particular partition are allowed, but we weren't able to conclude this in the producer
PartitionBecomesEmpty Partition
deriving stock (Eq, Ord, Show)
issueKind = \case
NoMatchingEnum _ -> ProbablyIssue
MatchingMaximumWeak _ -> ProbablyIssue -- interplay with MultipleOf could make this not an issue
MatchingMinimumWeak _ -> ProbablyIssue -- ditto
MatchingMultipleOfWeak _ -> ProbablyIssue -- ditto
NoMatchingFormat _ -> Unsupported
NoMatchingPattern _ -> Unsupported
ArrayToTuple -> Comment
TupleToArray -> Comment
NoMatchingProperties -> ProbablyIssue -- TODO: #109
TypeBecomesEmpty -> ProbablyIssue -- TODO: #70
PartitionBecomesEmpty _ -> ProbablyIssue -- ditto
_ -> CertainIssue
relatedIssues =
(==) `withClass` \case
EnumDoesntSatisfy v -> Just v
NoMatchingEnum v -> Just v
_ -> Nothing
`withClass` \case
NoMatchingMaximum _ -> Just ()
MatchingMaximumWeak _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingMinimum _ -> Just ()
MatchingMinimumWeak _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingMultipleOf _ -> Just ()
MatchingMultipleOfWeak _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingMaxLength _ -> Just ()
MatchingMaxLengthWeak _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingMinLength _ -> Just ()
MatchingMinLengthWeak _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingItems -> Just ()
ArrayToTuple -> Just ()
TupleToArray -> Just ()
NoMatchingTupleItems -> Just ()
TupleItemsLengthChanged _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingMaxItems _ -> Just ()
MatchingMaxItemsWeak _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingMinItems _ -> Just ()
MatchingMinItemsWeak _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingMaxProperties _ -> Just ()
MatchingMaxPropertiesWeak _ -> Just ()
_ -> Nothing
`withClass` \case
NoMatchingMinProperties _ -> Just ()
MatchingMinPropertiesWeak _ -> Just ()
_ -> Nothing
describeIssue Forward (EnumDoesntSatisfy v) = para "The following enum value was removed:" <> showJSONValue v
describeIssue Backward (EnumDoesntSatisfy v) = para "The following enum value was added:" <> showJSONValue v
describeIssue Forward (NoMatchingEnum v) = para "The following enum value has been added:" <> showJSONValue v
describeIssue Backward (NoMatchingEnum v) = para "The following enum value has been removed:" <> showJSONValue v
describeIssue Forward (NoMatchingMaximum b) = para $ "Upper bound has been added:" <> showBound b <> "."
describeIssue Backward (NoMatchingMaximum b) = para $ "Upper bound has been removed:" <> showBound b <> "."
describeIssue ori (MatchingMaximumWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Upper bound changed from " <> showBound p <> " to " <> showBound c <> "."
describeIssue Forward (NoMatchingMinimum b) = para $ "Lower bound has been added: " <> showBound b <> "."
describeIssue Backward (NoMatchingMinimum b) = para $ "Lower bound has been removed: " <> showBound b <> "."
describeIssue ori (MatchingMinimumWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Lower bound changed from " <> showBound p <> " to " <> showBound c <> "."
describeIssue Forward (NoMatchingMultipleOf n) = para $ "Value is now a multiple of " <> show' n <> "."
describeIssue Backward (NoMatchingMultipleOf n) = para $ "Value is no longer a multiple of " <> show' n <> "."
describeIssue ori (MatchingMultipleOfWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Value changed from being a multiple of " <> show' p <> " to being a multiple of " <> show' c <> "."
describeIssue Forward (NoMatchingFormat f) = para $ "Format added: " <> code f <> "."
describeIssue Backward (NoMatchingFormat f) = para $ "Format removed: " <> code f <> "."
describeIssue Forward (NoMatchingMaxLength n) = para $ "Maximum length added: " <> show' n <> "."
describeIssue Backward (NoMatchingMaxLength n) = para $ "Maximum length removed: " <> show' n <> "."
describeIssue ori (MatchingMaxLengthWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Maximum length of the string changed from " <> show' p <> " to " <> show' c <> "."
describeIssue Forward (NoMatchingMinLength n) = para $ "Minimum length of the string added: " <> show' n <> "."
describeIssue Backward (NoMatchingMinLength n) = para $ "Minimum length of the string removed: " <> show' n <> "."
describeIssue ori (MatchingMinLengthWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Minimum length of the string changed from " <> show' p <> " to " <> show' c <> "."
describeIssue Forward (NoMatchingPattern p) = para "Pattern (regular expression) added: " <> codeBlock p
describeIssue Backward (NoMatchingPattern p) = para "Pattern (regular expression) removed: " <> codeBlock p
describeIssue Forward NoMatchingItems = para "Array item schema has been added."
describeIssue Backward NoMatchingItems = para "Array item schema has been removed."
describeIssue ori (TupleItemsLengthChanged (orientProdCons ori -> ProdCons p c)) =
para $ "Tuple length changed from " <> show' p <> " to " <> show' c <> "."
describeIssue Forward ArrayToTuple = para "The array is now explicitly defined as a tuple."
describeIssue Backward ArrayToTuple = para "The array is no longer explicitly defined as a tuple."
describeIssue Forward TupleToArray = para "The array is no longer explicitly defined as a tuple."
describeIssue Backward TupleToArray = para "The array is now explicitly defined as a tuple."
describeIssue Forward NoMatchingTupleItems = para "The array is now explicitly defined as a tuple."
describeIssue Backward NoMatchingTupleItems = para "The array is no longer explicitly defined as a tuple."
describeIssue Forward (NoMatchingMaxItems n) = para $ "Maximum length of the array has been added " <> show' n <> "."
describeIssue Backward (NoMatchingMaxItems n) = para $ "Maximum length of the array has been removed " <> show' n <> "."
describeIssue ori (MatchingMaxItemsWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Maximum length of the array changed from " <> show' p <> " to " <> show' c <> "."
describeIssue Forward (NoMatchingMinItems n) = para $ "Minimum length of the array added: " <> show' n <> "."
describeIssue Backward (NoMatchingMinItems n) = para $ "Minimum length of the array removed: " <> show' n <> "."
describeIssue ori (MatchingMinItemsWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Minimum length of the array changed from " <> show' p <> " to " <> show' c <> "."
describeIssue Forward NoMatchingUniqueItems = para "Items are now required to be unique."
describeIssue Backward NoMatchingUniqueItems = para "Items are no longer required to be unique."
describeIssue Forward NoMatchingProperties = para "Property added."
describeIssue Backward NoMatchingProperties = para "Property removed."
describeIssue Forward NoAdditionalProperties = para "Additional properties have been removed."
describeIssue Backward NoAdditionalProperties = para "Additional properties have been added."
describeIssue Forward (NoMatchingMaxProperties n) = para $ "Maximum number of properties has been added: " <> show' n <> "."
describeIssue Backward (NoMatchingMaxProperties n) = para $ "Maximum number of properties has been removed: " <> show' n <> "."
describeIssue ori (MatchingMaxPropertiesWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Maximum number of properties has changed from " <> show' p <> " to " <> show' c <> "."
describeIssue Forward (NoMatchingMinProperties n) = para $ "Minimum number of properties added: " <> show' n <> "."
describeIssue Backward (NoMatchingMinProperties n) = para $ "Minimum number of properties removed: " <> show' n <> "."
describeIssue ori (MatchingMinPropertiesWeak (orientProdCons ori -> ProdCons p c)) =
para $ "Minimum number of properties has changed from " <> show' p <> " to " <> show' c <> "."
describeIssue _ (NoMatchingCondition mPart conds) =
para
( case mPart of
Nothing -> "Could not verify that the following conditions hold (please file a bug if you see this):"
Just locPart ->
"In cases where " <> showPartition locPart
<> " – could not verify that the following conditions hold (please file a bug if you see this):"
)
<> bulletList ((\(SomeCondition c) -> showCondition c) <$> conds)
describeIssue Forward TypeBecomesEmpty = para "The type has been removed."
describeIssue Backward TypeBecomesEmpty = para "The type has been added."
describeIssue Forward (PartitionBecomesEmpty part) = para $ "The case where " <> showPartition part <> " – has been removed."
describeIssue Backward (PartitionBecomesEmpty part) = para $ "The case where " <> showPartition part <> " – has been added."
show' :: Show x => x -> Inlines
show' = str . T.pack . show
instance Issuable 'SchemaLevel where
data Issue 'SchemaLevel
= -- | Some (openapi-supported) feature that we do not support was encountered in the schema
NotSupported Text
| -- | We couldn't prove that the branches of a oneOf are disjoint, and we will treat it as an anyOf, meaning we don't check whether the overlaps are excluded in a compatible way
OneOfNotDisjoint
| -- | The schema is actually invalid
InvalidSchema Text
| -- | The schema contains a reference loop along "anyOf"/"allOf"/"oneOf".
UnguardedRecursion
| -- | Producer doesn't place any restrictions on the types, but the consumer does. List what types remain available in the consumer.
TypesRestricted [JsonType]
| -- | in the producer this field used to be handled as part of "additionalProperties", and the consumer this is a specific "properties" entry. Only thrown when this change actually causes other issues
AdditionalToProperty
| -- | in the consumer this field used to be handled as part of "additionalProperties", and the producer this is a specific "properties" entry. Only thrown when this change actually causes other issues
PropertyToAdditional
| -- | consumer requires a property that is not required/allowed in the producer
PropertyNowRequired
| -- | producer allows a property that is not allowed in the consumer
UnexpectedProperty
deriving stock (Eq, Ord, Show)
issueKind = \case
NotSupported _ -> Unsupported
OneOfNotDisjoint -> Unsupported
InvalidSchema _ -> SchemaInvalid
UnguardedRecursion -> Unsupported
AdditionalToProperty -> Comment
PropertyToAdditional -> Comment
TypesRestricted _ -> ProbablyIssue -- TODO: #70
_ -> CertainIssue
relatedIssues =
(==) `withClass` \case
AdditionalToProperty -> Just ()
PropertyToAdditional -> Just ()
_ -> Nothing
`withClass` \case
PropertyNowRequired -> Just ()
UnexpectedProperty -> Just ()
_ -> Nothing
describeIssue _ (NotSupported i) =
para (emph "Encountered a feature that CompaREST does not support: " <> text i <> ".")
describeIssue _ OneOfNotDisjoint =
para $
"Could not deduce that " <> code "oneOf"
<> " cases don't overlap. Treating the "
<> code "oneOf"
<> " as an "
<> code "anyOf"
<> ". Reported errors might not be accurate."
describeIssue _ (InvalidSchema i) =
para (emph "The schema is invalid: " <> text i <> ".")
describeIssue _ UnguardedRecursion =
para "Encountered recursion that is too complex for CompaREST to untangle."
describeIssue Forward (TypesRestricted tys) = case tys of
[] -> para "No longer has any valid values." -- weird
_ -> para "Values are now limited to the following types: " <> bulletList (para . describeJSONType <$> tys)
describeIssue Backward (TypesRestricted tys) = case tys of
[] -> para "Any value of any type is now allowed." -- weird
_ -> para "Values are no longer limited to the following types: " <> bulletList (para . describeJSONType <$> tys)
describeIssue Forward AdditionalToProperty = para "The property was previously implicitly described by the catch-all \"additional properties\" case. It is now explicitly defined."
describeIssue Backward AdditionalToProperty = para "The property was previously explicitly defined. It is now implicitly described by the catch-all \"additional properties\" case."
describeIssue Forward PropertyToAdditional = para "The property was previously explicitly defined. It is now implicitly described by the catch-all \"additional properties\" case."
describeIssue Backward PropertyToAdditional = para "The property was previously implicitly described by the catch-all \"additional properties\" case. It is now explicitly defined."
describeIssue Forward PropertyNowRequired = para "The property has become required."
describeIssue Backward PropertyNowRequired = para "The property may not be present."
describeIssue Forward UnexpectedProperty = para "The property has been removed."
describeIssue Backward UnexpectedProperty = para "The property has been added."
instance Behavable 'SchemaLevel 'TypedSchemaLevel where
data Behave 'SchemaLevel 'TypedSchemaLevel
= OfType JsonType
deriving stock (Eq, Ord, Show)
describeBehavior (OfType t) = describeJSONType t
instance Behavable 'TypedSchemaLevel 'TypedSchemaLevel where
data Behave 'TypedSchemaLevel 'TypedSchemaLevel
= InPartition Partition
deriving stock (Eq, Ord, Show)
describeBehavior (InPartition partition) = "In cases where " <> showPartition partition
instance Behavable 'TypedSchemaLevel 'SchemaLevel where
data Behave 'TypedSchemaLevel 'SchemaLevel
= InItems
| InItem Integer
| InProperty Text
| InAdditionalProperty
deriving stock (Eq, Ord, Show)
describeBehavior InItems = "Items"
describeBehavior (InItem i) = "Item " <> show' i
describeBehavior (InProperty p) = "Property " <> code p
describeBehavior InAdditionalProperty = "Additional properties"