git.iko.soy/typeable/compaREST
on master help sign in
Turn Traced into a comonad (#41)

* Turn Traced into a comonad * tracedDePathFragment -> tracedPathFragmentParam * Fixed tracedOp Co-authored-by: iko <ilyakooo0@gmail.com>

commitc2968fee75d857b52796c3b54233c34da1352b5d authormniip <mniip@users.noreply.github.com> dateThu 6 May 2021, 13:53 (5 years ago) parent1d27fd1
34 files changed+568−572
openapi-diff.cabal+1 src/OpenAPI/Checker/Orphans.hs+5 src/OpenAPI/Checker/References.hs+5−11 src/OpenAPI/Checker/Run.hs+4−4 src/OpenAPI/Checker/Subtree.hs+38−87 src/OpenAPI/Checker/Trace.hs+10−20 src/OpenAPI/Checker/Validate/MediaTypeObject.hs+25−14 src/OpenAPI/Checker/Validate/OpenApi.hs+14−11 src/OpenAPI/Checker/Validate/Operation.hs+93−57 src/OpenAPI/Checker/Validate/Param.hs+29−21 src/OpenAPI/Checker/Validate/PathFragment.hs+26−26 src/OpenAPI/Checker/Validate/ProcessedPathItem.hs+50−60 src/OpenAPI/Checker/Validate/Products.hs+12−34 src/OpenAPI/Checker/Validate/RequestBody.hs+13−15 src/OpenAPI/Checker/Validate/Responses.hs+43−36 src/OpenAPI/Checker/Validate/Schema.hs+154−137 src/OpenAPI/Checker/Validate/SecurityRequirement.hs+3−3 src/OpenAPI/Checker/Validate/Server.hs+3−3 src/OpenAPI/Checker/Validate/Sums.hs+9−11 test/Spec/Golden/TraceTree.hs+2−1 test/golden/common/maximum-lowered/trace-tree.yaml+1−1 test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/trace-tree.yaml+2−2 test/golden/common/pathItem/operation/parameters/change/trace-tree.yaml+2−2 test/golden/common/pathItem/operation/parameters/required/set/trace-tree.yaml+2−2 test/golden/common/pathItem/operation/parameters/required/true/add/trace-tree.yaml+3−1 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/trace-tree.yaml+1−1 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/trace-tree.yaml+3−2 test/golden/common/pathItem/operation/requestBody/required/set/trace-tree.yaml+1−1 test/golden/common/pathItem/operation/responses/add/trace-tree.yaml+4−2 test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/trace-tree.yaml+4−2 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/trace-tree.yaml+3−2 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/trace-tree.yaml+1−1 test/golden/common/property-removed/trace-tree.yaml+1−1 test/golden/common/property-required/trace-tree.yaml+1−1
openapi-diff.cabal+1
@@ -34,6 +34,7 @@ common common-options
3434 , attoparsec
3535 , bytestring
3636 , containers
37+ , comonad
3738 , deriving-aeson
3839 , generic-data
3940 , generic-monoid
src/OpenAPI/Checker/Orphans.hs+5
@@ -2,6 +2,7 @@
22
33module OpenAPI.Checker.Orphans (Step (..)) where
44
5+import Control.Comonad.Env
56import Data.OpenApi
67import Data.Typeable
78import qualified Data.HashMap.Strict.InsOrd as IOHM
@@ -28,3 +29,7 @@ deriving stock instance Ord ParamLocation
2829
2930instance (Ord k, Ord v) => Ord (IOHM.InsOrdHashMap k v) where
3031 compare xs ys = compare (IOHM.toList xs) (IOHM.toList ys)
32+
33+deriving stock instance (Eq e, Eq (w a)) => Eq (EnvT e w a)
34+deriving stock instance (Ord e, Ord (w a)) => Ord (EnvT e w a)
35+deriving stock instance (Show e, Show (w a)) => Show (EnvT e w a)
src/OpenAPI/Checker/References.hs+5−11
@@ -1,7 +1,6 @@
11module OpenAPI.Checker.References
22 ( TracedReferences
33 , dereference
4− , dereferenceTraced
54 )
65where
76
@@ -16,17 +15,12 @@ import OpenAPI.Checker.Trace
1615type TracedReferences root a = Map Reference (Traced root a)
1716
1817dereference
19− :: Typeable a
20− => Definitions a
21− -> Referenced a
22− -> Traced (Referenced a) a
23−dereference _ (Inline a) = Traced (step InlineStep) a
24−dereference defs (Ref r@(Reference ref)) =
25− Traced (step $ ReferencedStep r) (fromJust $ IOHM.lookup ref defs)
26−
27−dereferenceTraced
2818 :: Typeable a
2919 => Definitions a
3020 -> Traced r (Referenced a)
3121 -> Traced r a
32−dereferenceTraced defs (Traced t x) = retrace t $ dereference defs x
22+dereference defs x = case extract x of
23+ Inline a
24+ -> traced (ask x >>> step InlineStep) a
25+ Ref r@(Reference ref)
26+ -> traced (ask x >>> step (ReferencedStep r)) (fromJust $ IOHM.lookup ref defs)
src/OpenAPI/Checker/Run.hs+4−4
@@ -1,12 +1,12 @@
11module OpenAPI.Checker.Run (runChecker) where
22
3−import Control.Category
43import Data.Aeson
54import qualified Data.ByteString.Char8 as BSC
65import Data.HList
76import qualified Data.Yaml as Yaml
87import OpenAPI.Checker.Options
98import OpenAPI.Checker.Subtree
9+import OpenAPI.Checker.Trace
1010import OpenAPI.Checker.Validate.OpenApi ()
1111import Prelude hiding (id, (.))
1212
@@ -24,7 +24,7 @@ runChecker = do
2424 fail "Exiting"
2525 Right s -> pure s
2626 Right s -> pure s
27− a <- parseSchema (clientFile opts)
28− b <- parseSchema (serverFile opts)
29− let report = runCompatFormula (pure id) $ checkCompatibility HNil (ProdCons a b)
27+ a <- traced Root <$> parseSchema (clientFile opts)
28+ b <- traced Root <$> parseSchema (serverFile opts)
29+ let report = runCompatFormula $ checkCompatibility HNil (ProdCons a b)
3030 BSC.putStrLn $ Yaml.encode report
src/OpenAPI/Checker/Subtree.hs+38−87
@@ -2,19 +2,13 @@ module OpenAPI.Checker.Subtree
22 ( APIStep (..)
33 , Subtree (..)
44 , CompatM (..)
5+ , CompatFormula'
56 , CompatFormula
67 , ProdCons (..)
78 , HasUnsupportedFeature (..)
8− , swapRoles
99 , swapProdCons
10− , checkProdCons
1110 , SubtreeCheckIssue (..)
1211 , runCompatFormula
13− , withTrace
14− , localM
15− , localTrace
16− , localStep
17− , localTrace'
1812 , anyOfM
1913 , anyOfAt
2014 , issueAtTrace
@@ -23,8 +17,8 @@ module OpenAPI.Checker.Subtree
2317 )
2418where
2519
20+import Control.Comonad.Env
2621import Control.Monad.Identity
27−import Control.Monad.Reader
2822import Control.Monad.State
2923import Data.Aeson
3024import Data.Functor.Compose
@@ -58,29 +52,19 @@ instance Applicative ProdCons where
5852 pure x = ProdCons x x
5953 ProdCons fp fc <*> ProdCons xp xc = ProdCons (fp xp) (fc xc)
6054
61−newtype CompatM t a = CompatM
55+newtype CompatM a = CompatM
6256 { unCompatM
63− :: ReaderT
64− (ProdCons (Trace OpenApi t))
65− (StateT (MemoState VarRef) Identity)
66− a
57+ :: (StateT (MemoState VarRef) Identity) a
6758 }
6859 deriving newtype
6960 ( Functor
7061 , Applicative
7162 , Monad
72− , MonadReader (ProdCons (Trace OpenApi t))
7363 , MonadState (MemoState VarRef)
7464 )
7565
76−type CompatFormula t = Compose (CompatM t) (FormulaF SubtreeCheckIssue OpenApi)
77−
78−-- | Swaps roles of producer and consumer. Used when we check the @Response@ at
79−-- least. In response producer and consumer swap their places because response
80−-- is generated by consumer and parsed by producer.
81−swapRoles :: CompatFormula t a -> CompatFormula t a
82−swapRoles (Compose r) = Compose $ do
83− local swapProdCons r
66+type CompatFormula' f r = Compose CompatM (FormulaF f r)
67+type CompatFormula = CompatFormula' SubtreeCheckIssue OpenApi
8468
8569class (Typeable t, Ord (CheckIssue t), Show (CheckIssue t)) => Subtree (t :: Type) where
8670 type CheckEnv t :: [Type]
@@ -93,14 +77,14 @@ class (Typeable t, Ord (CheckIssue t), Show (CheckIssue t)) => Subtree (t :: Typ
9377 normalizeTrace :: Trace OpenApi t -> Trace OpenApi t
9478 normalizeTrace = id
9579
96− checkCompatibility :: HasAll (CheckEnv t) xs => HList xs -> ProdCons t -> CompatFormula t ()
80+ checkCompatibility
81+ :: HasAll (CheckEnv t) xs
82+ => HList xs
83+ -> ProdCons (Traced OpenApi t)
84+ -> CompatFormula ()
9785
9886{-# WARNING normalizeTrace "It must be refactored. Does nothing for now" #-}
9987
100−checkProdCons :: (Subtree t, HasAll (CheckEnv t) env) => HList env -> ProdCons (Traced r t) -> CompatFormula r ()
101−checkProdCons env (ProdCons (Traced p x) (Traced c y)) =
102− localTrace (ProdCons p c) $ checkCompatibility env $ ProdCons x y
103−
10488class HasUnsupportedFeature x where
10589 hasUnsupportedFeature :: x -> Bool
10690
@@ -141,76 +125,41 @@ instance ToJSON (SubtreeCheckIssue t) where
141125 toJSON (SubtreeCheckIssue i) = toJSON i
142126
143127runCompatFormula
144− :: ProdCons (Trace OpenApi t)
145− -> Compose (CompatM t) (FormulaF f r) a
128+ :: CompatFormula' f r a
146129 -> Either (T.TracePrefixTree f r) a
147−runCompatFormula env (Compose f) =
148− calculate . runIdentity . runMemo 0 . (`runReaderT` env) . unCompatM $ f
149−
150−withTrace
151− :: (ProdCons (Trace OpenApi a) -> Compose (CompatM a) (FormulaF f r) x)
152− -> Compose (CompatM a) (FormulaF f r) x
153−withTrace k = Compose $ do
154− xs <- ask
155− getCompose $ k xs
156−
157−localM
158− :: ProdCons (Trace a b)
159− -> CompatM b x
160− -> CompatM a x
161−localM xs (CompatM k) =
162− CompatM $ ReaderT $ \env -> runReaderT k ((>>>) <$> env <*> xs)
163−
164−localTrace
165− :: ProdCons (Trace a b)
166− -> Compose (CompatM b) (FormulaF f r) x
167− -> Compose (CompatM a) (FormulaF f r) x
168−localTrace xs (Compose h) = Compose (localM xs h)
169−
170−localStep
171− :: Steppable a b
172− => Step a b
173− -> Compose (CompatM b) (FormulaF f r) x
174− -> Compose (CompatM a) (FormulaF f r) x
175−localStep xs (Compose h) = Compose (localM (pure $ step xs) h)
176−
177−localTrace'
178− :: ProdCons (Trace OpenApi b)
179− -> Compose (CompatM b) (FormulaF f r) x
180− -> Compose (CompatM a) (FormulaF f r) x
181−localTrace' xs (Compose (CompatM k)) = Compose $ CompatM$ ReaderT $ \_ -> runReaderT k xs
130+runCompatFormula (Compose f) =
131+ calculate . runIdentity . runMemo 0 . unCompatM $ f
182132
183133issueAtTrace
184− :: Subtree t => Trace OpenApi t -> CheckIssue t -> CompatFormula s a
134+ :: Subtree t
135+ => Trace r t
136+ -> CheckIssue t
137+ -> CompatFormula' SubtreeCheckIssue r a
185138issueAtTrace xs issue = Compose $ pure $ anError $ AnItem xs $ SubtreeCheckIssue issue
186139
187140issueAt
188− :: Subtree t
189− => (forall x. ProdCons x -> x)
141+ :: (Subtree t, ComonadEnv (Trace r t) w)
142+ => w x
190143 -> CheckIssue t
191− -> CompatFormula t a
192−issueAt f issue = Compose $ do
193− xs <- asks f
194− pure $ anError $ AnItem xs $ SubtreeCheckIssue issue
144+ -> CompatFormula' SubtreeCheckIssue r a
145+issueAt x = issueAtTrace (ask x)
195146
196147anyOfM
197− :: Ord (f t)
148+ :: Subtree t
198149 => Trace r t
199− -> f t
200− -> [Compose (CompatM t) (FormulaF f r) a]
201− -> Compose (CompatM t) (FormulaF f r) a
150+ -> CheckIssue t
151+ -> [CompatFormula' SubtreeCheckIssue r a]
152+ -> CompatFormula' SubtreeCheckIssue r a
202153anyOfM xs issue fs =
203− Compose $ (`eitherOf` AnItem xs issue) <$> sequenceA (getCompose <$> fs)
154+ Compose $ (`eitherOf` AnItem xs (SubtreeCheckIssue issue)) <$> sequenceA (getCompose <$> fs)
204155
205156anyOfAt
206− :: Subtree t
207− => (forall x. ProdCons x -> x)
157+ :: (Subtree t, ComonadEnv (Trace r t) w)
158+ => w x
208159 -> CheckIssue t
209− -> [CompatFormula t a]
210− -> CompatFormula t a
211−anyOfAt f issue fs = Compose $ do
212− xs <- asks f
213− (`eitherOf` AnItem xs (SubtreeCheckIssue issue)) <$> sequenceA (getCompose <$> fs)
160+ -> [CompatFormula' SubtreeCheckIssue r a]
161+ -> CompatFormula' SubtreeCheckIssue r a
162+anyOfAt x = anyOfM (ask x)
214163
215164fixpointKnot
216165 :: MonadState (MemoState VarRef) m
@@ -222,7 +171,9 @@ fixpointKnot =
222171 , tieKnot = \i x -> pure $ maxFixpoint i x
223172 }
224173
225−memo :: Subtree t => CompatFormula t () -> CompatFormula t ()
226−memo (Compose f) = Compose $ do
227− pxs <- asks (fmap normalizeTrace)
228− memoWithKnot fixpointKnot f pxs
174+memo
175+ :: (Typeable r, Subtree t)
176+ => (ProdCons (Traced r t) -> CompatFormula ())
177+ -> (ProdCons (Traced r t) -> CompatFormula ())
178+memo f pc = Compose $ do
179+ memoWithKnot fixpointKnot (getCompose $ f pc) (ask <$> pc)
src/OpenAPI/Checker/Trace.hs+10−20
@@ -9,18 +9,21 @@ module OpenAPI.Checker.Trace
99 , _DiffTrace
1010 , AnItem (..)
1111 , step
12− , Traced (..)
13− , mapTraced
14− , retrace
15− , deTraced
12+ , Traced
13+ , traced
1614
1715 -- * Reexports
1816 , (>>>)
1917 , (<<<)
18+ , extract
19+ , ask
20+ , asks
21+ , local
2022 )
2123where
2224
2325import Control.Category
26+import Control.Comonad.Env
2427import Control.Lens
2528import Data.Kind
2629import Data.Type.Equality
@@ -117,20 +120,7 @@ instance Typeable r => Ord (AnItem f r) where
117120 Root -> compare (someTypeRep xs) (someTypeRep ys)
118121 Snoc _ _ -> compare (someTypeRep xs) (someTypeRep ys)
119122
120−data Traced r a = Traced {getTrace :: Trace r a, getTraced :: a}
121− deriving (Eq, Show)
123+type Traced r a = Env (Trace r a) a
122124
123−-- | Reverse lexicographical order, so that getTraced is a monotonous function
124−instance Ord a => Ord (Traced r a) where
125− compare (Traced t1 a1) (Traced t2 a2) = compare a1 a2 <> compare t1 t2
126−
127−mapTraced :: (Trace r a -> Trace r b) -> (a -> b) -> Traced r a -> Traced r b
128−mapTraced f g (Traced t a) = Traced (f t) (g a)
129−
130−retrace :: Trace s r -> Traced r a -> Traced s a
131−retrace xs (Traced t a) = Traced (xs >>> t) a
132−
133−deTraced :: Traced r a -> (Trace r a, a)
134−deTraced (Traced a b) = (a, b)
135−
136−-- type APath = AnItem Proxy
125+traced :: Trace r a -> a -> Traced r a
126+traced = env
src/OpenAPI/Checker/Validate/MediaTypeObject.hs+25−14
@@ -1,9 +1,11 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.MediaTypeObject () where
3+module OpenAPI.Checker.Validate.MediaTypeObject
4+ ( CheckIssue(..)
5+ ) where
46
7+import Control.Lens
58import Data.Foldable as F
6−import Data.Functor
79import Data.HList
810import Data.HashMap.Strict.InsOrd as IOHM
911import Data.Map.Strict as M
@@ -15,13 +17,21 @@ import OpenAPI.Checker.Trace
1517import OpenAPI.Checker.Validate.Products
1618import OpenAPI.Checker.Validate.Schema ()
1719
20+tracedSchema :: Traced r MediaTypeObject -> Maybe (Traced r (Referenced Schema))
21+tracedSchema mto = _mediaTypeObjectSchema (extract mto) <&> traced (ask mto >>> step MediaTypeSchema)
22+
23+tracedEncoding :: Traced r MediaTypeObject -> InsOrdHashMap Text (Traced r Encoding)
24+tracedEncoding mto = IOHM.mapWithKey (\k -> traced (ask mto >>> step (MediaTypeParamEncoding k)))
25+ $ _mediaTypeObjectEncoding $ extract mto
26+
1827instance Subtree MediaTypeObject where
1928 type CheckEnv MediaTypeObject =
2029 '[ MediaType
2130 , ProdCons (Definitions Schema)
2231 ]
2332 data CheckIssue MediaTypeObject
24− = MediaEncodingMissing Text
33+ = RequestMediaTypeNotFound
34+ | ResponseMediaTypeMissing
2535 | MediaEncodingIncompat
2636 | MediaTypeSchemaRequired
2737 deriving (Eq, Ord, Show)
@@ -32,11 +42,10 @@ instance Subtree MediaTypeObject where
3242 | otherwise -> pure ()
3343 -- If consumer requires schema then producer must produce compatible
3444 -- request
35− for_ (_mediaTypeObjectSchema c) $ \consRef ->
36− case _mediaTypeObjectSchema p of
37− Nothing -> issueAt producer MediaTypeSchemaRequired
38− Just prodRef -> localStep MediaTypeSchema
39− $ checkCompatibility env $ ProdCons prodRef consRef
45+ for_ (tracedSchema c) $ \consRef ->
46+ case tracedSchema p of
47+ Nothing -> issueAt p MediaTypeSchemaRequired
48+ Just prodRef -> checkCompatibility env $ ProdCons prodRef consRef
4049 pure ()
4150 where
4251 mediaType = getH @MediaType env
@@ -44,23 +53,25 @@ instance Subtree MediaTypeObject where
4453 let
4554 -- Parameters of the media type are product-like entities
4655 getEncoding mt = M.fromList
47− $ (IOHM.toList $ _mediaTypeObjectEncoding mt) <&> \(k, enc) ->
56+ $ (IOHM.toList $ tracedEncoding mt) <&> \(k, enc) ->
4857 ( k
4958 , ProductLike
50− { traced = Traced (step $ MediaTypeParamEncoding k) enc
59+ { tracedValue = enc
5160 , required = True } )
5261 encProdCons = getEncoding <$> prodCons
53− in checkProducts MediaEncodingMissing
62+ in checkProducts (const MediaEncodingMissing)
5463 (const $ checkCompatibility HNil) encProdCons
5564
5665instance Subtree Encoding where
5766 type CheckEnv Encoding = '[]
58− data CheckIssue Encoding = EncodingNotSupported
67+ data CheckIssue Encoding
68+ = MediaEncodingMissing
69+ | EncodingNotSupported
5970 -- FIXME: Support only JSON body for now. Then Encoding is checked only for
6071 -- multipart/form-url-encoded
6172 deriving (Eq, Ord, Show)
62− checkCompatibility _env _prodCons =
63− issueAt producer EncodingNotSupported
73+ checkCompatibility _env pc =
74+ issueAt (producer pc) EncodingNotSupported
6475
6576instance Steppable MediaTypeObject (Referenced Schema) where
6677 data Step MediaTypeObject (Referenced Schema) = MediaTypeSchema
src/OpenAPI/Checker/Validate/OpenApi.hs+14−11
@@ -13,22 +13,25 @@ import OpenAPI.Checker.Subtree
1313import OpenAPI.Checker.Trace
1414import OpenAPI.Checker.Validate.ProcessedPathItem
1515
16+tracedPaths :: Traced r OpenApi -> Traced r ProcessedPathItems
17+tracedPaths oa = traced (ask oa >>> step OpenApiPathsStep)
18+ (processPathItems . IOHM.toList . _openApiPaths . extract $ oa)
19+
1620instance Subtree OpenApi where
1721 type CheckEnv OpenApi = '[]
1822 data CheckIssue OpenApi
1923 deriving (Eq, Ord, Show)
2024 checkCompatibility _ prodCons = do
21− let cs = _openApiComponents <$> prodCons
22− localStep OpenApiPathsStep $
23− checkCompatibility
24− ((_componentsRequestBodies <$> cs)
25− `HCons` (_componentsParameters <$> cs)
26− `HCons` (_componentsSecuritySchemes <$> cs)
27− `HCons` (_componentsResponses <$> cs)
28− `HCons` (_componentsHeaders <$> cs)
29− `HCons` (_componentsSchemas <$> cs)
30− `HCons` HNil)
31− (processPathItems . IOHM.toList . _openApiPaths <$> prodCons)
25+ let cs = _openApiComponents . extract <$> prodCons
26+ checkCompatibility @ProcessedPathItems
27+ ((_componentsRequestBodies <$> cs)
28+ `HCons` (_componentsParameters <$> cs)
29+ `HCons` (_componentsSecuritySchemes <$> cs)
30+ `HCons` (_componentsResponses <$> cs)
31+ `HCons` (_componentsHeaders <$> cs)
32+ `HCons` (_componentsSchemas <$> cs)
33+ `HCons` HNil)
34+ (tracedPaths <$> prodCons)
3235
3336instance Steppable OpenApi ProcessedPathItems where
3437 data Step OpenApi ProcessedPathItems = OpenApiPathsStep
src/OpenAPI/Checker/Validate/Operation.hs+93−57
@@ -2,7 +2,10 @@
22{-# OPTIONS_GHC -Wno-name-shadowing #-}
33
44module OpenAPI.Checker.Validate.Operation
5− ( MatchedOperation(..)
5+ ( MatchedOperation (..)
6+ , CheckIssue (..)
7+ , OperationMethod(..)
8+ , pathItemMethod
69 ) where
710
811
@@ -17,15 +20,11 @@ import Data.Text (Text)
1720import OpenAPI.Checker.References
1821import OpenAPI.Checker.Subtree
1922import OpenAPI.Checker.Trace
20−import OpenAPI.Checker.Validate.Param ()
23+import OpenAPI.Checker.Validate.Param
2124import OpenAPI.Checker.Validate.PathFragment
2225import OpenAPI.Checker.Validate.Products
23−import OpenAPI.Checker.Validate.RequestBody ()
26+import OpenAPI.Checker.Validate.RequestBody
2427import OpenAPI.Checker.Validate.Responses ()
25−import OpenAPI.Checker.Validate.SecurityRequirement ()
26−import OpenAPI.Checker.Validate.Server ()
27−
28−-- data ParamKey
2928
3029data MatchedOperation = MatchedOperation
3130 { operation :: !Operation
@@ -41,6 +40,31 @@ type ParamKey = (ParamLocation, Text)
4140paramKey :: Param -> ParamKey
4241paramKey param = (_paramIn param, _paramName param)
4342
43+tracedParameters :: Traced r MatchedOperation -> [Traced r (Referenced Param)]
44+tracedParameters oper =
45+ [ traced (ask oper >>> step (OperationParamsStep i)) x
46+ | (i, x) <- zip [0..] $ _operationParameters . operation $ extract oper
47+ ]
48+
49+tracedRequestBody :: Traced r MatchedOperation -> Maybe (Traced r (Referenced RequestBody))
50+tracedRequestBody oper = _operationRequestBody (operation $ extract oper) <&> traced (ask oper >>> step OperationRequestBodyStep)
51+
52+tracedResponses :: Traced r MatchedOperation -> Traced r Responses
53+tracedResponses oper = traced (ask oper >>> step OperationResponsesStep)
54+ $ _operationResponses . operation $ extract oper
55+
56+tracedSecurity :: Traced r MatchedOperation -> [Traced r SecurityRequirement]
57+tracedSecurity oper =
58+ [ traced (ask oper >>> step (OperationSecurityRequirementStep i)) x
59+ | (i, x) <- zip [0..] $ _operationSecurity . operation $ extract oper
60+ ]
61+
62+tracedServers :: Traced r MatchedOperation -> [Traced r Server]
63+tracedServers oper =
64+ [ traced (ask oper >>> step (OperationServerStep i)) x
65+ | (i, x) <- zip [0..] $ _operationServers . operation $ extract oper
66+ ]
67+
4468instance Subtree MatchedOperation where
4569 type CheckEnv MatchedOperation =
4670 '[ ProdCons (Definitions Param)
@@ -51,17 +75,11 @@ instance Subtree MatchedOperation where
5175 , ProdCons (Definitions Schema)
5276 ]
5377 data CheckIssue MatchedOperation
54− = ParamNotMatched ParamLocation Text
55− -- ^ Non-path param has no pair
56− | PathFragmentNotMatched Int
57− -- ^ Path fragment with given position has no match
58− | NoRequestBody
78+ = OperationMissing OperationMethod
5979 | CallbacksNotSupported
60− | SecurityRequirementNotMet Int -- security indexs
61− | ServerNotConsumed Int -- server index
6280 deriving (Eq, Ord, Show)
63− checkCompatibility env prodCons = withTrace $ \myTrace -> do
64− checkParameters myTrace
81+ checkCompatibility env prodCons = do
82+ checkParameters
6583 checkRequestBodies
6684 checkResponses
6785 checkCallbacks
@@ -69,88 +87,84 @@ instance Subtree MatchedOperation where
6987 checkServers
7088 pure ()
7189 where
72− checkParameters myTrace = do
90+ checkParameters = do
7391 let
7492 -- Merged parameters got from Operation and PathItem in one
7593 -- place. First element is path params, second is non-path params
7694 tracedParams :: ProdCons ([Traced OpenApi Param], [Traced OpenApi Param])
77− tracedParams = getParams <$> myTrace <*> paramDefs <*> prodCons
78− getParams root defs mp =
95+ tracedParams = getParams <$> paramDefs <*> prodCons
96+ getParams defs mp =
7997 let
8098 operationParamsMap :: Map ParamKey (Traced OpenApi Param)
8199 operationParamsMap = M.fromList $ do
82− paramRef <- _operationParameters $ operation mp
100+ paramRef <- tracedParameters mp
83101 let
84− tracedParam = retrace root
85− $ dereferenceTraced defs
86− $ Traced (step $ OperationParamsStep) paramRef
87− key = paramKey $ getTraced tracedParam
88− pure (key, tracedParam)
102+ param = dereference defs paramRef
103+ key = paramKey . extract $ param
104+ pure (key, param)
89105 pathParamsMap :: Map ParamKey (Traced OpenApi Param)
90106 pathParamsMap = M.fromList $ do
91− param <- pathParams mp
92− pure (paramKey $ getTraced param, param)
107+ param <- pathParams . extract $ mp
108+ pure (paramKey . extract $ param, param)
93109 params = M.elems $ M.union operationParamsMap pathParamsMap
94110 -- We prefer params from Operation
95111 splitted = L.partition
96− (\p -> (_paramIn $ getTraced p) == ParamPath) params
112+ (\p -> (_paramIn . extract $ p) == ParamPath) params
97113 in splitted
98114 checkNonPathParams $ snd <$> tracedParams
99115 checkPathParams $ fst <$> tracedParams
100116 pure ()
101− checkNonPathParams :: ProdCons [Traced OpenApi Param] -> CompatFormula MatchedOperation ()
117+ checkNonPathParams :: ProdCons [Traced OpenApi Param] -> CompatFormula ()
102118 checkNonPathParams params = do
103119 let
104120 elements = getEls <$> params
105− getEls traced = M.fromList $ do
106− p <- traced
121+ getEls params = M.fromList $ do
122+ p <- params
107123 let
108− param = getTraced p
109− k = (_paramIn param, _paramName param)
124+ k = (_paramIn . extract $ p, _paramName . extract $ p)
110125 v = ProductLike
111− { traced = p
112− , required = fromMaybe False $ _paramRequired param
126+ { tracedValue = p
127+ , required = fromMaybe False . _paramRequired . extract $ p
113128 }
114129 pure (k, v)
115− check _ param = do
130+ check param = do
116131 checkCompatibility @Param (singletonH schemaDefs) param
117− checkProducts' (uncurry ParamNotMatched) check elements
132+ checkProducts (ParamNotMatched . snd) (const check) elements
133+ checkPathParams :: ProdCons [Traced OpenApi Param] -> CompatFormula ()
118134 checkPathParams pathParams = do
119135 let
120136 fragments :: ProdCons [Traced OpenApi PathFragmentParam]
121137 fragments = getFragments <$> pathParams <*> prodCons
122− getFragments params mop = (getPathFragments mop) params
138+ getFragments params mop = getPathFragments (extract mop) params
123139 -- Feed path parameters to the fragments getter
124− check _ frags = checkCompatibility @PathFragmentParam env frags
125− elements = fragments <&> \frags -> M.fromList $ zip [0..] $ do
140+ check frags = checkCompatibility @PathFragmentParam env frags
141+ elements = fragments <&> \frags -> M.fromList $ zip [0 :: Int ..] $ do
126142 frag <- frags
127143 pure $ ProductLike
128− { traced = frag
144+ { tracedValue = frag
129145 , required = True }
130− checkProducts' PathFragmentNotMatched check elements
146+ checkProducts (const PathFragmentNotMatched) (const check) elements
131147 checkRequestBodies = do
132148 let
133− check _ reqBody = checkCompatibility @RequestBody env reqBody
149+ check reqBody = checkCompatibility @RequestBody env reqBody
134150 elements = getReqBody <$> bodyDefs <*> prodCons
135151 getReqBody bodyDef mop = M.fromList $ do
136− bodyRef <- F.toList $ _operationRequestBody $ operation mop
152+ bodyRef <- F.toList . tracedRequestBody $ mop
137153 let
138− traced = dereferenceTraced bodyDef
139− $ Traced (step $ OperationRequestBodyStep) bodyRef
140− required = fromMaybe False
141− $ _requestBodyRequired $ getTraced traced
142− elt = ProductLike { traced, required }
154+ body = dereference bodyDef bodyRef
143155 -- Single element map
144− pure ((), elt)
145− checkProducts (const NoRequestBody) check elements
156+ pure ((), ProductLike
157+ { tracedValue = body
158+ , required = fromMaybe False . _requestBodyRequired . extract $ body
159+ })
160+ checkProducts (const NoRequestBody) (const check) elements
146161 checkResponses = do
147162 let
148− resps = (_operationResponses . operation) <$> prodCons
149163 respEnv = HCons (swapProdCons respDefs)
150164 $ HCons (swapProdCons headerDefs)
151165 $ HCons (swapProdCons schemaDefs) HNil
152− localStep OperationResponsesStep
153− $ swapRoles $ checkCompatibility respEnv $ swapProdCons resps
166+ resps = tracedResponses <$> prodCons
167+ checkCompatibility respEnv $ swapProdCons resps
154168 checkCallbacks = pure () -- (error "FIXME: not implemented")
155169 checkOperationSecurity = pure () -- (error "FIXME: not implemented")
156170 checkServers = pure () -- (error "FIXME: not implemented")
@@ -160,8 +174,30 @@ instance Subtree MatchedOperation where
160174 schemaDefs = getH @(ProdCons (Definitions Schema)) env
161175 paramDefs = getH @(ProdCons (Definitions Param)) env
162176
177+data OperationMethod =
178+ GetMethod
179+ | PutMethod
180+ | PostMethod
181+ | DeleteMethod
182+ | OptionsMethod
183+ | HeadMethod
184+ | PatchMethod
185+ | TraceMethod
186+ deriving (Eq, Ord, Show)
187+
188+pathItemMethod :: OperationMethod -> PathItem -> Maybe Operation
189+pathItemMethod = \case
190+ GetMethod -> _pathItemGet
191+ PutMethod -> _pathItemPut
192+ PostMethod -> _pathItemPost
193+ DeleteMethod -> _pathItemDelete
194+ OptionsMethod -> _pathItemOptions
195+ HeadMethod -> _pathItemHead
196+ PatchMethod -> _pathItemPatch
197+ TraceMethod -> _pathItemTrace
198+
163199instance Steppable MatchedOperation (Referenced Param) where
164− data Step MatchedOperation (Referenced Param) = OperationParamsStep
200+ data Step MatchedOperation (Referenced Param) = OperationParamsStep Int
165201 deriving (Eq, Ord, Show)
166202
167203instance Steppable MatchedOperation (Referenced RequestBody) where
@@ -173,9 +209,9 @@ instance Steppable MatchedOperation Responses where
173209 deriving (Eq, Ord, Show)
174210
175211instance Steppable MatchedOperation SecurityRequirement where
176− data Step MatchedOperation SecurityRequirement = OperationSecurityRequirementStep
212+ data Step MatchedOperation SecurityRequirement = OperationSecurityRequirementStep Int
177213 deriving (Eq, Ord, Show)
178214
179215instance Steppable MatchedOperation Server where
180− data Step MatchedOperation Server = OperationServerStep
216+ data Step MatchedOperation Server = OperationServerStep Int
181217 deriving (Eq, Ord, Show)
src/OpenAPI/Checker/Validate/Param.hs+29−21
@@ -1,11 +1,15 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
33
4−module OpenAPI.Checker.Validate.Param () where
4+module OpenAPI.Checker.Validate.Param
5+ ( CheckIssue (..)
6+ ) where
57
8+import Control.Lens
69import Control.Monad
710import Data.Maybe
811import Data.OpenApi
12+import Data.Text
913import OpenAPI.Checker.Orphans
1014import OpenAPI.Checker.Subtree
1115import OpenAPI.Checker.Trace
@@ -38,10 +42,14 @@ paramEncoding p = EncodingStyle
3842 ParamQuery -> Just $ fromMaybe False $ _paramAllowReserved p
3943 _ -> Nothing
4044
45+tracedSchema :: Traced r Param -> Maybe (Traced r (Referenced Schema))
46+tracedSchema par = _paramSchema (extract par) <&> traced (ask par >>> step ParamSchema)
47+
4148instance Subtree Param where
4249 type CheckEnv Param = '[ProdCons (Definitions Schema)]
4350 data CheckIssue Param
44− = ParamNameMismatch
51+ = ParamNotMatched Text
52+ | ParamNameMismatch
4553 -- ^ Params have different names
4654 | ParamEmptinessIncompatible
4755 -- ^ Consumer requires non-empty param, but producer gives emptyable
@@ -53,28 +61,28 @@ instance Subtree Param where
5361 | ParamSchemaMismatch
5462 -- ^ One of schemas not presented
5563 deriving (Eq, Ord, Show)
56− checkCompatibility env (ProdCons p c) = do
57− when (_paramName p /= _paramName c)
58− $ issueAt producer ParamNameMismatch
59− when ((fromMaybe False $ _paramRequired c) &&
60− not (fromMaybe False $ _paramRequired p))
61− $ issueAt producer ParamRequired
62− case (_paramIn p, _paramIn c) of
64+ checkCompatibility env pc@(ProdCons p c) = do
65+ when (_paramName (extract p) /= _paramName (extract c))
66+ $ issueAt p ParamNameMismatch
67+ when ((fromMaybe False . _paramRequired . extract $ c) &&
68+ not (fromMaybe False . _paramRequired . extract $ p))
69+ $ issueAt p ParamRequired
70+ case (_paramIn . extract $ p, _paramIn . extract $ c) of
6371 (ParamQuery, ParamQuery) -> do
6472 -- Emptiness is only for query params
65− when ((fromMaybe False $ _paramAllowEmptyValue p)
66− && not (fromMaybe False $ _paramAllowEmptyValue c))
67− $ issueAt producer ParamEmptinessIncompatible
73+ when ((fromMaybe False . _paramAllowEmptyValue . extract $ p)
74+ && not (fromMaybe False . _paramAllowEmptyValue . extract $ c))
75+ $ issueAt p ParamEmptinessIncompatible
6876 (a, b) | a == b -> pure ()
69− _ -> issueAt producer ParamPlaceIncompatible
70− unless (paramEncoding p == paramEncoding c)
71− $ issueAt producer ParamStyleMismatch
72− case (_paramSchema p, _paramSchema c) of
73− (Just prodSchema, Just consSchema) -> localStep ParamSchema
74− $ checkCompatibility env (ProdCons prodSchema consSchema)
75− (Nothing, Nothing) -> pure ()
76− (Nothing, Just _consSchema) -> issueAt producer ParamSchemaMismatch
77− (Just _prodSchema, Nothing) -> pure ()
77+ _ -> issueAt p ParamPlaceIncompatible
78+ unless (paramEncoding (extract p) == paramEncoding (extract c))
79+ $ issueAt p ParamStyleMismatch
80+ case tracedSchema <$> pc of
81+ ProdCons (Just prodSchema) (Just consSchema) -> do
82+ checkCompatibility env $ ProdCons prodSchema consSchema
83+ ProdCons Nothing Nothing -> pure ()
84+ ProdCons Nothing (Just _consSchema) -> issueAt p ParamSchemaMismatch
85+ ProdCons (Just _prodSchema) Nothing -> pure ()
7886 -- If consumer doesn't care then why we should?
7987 pure ()
8088
src/OpenAPI/Checker/Validate/PathFragment.hs+26−26
@@ -2,6 +2,7 @@ module OpenAPI.Checker.Validate.PathFragment
22 ( parsePath
33 , PathFragment (..)
44 , PathFragmentParam
5+ , CheckIssue (..)
56 )
67where
78
@@ -40,32 +41,16 @@ instance (Typeable param) => Steppable (PathFragment param) Param where
4041 data Step (PathFragment param) Param = StaticPathParam Text
4142 deriving (Eq, Ord, Show)
4243
43−instance Subtree PathFragmentParam where
44− type CheckEnv PathFragmentParam =
45− '[ ProdCons (Definitions Schema) ]
46− data CheckIssue PathFragmentParam =
47− PathFragmentsDontMatch Text Text
48− deriving (Eq, Ord, Show)
49− -- This case isn't strictly needed. It is here for optimization.
50− checkCompatibility _ ProdCons {producer = (StaticPath x), consumer = (StaticPath y)} =
51− if x == y
52− then pure ()
53− else issueAt consumer (PathFragmentsDontMatch x y)
54− checkCompatibility env prodCons = withTrace $ \myTrace -> do
55− let
56− tracedParams = dePathFragment <$> myTrace <*> prodCons
57− dePathFragment root = \case
58− StaticPath s -> retrace root $ Traced (step $ StaticPathParam s)
59− $ mempty
60− { _paramRequired = Just True
61− , _paramIn = ParamPath
62− , _paramAllowEmptyValue = Just False
63− , _paramAllowReserved = Just False
64− , _paramSchema = Just $ Inline $ staticStringSchema s }
65− DynamicPath p -> p
66− params = getTraced <$> tracedParams
67− paramTrace = getTrace <$> tracedParams
68− localTrace' paramTrace $ checkCompatibility env params
44+tracedPathFragmentParam :: Traced OpenApi PathFragmentParam -> Traced OpenApi Param
45+tracedPathFragmentParam pfp = case extract pfp of
46+ StaticPath s -> traced (ask pfp >>> step (StaticPathParam s))
47+ $ mempty
48+ { _paramRequired = Just True
49+ , _paramIn = ParamPath
50+ , _paramAllowEmptyValue = Just False
51+ , _paramAllowReserved = Just False
52+ , _paramSchema = Just $ Inline $ staticStringSchema s }
53+ DynamicPath p -> p
6954
7055staticStringSchema :: Text -> Schema
7156staticStringSchema t =
@@ -74,3 +59,18 @@ staticStringSchema t =
7459 , _schemaType = Just OpenApiString
7560 , _schemaEnum = Just [A.String t]
7661 }
62+
63+instance Subtree PathFragmentParam where
64+ type CheckEnv PathFragmentParam =
65+ '[ ProdCons (Definitions Schema) ]
66+ data CheckIssue PathFragmentParam
67+ = PathFragmentNotMatched
68+ | PathFragmentsDontMatch Text Text
69+ deriving (Eq, Ord, Show)
70+ -- This case isn't strictly needed. It is here for optimization.
71+ checkCompatibility _ (ProdCons (extract -> StaticPath x) c@(extract -> StaticPath y))
72+ = if x == y
73+ then pure ()
74+ else issueAt c (PathFragmentsDontMatch x y)
75+ checkCompatibility env prodCons = do
76+ checkCompatibility env (tracedPathFragmentParam <$> prodCons)
src/OpenAPI/Checker/Validate/ProcessedPathItem.hs+50−60
@@ -9,6 +9,7 @@ module OpenAPI.Checker.Validate.ProcessedPathItem
99 )
1010where
1111
12+import Control.Comonad.Env
1213import Control.Monad
1314import Data.Foldable as F
1415import Data.Functor
@@ -52,29 +53,24 @@ instance Subtree ProcessedPathItems where
5253 | AllPathsFailed FilePath
5354 -- When several paths match given but all checks failed
5455 deriving (Eq, Ord, Show)
55− checkCompatibility env (ProdCons p c) = do
56+ checkCompatibility env pc@(ProdCons p c) = do
5657 -- Each path generated by producer must be handled by consumer with exactly
5758 -- one way
58− for_ (unProcessedPathItems p) $ \ prodItem -> do
59+ for_ (unProcessedPathItems . extract $ p) $ \ prodItem -> do
5960 let
6061 prodPath = path prodItem
6162 matchedItems = do
62− consItem <- unProcessedPathItems c
63+ consItem <- unProcessedPathItems . extract $ c
6364 matched <- F.toList $ matchingPathItems $ ProdCons prodItem consItem
6465 return matched
6566 case matchedItems of
66− [] -> issueAt producer $ NoPathsMatched prodPath
67− [matched] -> do
68− -- Checking exact match with no wrapper
69− let trace = matchedTrace <$> matched
70− localTrace trace $ checkCompatibility env matched
71− matches -> anyOfAt consumer (AllPathsFailed prodPath) $ do
67+ [] -> issueAt p $ NoPathsMatched prodPath
68+ [match] -> checkCompatibility env (retraced <$> pc <*> match)
69+ matches -> anyOfAt c (AllPathsFailed prodPath) $ do
7270 match <- matches
73− let trace = matchedTrace <$> match
74− pure $ localTrace trace $ checkCompatibility env match
71+ pure $ checkCompatibility env (retraced <$> pc <*> match)
7572 where
76− matchedTrace :: MatchedPathItem -> Trace ProcessedPathItems MatchedPathItem
77− matchedTrace mpi = step $ MatchedPathStep $ matchedPath mpi
73+ retraced pc mpi = traced (ask pc >>> step (MatchedPathStep $ matchedPath mpi)) mpi
7874
7975-- | Preliminary checks two paths for compatibility. Returns Nothing if two
8076-- paths obviously do not match: static parts differ or count of path elements
@@ -109,6 +105,25 @@ data MatchedPathItem = MatchedPathItem
109105 -- ^ Pre-parsed path from PathItem
110106 }
111107
108+tracedParameters :: Traced r MatchedPathItem -> [Traced r (Referenced Param)]
109+tracedParameters mpi =
110+ [ traced (ask mpi >>> step (PathItemParam i)) x
111+ | (i, x) <- L.zip [0..] $ _pathItemParameters . pathItem $ extract mpi
112+ ]
113+
114+-- TODO: simplify?
115+tracedFragments :: Traced r MatchedPathItem -> [Env (Trace r PathFragmentParam) (PathFragment Text)]
116+tracedFragments mpi =
117+ [ env (ask mpi >>> step (PathFragmentStep i)) x
118+ | (i, x) <- L.zip [0..] $ pathFragments $ extract mpi
119+ ]
120+
121+tracedMethod
122+ :: OperationMethod
123+ -> Traced r MatchedPathItem
124+ -> Maybe (Env (Trace r MatchedOperation) Operation)
125+tracedMethod s mpi = env (ask mpi >>> step (OperationMethodStep s)) <$> (pathItemMethod s . pathItem . extract $ mpi)
126+
112127instance Subtree MatchedPathItem where
113128 type CheckEnv MatchedPathItem =
114129 '[ ProdCons (Definitions Param)
@@ -119,63 +134,46 @@ instance Subtree MatchedPathItem where
119134 , ProdCons (Definitions Schema)
120135 ]
121136 data CheckIssue MatchedPathItem
122− = OperationMissing (Step MatchedPathItem MatchedOperation)
123137 deriving (Eq, Ord, Show)
124− checkCompatibility env prodCons = withTrace $ \rootTrace -> do
138+ checkCompatibility env prodCons = do
125139 let
126140 paramDefs = getH @(ProdCons (Definitions Param)) env
127− pathTracedParams = getPathParams <$> rootTrace <*> paramDefs <*> prodCons
141+ pathTracedParams = getPathParams <$> paramDefs <*> prodCons
128142 getPathParams
129− :: Trace OpenApi MatchedPathItem
130− -> Definitions Param
131− -> MatchedPathItem
132− -> [Traced OpenApi Param]
133− getPathParams root defs mpi = do
134− paramRef <- _pathItemParameters $ pathItem mpi
135− let
136− traced = dereferenceTraced defs
137− $ Traced (step PathItemParam) paramRef
138− res = retrace root traced
139− pure res
140− pathTracedFragments = mkPathFragments <$> rootTrace <*> prodCons
141− mkPathFragments myRoot mpi operationParams =
143+ :: Definitions Param
144+ -> Traced r MatchedPathItem
145+ -> [Traced r Param]
146+ getPathParams defs mpi = do
147+ paramRef <- tracedParameters mpi
148+ pure $ dereference defs paramRef
149+ pathTracedFragments = mkPathFragments <$> prodCons
150+ mkPathFragments mpi operationParams =
142151 -- operationParams will be known on Operation check stage, so we give a
143152 -- function, returning fragments
144153 let
145154 paramsMap :: Map Text (Traced OpenApi Param)
146155 paramsMap = M.fromList $ do
147156 tracedParam <- operationParams
148− let pname = _paramName $ getTraced tracedParam
157+ let pname = _paramName . extract $ tracedParam
149158 pure (pname, tracedParam)
150− fragments :: [PathFragmentParam]
151− fragments = (pathFragments mpi) <&> \case
159+ convertFragment = \case
152160 StaticPath t -> StaticPath t
153161 DynamicPath pname -> DynamicPath
154162 $ fromMaybe (error $ "Param not found " <> T.unpack pname)
155163 $ M.lookup pname paramsMap
156− tracedFragments :: [Traced OpenApi PathFragmentParam]
157− tracedFragments = L.zip [0..] fragments <&> \(pos, frag) ->
158− retrace myRoot $ Traced (step $ PathFragmentStep pos) frag
159− in tracedFragments
164+ in tracedFragments mpi <&> fmap convertFragment
160165 operations = getOperations <$> pathTracedParams <*> pathTracedFragments <*> prodCons
161166 getOperations pathParams getPathFragments mpi = M.fromList $ do
162− (getOp, s) <-
163− [ (_pathItemGet, GetStep)
164− , (_pathItemPut, PutStep)
165− , (_pathItemPost, PostStep)
166− , (_pathItemDelete, DeleteStep)
167− , (_pathItemOptions, OptionsStep)
168− , (_pathItemHead, HeadStep)
169− , (_pathItemPatch, PatchStep)
170− , (_pathItemTrace, TraceStep) ]
171− operation <- F.toList $ getOp $ pathItem mpi
167+ (i, getOp) <- (\m -> (m, tracedMethod m)) <$>
168+ [GetMethod, PutMethod, PostMethod, DeleteMethod, OptionsMethod, HeadMethod, PatchMethod, DeleteMethod]
169+ operation <- F.toList $ getOp mpi
172170 -- Got only Justs here
173− let mop = MatchedOperation { operation , pathParams, getPathFragments }
174− pure (s, Traced (step s) mop)
175− check _ pc = checkCompatibility @MatchedOperation env pc
171+ let retraced = \op -> MatchedOperation { operation = op, pathParams, getPathFragments }
172+ pure (i, retraced <$> operation)
173+ check pc = checkCompatibility @MatchedOperation env pc
176174 -- Operations are sum-like entities. Use step to operation as key because
177175 -- why not
178− checkSums OperationMissing check operations
176+ checkSums OperationMissing (const check) operations
179177
180178
181179instance Steppable ProcessedPathItems MatchedPathItem where
@@ -183,19 +181,11 @@ instance Steppable ProcessedPathItems MatchedPathItem where
183181 deriving (Eq, Ord, Show)
184182
185183instance Steppable MatchedPathItem MatchedOperation where
186− data Step MatchedPathItem MatchedOperation
187− = GetStep
188− | PutStep
189− | PostStep
190− | DeleteStep
191− | OptionsStep
192− | HeadStep
193− | PatchStep
194− | TraceStep
184+ data Step MatchedPathItem MatchedOperation = OperationMethodStep OperationMethod
195185 deriving (Eq, Ord, Show)
196186
197187instance Steppable MatchedPathItem (Referenced Param) where
198− data Step MatchedPathItem (Referenced Param) = PathItemParam
188+ data Step MatchedPathItem (Referenced Param) = PathItemParam Int
199189 deriving (Eq, Ord, Show)
200190
201191instance Steppable MatchedPathItem PathFragmentParam where
src/OpenAPI/Checker/Validate/Products.hs+12−34
@@ -14,58 +14,36 @@ This module abstracts this logic for arbitrary elements -}
1414
1515module OpenAPI.Checker.Validate.Products
1616 ( checkProducts
17− , checkProducts'
1817 , ProductLike(..)
1918 ) where
2019
2120import Data.Foldable
22−import Data.Functor
2321import Data.Map.Strict (Map)
2422import qualified Data.Map.Strict as M
25−import Data.OpenApi.Internal
2623import OpenAPI.Checker.Subtree
2724import OpenAPI.Checker.Trace
2825
2926-- | Some entity which is product-like
3027data ProductLike root a = ProductLike
31− { traced :: Traced root a
28+ { tracedValue :: Traced root a
3229 , required :: Bool
3330 }
3431
35−checkProducts'
36− :: forall k root t
37− . (Subtree root, Ord k)
38− => (k -> CheckIssue root)
32+checkProducts
33+ :: forall k r t
34+ . (Subtree t, Ord k)
35+ => (k -> CheckIssue t)
3936 -- ^ No required element found
40− -> (k -> ProdCons t -> CompatFormula t ())
41− -> ProdCons (Map k (ProductLike OpenApi t))
42− -> CompatFormula root ()
43−checkProducts' noElt check (ProdCons p c) = for_ (M.toList c) $ \(key, consElt) ->
37+ -> (k -> ProdCons (Traced r t) -> CompatFormula' SubtreeCheckIssue r ())
38+ -> ProdCons (Map k (ProductLike r t))
39+ -> CompatFormula' SubtreeCheckIssue r ()
40+checkProducts noElt check (ProdCons p c) = for_ (M.toList c) $ \(key, consElt) ->
4441 case M.lookup key p of
4542 Nothing -> case required consElt of
46− True -> issueAt producer $ noElt key
43+ True -> issueAt (tracedValue consElt) $ noElt key
4744 False -> pure ()
4845 Just prodElt -> do
4946 let
50− elts :: ProdCons (ProductLike OpenApi t)
47+ elts :: ProdCons (ProductLike r t)
5148 elts = ProdCons prodElt consElt
52− trace = getTrace . traced <$> elts
53− elements = getTraced . traced <$> elts
54− localTrace' trace $ check key elements
55−
56−checkProducts
57− :: forall k root t
58− . (Subtree root, Ord k)
59− => (k -> CheckIssue root)
60− -- ^ No required element found
61− -> (k -> ProdCons t -> CompatFormula t ())
62− -> ProdCons (Map k (ProductLike root t))
63− -> CompatFormula root ()
64−checkProducts noElt check prodCons = withTrace $ \myTrace ->
65− let
66− retracedPC = retracePC <$> myTrace <*> prodCons
67− retracePC rootTrace els = els <&> \productLike ->
68− ProductLike
69− { traced = retrace rootTrace $ traced productLike
70− , required = required productLike }
71− in checkProducts' noElt check retracedPC
49+ check key (tracedValue <$> elts)
src/OpenAPI/Checker/Validate/RequestBody.hs+13−15
@@ -1,11 +1,10 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
33module OpenAPI.Checker.Validate.RequestBody
4− (
4+ ( CheckIssue (..)
55 )
66where
77
8−import Data.Functor
98import Data.HList
109import Data.HashMap.Strict.InsOrd as IOHM
1110import Data.Map.Strict as M
@@ -14,32 +13,31 @@ import Data.OpenApi
1413import Network.HTTP.Media (MediaType)
1514import OpenAPI.Checker.Subtree
1615import OpenAPI.Checker.Trace
17−import OpenAPI.Checker.Validate.MediaTypeObject ()
16+import OpenAPI.Checker.Validate.MediaTypeObject
1817import OpenAPI.Checker.Validate.Sums
1918
19+tracedContent :: Traced r RequestBody -> IOHM.InsOrdHashMap MediaType (Traced r MediaTypeObject)
20+tracedContent resp = IOHM.mapWithKey (\k -> traced (ask resp >>> step (RequestMediaTypeObject k)))
21+ $ _requestBodyContent $ extract resp
22+
2023instance Subtree RequestBody where
2124 type CheckEnv RequestBody =
2225 '[ ProdCons (Definitions Schema) ]
2326 data CheckIssue RequestBody
24− = RequestBodyRequired
25− | RequestMediaTypeNotFound MediaType
27+ = NoRequestBody
28+ | RequestBodyRequired
2629 deriving (Eq, Ord, Show)
2730 checkCompatibility env prodCons@(ProdCons p c) =
28− if not (fromMaybe False $ _requestBodyRequired p)
29− && (fromMaybe False $ _requestBodyRequired c)
30− then issueAt producer RequestBodyRequired
31+ if not (fromMaybe False . _requestBodyRequired . extract $ p)
32+ && (fromMaybe False . _requestBodyRequired . extract $ c)
33+ then issueAt p RequestBodyRequired
3134 else
3235 -- Media type object are sums-like entities.
3336 let
3437 check mediaType pc = checkCompatibility @MediaTypeObject (HCons mediaType env) pc
3538 sumElts = getSum <$> prodCons
36− getSum rb = M.fromList
37− $ (IOHM.toList $ _requestBodyContent rb) <&> \(mt, mto) ->
38− ( mt
39− , Traced
40− { getTraced = mto
41− , getTrace = step $ RequestMediaTypeObject mt })
42− in checkSums RequestMediaTypeNotFound check sumElts
39+ getSum rb = M.fromList . IOHM.toList $ tracedContent rb
40+ in checkSums (const RequestMediaTypeNotFound) check sumElts
4341
4442instance Steppable RequestBody MediaTypeObject where
4543 data Step RequestBody MediaTypeObject = RequestMediaTypeObject MediaType
src/OpenAPI/Checker/Validate/Responses.hs+43−36
@@ -6,6 +6,7 @@ module OpenAPI.Checker.Validate.Responses
66 )
77where
88
9+import Control.Lens
910import Data.Foldable
1011import Data.HList
1112import Data.HashMap.Strict.InsOrd as IOHM
@@ -16,18 +17,22 @@ import Network.HTTP.Media (MediaType)
1617import OpenAPI.Checker.References
1718import OpenAPI.Checker.Subtree
1819import OpenAPI.Checker.Trace
19−import OpenAPI.Checker.Validate.MediaTypeObject ()
20+import OpenAPI.Checker.Validate.MediaTypeObject
2021import OpenAPI.Checker.Validate.Products
2122import OpenAPI.Checker.Validate.Schema ()
2223import OpenAPI.Checker.Validate.Sums
2324
25+tracedResponses :: Traced r Responses -> IOHM.InsOrdHashMap HttpStatusCode (Traced r (Referenced Response))
26+tracedResponses resp = IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseCodeStep k)))
27+ $ _responsesResponses $ extract resp
28+
2429instance Subtree Responses where
2530 type CheckEnv Responses =
2631 '[ ProdCons (Definitions Response)
2732 , ProdCons (Definitions Header)
2833 , ProdCons (Definitions Schema)
2934 ]
30− data CheckIssue Responses = ResponseCodeNotFound HttpStatusCode
35+ data CheckIssue Responses
3136 deriving (Eq, Ord, Show)
3237 -- Roles are already swapped. Producer is a server and consumer is a
3338 -- client. Response codes are sum-like entity because we can answer with only
@@ -35,15 +40,20 @@ instance Subtree Responses where
3540 checkCompatibility env prodCons = do
3641 let
3742 defs = getH @(ProdCons (Definitions Response)) env
38− check _ resps = checkCompatibility @Response env resps
43+ check resps = checkCompatibility @Response env resps
3944 elements = getEls <$> defs <*> prodCons
4045 getEls respDef resps = M.fromList $ do
41− (code, respRef) <- IOHM.toList $ _responsesResponses resps
42− let
43− traced = dereferenceTraced respDef
44− $ Traced (step $ ResponseCodeStep code) respRef
45− pure (code, traced)
46− checkSums ResponseCodeNotFound check elements
46+ (code, respRef) <- IOHM.toList $ tracedResponses resps
47+ pure (code, dereference respDef respRef)
48+ checkSums (const ResponseCodeNotFound) (const check) elements
49+
50+tracedContent :: Traced r Response -> IOHM.InsOrdHashMap MediaType (Traced r MediaTypeObject)
51+tracedContent resp = IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseMediaObject k)))
52+ $ _responseContent $ extract resp
53+
54+tracedHeaders :: Traced r Response -> IOHM.InsOrdHashMap HeaderName (Traced r (Referenced Header))
55+tracedHeaders resp = IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseHeader k)))
56+ $ _responseHeaders $ extract resp
4757
4858instance Subtree Response where
4959 type CheckEnv Response =
@@ -51,8 +61,7 @@ instance Subtree Response where
5161 , ProdCons (Definitions Schema)
5262 ]
5363 data CheckIssue Response
54− = ResponseMediaTypeMissing MediaType
55− | ResponseHeaderMissing HeaderName
64+ = ResponseCodeNotFound
5665 deriving (Eq, Ord, Show)
5766 checkCompatibility env prodCons = do
5867 -- Roles are already swapped. Producer is a server and consumer is a client
@@ -67,46 +76,44 @@ instance Subtree Response where
6776 let mtEnv = HCons mediaType $ env
6877 in checkCompatibility @MediaTypeObject mtEnv mtObj
6978 elements = getEls <$> prodCons
70− getEls resp = M.fromList $ do
71− (mediaType, mtObj) <- IOHM.toList $ _responseContent resp
72− let traced = Traced (step $ ResponseMediaObject mediaType) mtObj
73− pure (mediaType, traced)
74− checkSums ResponseMediaTypeMissing check elements
79+ getEls resp = M.fromList . IOHM.toList $ tracedContent resp
80+ checkSums (const ResponseMediaTypeMissing) check elements
7581
7682 checkHeaders = do
7783 -- Headers are product-like entities
7884 let
79− check _hname hdrs = checkCompatibility @Header env hdrs
85+ check hdrs = checkCompatibility @Header env hdrs
8086 elements = getEls <$> headerDefs <*> prodCons
8187 getEls headerDef resp = M.fromList $ do
82− (hname, headerRef) <- IOHM.toList $ _responseHeaders resp
83− let
84− traced = dereferenceTraced headerDef
85− $ Traced (step $ ResponseHeader hname) headerRef
86− required = fromMaybe False $ _headerRequired $ getTraced traced
87− elt = ProductLike { traced, required }
88− pure (hname, elt)
89− checkProducts ResponseHeaderMissing check elements
88+ (hname, headerRef) <- IOHM.toList $ tracedHeaders resp
89+ let header = dereference headerDef headerRef
90+ pure (hname, ProductLike
91+ { tracedValue = header
92+ , required = fromMaybe False . _headerRequired . extract $ header
93+ })
94+ checkProducts (const ResponseHeaderMissing) (const check) elements
9095 headerDefs = getH @(ProdCons (Definitions Header)) env
9196
97+tracedSchema :: Traced r Header -> Maybe (Traced r (Referenced Schema))
98+tracedSchema hdr = _headerSchema (extract hdr) <&> traced (ask hdr >>> step HeaderSchema)
99+
92100instance Subtree Header where
93101 type CheckEnv Header = '[ProdCons (Definitions Schema)]
94102 data CheckIssue Header
95− = RequiredHeaderMissing
103+ = ResponseHeaderMissing
104+ | RequiredHeaderMissing
96105 | NonEmptyHeaderRequired
97106 | HeaderSchemaRequired
98107 deriving (Eq, Ord, Show)
99108 checkCompatibility env (ProdCons p c) = do
100− if (fromMaybe False $ _headerRequired c) && not (fromMaybe False $ _headerRequired p)
101− then issueAt producer RequiredHeaderMissing else pure ()
102− if not (fromMaybe False $ _headerAllowEmptyValue c) && (fromMaybe False $ _headerAllowEmptyValue p)
103− then issueAt producer NonEmptyHeaderRequired else pure ()
104− for_ (_headerSchema c) $ \consRef ->
105− case (_headerSchema p) of
106− Nothing -> issueAt producer HeaderSchemaRequired
107− Just prodRef -> do
108− localStep HeaderSchema
109− $ checkCompatibility env $ ProdCons prodRef consRef
109+ if (fromMaybe False $ _headerRequired $ extract c) && not (fromMaybe False $ _headerRequired $ extract p)
110+ then issueAt p RequiredHeaderMissing else pure ()
111+ if not (fromMaybe False $ _headerAllowEmptyValue $ extract c) && (fromMaybe False $ _headerAllowEmptyValue $ extract p)
112+ then issueAt p NonEmptyHeaderRequired else pure ()
113+ for_ (tracedSchema c) $ \consRef ->
114+ case tracedSchema p of
115+ Nothing -> issueAt p HeaderSchemaRequired
116+ Just prodRef -> checkCompatibility env (ProdCons prodRef consRef)
110117 pure ()
111118
112119instance Steppable Responses (Referenced Response) where
src/OpenAPI/Checker/Validate/Schema.hs+154−137
@@ -14,7 +14,11 @@ module OpenAPI.Checker.Validate.Schema
1414
1515import Algebra.Lattice
1616import Control.Applicative
17−import Control.Monad.Reader
17+import Control.Arrow
18+import Control.Comonad.Env hiding (env)
19+import Control.Lens hiding (cons)
20+import Control.Monad.Reader hiding (ask)
21+import qualified Control.Monad.Reader as R
1822import Control.Monad.Writer
1923import qualified Data.Aeson as A
2024import Data.Coerce
@@ -154,7 +158,7 @@ deriving stock instance Ord (Condition t)
154158deriving stock instance Show (Condition t)
155159
156160data SomeCondition where
157− SomeCondition :: Typeable t => Condition t -> SomeCondition
161+ SomeCondition :: Typeable t => Traced OpenApi (Condition t) -> SomeCondition
158162
159163instance Eq SomeCondition where
160164 SomeCondition x == SomeCondition y = case cast x of
@@ -200,8 +204,8 @@ isSingleton s
200204 | otherwise = Nothing
201205
202206pattern Conjunct :: [Traced r (Condition t)] -> M.Map (Condition t) (Trace r (Condition t))
203−pattern Conjunct xs <- (map (uncurry $ flip Traced) . M.toList -> xs)
204− where Conjunct xs = M.fromList [(x, t) | Traced t x <- xs]
207+pattern Conjunct xs <- (map (uncurry $ flip traced) . M.toList -> xs)
208+ where Conjunct xs = M.fromList $ (extract &&& ask) <$> xs
205209{-# COMPLETE Conjunct #-}
206210
207211pattern SingleConjunct :: [Traced r (Condition t)] -> JsonFormula r t
@@ -218,19 +222,16 @@ instance BoundedJoinSemiLattice (JsonFormula r t) where
218222instance BoundedMeetSemiLattice (JsonFormula r t) where
219223 top = TopFormula
220224
221−singletonFormula :: Trace r (Condition t) -> Condition t -> JsonFormula r t
222−singletonFormula t x = SingleConjunct [Traced t x]
223−
224225foldLattice
225226 :: BoundedLattice l
226227 => (Traced r (Condition t) -> l)
227228 -> JsonFormula r t
228229 -> l
229230foldLattice f (DNF xss) = S.foldl' (\z w ->
230− z \/ M.foldlWithKey' (\x y t -> x /\ f (Traced t y)) top w) bottom xss
231+ z \/ M.foldlWithKey' (\x y t -> x /\ f (traced t y)) top w) bottom xss
231232
232233satisfiesFormula :: TypedValue t -> JsonFormula r t -> Bool
233−satisfiesFormula val = foldLattice (satisfiesTyped val . getTraced)
234+satisfiesFormula val = foldLattice (satisfiesTyped val . extract)
234235
235236data ForeachType (f :: JsonType -> Type) = ForeachType
236237 { forNull :: f 'Null
@@ -345,22 +346,52 @@ instance Steppable Schema (Referenced Schema) where
345346 = AllOfStep Int
346347 | OneOfStep Int
347348 | AnyOfStep Int
348− | ItemsStep
349+ | ItemsObjectStep
350+ | ItemsArrayStep Int
349351 | AdditionalPropertiesStep
350352 | PropertiesStep Text
351353 deriving (Eq, Ord, Show)
352354
353355type ProcessM = ReaderT (Definitions Schema) (Writer (T.TracePrefixTree SubtreeCheckIssue OpenApi))
354356
355−warn :: Subtree t => Trace OpenApi t -> CheckIssue t -> ProcessM ()
356−warn t x = tell $ T.singleton $ AnItem t $ SubtreeCheckIssue x
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
357361
358362processRefSchema
359363 :: Traced OpenApi (Referenced Schema)
360364 -> ProcessM (ForeachType (JsonFormula OpenApi))
361365processRefSchema x = do
362− defs <- ask
363− processSchema $ dereferenceTraced defs x
366+ defs <- R.ask
367+ processSchema $ dereference defs x
368+
369+tracedAllOf :: Traced r Schema -> Maybe [Traced r (Referenced Schema)]
370+tracedAllOf sch = _schemaAllOf (extract sch) <&> \xs ->
371+ [ traced (ask sch >>> step (AllOfStep i)) x | (i, x) <- zip [0..] xs ]
372+
373+tracedAnyOf :: Traced r Schema -> Maybe [Traced r (Referenced Schema)]
374+tracedAnyOf sch = _schemaAnyOf (extract sch) <&> \xs ->
375+ [ traced (ask sch >>> step (AnyOfStep i)) x | (i, x) <- zip [0..] xs ]
376+
377+tracedOneOf :: Traced r Schema -> Maybe [Traced r (Referenced Schema)]
378+tracedOneOf sch = _schemaOneOf (extract sch) <&> \xs ->
379+ [ traced (ask sch >>> step (OneOfStep i)) x | (i, x) <- zip [0..] xs ]
380+
381+tracedItems :: Traced r Schema -> Maybe (Either (Traced r (Referenced Schema)) [Traced r (Referenced Schema)])
382+tracedItems sch = _schemaItems (extract sch) <&> \case
383+ OpenApiItemsObject x -> Left $ traced (ask sch >>> step ItemsObjectStep) x
384+ OpenApiItemsArray xs -> Right
385+ [ traced (ask sch >>> step (ItemsArrayStep i)) x | (i, x) <- zip [0..] xs ]
386+
387+tracedAdditionalProperties :: Traced r Schema -> Maybe (Either Bool (Traced r (Referenced Schema)))
388+tracedAdditionalProperties sch = _schemaAdditionalProperties (extract sch) <&> \case
389+ AdditionalPropertiesAllowed b -> Left b
390+ AdditionalPropertiesSchema x -> Right $ traced (ask sch >>> step AdditionalPropertiesStep) x
391+
392+tracedProperties :: Traced r Schema -> IOHM.InsOrdHashMap Text (Traced r (Referenced Schema))
393+tracedProperties sch = IOHM.mapWithKey (\k -> traced (ask sch >>> step (PropertiesStep k)))
394+ $ _schemaProperties $ extract sch
364395
365396-- | Turn a schema into a tuple of 'JsonFormula's that describes the condition
366397-- for every possible type of a JSON value. The conditions are independent, and
@@ -368,36 +399,33 @@ processRefSchema x = do
368399processSchema
369400 :: Traced OpenApi Schema
370401 -> ProcessM (ForeachType (JsonFormula OpenApi))
371−processSchema (Traced t Schema{..}) = do
402+processSchema sch@(extract -> Schema{..}) = do
403+ 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]
372406
373− allClauses <- case _schemaAllOf of
407+ allClauses <- case tracedAllOf sch of
374408 Nothing -> pure []
375− Just [] -> [] <$ warn t (InvalidSchema "no items in allOf")
376− Just xs -> sequence
377− [ processRefSchema (Traced (t `Snoc` AllOfStep i) rs)
378− | (i, rs) <- zip [0..] xs ]
409+ Just [] -> [] <$ warn sch (InvalidSchema "no items in allOf")
410+ Just xs -> mapM processRefSchema xs
379411
380− anyClause <- case _schemaAnyOf of
412+ anyClause <- case tracedAnyOf sch of
381413 Nothing -> pure top
382− Just [] -> bottom <$ warn t (InvalidSchema "no items in anyOf")
383− Just xs -> joins <$> sequence
384− [ processRefSchema (Traced (t `Snoc` AnyOfStep i) rs)
385− | (i, rs) <- zip [0..] xs ]
414+ Just [] -> bottom <$ warn sch (InvalidSchema "no items in anyOf")
415+ Just xs -> joins <$> mapM processRefSchema xs
386416
387− oneClause <- case _schemaOneOf of
417+ oneClause <- case tracedOneOf sch of
388418 Nothing -> pure top
389− Just [] -> bottom <$ warn t (InvalidSchema "no items in oneOf")
419+ Just [] -> bottom <$ warn sch (InvalidSchema "no items in oneOf")
390420 Just xs -> do
391421 checkOneOfDisjoint xs >>= \case
392422 True -> pure ()
393− False -> warn t (NotSupported "Could not determine that oneOf branches are disjoint")
394− joins <$> sequence
395− [ processRefSchema (Traced (t `Snoc` OneOfStep i) rs)
396− | (i, rs) <- zip [0..] xs ]
423+ False -> warn sch (NotSupported "Could not determine that oneOf branches are disjoint")
424+ joins <$> mapM processRefSchema xs
397425
398426 case _schemaNot of
399427 Nothing -> pure ()
400− Just _ -> warn t (NotSupported "not clause is unsupported")
428+ Just _ -> warn sch (NotSupported "not clause is unsupported")
401429
402430 let
403431 typeClause = case _schemaType of
@@ -409,7 +437,7 @@ processSchema (Traced t Schema{..}) = do
409437 Just OpenApiNumber -> bottom
410438 { forBoolean = top }
411439 Just OpenApiInteger -> bottom
412− { forNumber = singletonFormula (t `Snoc` IntegerType) $ MultipleOf 1 }
440+ { forNumber = singletonFormula IntegerType $ MultipleOf 1 }
413441 Just OpenApiString -> bottom
414442 { forString = top }
415443 Just OpenApiArray -> bottom
@@ -419,27 +447,27 @@ processSchema (Traced t Schema{..}) = do
419447
420448 let
421449 valueEnum A.Null = bottom
422− { forNull = singletonFormula (t `Snoc` EnumField) $ Exactly TNull }
450+ { forNull = singletonFormula EnumField $ Exactly TNull }
423451 valueEnum (A.Bool b) = bottom
424− { forBoolean = singletonFormula (t `Snoc` EnumField) $ Exactly $ TBool b }
452+ { forBoolean = singletonFormula EnumField $ Exactly $ TBool b }
425453 valueEnum (A.Number n) = bottom
426− { forNumber = singletonFormula (t `Snoc` EnumField) $ Exactly $ TNumber n }
454+ { forNumber = singletonFormula EnumField $ Exactly $ TNumber n }
427455 valueEnum (A.String s) = bottom
428− { forString = singletonFormula (t `Snoc` EnumField) $ Exactly $ TString s }
456+ { forString = singletonFormula EnumField $ Exactly $ TString s }
429457 valueEnum (A.Array a) = bottom
430− { forArray = singletonFormula (t `Snoc` EnumField) $ Exactly $ TArray a }
458+ { forArray = singletonFormula EnumField $ Exactly $ TArray a }
431459 valueEnum (A.Object o) = bottom
432− { forObject = singletonFormula (t `Snoc` EnumField) $ Exactly $ TObject o }
460+ { forObject = singletonFormula EnumField $ Exactly $ TObject o }
433461 enumClause <- case _schemaEnum of
434462 Nothing -> pure top
435− Just [] -> bottom <$ warn t (InvalidSchema "no items in enum")
463+ Just [] -> bottom <$ warn sch (InvalidSchema "no items in enum")
436464 Just xs -> pure $ joins (valueEnum <$> xs)
437465
438466 let
439467 maximumClause = case _schemaMaximum of
440468 Nothing -> top
441469 Just n -> top
442− { forNumber = singletonFormula (t `Snoc` MaximumFields) $ Maximum $
470+ { forNumber = singletonFormula MaximumFields $ Maximum $
443471 case _schemaExclusiveMaximum of
444472 Just True -> Exclusive n
445473 _ -> Inclusive n }
@@ -447,7 +475,7 @@ processSchema (Traced t Schema{..}) = do
447475 minimumClause = case _schemaMinimum of
448476 Nothing -> top
449477 Just n -> top
450− { forNumber = singletonFormula (t `Snoc` MinimumFields) $ Minimum $ Down $
478+ { forNumber = singletonFormula MinimumFields $ Minimum $ Down $
451479 case _schemaExclusiveMinimum of
452480 Just True -> Exclusive $ Down n
453481 _ -> Inclusive $ Down n }
@@ -455,72 +483,68 @@ processSchema (Traced t Schema{..}) = do
455483 multipleOfClause = case _schemaMultipleOf of
456484 Nothing -> top
457485 Just n -> top
458− { forNumber = singletonFormula (t `Snoc` MultipleOfField) $ MultipleOf n }
486+ { forNumber = singletonFormula MultipleOfField $ MultipleOf n }
459487
460488 formatClause <- case _schemaFormat of
461489 Nothing -> pure top
462490 Just f | f `elem` ["int32", "int64", "float", "double"] -> pure top
463− { forNumber = singletonFormula (t `Snoc` FormatField) $ NumberFormat f }
491+ { forNumber = singletonFormula FormatField $ NumberFormat f }
464492 Just f | f `elem` ["byte", "binary", "date", "date-time", "password"] -> pure top
465− { forString = singletonFormula (t `Snoc` FormatField) $ StringFormat f }
466− Just f -> top <$ warn t (NotSupported $ "Unknown format: " <> f)
493+ { forString = singletonFormula FormatField $ StringFormat f }
494+ Just f -> top <$ warn sch (NotSupported $ "Unknown format: " <> f)
467495
468496 let
469497 maxLengthClause = case _schemaMaxLength of
470498 Nothing -> top
471499 Just n -> top
472− { forString = singletonFormula (t `Snoc` MaxLengthField) $ MaxLength n }
500+ { forString = singletonFormula MaxLengthField $ MaxLength n }
473501
474502 minLengthClause = case _schemaMinLength of
475503 Nothing -> top
476504 Just n -> top
477− { forString = singletonFormula (t `Snoc` MinLengthField) $ MinLength n }
505+ { forString = singletonFormula MinLengthField $ MinLength n }
478506
479507 patternClause = case _schemaPattern of
480508 Nothing -> top
481509 Just p -> top
482− { forString = singletonFormula (t `Snoc` PatternField) $ Pattern p }
510+ { forString = singletonFormula PatternField $ Pattern p }
483511
484− itemsClause <- case _schemaItems of
512+ itemsClause <- case tracedItems sch of
485513 Nothing -> pure top
486− Just (OpenApiItemsObject rs) -> do
487− let trs = Traced (t `Snoc` ItemsStep) rs
488− f <- processRefSchema trs
489− pure top { forArray = singletonFormula (t `Snoc` ItemsField) $ Items f trs }
490− Just (OpenApiItemsArray _) -> top <$ warn t (NotSupported "array in items is not supported")
514+ Just (Left rs) -> do
515+ 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")
491518
492519 let
493520 maxItemsClause = case _schemaMaxItems of
494521 Nothing -> top
495522 Just n -> top
496− { forArray = singletonFormula (t `Snoc` MaxItemsField) $ MaxItems n }
523+ { forArray = singletonFormula MaxItemsField $ MaxItems n }
497524
498525 minItemsClause = case _schemaMinItems of
499526 Nothing -> top
500527 Just n -> top
501− { forArray = singletonFormula (t `Snoc` MinItemsField) $ MinItems n }
528+ { forArray = singletonFormula MinItemsField $ MinItems n }
502529
503530 uniqueItemsClause = case _schemaUniqueItems of
504531 Just True -> top
505− { forArray = singletonFormula (t `Snoc` UniqueItemsField) UniqueItems }
532+ { forArray = singletonFormula UniqueItemsField UniqueItems }
506533 _ -> top
507534
508− (addProps, addPropSchema) <- case _schemaAdditionalProperties of
509− Just (AdditionalPropertiesSchema rs) -> do
510− let trs = Traced (t `Snoc` AdditionalPropertiesStep) rs
511− (,Just trs) <$> processRefSchema trs
512− Just (AdditionalPropertiesAllowed False) -> pure (bottom, Nothing)
513− _ -> pure (top, Just $ Traced (t `Snoc` AdditionalPropertiesStep) $ Inline mempty)
535+ (addProps, addPropSchema) <- case tracedAdditionalProperties sch of
536+ Just (Right rs) -> (,Just rs) <$> processRefSchema rs
537+ Just (Left False) -> pure (bottom, Nothing)
538+ _ -> pure (top, Just $ traced (ask sch `Snoc` AdditionalPropertiesStep) $ Inline mempty)
514539 propList <- forM (S.toList . S.fromList $ IOHM.keys _schemaProperties <> _schemaRequired) $ \k -> do
515− (f, sch) <- case IOHM.lookup k _schemaProperties of
516− Just rs -> do
517− let trs = Traced (t `Snoc` PropertiesStep k) rs
518− (,trs) <$> processRefSchema trs
519− Nothing -> pure (addProps, fromMaybe (Traced (t `Snoc` AdditionalPropertiesStep) $ Inline mempty) addPropSchema)
540+ (f, psch) <- case IOHM.lookup k $ tracedProperties sch of
541+ Just rs -> (,rs) <$> processRefSchema rs
542+ Nothing -> let fakeSchema = traced (ask sch `Snoc` AdditionalPropertiesStep) $ Inline mempty
520543 -- The mempty here is incorrect, but if addPropSchema was Nothing, then
521544 -- addProps is bottom, and k is in _schemaRequired. We handle this situation
522545 -- below and short-circuit the entire Properties condition to bottom
523− pure (k, Property (k `elem` _schemaRequired) f sch)
546+ in pure (addProps, fromMaybe fakeSchema addPropSchema)
547+ pure (k, Property (k `elem` _schemaRequired) f psch)
524548 let
525549 allBottom f = getAll $ foldType $ \ty -> case ty f of
526550 BottomFormula -> All True
@@ -537,21 +561,21 @@ processSchema (Traced t Schema{..}) = do
537561 = top -- if all fields are optional and have trivial schemata
538562 | otherwise
539563 = top
540− { forObject = singletonFormula (t `Snoc` PropertiesFields) $ Properties propMap addProps addPropSchema }
564+ { forObject = singletonFormula PropertiesFields $ Properties propMap addProps addPropSchema }
541565
542566 maxPropertiesClause = case _schemaMaxProperties of
543567 Nothing -> top
544568 Just n -> top
545− { forObject = singletonFormula (t `Snoc` MaxPropertiesField) $ MaxProperties n }
569+ { forObject = singletonFormula MaxPropertiesField $ MaxProperties n }
546570
547571 minPropertiesClause = case _schemaMinProperties of
548572 Nothing -> top
549573 Just n -> top
550− { forObject = singletonFormula (t `Snoc` MinPropertiesField) $ MinProperties n }
574+ { forObject = singletonFormula MinPropertiesField $ MinProperties n }
551575
552576 nullableClause
553577 | Just True <- _schemaNullable = bottom
554− { forNull = singletonFormula (t `Snoc` NullableField) $ Exactly TNull }
578+ { forNull = singletonFormula NullableField $ Exactly TNull }
555579 | otherwise = bottom
556580
557581 pure $ nullableClause \/ meets (allClauses <>
@@ -561,7 +585,7 @@ processSchema (Traced t Schema{..}) = do
561585 , uniqueItemsClause, propertiesClause, maxPropertiesClause, minPropertiesClause])
562586{- TODO: ReadOnly/WriteOnly -}
563587
564−checkOneOfDisjoint :: [Referenced Schema] -> ProcessM Bool
588+checkOneOfDisjoint :: [Traced OpenApi (Referenced Schema)] -> ProcessM Bool
565589checkOneOfDisjoint = const $ pure True -- TODO
566590
567591schemaToFormula
@@ -575,7 +599,7 @@ checkFormulas
575599 => HList xs
576600 -> Trace OpenApi Schema
577601 -> ProdCons (ForeachType (JsonFormula OpenApi), T.TracePrefixTree SubtreeCheckIssue OpenApi)
578− -> CompatFormula Schema ()
602+ -> CompatFormula ()
579603checkFormulas env tr (ProdCons (fp, ep) (fc, ec)) =
580604 case T.toList ep ++ T.toList ec of
581605 issues@(_:_) -> F.for_ issues $ \(AnItem t (SubtreeCheckIssue e)) -> issueAtTrace t e
@@ -613,13 +637,13 @@ checkFormulas env tr (ProdCons (fp, ep) (fc, ec)) =
613637 (DNF pss, SingleConjunct cs) -> F.for_ pss $ \(Conjunct ps) -> do
614638 F.for_ cs $ checkImplication env ps -- avoid disjuntion if there's only one conjunct
615639 (DNF pss, DNF css) -> F.for_ pss $ \(Conjunct ps) -> do
616− anyOfM tr (SubtreeCheckIssue $ NoMatchingCondition $ SomeCondition . getTraced <$> ps)
640+ anyOfM tr (NoMatchingCondition $ SomeCondition <$> ps)
617641 [F.for_ cs $ checkImplication env ps | Conjunct cs <- S.toList css]
618642
619643checkContradiction
620644 :: Trace OpenApi Schema
621645 -> [Traced OpenApi (Condition t)]
622− -> CompatFormula s ()
646+ -> CompatFormula ()
623647checkContradiction tr _ = issueAtTrace tr NoContradiction -- TODO
624648
625649checkImplication
@@ -627,96 +651,93 @@ checkImplication
627651 => HList xs
628652 -> [Traced OpenApi (Condition t)]
629653 -> Traced OpenApi (Condition t)
630− -> CompatFormula s ()
631−checkImplication env prods (Traced t cons) = case findExactly prods of
654+ -> CompatFormula ()
655+checkImplication env prods cons = case findExactly prods of
632656 Just e
633− | all (satisfiesTyped e) (getTraced <$> prods) ->
634− if satisfiesTyped e cons then pure ()
635− else issueAtTrace t (EnumDoesntSatisfy e)
657+ | all (satisfiesTyped e) (extract <$> prods) ->
658+ if satisfiesTyped e $ extract cons then pure ()
659+ else issueAt cons (EnumDoesntSatisfy e)
636660 | otherwise -> pure () -- vacuously true
637− Nothing -> case cons of
661+ Nothing -> case extract cons of
638662 -- the above code didn't catch it, so there's no Exactly condition on the lhs
639− Exactly e -> issueAtTrace t (NoMatchingEnum e)
663+ Exactly e -> issueAt cons (NoMatchingEnum e)
640664 Maximum m -> case findRelevant min (\case Maximum m' -> Just m'; _ -> Nothing) prods of
641665 Just m' -> if m' <= m then pure ()
642− else issueAtTrace t (MatchingMaximumWeak m m')
643− Nothing -> issueAtTrace t (NoMatchingMaximum m)
666+ else issueAt cons (MatchingMaximumWeak m m')
667+ Nothing -> issueAt cons (NoMatchingMaximum m)
644668 Minimum m -> case findRelevant max (\case Minimum m' -> Just m'; _ -> Nothing) prods of
645669 Just m' -> if m' >= m then pure ()
646− else issueAtTrace t (MatchingMinimumWeak (coerce m) (coerce m'))
647− Nothing -> issueAtTrace t (NoMatchingMinimum (coerce m))
670+ else issueAt cons (MatchingMinimumWeak (coerce m) (coerce m'))
671+ Nothing -> issueAt cons (NoMatchingMinimum (coerce m))
648672 MultipleOf m -> case findRelevant lcmScientific (\case MultipleOf m' -> Just m'; _ -> Nothing) prods of
649673 Just m' -> if lcmScientific m m' == m' then pure ()
650− else issueAtTrace t (MatchingMultipleOfWeak m m')
651− Nothing -> issueAtTrace t (NoMatchingMultipleOf m)
652− NumberFormat f -> if any (\case NumberFormat f' -> f == f'; _ -> False) $ getTraced <$> prods
653− then pure () else issueAtTrace t (NoMatchingFormat f)
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)
654678 MaxLength m -> case findRelevant min (\case MaxLength m' -> Just m'; _ -> Nothing) prods of
655679 Just m' -> if m' <= m then pure ()
656− else issueAtTrace t (MatchingMaxLengthWeak m m')
657− Nothing -> issueAtTrace t (NoMatchingMaxLength m)
680+ else issueAt cons (MatchingMaxLengthWeak m m')
681+ Nothing -> issueAt cons (NoMatchingMaxLength m)
658682 MinLength m -> case findRelevant max (\case MinLength m' -> Just m'; _ -> Nothing) prods of
659683 Just m' -> if m' >= m then pure ()
660− else issueAtTrace t (MatchingMinLengthWeak m m')
661− Nothing -> issueAtTrace t (NoMatchingMinLength m)
662− Pattern p -> if any (\case Pattern p' -> p == p'; _ -> False) $ getTraced <$> prods
663− then pure () else issueAtTrace t (NoMatchingPattern p)
664− StringFormat f -> if any (\case StringFormat f' -> f == f'; _ -> False) $ getTraced <$> prods
665− then pure () else issueAtTrace t (NoMatchingFormat f)
666− Items _ (Traced t' cons') -> case findRelevant (<>) (\case Items _ rs -> Just (rs NE.:| []); _ -> Nothing) prods of
667− Just (rs NE.:| []) -> localTrace' (ProdCons (getTrace rs) t') $ checkCompatibility env $ ProdCons (getTraced rs) cons'
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)
690+ Items _ cons' -> case findRelevant (<>) (\case Items _ rs -> Just (rs NE.:| []); _ -> Nothing) prods of
691+ Just (rs NE.:| []) -> checkCompatibility env $ ProdCons rs cons'
668692 Just rs -> do
669− let sch = Inline mempty { _schemaAllOf = Just . NE.toList $ getTraced <$> rs }
670− localTrace' (pure t' {- TODO: what? -}) $ checkCompatibility env $ ProdCons sch cons'
671− Nothing -> issueAtTrace t NoMatchingItems
693+ 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
672696 MaxItems m -> case findRelevant min (\case MaxItems m' -> Just m'; _ -> Nothing) prods of
673697 Just m' -> if m' <= m then pure ()
674− else issueAtTrace t (MatchingMaxItemsWeak m m')
675− Nothing -> issueAtTrace t (NoMatchingMaxItems m)
698+ else issueAt cons (MatchingMaxItemsWeak m m')
699+ Nothing -> issueAt cons (NoMatchingMaxItems m)
676700 MinItems m -> case findRelevant max (\case MinItems m' -> Just m'; _ -> Nothing) prods of
677701 Just m' -> if m' >= m then pure ()
678− else issueAtTrace t (MatchingMinItemsWeak m m')
679− Nothing -> issueAtTrace t (NoMatchingMinItems m)
680− UniqueItems -> if any ((== UniqueItems) . getTraced) prods then pure ()
681− else issueAtTrace t NoMatchingUniqueItems
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
682706 Properties props _ madd -> case findRelevant (<>) (\case Properties props' _ madd' -> Just $ (props', madd') NE.:| []; _ -> Nothing) prods of
683707 Just ((props', madd') NE.:| []) -> do
684708 F.for_ (S.fromList $ M.keys props <> M.keys props') $ \k -> do
685− let
686− go sch' sch = let schs = ProdCons sch' sch
687− in localTrace' (getTrace <$> schs) $ checkCompatibility env $ getTraced <$> schs
709+ let go sch sch' = checkCompatibility env (ProdCons sch sch')
688710 case (M.lookup k props', madd', M.lookup k props, madd) of
689711 (Nothing, Nothing, _, _) -> pure () -- vacuously
690− (_, _, Nothing, Nothing) -> issueAtTrace t (UnexpectedProperty k)
712+ (_, _, Nothing, Nothing) -> issueAt cons (UnexpectedProperty k)
691713 (Just p', _, Just p, _) -> go (propRefSchema p') (propRefSchema p)
692714 (Nothing, Just add', Just p, _) -> go add' (propRefSchema p)
693715 (Just p', _, Nothing, Just add) -> go (propRefSchema p') add
694716 (Nothing, Just _, Nothing, Just _) -> pure ()
695717 case (maybe False propRequired $ M.lookup k props', maybe False propRequired $ M.lookup k props) of
696− (False, True) -> issueAtTrace t (PropertyNowRequired k)
718+ (False, True) -> issueAt cons (PropertyNowRequired k)
697719 _ -> pure ()
698720 pure ()
699721 case (madd', madd) of
700722 (Nothing, _) -> pure () -- vacuously
701− (_, Nothing) -> issueAtTrace t NoAdditionalProperties
702− (Just add', Just add) -> let schs = ProdCons add' add
703− in localTrace' (getTrace <$> schs) $ checkCompatibility env $ getTraced <$> schs
723+ (_, Nothing) -> issueAt cons NoAdditionalProperties
724+ (Just add', Just add) -> checkCompatibility env (ProdCons add' add)
704725 pure ()
705− Nothing -> issueAtTrace t NoMatchingProperties
726+ Nothing -> issueAt cons NoMatchingProperties
706727 MaxProperties m -> case findRelevant min (\case MaxProperties m' -> Just m'; _ -> Nothing) prods of
707728 Just m' -> if m' <= m then pure ()
708− else issueAtTrace t (MatchingMaxPropertiesWeak m m')
709− Nothing -> issueAtTrace t (NoMatchingMaxProperties m)
729+ else issueAt cons (MatchingMaxPropertiesWeak m m')
730+ Nothing -> issueAt cons (NoMatchingMaxProperties m)
710731 MinProperties m -> case findRelevant max (\case MinProperties m' -> Just m'; _ -> Nothing) prods of
711732 Just m' -> if m' >= m then pure ()
712− else issueAtTrace t (MatchingMinPropertiesWeak m m')
713− Nothing -> issueAtTrace t (NoMatchingMinProperties m)
733+ else issueAt cons (MatchingMinPropertiesWeak m m')
734+ Nothing -> issueAt cons (NoMatchingMinProperties m)
714735 where
715− findExactly (Traced _ (Exactly x):_) = Just x
736+ findExactly ((extract -> Exactly x):_) = Just x
716737 findExactly (_:xs) = findExactly xs
717738 findExactly [] = Nothing
718− findRelevant combine extract
719− = fmap (foldr1 combine) . NE.nonEmpty . mapMaybe (extract . getTraced)
739+ findRelevant combine extr
740+ = fmap (foldr1 combine) . NE.nonEmpty . mapMaybe (extr . extract)
720741 lcmScientific (toRational -> a) (toRational -> b)
721742 = fromRational $ lcm (numerator a) (numerator b) % gcd (denominator a) (denominator b)
722743
@@ -753,9 +774,7 @@ instance Typeable t => Subtree (Condition t) where
753774 deriving stock (Eq, Ord, Show)
754775 type CheckEnv (Condition t) = CheckEnv Schema
755776 normalizeTrace = undefined
756− checkCompatibility env conds = withTrace $ \traces -> do
757− case Traced <$> traces <*> conds of
758− ProdCons prod cons -> checkImplication env [prod] cons
777+ checkCompatibility env pc = checkImplication env [producer pc] (consumer pc)
759778
760779instance Subtree Schema where
761780 data CheckIssue Schema
@@ -765,18 +784,16 @@ instance Subtree Schema where
765784 | NoContradiction
766785 deriving stock (Eq, Ord, Show)
767786 type CheckEnv Schema = '[ProdCons (Definitions Schema)]
768− checkCompatibility env schs = withTrace $ \traces -> do
787+ checkCompatibility env schs = do
769788 let defs = getH env
770− checkFormulas env (producer traces) $ schemaToFormula <$> defs <*> (Traced <$> traces <*> schs)
789+ checkFormulas env (ask $ producer schs) $ schemaToFormula <$> defs <*> schs
771790
772791instance Subtree (Referenced Schema) where
773792 data CheckIssue (Referenced Schema)
774793 deriving stock (Eq, Ord, Show)
775794 type CheckEnv (Referenced Schema) = CheckEnv Schema
776− checkCompatibility env refs = withTrace $ \traces -> do
795+ checkCompatibility env refs = do
777796 let
778797 defs = getH env
779798 schs = dereference <$> defs <*> refs
780− schs' = retrace <$> traces <*> schs
781− localTrace (getTrace <$> schs) $ do
782− checkFormulas env (producer $ getTrace <$> schs') $ schemaToFormula <$> defs <*> schs'
799+ checkFormulas env (ask $ producer schs) $ schemaToFormula <$> defs <*> schs
src/OpenAPI/Checker/Validate/SecurityRequirement.hs+3−3
@@ -1,9 +1,8 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
33module OpenAPI.Checker.Validate.SecurityRequirement
4− (
5− )
6−where
4+ ( CheckIssue (..)
5+ ) where
76
87import Data.OpenApi
98import OpenAPI.Checker.Subtree
@@ -14,5 +13,6 @@ instance Subtree SecurityRequirement where
1413 '[ ProdCons (Definitions SecurityScheme)
1514 ]
1615 data CheckIssue SecurityRequirement
16+ = SecurityRequirementNotMet
1717 deriving (Eq, Ord, Show)
1818 checkCompatibility = undefined
src/OpenAPI/Checker/Validate/Server.hs+3−3
@@ -1,9 +1,8 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
33module OpenAPI.Checker.Validate.Server
4− (
5− )
6−where
4+ ( CheckIssue(..)
5+ ) where
76
87import Data.OpenApi
98import OpenAPI.Checker.Subtree
@@ -11,5 +10,6 @@ import OpenAPI.Checker.Subtree
1110instance Subtree Server where
1211 type CheckEnv Server = '[]
1312 data CheckIssue Server
13+ = ServerNotConsumed
1414 deriving (Eq, Ord, Show)
1515 checkCompatibility = undefined
src/OpenAPI/Checker/Validate/Sums.hs+9−11
@@ -10,19 +10,17 @@ import OpenAPI.Checker.Trace
1010
1111
1212checkSums
13− :: forall k root t
14− . (Ord k, Subtree root)
15− => (k -> CheckIssue root)
16− -> (k -> ProdCons t -> CompatFormula t ())
17− -> ProdCons (Map k (Traced root t))
18− -> CompatFormula root ()
13+ :: forall k r t
14+ . (Ord k, Subtree t)
15+ => (k -> CheckIssue t)
16+ -> (k -> ProdCons (Traced r t) -> CompatFormula' SubtreeCheckIssue r ())
17+ -> ProdCons (Map k (Traced r t))
18+ -> CompatFormula' SubtreeCheckIssue r ()
1919checkSums noElt check (ProdCons p c) = for_ (M.toList p) $ \(key, prodElt) ->
2020 case M.lookup key c of
21− Nothing -> issueAt consumer $ noElt key
21+ Nothing -> issueAt prodElt $ noElt key
2222 Just consElt ->
2323 let
24− sumElts :: ProdCons (Traced root t)
24+ sumElts :: ProdCons (Traced r t)
2525 sumElts = ProdCons prodElt consElt
26− trace = getTrace <$> sumElts
27− elements = getTraced <$> sumElts
28− in localTrace trace $ check key elements
26+ in check key sumElts
test/Spec/Golden/TraceTree.hs+2−1
@@ -7,6 +7,7 @@ import Control.Category
77import Data.HList
88import qualified Data.Yaml as Yaml
99import OpenAPI.Checker.Subtree
10+import OpenAPI.Checker.Trace
1011import OpenAPI.Checker.Validate.OpenApi ()
1112import Spec.Golden.Extra
1213import Test.Tasty (TestTree)
@@ -20,4 +21,4 @@ tests =
2021 "trace-tree.yaml"
2122 ("a.yaml", "b.yaml")
2223 Yaml.decodeFileThrow
23− (runCompatFormula (pure id) . checkCompatibility HNil . uncurry ProdCons)
24+ (runCompatFormula . checkCompatibility HNil . fmap (traced Root) . uncurry ProdCons)
test/golden/common/maximum-lowered/trace-tree.yaml+1−1
@@ -1,7 +1,7 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationRequestBodyStep:
66 InlineStep:
77 RequestMediaTypeObject application/json:
test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/trace-tree.yaml+2−2
@@ -1,6 +1,6 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
5− OperationParamsStep:
4+ OperationMethodStep PostMethod:
5+ OperationParamsStep 0:
66 InlineStep: ParamEmptinessIncompatible
test/golden/common/pathItem/operation/parameters/change/trace-tree.yaml+2−2
@@ -1,8 +1,8 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
5− OperationParamsStep:
4+ OperationMethodStep PostMethod:
5+ OperationParamsStep 0:
66 InlineStep:
77 ParamSchema:
88 InlineStep: NoContradiction
test/golden/common/pathItem/operation/parameters/required/set/trace-tree.yaml+2−2
@@ -1,6 +1,6 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
5− OperationParamsStep:
4+ OperationMethodStep PostMethod:
5+ OperationParamsStep 0:
66 InlineStep: ParamRequired
test/golden/common/pathItem/operation/parameters/required/true/add/trace-tree.yaml+3−1
@@ -1,4 +1,6 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep: ParamNotMatched ParamQuery "test2"
4+ OperationMethodStep PostMethod:
5+ OperationParamsStep 1:
6+ InlineStep: ParamNotMatched "test2"
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/trace-tree.yaml+1−1
@@ -1,7 +1,7 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationRequestBodyStep:
66 InlineStep:
77 RequestMediaTypeObject application/json:
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/trace-tree.yaml+3−2
@@ -1,6 +1,7 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationRequestBodyStep:
6− InlineStep: RequestMediaTypeNotFound application/x-www-form-urlencoded
6+ InlineStep:
7+ RequestMediaTypeObject application/x-www-form-urlencoded: RequestMediaTypeNotFound
test/golden/common/pathItem/operation/requestBody/required/set/trace-tree.yaml+1−1
@@ -1,6 +1,6 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationRequestBodyStep:
66 InlineStep: RequestBodyRequired
test/golden/common/pathItem/operation/responses/add/trace-tree.yaml+4−2
@@ -1,5 +1,7 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
5− OperationResponsesStep: ResponseCodeNotFound 500
4+ OperationMethodStep PostMethod:
5+ OperationResponsesStep:
6+ ResponseCodeStep 500:
7+ InlineStep: ResponseCodeNotFound
test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/trace-tree.yaml+4−2
@@ -1,7 +1,9 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationResponsesStep:
66 ResponseCodeStep 200:
7− InlineStep: ResponseHeaderMissing "Test2"
7+ InlineStep:
8+ ResponseHeader "Test2":
9+ InlineStep: ResponseHeaderMissing
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/trace-tree.yaml+3−2
@@ -1,7 +1,8 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationResponsesStep:
66 ResponseCodeStep 200:
7− InlineStep: ResponseMediaTypeMissing application/x-www-form-urlencoded
7+ InlineStep:
8+ ResponseMediaObject application/x-www-form-urlencoded: ResponseMediaTypeMissing
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/trace-tree.yaml+1−1
@@ -1,7 +1,7 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationResponsesStep:
66 ResponseCodeStep 200:
77 InlineStep:
test/golden/common/property-removed/trace-tree.yaml+1−1
@@ -1,7 +1,7 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationResponsesStep:
66 ResponseCodeStep 200:
77 InlineStep:
test/golden/common/property-required/trace-tree.yaml+1−1
@@ -1,7 +1,7 @@
11Left:
22 OpenApiPathsStep:
33 MatchedPathStep "/test":
4− PostStep:
4+ OperationMethodStep PostMethod:
55 OperationRequestBodyStep:
66 InlineStep:
77 RequestMediaTypeObject application/json: