compaREST-0.1.0.0: src/Data/OpenApi/Compare/Validate/Param.hs
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module Data.OpenApi.Compare.Validate.Param
( Behave (..),
Issue (..),
)
where
import Control.Monad
import Data.Functor
import Data.Maybe
import Data.OpenApi
import Data.OpenApi.Compare.Behavior
import Data.OpenApi.Compare.Orphans ()
import Data.OpenApi.Compare.Subtree
import Data.OpenApi.Compare.Validate.Schema ()
import Data.Text as T
import Text.Pandoc.Builder
-- | The type is normalized encoding style of the parameter. If two encoding
-- styles are equal then parameters are compatible with their encoding style
data EncodingStyle = EncodingStyle
{ style :: Style
, explode :: Bool
, -- | Nothing when @in@ parameter is not @query@
allowReserved :: Maybe Bool
}
deriving stock (Eq, Ord, Show)
paramEncoding :: Param -> EncodingStyle
paramEncoding p =
EncodingStyle
{ style
, explode
, allowReserved
}
where
style = fromMaybe defaultStyle $ _paramStyle p
defaultStyle = case _paramIn p of
ParamQuery -> StyleForm
ParamPath -> StyleSimple
ParamHeader -> StyleSimple
ParamCookie -> StyleForm
explode = fromMaybe defaultExplode $ _paramExplode p
defaultExplode = case style of
StyleForm -> True
_ -> False
allowReserved = case _paramIn p of
ParamQuery -> Just $ fromMaybe False $ _paramAllowReserved p
_ -> Nothing
tracedSchema :: Traced Param -> Maybe (Traced (Referenced Schema))
tracedSchema par = _paramSchema (extract par) <&> traced (ask par >>> step ParamSchema)
instance Issuable 'PathFragmentLevel where
data Issue 'PathFragmentLevel
= -- | Params have different names
ParamNameMismatch
| -- | Consumer requires non-empty param, but producer gives emptyable
ParamEmptinessIncompatible
| -- | Consumer requires mandatory parm, but producer optional
ParamRequired
| ParamPlaceIncompatible
| -- | Params encoded in different styles
ParamStyleMismatch
| -- | One of schemas not presented
ParamSchemaMismatch
| PathFragmentsDontMatch (ProdCons Text)
deriving stock (Eq, Ord, Show)
issueKind = \case
ParamSchemaMismatch -> ProbablyIssue -- the schema could be catch-all (?)
_ -> CertainIssue
describeIssue _ ParamNameMismatch = para "The path fragments don't match."
describeIssue Forward ParamEmptinessIncompatible = para "The parameter can no longer be empty."
describeIssue Backward ParamEmptinessIncompatible = para "The parameter can now be empty."
describeIssue Forward ParamRequired = para "Parameter has become required."
describeIssue Backward ParamRequired = para "Parameter is no longer required."
describeIssue _ ParamPlaceIncompatible = para "Parameters in incompatible locations."
describeIssue _ ParamStyleMismatch = para "Different parameter styles (encodings)."
describeIssue _ ParamSchemaMismatch = para "Expected a schema, but didn't find one."
describeIssue ori (PathFragmentsDontMatch (orientProdCons ori -> ProdCons e a)) =
para $ "Parameter changed from " <> code e <> " to " <> code a <> "."
instance Behavable 'PathFragmentLevel 'SchemaLevel where
data Behave 'PathFragmentLevel 'SchemaLevel
= InParamSchema
deriving stock (Eq, Ord, Show)
describeBehavior InParamSchema = "JSON Schema"
instance Subtree Param where
type SubtreeLevel Param = 'PathFragmentLevel
type CheckEnv Param = '[ProdCons (Traced (Definitions Schema))]
checkStructuralCompatibility env pc = do
structuralEq $ fmap _paramName <$> pc
structuralEq $ fmap _paramRequired <$> pc
structuralEq $ fmap _paramIn <$> pc
structuralEq $ fmap _paramAllowEmptyValue <$> pc
structuralEq $ fmap _paramAllowReserved <$> pc
structuralMaybe env $ tracedSchema <$> pc
structuralEq $ fmap _paramStyle <$> pc
structuralEq $ fmap _paramExplode <$> pc
pure ()
checkSemanticCompatibility env beh pc@(ProdCons p c) = do
when (_paramName (extract p) /= _paramName (extract c)) $
issueAt beh ParamNameMismatch
when
( (fromMaybe False . _paramRequired . extract $ c)
&& not (fromMaybe False . _paramRequired . extract $ p)
)
$ issueAt beh ParamRequired
case (_paramIn . extract $ p, _paramIn . extract $ c) of
(ParamQuery, ParamQuery) -> do
-- Emptiness is only for query params
when
( (fromMaybe False . _paramAllowEmptyValue . extract $ p)
&& not (fromMaybe False . _paramAllowEmptyValue . extract $ c)
)
$ issueAt beh ParamEmptinessIncompatible
(a, b) | a == b -> pure ()
_ -> issueAt beh ParamPlaceIncompatible
unless (paramEncoding (extract p) == paramEncoding (extract c)) $
issueAt beh ParamStyleMismatch
case tracedSchema <$> pc of
ProdCons (Just prodSchema) (Just consSchema) -> do
checkCompatibility (beh >>> step InParamSchema) env $ ProdCons prodSchema consSchema
ProdCons Nothing Nothing -> pure ()
ProdCons Nothing (Just _consSchema) -> issueAt beh ParamSchemaMismatch
ProdCons (Just _prodSchema) Nothing -> pure ()
-- If consumer doesn't care then why we should?
pure ()
instance Steppable Param (Referenced Schema) where
data Step Param (Referenced Schema) = ParamSchema
deriving stock (Eq, Ord, Show)