on master help sign in
Structural compatibility (#49)

* First very rough draft of structural compatibility * Horrible schema hack * absurdIssue -> structuralIssue * Added some structural comparing * Added yet more structural comparisons * dropped ProdConsEqHList * Bumped resolver * Nonrecursive schema

commitcbfeeedc1e89987b0ba89837000701b3f8d157f7 authoriko <ilyakooo0@gmail.com> dateMon 17 May 2021, 17:38 (5 years ago) parent8687e5f
21 files changed+601−248
openapi-diff.cabal+5 src/Data/HList.hs+6 src/OpenAPI/Checker/Behavior.hs+1 src/OpenAPI/Checker/Common.hs+10 src/OpenAPI/Checker/References.hs+25−4 src/OpenAPI/Checker/Subtree.hs+103−5 src/OpenAPI/Checker/Validate/Header.hs+57 src/OpenAPI/Checker/Validate/Link.hs+20 src/OpenAPI/Checker/Validate/MediaTypeObject.hs+27−6 src/OpenAPI/Checker/Validate/OpenApi.hs+12−1 src/OpenAPI/Checker/Validate/Operation.hs+135−101 src/OpenAPI/Checker/Validate/Param.hs+11−1 src/OpenAPI/Checker/Validate/PathFragment.hs+4−2 src/OpenAPI/Checker/Validate/ProcessedPathItem.hs+11−6 src/OpenAPI/Checker/Validate/RequestBody.hs+21−14 src/OpenAPI/Checker/Validate/Responses.hs+62−78 src/OpenAPI/Checker/Validate/Schema.hs+63−14 src/OpenAPI/Checker/Validate/SecurityRequirement.hs+4−2 src/OpenAPI/Checker/Validate/Server.hs+19−9 stack.yaml+1−1 stack.yaml.lock+4−4
openapi-diff.cabal+5
@@ -53,6 +53,7 @@ common common-options
5353 , unordered-containers
5454 , vector
5555 , yaml
56+ , hashable
5657
5758 default-extensions: ApplicativeDo
5859 , BangPatterns
@@ -93,6 +94,7 @@ common common-options
9394 , UndecidableInstances
9495 , ViewPatterns
9596 , QuantifiedConstraints
97+ , DefaultSignatures
9698
9799library
98100 import: common-options
@@ -121,6 +123,9 @@ library
121123 , OpenAPI.Checker.Validate.SecurityRequirement
122124 , OpenAPI.Checker.Validate.Server
123125 , OpenAPI.Checker.Validate.Sums
126+ , OpenAPI.Checker.Validate.Header
127+ , OpenAPI.Checker.Validate.Link
128+ , OpenAPI.Checker.Common
124129
125130executable openapi-diff
126131 import: common-options
src/Data/HList.hs+6
@@ -43,3 +43,9 @@ instance
4343
4444singletonH :: a -> HList '[a]
4545singletonH a = a `HCons` HNil
46+
47+instance Eq (HList '[]) where
48+ HNil == HNil = True
49+
50+instance (Eq x, Eq (HList xs)) => Eq (HList (x ': xs)) where
51+ (HCons x xs) == (HCons y ys) = x == y && xs == ys
src/OpenAPI/Checker/Behavior.hs+1
@@ -26,6 +26,7 @@ data BehaviorLevel
2626 PayloadLevel
2727 | SchemaLevel
2828 | TypedSchemaLevel
29+ | LinkLevel
2930
3031class (Ord (Behave a b), Show (Behave a b))
3132 => Behavable (a :: BehaviorLevel) (b :: BehaviorLevel) where
src/OpenAPI/Checker/Common.hsnew file+10
@@ -0,0 +1,10 @@
1+module OpenAPI.Checker.Common
2+ ( zipAll
3+ )
4+where
5+
6+zipAll :: [a] -> [b] -> Maybe [(a, b)]
7+zipAll [] [] = Just []
8+zipAll (x : xs) (y : ys) = ((x, y) :) <$> zipAll xs ys
9+zipAll (_ : _) [] = Nothing
10+zipAll [] (_ : _) = Nothing
src/OpenAPI/Checker/References.hs+25−4
@@ -1,13 +1,16 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
2+
23module OpenAPI.Checker.References
34 ( Step (..)
45 , dereference
56 )
67where
78
9+import Data.HList
810import qualified Data.HashMap.Strict.InsOrd as IOHM
911import Data.Maybe
1012import Data.OpenApi
13+import qualified Data.OpenApi.Schema.Generator as G
1114import Data.Typeable
1215import OpenAPI.Checker.Orphans ()
1316import OpenAPI.Checker.Subtree
@@ -26,7 +29,25 @@ dereference
2629 -> Traced (Referenced a)
2730 -> Traced a
2831dereference defs x = case extract x of
29− Inline a
30− -> traced (ask x >>> step InlineStep) a
31− Ref r@(Reference ref)
32− -> traced (ask defs >>> step (ReferencedStep r)) (fromJust $ IOHM.lookup ref $ extract defs)
32+ Inline a ->
33+ traced (ask x >>> step InlineStep) a
34+ Ref r@(Reference ref) ->
35+ traced (ask defs >>> step (ReferencedStep r)) (fromJust $ IOHM.lookup ref $ extract defs)
36+
37+instance Subtree a => Subtree (Referenced a) where
38+ type CheckEnv (Referenced a) = ProdCons (Traced (Definitions a)) ': CheckEnv a
39+ type SubtreeLevel (Referenced a) = SubtreeLevel a
40+
41+ checkStructuralCompatibility env pc' = do
42+ let pc = do
43+ x <- pc'
44+ defs <- extract <$> getH @(ProdCons (Traced (Definitions a))) env
45+ pure (G.dereference defs x)
46+ checkStructuralCompatibility env pc
47+
48+ checkSemanticCompatibility env bhv pc' = do
49+ let pc = do
50+ x <- pc'
51+ defs <- getH @(ProdCons (Traced (Definitions a))) env
52+ pure (dereference defs x)
53+ checkCompatibility env bhv pc
src/OpenAPI/Checker/Subtree.hs+103−5
@@ -1,3 +1,5 @@
1+{-# OPTIONS_GHC -Wno-orphans #-}
2+
13module OpenAPI.Checker.Subtree
24 ( Steppable (..)
35 , Trace
@@ -6,17 +8,27 @@ module OpenAPI.Checker.Subtree
68 , pattern Traced
79 , traced
810 , Subtree (..)
11+ , checkCompatibility
912 , CompatM (..)
1013 , CompatFormula'
11− , CompatFormula
14+ , SemanticCompatFormula
1215 , ProdCons (..)
1316 , HasUnsupportedFeature (..)
1417 , swapProdCons
1518 , runCompatFormula
1619 , issueAt
1720 , anyOfAt
21+ , structuralIssue
1822 , memo
1923
24+ -- * Structural helpers
25+ , structuralMaybe
26+ , structuralMaybeWith
27+ , structuralEq
28+ , iohmStructural
29+ , iohmStructuralWith
30+ , structuralList
31+
2032 -- * Reexports
2133 , (>>>)
2234 , (<<<)
@@ -30,11 +42,15 @@ where
3042import Control.Comonad.Env
3143import Control.Monad.Identity
3244import Control.Monad.State
45+import Data.Foldable
3346import Data.Functor.Compose
3447import Data.HList
48+import qualified Data.HashMap.Strict.InsOrd as IOHM
49+import Data.Hashable
3550import Data.Kind
3651import Data.Monoid
3752import Data.OpenApi
53+import qualified Data.Set as S
3854import Data.Typeable
3955import OpenAPI.Checker.Behavior
4056import OpenAPI.Checker.Formula
@@ -89,18 +105,97 @@ newtype CompatM a = CompatM
89105
90106type CompatFormula' q f r = Compose CompatM (FormulaF q f r)
91107
92−type CompatFormula = CompatFormula' Behave AnIssue 'APILevel
108+type SemanticCompatFormula = CompatFormula' Behave AnIssue 'APILevel
109+
110+type StructuralCompatFormula = CompatFormula' UnitQuiver Proxy ()
111+
112+data UnitQuiver a b where
113+ UnitQuiver :: UnitQuiver () ()
114+
115+deriving stock instance Eq (UnitQuiver a b)
116+
117+deriving stock instance Ord (UnitQuiver a b)
118+
119+deriving stock instance Show (UnitQuiver a b)
93120
94121class (Typeable t, Issuable (SubtreeLevel t)) => Subtree (t :: Type) where
95122 type CheckEnv t :: [Type]
96123 type SubtreeLevel t :: BehaviorLevel
97124
98− checkCompatibility
99− :: HasAll (CheckEnv t) xs
125+ checkStructuralCompatibility
126+ :: (HasAll (CheckEnv t) xs)
127+ => HList xs
128+ -> ProdCons t
129+ -> StructuralCompatFormula ()
130+
131+ checkSemanticCompatibility
132+ :: (HasAll (CheckEnv t) xs)
100133 => HList xs
101134 -> Behavior (SubtreeLevel t)
102135 -> ProdCons (Traced t)
103− -> CompatFormula ()
136+ -> SemanticCompatFormula ()
137+
138+checkCompatibility
139+ :: (HasAll (CheckEnv t) xs, Subtree t)
140+ => HList xs
141+ -> Behavior (SubtreeLevel t)
142+ -> ProdCons (Traced t)
143+ -> SemanticCompatFormula ()
144+checkCompatibility e bhv pc =
145+ case runCompatFormula $ checkStructuralCompatibility e $ fmap extract pc of
146+ Left _ -> checkSemanticCompatibility e bhv pc
147+ Right () -> pure ()
148+
149+structuralMaybe
150+ :: (Subtree a, HasAll (CheckEnv a) xs)
151+ => HList xs
152+ -> ProdCons (Maybe a)
153+ -> StructuralCompatFormula ()
154+structuralMaybe e = structuralMaybeWith (checkStructuralCompatibility e)
155+
156+structuralMaybeWith
157+ :: (ProdCons a -> StructuralCompatFormula ())
158+ -> ProdCons (Maybe a)
159+ -> StructuralCompatFormula ()
160+structuralMaybeWith f (ProdCons (Just a) (Just b)) = f $ ProdCons a b
161+structuralMaybeWith _ (ProdCons Nothing Nothing) = pure ()
162+structuralMaybeWith _ _ = structuralIssue
163+
164+structuralList
165+ :: (Subtree a, HasAll (CheckEnv a) xs)
166+ => HList xs -> ProdCons [a] -> StructuralCompatFormula ()
167+structuralList _ (ProdCons [] []) = pure ()
168+structuralList e (ProdCons (a:aa) (b:bb)) = do
169+ checkStructuralCompatibility e $ ProdCons a b
170+ structuralList e $ ProdCons aa bb
171+ pure ()
172+structuralList _ _ = structuralIssue
173+
174+structuralEq :: Eq a => ProdCons a -> StructuralCompatFormula ()
175+structuralEq (ProdCons a b) = if a == b then pure () else structuralIssue
176+
177+iohmStructural
178+ :: (HasAll (CheckEnv v) (k ': xs), Ord k, Subtree v, Hashable k)
179+ => HList xs
180+ -> ProdCons (IOHM.InsOrdHashMap k v)
181+ -> StructuralCompatFormula ()
182+iohmStructural e =
183+ iohmStructuralWith (\k -> checkStructuralCompatibility (k `HCons` e))
184+
185+iohmStructuralWith
186+ :: (Ord k, Hashable k)
187+ => (k -> ProdCons v -> StructuralCompatFormula ())
188+ -> ProdCons (IOHM.InsOrdHashMap k v)
189+ -> StructuralCompatFormula ()
190+iohmStructuralWith f pc = do
191+ let ProdCons pEKeys cEKeys = S.fromList . IOHM.keys <$> pc
192+ if pEKeys == cEKeys
193+ then
194+ for_
195+ pEKeys
196+ (\eKey ->
197+ f eKey $ IOHM.lookupDefault (error "impossible") eKey <$> pc)
198+ else structuralIssue
104199
105200class HasUnsupportedFeature x where
106201 hasUnsupportedFeature :: x -> Bool
@@ -137,6 +232,9 @@ runCompatFormula (Compose f) =
137232issueAt :: Issuable l => Paths q r l -> Issue l -> CompatFormula' q AnIssue r a
138233issueAt xs issue = Compose $ pure $ anError $ AnItem xs $ AnIssue issue
139234
235+structuralIssue :: StructuralCompatFormula a
236+structuralIssue = Compose $ pure $ anError $ AnItem (step UnitQuiver) Proxy
237+
140238anyOfAt
141239 :: Issuable l
142240 => Paths q r l
src/OpenAPI/Checker/Validate/Header.hsnew file+57
@@ -0,0 +1,57 @@
1+{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+module OpenAPI.Checker.Validate.Header
4+ (
5+ )
6+where
7+
8+import Data.Foldable
9+import Data.Functor
10+import Data.Maybe
11+import Data.OpenApi
12+import OpenAPI.Checker.Behavior
13+import OpenAPI.Checker.References ()
14+import OpenAPI.Checker.Subtree
15+import OpenAPI.Checker.Validate.Schema ()
16+
17+instance Subtree Header where
18+ type SubtreeLevel Header = 'HeaderLevel
19+ type CheckEnv Header = '[ProdCons (Traced (Definitions Schema))]
20+ checkStructuralCompatibility env pc = do
21+ structuralEq $ _headerRequired <$> pc
22+ structuralEq $ _headerAllowEmptyValue <$> pc
23+ structuralEq $ _headerExplode <$> pc
24+ structuralMaybe env $ _headerSchema <$> pc
25+ pure ()
26+ checkSemanticCompatibility env beh (ProdCons p c) = do
27+ if (fromMaybe False $ _headerRequired $ extract c) && not (fromMaybe False $ _headerRequired $ extract p)
28+ then issueAt beh RequiredHeaderMissing
29+ else pure ()
30+ if not (fromMaybe False $ _headerAllowEmptyValue $ extract c) && (fromMaybe False $ _headerAllowEmptyValue $ extract p)
31+ then issueAt beh NonEmptyHeaderRequired
32+ else pure ()
33+ for_ (tracedSchema c) $ \consRef ->
34+ case tracedSchema p of
35+ Nothing -> issueAt beh HeaderSchemaRequired
36+ Just prodRef -> checkCompatibility env (beh >>> step InSchema) (ProdCons prodRef consRef)
37+ pure ()
38+
39+instance Steppable Header (Referenced Schema) where
40+ data Step Header (Referenced Schema) = HeaderSchema
41+ deriving stock (Eq, Ord, Show)
42+
43+tracedSchema :: Traced Header -> Maybe (Traced (Referenced Schema))
44+tracedSchema hdr = _headerSchema (extract hdr) <&> traced (ask hdr >>> step HeaderSchema)
45+
46+instance Issuable 'HeaderLevel where
47+ data Issue 'HeaderLevel
48+ = RequiredHeaderMissing
49+ | NonEmptyHeaderRequired
50+ | HeaderSchemaRequired
51+ deriving stock (Eq, Ord, Show)
52+ issueIsUnsupported _ = False
53+
54+instance Behavable 'HeaderLevel 'SchemaLevel where
55+ data Behave 'HeaderLevel 'SchemaLevel
56+ = InSchema
57+ deriving stock (Eq, Ord, Show)
src/OpenAPI/Checker/Validate/Link.hsnew file+20
@@ -0,0 +1,20 @@
1+{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+module OpenAPI.Checker.Validate.Link () where
4+
5+import Data.OpenApi
6+import OpenAPI.Checker.Behavior
7+import OpenAPI.Checker.Subtree
8+
9+instance Subtree Link where
10+ type SubtreeLevel Link = 'LinkLevel
11+ type CheckEnv Link = '[]
12+ checkStructuralCompatibility _ _ = structuralIssue
13+ checkSemanticCompatibility _ bhv _ = issueAt bhv LinksUnsupported
14+
15+instance Issuable 'LinkLevel where
16+ data Issue 'LinkLevel
17+ = LinksUnsupported
18+ deriving (Eq, Ord, Show)
19+ issueIsUnsupported = \case
20+ LinksUnsupported -> True
src/OpenAPI/Checker/Validate/MediaTypeObject.hs+27−6
@@ -17,6 +17,8 @@ import OpenAPI.Checker.Behavior
1717import OpenAPI.Checker.Subtree
1818import OpenAPI.Checker.Validate.Products
1919import OpenAPI.Checker.Validate.Schema ()
20+import OpenAPI.Checker.Validate.Header ()
21+
2022
2123tracedSchema :: Traced MediaTypeObject -> Maybe (Traced (Referenced Schema))
2224tracedSchema mto = _mediaTypeObjectSchema (extract mto) <&> traced (ask mto >>> step MediaTypeSchema)
@@ -47,8 +49,14 @@ instance Subtree MediaTypeObject where
4749 type CheckEnv MediaTypeObject =
4850 '[ MediaType
4951 , ProdCons (Traced (Definitions Schema))
52+ , ProdCons (Traced (Definitions Header))
5053 ]
51− checkCompatibility env beh prodCons@(ProdCons p c) = do
54+ checkStructuralCompatibility env pc = do
55+ structuralMaybe env $ _mediaTypeObjectSchema <$> pc
56+ structuralEq $ _mediaTypeObjectExample <$> pc
57+ iohmStructural env $ _mediaTypeObjectEncoding <$> pc
58+ pure ()
59+ checkSemanticCompatibility env beh prodCons@(ProdCons p c) = do
5260 if | "multipart" == mainType mediaType -> checkEncoding
5361 | "application" == mainType mediaType &&
5462 "x-www-form-urlencoded" == subType mediaType -> checkEncoding
@@ -74,14 +82,27 @@ instance Subtree MediaTypeObject where
7482 , required = True } )
7583 encProdCons = getEncoding <$> prodCons
7684 in checkProducts beh MediaEncodingMissing
77− (const $ checkCompatibility HNil beh) encProdCons
85+ (const $ checkCompatibility env beh) encProdCons
7886
7987instance Subtree Encoding where
8088 type SubtreeLevel Encoding = 'PayloadLevel
81− type CheckEnv Encoding = '[]
82− -- FIXME: Support only JSON body for now. Then Encoding is checked only for
83− -- multipart/form-url-encoded
84− checkCompatibility _env beh _pc =
89+ type
90+ CheckEnv Encoding =
91+ '[ ProdCons (Traced (Definitions Header))
92+ , ProdCons (Traced (Definitions Schema))
93+ ]
94+ checkStructuralCompatibility env pc = do
95+ structuralEq $ _encodingContentType <$> pc
96+ iohmStructural env $ _encodingHeaders <$> pc
97+ structuralEq $ _encodingStyle <$> pc
98+ structuralEq $ _encodingExplode <$> pc
99+ structuralEq $ _encodingAllowReserved <$> pc
100+ pure ()
101+
102+ -- FIXME: Support only JSON body for now. Then Encoding is checked only for
103+ -- multipart/form-url-encoded
104+ -- https://github.com/typeable/openapi-diff/issues/54
105+ checkSemanticCompatibility _env beh _pc =
85106 issueAt beh EncodingNotSupported
86107
87108instance Steppable MediaTypeObject (Referenced Schema) where
src/OpenAPI/Checker/Validate/OpenApi.hs+12−1
@@ -43,10 +43,16 @@ tracedSchemas :: Traced OpenApi -> Traced (Definitions Schema)
4343tracedSchemas oa = traced (ask oa >>> step ComponentsSchema)
4444 (_componentsSchemas . _openApiComponents . extract $ oa)
4545
46+tracedLinks :: Traced OpenApi -> Traced (Definitions Link)
47+tracedLinks oa = traced (ask oa >>> step ComponentsLink)
48+ (_componentsLinks . _openApiComponents . extract $ oa)
49+
4650instance Subtree OpenApi where
4751 type SubtreeLevel OpenApi = 'APILevel
4852 type CheckEnv OpenApi = '[]
49− checkCompatibility _ beh prodCons = do
53+ -- There is no real reason to do a proper implementation
54+ checkStructuralCompatibility _ _ = structuralIssue
55+ checkSemanticCompatibility _ beh prodCons = do
5056 checkCompatibility @ProcessedPathItems
5157 ((tracedRequestBodies <$> prodCons)
5258 `HCons` (tracedParameters <$> prodCons)
@@ -55,6 +61,7 @@ instance Subtree OpenApi where
5561 `HCons` (tracedHeaders <$> prodCons)
5662 `HCons` (tracedSchemas <$> prodCons)
5763 `HCons` (_openApiServers . extract <$> prodCons)
64+ `HCons` (tracedLinks <$> prodCons)
5865 `HCons` HNil)
5966 beh (tracedPaths <$> prodCons)
6067
@@ -85,3 +92,7 @@ instance Steppable OpenApi (Definitions Header) where
8592instance Steppable OpenApi (Definitions Schema) where
8693 data Step OpenApi (Definitions Schema) = ComponentsSchema
8794 deriving (Eq, Ord, Show)
95+
96+instance Steppable OpenApi (Definitions Link) where
97+ data Step OpenApi (Definitions Link) = ComponentsLink
98+ deriving (Eq, Ord, Show)
src/OpenAPI/Checker/Validate/Operation.hs+135−101
@@ -1,12 +1,12 @@
1−{-# OPTIONS_GHC -Wno-orphans #-}
21{-# OPTIONS_GHC -Wno-name-shadowing #-}
2+{-# OPTIONS_GHC -Wno-orphans #-}
33
44module OpenAPI.Checker.Validate.Operation
55 ( MatchedOperation (..)
6− , OperationMethod(..)
6+ , OperationMethod (..)
77 , pathItemMethod
8− ) where
9−
8+ )
9+where
1010
1111import Data.Foldable as F
1212import Data.Functor
@@ -15,8 +15,10 @@ import qualified Data.List as L
1515import Data.Map.Strict as M
1616import Data.Maybe
1717import Data.OpenApi
18+import qualified Data.OpenApi.Schema.Generator as G
1819import Data.Text (Text)
1920import OpenAPI.Checker.Behavior
21+import OpenAPI.Checker.Common
2022import OpenAPI.Checker.References
2123import OpenAPI.Checker.Subtree
2224import OpenAPI.Checker.Validate.MediaTypeObject
@@ -28,11 +30,11 @@ import OpenAPI.Checker.Validate.Server ()
2830
2931data MatchedOperation = MatchedOperation
3032 { operation :: !Operation
31− , pathParams :: ![Traced Param]
32− -- ^ Params from the PathItem
33− , getPathFragments :: !([Traced Param] -> [Traced PathFragmentParam])
34− -- ^ Path fragments traced from PathItem. Takes full list of
35− -- operation-specific parameters
33+ , -- | Params from the PathItem
34+ pathParams :: ![Traced Param]
35+ , -- | Path fragments traced from PathItem. Takes full list of
36+ -- operation-specific parameters
37+ getPathFragments :: !([Traced Param] -> [Traced PathFragmentParam])
3638 }
3739
3840type ParamKey = (ParamLocation, Text)
@@ -43,32 +45,32 @@ paramKey param = (_paramIn param, _paramName param)
4345tracedParameters :: Traced MatchedOperation -> [Traced (Referenced Param)]
4446tracedParameters oper =
4547 [ traced (ask oper >>> step (OperationParamsStep i)) x
46− | (i, x) <- zip [0..] $ _operationParameters . operation $ extract oper
48+ | (i, x) <- zip [0 ..] $ _operationParameters . operation $ extract oper
4749 ]
4850
4951tracedRequestBody :: Traced MatchedOperation -> Maybe (Traced (Referenced RequestBody))
5052tracedRequestBody oper = _operationRequestBody (operation $ extract oper) <&> traced (ask oper >>> step OperationRequestBodyStep)
5153
5254tracedResponses :: Traced MatchedOperation -> Traced Responses
53−tracedResponses oper = traced (ask oper >>> step OperationResponsesStep)
54− $ _operationResponses . operation $ extract oper
55+tracedResponses oper =
56+ traced (ask oper >>> step OperationResponsesStep) $
57+ _operationResponses . operation $ extract oper
5558
5659tracedSecurity :: Traced MatchedOperation -> [Traced SecurityRequirement]
5760tracedSecurity oper =
5861 [ traced (ask oper >>> step (OperationSecurityRequirementStep i)) x
59− | (i, x) <- zip [0..] $ _operationSecurity . operation $ extract oper
62+ | (i, x) <- zip [0 ..] $ _operationSecurity . operation $ extract oper
6063 ]
6164
6265-- FIXME: https://github.com/typeable/openapi-diff/issues/28
63−tracedServers
66+getServers
6467 :: [Server] -- ^ Servers from env
65− -> Traced MatchedOperation
66− -> Traced [Server]
67−tracedServers env oper =
68− traced (ask oper >>> step OperationServersStep) $
69− case _operationServers . operation $ extract oper of
70− [] -> env
71− ss -> ss
68+ -> MatchedOperation
69+ -> [Server]
70+getServers env oper =
71+ case _operationServers . operation $ oper of
72+ [] -> env
73+ ss -> ss
7274
7375instance Behavable 'OperationLevel 'PathFragmentLevel where
7476 data Behave 'OperationLevel 'PathFragmentLevel
@@ -83,16 +85,42 @@ instance Behavable 'OperationLevel 'RequestLevel where
8385
8486instance Subtree MatchedOperation where
8587 type SubtreeLevel MatchedOperation = 'OperationLevel
86− type CheckEnv MatchedOperation =
87− '[ ProdCons (Traced (Definitions Param))
88− , ProdCons (Traced (Definitions RequestBody))
89− , ProdCons (Traced (Definitions SecurityScheme))
90− , ProdCons (Traced (Definitions Response))
91− , ProdCons (Traced (Definitions Header))
92− , ProdCons (Traced (Definitions Schema))
93− , ProdCons [Server]
94− ]
95− checkCompatibility env beh prodCons = do
88+ type
89+ CheckEnv MatchedOperation =
90+ '[ ProdCons (Traced (Definitions Param))
91+ , ProdCons (Traced (Definitions RequestBody))
92+ , ProdCons (Traced (Definitions SecurityScheme))
93+ , ProdCons (Traced (Definitions Response))
94+ , ProdCons (Traced (Definitions Header))
95+ , ProdCons (Traced (Definitions Schema))
96+ , ProdCons [Server]
97+ , ProdCons (Traced (Definitions Link))
98+ ]
99+ checkStructuralCompatibility env pc = do
100+ let pParams :: ProdCons [Param]
101+ pParams = do
102+ defs <- extract <$> getH @(ProdCons (Traced (Definitions Param))) env
103+ op' <- _operationParameters . operation <$> pc
104+ pp <- fmap extract . pathParams <$> pc
105+ pure $
106+ let o = M.fromList $ do
107+ param <- G.dereference defs <$> op'
108+ let key = paramKey param
109+ pure (key, param)
110+ p = M.fromList $ do
111+ param <- pp
112+ pure (paramKey param, param)
113+ in M.elems $ o <> p
114+ case zipAll (producer pParams) (consumer pParams) of
115+ Nothing -> structuralIssue
116+ Just xs -> for_ xs $ \(p, c) -> checkStructuralCompatibility env $ ProdCons p c
117+ structuralMaybe env $ _operationRequestBody . operation <$> pc
118+ checkStructuralCompatibility env $ _operationResponses . operation <$> pc
119+ checkStructuralCompatibility env $ getServers <$> getH env <*> pc
120+ -- TODO: Callbacks
121+ -- TODO: Security
122+ pure ()
123+ checkSemanticCompatibility env beh prodCons = do
96124 checkParameters
97125 checkRequestBodies
98126 checkResponses
@@ -102,97 +130,103 @@ instance Subtree MatchedOperation where
102130 pure ()
103131 where
104132 checkParameters = do
105− let
106− -- Merged parameters got from Operation and PathItem in one
107− -- place. First element is path params, second is non-path params
108− tracedParams :: ProdCons ([Traced Param], [Traced Param])
109− tracedParams = getParams <$> paramDefs <*> prodCons
110− getParams defs mp =
111− let
112− operationParamsMap :: Map ParamKey (Traced Param)
113− operationParamsMap = M.fromList $ do
114− paramRef <- tracedParameters mp
115− let
116− param = dereference defs paramRef
117− key = paramKey . extract $ param
118− pure (key, param)
119− pathParamsMap :: Map ParamKey (Traced Param)
120− pathParamsMap = M.fromList $ do
121− param <- pathParams . extract $ mp
122− pure (paramKey . extract $ param, param)
123− params = M.elems $ M.union operationParamsMap pathParamsMap -- We prefer params from Operation
124− splitted = L.partition
125− (\p -> (_paramIn . extract $ p) == ParamPath) params
126− in splitted
133+ let -- Merged parameters got from Operation and PathItem in one
134+ -- place. First element is path params, second is non-path params
135+ tracedParams :: ProdCons ([Traced Param], [Traced Param])
136+ tracedParams = getParams <$> paramDefs <*> prodCons
137+ getParams defs mp =
138+ let operationParamsMap :: Map ParamKey (Traced Param)
139+ operationParamsMap = M.fromList $ do
140+ paramRef <- tracedParameters mp
141+ let param = dereference defs paramRef
142+ key = paramKey . extract $ param
143+ pure (key, param)
144+ pathParamsMap :: Map ParamKey (Traced Param)
145+ pathParamsMap = M.fromList $ do
146+ param <- pathParams . extract $ mp
147+ pure (paramKey . extract $ param, param)
148+ params = M.elems $ M.union operationParamsMap pathParamsMap -- We prefer params from Operation
149+ splitted =
150+ L.partition
151+ (\p -> (_paramIn . extract $ p) == ParamPath)
152+ params
153+ in splitted
127154 checkNonPathParams $ snd <$> tracedParams
128155 checkPathParams $ fst <$> tracedParams
129156 pure ()
130− checkNonPathParams :: ProdCons [Traced Param] -> CompatFormula ()
157+ checkNonPathParams :: ProdCons [Traced Param] -> SemanticCompatFormula ()
131158 checkNonPathParams params = do
132− let
133− elements = getEls <$> params
134− getEls params = M.fromList $ do
135− p <- params
136− let
137− k = (_paramIn . extract $ p, _paramName . extract $ p)
138− v = ProductLike
139− { productValue = p
140− , required = fromMaybe False . _paramRequired . extract $ p
141− }
142− pure (k, v)
143− check (_, name) param = do
144− checkCompatibility @Param (singletonH schemaDefs) (beh >>> step (InParam name)) param
159+ let elements = getEls <$> params
160+ getEls params = M.fromList $ do
161+ p <- params
162+ let k = (_paramIn . extract $ p, _paramName . extract $ p)
163+ v =
164+ ProductLike
165+ { productValue = p
166+ , required = fromMaybe False . _paramRequired . extract $ p
167+ }
168+ pure (k, v)
169+ check (_, name) param = do
170+ checkCompatibility @Param (singletonH schemaDefs) (beh >>> step (InParam name)) param
145171 checkProducts beh (ParamNotMatched . snd) check elements
146− checkPathParams :: ProdCons [Traced Param] -> CompatFormula ()
172+ checkPathParams :: ProdCons [Traced Param] -> SemanticCompatFormula ()
147173 checkPathParams pathParams = do
148− let
149− fragments :: ProdCons [Traced PathFragmentParam]
150− fragments = getFragments <$> pathParams <*> prodCons
151− getFragments params mop = getPathFragments (extract mop) params
152− -- Feed path parameters to the fragments getter
153− check idx frags = checkCompatibility @PathFragmentParam env (beh >>> step (InFragment idx)) frags
154− elements = fragments <&> \frags -> M.fromList $ zip [0 :: Int ..] $ do
155− frag <- frags
156− pure $ ProductLike
157− { productValue = frag
158− , required = True }
174+ let fragments :: ProdCons [Traced PathFragmentParam]
175+ fragments = getFragments <$> pathParams <*> prodCons
176+ getFragments params mop = getPathFragments (extract mop) params
177+ -- Feed path parameters to the fragments getter
178+ check idx frags = checkCompatibility @PathFragmentParam env (beh >>> step (InFragment idx)) frags
179+ elements =
180+ fragments <&> \frags -> M.fromList $
181+ zip [0 :: Int ..] $ do
182+ frag <- frags
183+ pure $
184+ ProductLike
185+ { productValue = frag
186+ , required = True
187+ }
159188 checkProducts beh PathFragmentNotMatched check elements
160189 checkRequestBodies = do
161− let
162− check reqBody = checkCompatibility @RequestBody env (beh >>> step InRequest) reqBody
163− elements = getReqBody <$> bodyDefs <*> prodCons
164− getReqBody bodyDef mop = M.fromList $ do
165− bodyRef <- F.toList . tracedRequestBody $ mop
166− let
167− body = dereference bodyDef bodyRef
168− -- Single element map
169− pure ((), ProductLike
170− { productValue = body
171− , required = fromMaybe False . _requestBodyRequired . extract $ body
172− })
190+ let check reqBody = checkCompatibility @RequestBody env (beh >>> step InRequest) reqBody
191+ elements = getReqBody <$> bodyDefs <*> prodCons
192+ getReqBody bodyDef mop = M.fromList $ do
193+ bodyRef <- F.toList . tracedRequestBody $ mop
194+ let body = dereference bodyDef bodyRef
195+ -- Single element map
196+ pure
197+ ( ()
198+ , ProductLike
199+ { productValue = body
200+ , required = fromMaybe False . _requestBodyRequired . extract $ body
201+ }
202+ )
173203 checkProducts beh (const NoRequestBody) (const check) elements
174204 checkResponses = do
175− let
176− respEnv = HCons (swapProdCons respDefs)
177− $ HCons (swapProdCons headerDefs)
178− $ HCons (swapProdCons schemaDefs) HNil
179− resps = tracedResponses <$> prodCons
205+ let respEnv =
206+ HCons (swapProdCons respDefs) $
207+ HCons (swapProdCons headerDefs) $
208+ HCons (swapProdCons schemaDefs) $
209+ HCons (swapProdCons linkDefs) HNil
210+ resps = tracedResponses <$> prodCons
180211 checkCompatibility respEnv beh $ swapProdCons resps
181212 -- FIXME: https://github.com/typeable/openapi-diff/issues/27
182213 checkCallbacks = pure () -- (error "FIXME: not implemented")
183214 -- FIXME: https://github.com/typeable/openapi-diff/issues/28
184215 checkOperationSecurity = pure () -- (error "FIXME: not implemented")
185216 checkServers =
186− checkCompatibility env beh $
187− tracedServers <$> getH @(ProdCons [Server]) env <*> prodCons
217+ checkCompatibility env beh $ do
218+ x <- prodCons
219+ se <- getH @(ProdCons [Server]) env
220+ pure $ Traced (ask x >>> step OperationServersStep) (getServers se (extract x))
188221 bodyDefs = getH @(ProdCons (Traced (Definitions RequestBody))) env
189222 respDefs = getH @(ProdCons (Traced (Definitions Response))) env
190− headerDefs = getH @(ProdCons (Traced (Definitions Header))) env
223+ headerDefs = getH @(ProdCons (Traced (Definitions Header))) env
191224 schemaDefs = getH @(ProdCons (Traced (Definitions Schema))) env
192225 paramDefs = getH @(ProdCons (Traced (Definitions Param))) env
226+ linkDefs = getH @(ProdCons (Traced (Definitions Link))) env
193227
194−data OperationMethod =
195− GetMethod
228+data OperationMethod
229+ = GetMethod
196230 | PutMethod
197231 | PostMethod
198232 | DeleteMethod
src/OpenAPI/Checker/Validate/Param.hs+11−1
@@ -71,7 +71,17 @@ instance Behavable 'PathFragmentLevel 'SchemaLevel where
7171instance Subtree Param where
7272 type SubtreeLevel Param = 'PathFragmentLevel
7373 type CheckEnv Param = '[ProdCons (Traced (Definitions Schema))]
74− checkCompatibility env beh pc@(ProdCons p c) = do
74+ checkStructuralCompatibility env pc = do
75+ structuralEq $ _paramName <$> pc
76+ structuralEq $ _paramRequired <$> pc
77+ structuralEq $ _paramIn <$> pc
78+ structuralEq $ _paramAllowEmptyValue <$> pc
79+ structuralEq $ _paramAllowReserved <$> pc
80+ structuralMaybe env $ _paramSchema <$> pc
81+ structuralEq $ _paramStyle <$> pc
82+ structuralEq $ _paramExplode <$> pc
83+ pure ()
84+ checkSemanticCompatibility env beh pc@(ProdCons p c) = do
7585 when (_paramName (extract p) /= _paramName (extract c))
7686 $ issueAt beh ParamNameMismatch
7787 when ((fromMaybe False . _paramRequired . extract $ c) &&
src/OpenAPI/Checker/Validate/PathFragment.hs+4−2
@@ -63,10 +63,12 @@ instance Subtree PathFragmentParam where
6363 type SubtreeLevel PathFragmentParam = 'PathFragmentLevel
6464 type CheckEnv PathFragmentParam =
6565 '[ ProdCons (Traced (Definitions Schema)) ]
66+ -- Not much to compare at this level
67+ checkStructuralCompatibility _ _ = structuralIssue
6668 -- This case isn't strictly needed. It is here for optimization.
67− checkCompatibility _ beh (ProdCons (extract -> StaticPath x) (extract -> StaticPath y))
69+ checkSemanticCompatibility _ beh (ProdCons (extract -> StaticPath x) (extract -> StaticPath y))
6870 = if x == y
6971 then pure ()
7072 else issueAt beh (PathFragmentsDontMatch x y)
71− checkCompatibility env beh prodCons = do
73+ checkSemanticCompatibility env beh prodCons = do
7274 checkCompatibility env beh (tracedPathFragmentParam <$> prodCons)
src/OpenAPI/Checker/Validate/ProcessedPathItem.hs+11−6
@@ -33,13 +33,14 @@ import OpenAPI.Checker.Validate.Sums
3333data ProcessedPathItem = ProcessedPathItem
3434 { path :: FilePath
3535 , item :: PathItem
36− }
36+ } deriving stock (Eq, Show)
3737
3838processPathItems :: [(FilePath, PathItem)] -> ProcessedPathItems
3939processPathItems = ProcessedPathItems . fmap (uncurry ProcessedPathItem)
4040
4141newtype ProcessedPathItems =
4242 ProcessedPathItems {unProcessedPathItems :: [ProcessedPathItem]}
43+ deriving newtype (Eq, Show)
4344
4445instance Issuable 'APILevel where
4546 data Issue 'APILevel
@@ -65,8 +66,11 @@ instance Subtree ProcessedPathItems where
6566 , ProdCons (Traced (Definitions Header))
6667 , ProdCons (Traced (Definitions Schema))
6768 , ProdCons [Server]
69+ , ProdCons (Traced (Definitions Link))
6870 ]
69− checkCompatibility env beh pc@(ProdCons p c) = do
71+ -- No real way to check it at this level
72+ checkStructuralCompatibility _ _ = structuralIssue
73+ checkSemanticCompatibility env beh pc@(ProdCons p c) = do
7074 -- Each path generated by producer must be handled by consumer with exactly
7175 -- one way
7276 for_ (unProcessedPathItems . extract $ p) $ \ prodItem -> do
@@ -74,8 +78,7 @@ instance Subtree ProcessedPathItems where
7478 prodPath = path prodItem
7579 matchedItems = do
7680 consItem <- unProcessedPathItems . extract $ c
77− matched <- F.toList $ matchingPathItems $ ProdCons prodItem consItem
78− return matched
81+ F.toList $ matchingPathItems $ ProdCons prodItem consItem
7982 case matchedItems of
8083 [] -> issueAt beh $ NoPathsMatched prodPath
8184 [match] -> checkCompatibility env (beh >>> step (AtPath $ matchedPath <$> match)) (retraced <$> pc <*> match)
@@ -116,7 +119,7 @@ data MatchedPathItem = MatchedPathItem
116119 , matchedPath :: !FilePath
117120 , pathFragments :: ![PathFragment Text]
118121 -- ^ Pre-parsed path from PathItem
119− }
122+ } deriving stock (Eq)
120123
121124tracedParameters :: Traced MatchedPathItem -> [Traced (Referenced Param)]
122125tracedParameters mpi =
@@ -158,8 +161,10 @@ instance Subtree MatchedPathItem where
158161 , ProdCons (Traced (Definitions Header))
159162 , ProdCons (Traced (Definitions Schema))
160163 , ProdCons [Server]
164+ , ProdCons (Traced (Definitions Link))
161165 ]
162− checkCompatibility env beh prodCons = do
166+ checkStructuralCompatibility _ _ = structuralIssue
167+ checkSemanticCompatibility env beh prodCons = do
163168 let
164169 paramDefs = getH @(ProdCons (Traced (Definitions Param))) env
165170 pathTracedParams = getPathParams <$> paramDefs <*> prodCons
src/OpenAPI/Checker/Validate/RequestBody.hs+21−14
@@ -17,8 +17,9 @@ import OpenAPI.Checker.Validate.MediaTypeObject
1717import OpenAPI.Checker.Validate.Sums
1818
1919tracedContent :: Traced RequestBody -> IOHM.InsOrdHashMap MediaType (Traced MediaTypeObject)
20−tracedContent resp = IOHM.mapWithKey (\k -> traced (ask resp >>> step (RequestMediaTypeObject k)))
21− $ _requestBodyContent $ extract resp
20+tracedContent resp =
21+ IOHM.mapWithKey (\k -> traced (ask resp >>> step (RequestMediaTypeObject k))) $
22+ _requestBodyContent $ extract resp
2223
2324instance Issuable 'RequestLevel where
2425 data Issue 'RequestLevel
@@ -34,19 +35,25 @@ instance Behavable 'RequestLevel 'PayloadLevel where
3435
3536instance Subtree RequestBody where
3637 type SubtreeLevel RequestBody = 'RequestLevel
37− type CheckEnv RequestBody =
38− '[ ProdCons (Traced (Definitions Schema)) ]
39− checkCompatibility env beh prodCons@(ProdCons p c) =
38+ type
39+ CheckEnv RequestBody =
40+ '[ ProdCons (Traced (Definitions Schema))
41+ , ProdCons (Traced (Definitions Header))
42+ ]
43+ checkStructuralCompatibility env pc = do
44+ structuralEq $ _requestBodyRequired <$> pc
45+ iohmStructural env $ _requestBodyContent <$> pc
46+ pure ()
47+ checkSemanticCompatibility env beh prodCons@(ProdCons p c) =
4048 if not (fromMaybe False . _requestBodyRequired . extract $ p)
41− && (fromMaybe False . _requestBodyRequired . extract $ c)
42− then issueAt beh RequestBodyRequired
43− else
44− -- Media type object are sums-like entities.
45− let
46− check mediaType pc = checkCompatibility @MediaTypeObject (HCons mediaType env) (beh >>> step InPayload) pc
47− sumElts = getSum <$> prodCons
48− getSum rb = M.fromList . IOHM.toList $ tracedContent rb
49− in checkSums beh RequestMediaTypeNotFound check sumElts
49+ && (fromMaybe False . _requestBodyRequired . extract $ c)
50+ then issueAt beh RequestBodyRequired
51+ else -- Media type object are sums-like entities.
52+
53+ let check mediaType pc = checkCompatibility @MediaTypeObject (HCons mediaType env) (beh >>> step InPayload) pc
54+ sumElts = getSum <$> prodCons
55+ getSum rb = M.fromList . IOHM.toList $ tracedContent rb
56+ in checkSums beh RequestMediaTypeNotFound check sumElts
5057
5158instance Steppable RequestBody MediaTypeObject where
5259 data Step RequestBody MediaTypeObject = RequestMediaTypeObject MediaType
src/OpenAPI/Checker/Validate/Responses.hs+62−78
@@ -6,8 +6,6 @@ module OpenAPI.Checker.Validate.Responses
66 )
77where
88
9−import Control.Lens
10−import Data.Foldable
119import Data.HList
1210import Data.HashMap.Strict.InsOrd as IOHM
1311import Data.Map.Strict as M
@@ -17,42 +15,54 @@ import Network.HTTP.Media (MediaType)
1715import OpenAPI.Checker.Behavior
1816import OpenAPI.Checker.References
1917import OpenAPI.Checker.Subtree
18+import OpenAPI.Checker.Validate.Header ()
19+import OpenAPI.Checker.Validate.Link ()
2020import OpenAPI.Checker.Validate.MediaTypeObject
2121import OpenAPI.Checker.Validate.Products
2222import OpenAPI.Checker.Validate.Schema ()
2323import OpenAPI.Checker.Validate.Sums
2424
2525tracedResponses :: Traced Responses -> IOHM.InsOrdHashMap HttpStatusCode (Traced (Referenced Response))
26−tracedResponses resp = IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseCodeStep k)))
27− $ _responsesResponses $ extract resp
26+tracedResponses resp =
27+ IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseCodeStep k))) $
28+ _responsesResponses $ extract resp
2829
2930instance Subtree Responses where
3031 type SubtreeLevel Responses = 'OperationLevel
31− type CheckEnv Responses =
32− '[ ProdCons (Traced (Definitions Response))
33− , ProdCons (Traced (Definitions Header))
34− , ProdCons (Traced (Definitions Schema))
35− ]
32+ type
33+ CheckEnv Responses =
34+ '[ ProdCons (Traced (Definitions Response))
35+ , ProdCons (Traced (Definitions Header))
36+ , ProdCons (Traced (Definitions Schema))
37+ , ProdCons (Traced (Definitions Link))
38+ ]
39+
40+ checkStructuralCompatibility env pc = do
41+ structuralMaybe env $ _responsesDefault <$> pc
42+ iohmStructural env $ _responsesResponses <$> pc
43+ pure ()
44+
3645 -- Roles are already swapped. Producer is a server and consumer is a
3746 -- client. Response codes are sum-like entity because we can answer with only
3847 -- one element
39− checkCompatibility env beh prodCons = do
40− let
41− defs = getH @(ProdCons (Traced (Definitions Response))) env
42− check code resps = checkCompatibility @Response env (beh >>> step (WithStatusCode code)) resps
43− elements = getEls <$> defs <*> prodCons
44− getEls respDef resps = M.fromList $ do
45− (code, respRef) <- IOHM.toList $ tracedResponses resps
46− pure (code, dereference respDef respRef)
48+ checkSemanticCompatibility env beh prodCons = do
49+ let defs = getH @(ProdCons (Traced (Definitions Response))) env
50+ check code resps = checkCompatibility @Response env (beh >>> step (WithStatusCode code)) resps
51+ elements = getEls <$> defs <*> prodCons
52+ getEls respDef resps = M.fromList $ do
53+ (code, respRef) <- IOHM.toList $ tracedResponses resps
54+ pure (code, dereference respDef respRef)
4755 checkSums beh ResponseCodeNotFound check elements
4856
4957tracedContent :: Traced Response -> IOHM.InsOrdHashMap MediaType (Traced MediaTypeObject)
50−tracedContent resp = IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseMediaObject k)))
51− $ _responseContent $ extract resp
58+tracedContent resp =
59+ IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseMediaObject k))) $
60+ _responseContent $ extract resp
5261
5362tracedHeaders :: Traced Response -> IOHM.InsOrdHashMap HeaderName (Traced (Referenced Header))
54−tracedHeaders resp = IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseHeader k)))
55− $ _responseHeaders $ extract resp
63+tracedHeaders resp =
64+ IOHM.mapWithKey (\k -> traced (ask resp >>> step (ResponseHeader k))) $
65+ _responseHeaders $ extract resp
5666
5767instance Issuable 'ResponseLevel where
5868 data Issue 'ResponseLevel
@@ -73,11 +83,18 @@ instance Behavable 'ResponseLevel 'HeaderLevel where
7383
7484instance Subtree Response where
7585 type SubtreeLevel Response = 'ResponseLevel
76− type CheckEnv Response =
77− '[ ProdCons (Traced (Definitions Header))
78− , ProdCons (Traced (Definitions Schema))
79− ]
80− checkCompatibility env beh prodCons = do
86+ type
87+ CheckEnv Response =
88+ '[ ProdCons (Traced (Definitions Header))
89+ , ProdCons (Traced (Definitions Schema))
90+ , ProdCons (Traced (Definitions Link))
91+ ]
92+ checkStructuralCompatibility env pc = do
93+ iohmStructural env $ _responseContent <$> pc
94+ iohmStructural env $ _responseHeaders <$> pc
95+ iohmStructural env $ _responseLinks <$> pc
96+ pure ()
97+ checkSemanticCompatibility env beh prodCons = do
8198 -- Roles are already swapped. Producer is a server and consumer is a client
8299 checkMediaTypes
83100 checkHeaders
@@ -85,71 +102,38 @@ instance Subtree Response where
85102 where
86103 checkMediaTypes = do
87104 -- Media types are sum-like entity
88− let
89− check mediaType mtObj =
90− let mtEnv = HCons mediaType $ env
91− in checkCompatibility @MediaTypeObject mtEnv (beh >>> step ResponsePayload) mtObj
92− elements = getEls <$> prodCons
93− getEls resp = M.fromList . IOHM.toList $ tracedContent resp
105+ let check mediaType mtObj =
106+ let mtEnv = HCons mediaType $ env
107+ in checkCompatibility @MediaTypeObject mtEnv (beh >>> step ResponsePayload) mtObj
108+ elements = getEls <$> prodCons
109+ getEls resp = M.fromList . IOHM.toList $ tracedContent resp
94110 checkSums beh ResponseMediaTypeMissing check elements
95111
96112 checkHeaders = do
97113 -- Headers are product-like entities
98− let
99− check name hdrs = checkCompatibility @Header env (beh >>> step (InHeader name)) hdrs
100− elements = getEls <$> headerDefs <*> prodCons
101− getEls headerDef resp = M.fromList $ do
102− (hname, headerRef) <- IOHM.toList $ tracedHeaders resp
103− let header = dereference headerDef headerRef
104− pure (hname, ProductLike
105− { productValue = header
106− , required = fromMaybe False . _headerRequired . extract $ header
107− })
114+ let check name hdrs = checkCompatibility @Header env (beh >>> step (InHeader name)) hdrs
115+ elements = getEls <$> headerDefs <*> prodCons
116+ getEls headerDef resp = M.fromList $ do
117+ (hname, headerRef) <- IOHM.toList $ tracedHeaders resp
118+ let header = dereference headerDef headerRef
119+ pure
120+ ( hname
121+ , ProductLike
122+ { productValue = header
123+ , required = fromMaybe False . _headerRequired . extract $ header
124+ }
125+ )
108126 checkProducts beh ResponseHeaderMissing check elements
109127 headerDefs = getH @(ProdCons (Traced (Definitions Header))) env
110128
111−tracedSchema :: Traced Header -> Maybe (Traced (Referenced Schema))
112−tracedSchema hdr = _headerSchema (extract hdr) <&> traced (ask hdr >>> step HeaderSchema)
113−
114−instance Issuable 'HeaderLevel where
115− data Issue 'HeaderLevel
116− = RequiredHeaderMissing
117− | NonEmptyHeaderRequired
118− | HeaderSchemaRequired
119− deriving stock (Eq, Ord, Show)
120− issueIsUnsupported _ = False
121−
122−instance Behavable 'HeaderLevel 'SchemaLevel where
123− data Behave 'HeaderLevel 'SchemaLevel
124− = InSchema
125− deriving stock (Eq, Ord, Show)
126−
127−instance Subtree Header where
128− type SubtreeLevel Header = 'HeaderLevel
129− type CheckEnv Header = '[ProdCons (Traced (Definitions Schema))]
130− checkCompatibility env beh (ProdCons p c) = do
131− if (fromMaybe False $ _headerRequired $ extract c) && not (fromMaybe False $ _headerRequired $ extract p)
132− then issueAt beh RequiredHeaderMissing else pure ()
133− if not (fromMaybe False $ _headerAllowEmptyValue $ extract c) && (fromMaybe False $ _headerAllowEmptyValue $ extract p)
134− then issueAt beh NonEmptyHeaderRequired else pure ()
135− for_ (tracedSchema c) $ \consRef ->
136− case tracedSchema p of
137− Nothing -> issueAt beh HeaderSchemaRequired
138− Just prodRef -> checkCompatibility env (beh >>> step InSchema) (ProdCons prodRef consRef)
139− pure ()
140−
141129instance Steppable Responses (Referenced Response) where
142130 data Step Responses (Referenced Response) = ResponseCodeStep HttpStatusCode
143131 deriving stock (Eq, Ord, Show)
144132
145−instance Steppable Header (Referenced Schema) where
146− data Step Header (Referenced Schema) = HeaderSchema
133+instance Steppable Response MediaTypeObject where
134+ data Step Response MediaTypeObject = ResponseMediaObject MediaType
147135 deriving stock (Eq, Ord, Show)
148136
149137instance Steppable Response (Referenced Header) where
150138 data Step Response (Referenced Header) = ResponseHeader HeaderName
151139 deriving stock (Eq, Ord, Show)
152−
153−instance Steppable Response MediaTypeObject where
154− data Step Response MediaTypeObject = ResponseMediaObject MediaType
155− deriving stock (Eq, Ord, Show)
src/OpenAPI/Checker/Validate/Schema.hs+63−14
@@ -38,6 +38,7 @@ import Data.Scientific
3838import qualified Data.Set as S
3939import Data.Text (Text)
4040import qualified Data.Text as T hiding (singleton)
41+import qualified Data.Text.Encoding as T
4142import Data.Typeable
4243import OpenAPI.Checker.Behavior
4344import OpenAPI.Checker.Orphans ()
@@ -45,6 +46,7 @@ import OpenAPI.Checker.References
4546import OpenAPI.Checker.Paths
4647import qualified OpenAPI.Checker.PathsPrefixTree as P
4748import OpenAPI.Checker.Subtree
49+import Debug.Trace
4850
4951-- | Type of a JSON value
5052data JsonType
@@ -598,11 +600,11 @@ schemaToFormula
598600schemaToFormula defs rs = runWriter . (`runReaderT` defs) $ processSchema rs
599601
600602checkFormulas
601− :: HasAll (CheckEnv Schema) xs
603+ :: (HasAll (CheckEnv Schema) xs)
602604 => HList xs
603605 -> Behavior 'SchemaLevel
604606 -> ProdCons (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel)
605− -> CompatFormula ()
607+ -> SemanticCompatFormula ()
606608checkFormulas env beh (ProdCons (fp, ep) (fc, ec)) =
607609 case P.toList ep ++ P.toList ec of
608610 issues@(_:_) -> F.for_ issues $ \(AnItem t (AnIssue e)) -> issueAt (beh >>> t) e
@@ -647,7 +649,7 @@ checkFormulas env beh (ProdCons (fp, ep) (fc, ec)) =
647649checkContradiction
648650 :: Behavior 'TypedSchemaLevel
649651 -> [Condition t]
650− -> CompatFormula ()
652+ -> SemanticCompatFormula ()
651653checkContradiction beh _ = issueAt beh NoContradiction -- TODO
652654
653655checkImplication
@@ -656,7 +658,7 @@ checkImplication
656658 -> Behavior 'TypedSchemaLevel
657659 -> [Condition t]
658660 -> Condition t
659− -> CompatFormula ()
661+ -> SemanticCompatFormula ()
660662checkImplication env beh prods cons = case findExactly prods of
661663 Just e
662664 | all (satisfiesTyped e) prods ->
@@ -803,15 +805,62 @@ instance Behavable 'TypedSchemaLevel 'SchemaLevel where
803805instance Subtree Schema where
804806 type SubtreeLevel Schema = 'SchemaLevel
805807 type CheckEnv Schema = '[ProdCons (Traced (Definitions Schema))]
806− checkCompatibility env beh schs = do
808+ checkStructuralCompatibility env pc = do
809+ traceShowM pc
810+ structuralEq $ _schemaRequired <$> pc
811+ structuralMaybeWith structuralEq $ _schemaNullable <$> pc
812+ structuralMaybeWith (structuralList env) $ _schemaAllOf <$> pc
813+ structuralMaybeWith (structuralList env) $ _schemaOneOf <$> pc
814+ structuralMaybe env $ _schemaNot <$> pc
815+ structuralMaybeWith (structuralList env) $ _schemaAnyOf <$> pc
816+ iohmStructural env $ _schemaProperties <$> pc
817+ structuralMaybeWith structuralAdditionalProperties $ _schemaAdditionalProperties <$> pc
818+ structuralMaybeWith structuralDiscriminator $ _schemaDiscriminator <$> pc
819+ structuralEq $ _schemaReadOnly <$> pc
820+ structuralEq $ _schemaWriteOnly <$> pc
821+ structuralEq $ _schemaXml <$> pc
822+ structuralEq $ _schemaMaxProperties <$> pc
823+ structuralEq $ _schemaMinProperties <$> pc
824+ structuralEq $ _schemaDefault <$> pc
825+ structuralEq $ _schemaType <$> pc
826+ structuralEq $ _schemaFormat <$> pc
827+ structuralMaybeWith structuralItems $ _schemaItems <$> pc
828+ structuralEq $ _schemaMaximum <$> pc
829+ structuralEq $ _schemaExclusiveMaximum <$> pc
830+ structuralEq $ _schemaMinimum <$> pc
831+ structuralEq $ _schemaExclusiveMinimum <$> pc
832+ structuralEq $ _schemaMaxLength <$> pc
833+ structuralEq $ _schemaMinLength <$> pc
834+ structuralEq $ _schemaPattern <$> pc
835+ structuralEq $ _schemaMaxItems <$> pc
836+ structuralEq $ _schemaMinItems <$> pc
837+ structuralEq $ _schemaUniqueItems <$> pc
838+ structuralEq $ _schemaEnum <$> pc
839+ structuralEq $ _schemaMultipleOf <$> pc
840+ pure ()
841+ where
842+ structuralAdditionalProperties
843+ (ProdCons (AdditionalPropertiesAllowed x) (AdditionalPropertiesAllowed y)) =
844+ structuralEq $ ProdCons x y
845+ structuralAdditionalProperties
846+ (ProdCons (AdditionalPropertiesSchema x) (AdditionalPropertiesSchema y)) =
847+ checkStructuralCompatibility env $ ProdCons x y
848+ structuralAdditionalProperties _ = structuralIssue
849+ structuralDiscriminator pc' = do
850+ structuralEq $ _discriminatorPropertyName <$> pc'
851+ iohmStructuralWith
852+ (\_ mappingPC -> case A.decodeStrict @(Referenced Schema) . T.encodeUtf8 <$> mappingPC of
853+ ProdCons (Just a) (Just b) -> checkStructuralCompatibility env $ ProdCons a b
854+ ProdCons Nothing Nothing -> structuralEq mappingPC
855+ _ -> structuralIssue
856+ )
857+ (_discriminatorMapping <$> pc')
858+ pure ()
859+ structuralItems (ProdCons (OpenApiItemsObject a) (OpenApiItemsObject b)) =
860+ checkStructuralCompatibility env $ ProdCons a b
861+ structuralItems (ProdCons (OpenApiItemsArray a) (OpenApiItemsArray b)) =
862+ structuralList env $ ProdCons a b
863+ structuralItems _ = structuralIssue
864+ checkSemanticCompatibility env beh schs = do
807865 let defs = getH env
808866 checkFormulas env beh $ schemaToFormula <$> defs <*> schs
809−
810−instance Subtree (Referenced Schema) where
811− type SubtreeLevel (Referenced Schema) = 'SchemaLevel
812− type CheckEnv (Referenced Schema) = CheckEnv Schema
813− checkCompatibility env beh refs = do
814− let
815− defs = getH env
816− schs = dereference <$> defs <*> refs
817− checkFormulas env beh $ schemaToFormula <$> defs <*> schs
src/OpenAPI/Checker/Validate/SecurityRequirement.hs+4−2
@@ -2,7 +2,8 @@
22
33module OpenAPI.Checker.Validate.SecurityRequirement
44 ( Issue (..)
5− ) where
5+ )
6+where
67
78import Data.OpenApi
89import OpenAPI.Checker.Behavior
@@ -20,4 +21,5 @@ instance Subtree SecurityRequirement where
2021 CheckEnv SecurityRequirement =
2122 '[ ProdCons (Traced (Definitions SecurityScheme))
2223 ]
23− checkCompatibility = undefined
24+ checkStructuralCompatibility = undefined
25+ checkSemanticCompatibility = undefined
src/OpenAPI/Checker/Validate/Server.hs+19−9
@@ -6,6 +6,7 @@ module OpenAPI.Checker.Validate.Server
66where
77
88import Control.Applicative
9+import Control.Arrow ((&&&))
910import Control.Comonad
1011import Control.Monad
1112import Data.Attoparsec.Text
@@ -14,13 +15,16 @@ import Data.Foldable
1415import Data.Function
1516import Data.Functor
1617import Data.HashMap.Strict.InsOrd as IOHM
18+import qualified Data.HashSet.InsOrd as IOHM
1719import qualified Data.HashSet.InsOrd as IOHS
1820import Data.Maybe
1921import Data.OpenApi
22+import qualified Data.Set as S
2023import Data.Text (Text)
2124import qualified Data.Text as T
2225import Data.Traversable
2326import OpenAPI.Checker.Behavior
27+import OpenAPI.Checker.Common
2428import OpenAPI.Checker.Paths
2529import OpenAPI.Checker.Subtree
2630import OpenAPI.Checker.Validate.MediaTypeObject
@@ -44,7 +48,14 @@ instance Behavable 'OperationLevel 'ServerLevel where
4448instance Subtree [Server] where
4549 type SubtreeLevel [Server] = 'OperationLevel
4650 type CheckEnv [Server] = '[]
47− checkCompatibility env beh pcServer = do
51+ checkStructuralCompatibility _ pc =
52+ structuralEq $ S.fromList . fmap reduceServer <$> pc
53+ where
54+ reducerServerVariable =
55+ fmap IOHM.toHashSet . _serverVariableEnum &&& _serverVariableDefault
56+ reduceServer =
57+ _serverUrl &&& fmap reducerServerVariable . IOHM.toHashMap . _serverVariables
58+ checkSemanticCompatibility env beh pcServer = do
4859 let (ProdCons (pErrs, pUrls) (cErrs, cUrls)) =
4960 pcServer
5061 <&> partitionEithers
@@ -74,12 +85,6 @@ unifyPart :: ServerUrlPart ServerVariable -> Maybe (IOHS.InsOrdHashSet Text)
7485unifyPart (ServerUrlVariable v) = _serverVariableEnum v
7586unifyPart (ServerUrlConstant c) = Just $ IOHS.singleton c
7687
77−zipAll :: [a] -> [b] -> Maybe [(a, b)]
78−zipAll [] [] = Just []
79−zipAll (x : xs) (y : ys) = ((x, y) :) <$> zipAll xs ys
80−zipAll (_ : _) [] = Nothing
81−zipAll [] (_ : _) = Nothing
82−
8388staticCompatiblePart :: ServerUrlPart x -> ServerUrlPart x -> Bool
8489staticCompatiblePart (ServerUrlConstant x) (ServerUrlConstant y) = x == y
8590staticCompatiblePart _ _ = True
@@ -90,7 +95,7 @@ staticCompatible a b = maybe False (all $ uncurry staticCompatiblePart) $ zipAll
9095data ServerUrlPart var
9196 = ServerUrlVariable var
9297 | ServerUrlConstant Text
93− deriving stock (Show, Functor, Foldable, Traversable)
98+ deriving stock (Eq, Show, Functor, Foldable, Traversable)
9499
95100-- | This is super rough. Things like @{a|b}c@ will not match @ac@.
96101-- FIXME: https://github.com/typeable/openapi-diff/issues/46
@@ -130,7 +135,12 @@ instance Issuable 'ServerLevel where
130135instance Subtree ProcessedServer where
131136 type SubtreeLevel ProcessedServer = 'ServerLevel
132137 type CheckEnv ProcessedServer = '[]
133− checkCompatibility _ beh pc =
138+ checkStructuralCompatibility _ pc =
139+ structuralEq $ (fmap . fmap . fmap) reducerServerVariable pc
140+ where
141+ reducerServerVariable =
142+ fmap IOHM.toHashSet . _serverVariableEnum &&& _serverVariableDefault
143+ checkSemanticCompatibility _ beh pc =
134144 -- traversing here is fine because we have already filtered for length
135145 for_ (zip [0 ..] $ zipProdCons . fmap (fmap unifyPart . extract) $ pc) $ \(i, pcPart) -> case pcPart of
136146 (Just x, Just y) -> for_ x $ \v -> unless (v `IOHS.member` y) (issueAt beh $ EnumValueNotConsumed i v)
stack.yaml+1−1
@@ -1 +1 @@
1−resolver: lts-17.4
1+resolver: lts-17.11
stack.yaml.lock+4−4
@@ -6,7 +6,7 @@
66packages: []
77snapshots:
88- completed:
9− size: 563103
10− url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/4.yaml
11− sha256: f11e2153044f5f71ea7b1c9398f4721f517c9bd37642ed769647b896564021f3
12− original: lts-17.4
9+ size: 567672
10+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/11.yaml
11+ sha256: 03181cdbeb671eb605bbcf6f285bea4d094b6ac7433a0e14a9f1dd54ad995938
12+ original: lts-17.11