@@ -1,12 +1,12 @@
1−{-# OPTIONS_GHC -Wno-orphans #-}
2{-# OPTIONS_GHC -Wno-name-shadowing #-}
1{-# OPTIONS_GHC -Wno-name-shadowing #-}
2+{-# OPTIONS_GHC -Wno-orphans #-}
4module OpenAPI.Checker.Validate.Operation
4module OpenAPI.Checker.Validate.Operation
5 ( MatchedOperation (..)
5 ( MatchedOperation (..)
6− , OperationMethod(..)
6+ , OperationMethod (..)
7 , pathItemMethod
7 , pathItemMethod
11import Data.Foldable as F
11import Data.Foldable as F
12import Data.Functor
12import Data.Functor
@@ -15,8 +15,10 @@ import qualified Data.List as L
15import Data.Map.Strict as M
15import Data.Map.Strict as M
16import Data.Maybe
16import Data.Maybe
17import Data.OpenApi
17import Data.OpenApi
18+import qualified Data.OpenApi.Schema.Generator as G
18import Data.Text (Text)
19import Data.Text (Text)
19import OpenAPI.Checker.Behavior
20import OpenAPI.Checker.Behavior
21+import OpenAPI.Checker.Common
20import OpenAPI.Checker.References
22import OpenAPI.Checker.References
21import OpenAPI.Checker.Subtree
23import OpenAPI.Checker.Subtree
22import OpenAPI.Checker.Validate.MediaTypeObject
24import OpenAPI.Checker.Validate.MediaTypeObject
@@ -28,11 +30,11 @@ import OpenAPI.Checker.Validate.Server ()
29data MatchedOperation = MatchedOperation
31data MatchedOperation = MatchedOperation
30 { operation :: !Operation
32 { operation :: !Operation
31− , pathParams :: ![Traced Param]
32− -- ^ Params from the PathItem
33+ , -- | Params from the PathItem
33− , getPathFragments :: !([Traced Param] -> [Traced PathFragmentParam])
34+ pathParams :: ![Traced Param]
34− -- ^ Path fragments traced from PathItem. Takes full list of
35+ , -- | Path fragments traced from PathItem. Takes full list of
35− -- operation-specific parameters
36+ -- operation-specific parameters
37+ getPathFragments :: !([Traced Param] -> [Traced PathFragmentParam])
38type ParamKey = (ParamLocation, Text)
40type ParamKey = (ParamLocation, Text)
@@ -43,32 +45,32 @@ paramKey param = (_paramIn param, _paramName param)
43tracedParameters :: Traced MatchedOperation -> [Traced (Referenced Param)]
45tracedParameters :: Traced MatchedOperation -> [Traced (Referenced Param)]
44tracedParameters oper =
46tracedParameters oper =
45 [ traced (ask oper >>> step (OperationParamsStep i)) x
47 [ 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
49tracedRequestBody :: Traced MatchedOperation -> Maybe (Traced (Referenced RequestBody))
51tracedRequestBody :: Traced MatchedOperation -> Maybe (Traced (Referenced RequestBody))
50tracedRequestBody oper = _operationRequestBody (operation $ extract oper) <&> traced (ask oper >>> step OperationRequestBodyStep)
52tracedRequestBody oper = _operationRequestBody (operation $ extract oper) <&> traced (ask oper >>> step OperationRequestBodyStep)
52tracedResponses :: Traced MatchedOperation -> Traced Responses
54tracedResponses :: Traced MatchedOperation -> Traced Responses
55+tracedResponses oper =
53−tracedResponses oper = traced (ask oper >>> step OperationResponsesStep)
56+ traced (ask oper >>> step OperationResponsesStep) $
54− $ _operationResponses . operation $ extract oper
57+ _operationResponses . operation $ extract oper
56tracedSecurity :: Traced MatchedOperation -> [Traced SecurityRequirement]
59tracedSecurity :: Traced MatchedOperation -> [Traced SecurityRequirement]
57tracedSecurity oper =
60tracedSecurity oper =
58 [ traced (ask oper >>> step (OperationSecurityRequirementStep i)) x
61 [ 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
62-- FIXME: https://github.com/typeable/openapi-diff/issues/28
65-- FIXME: https://github.com/typeable/openapi-diff/issues/28
63−tracedServers
66+getServers
64 :: [Server] -- ^ Servers from env
67 :: [Server] -- ^ Servers from env
65− -> Traced MatchedOperation
68+ -> MatchedOperation
66− -> Traced [Server]
69+ -> [Server]
67−tracedServers env oper =
70+getServers env oper =
68− traced (ask oper >>> step OperationServersStep) $
69− case _operationServers . operation $ extract oper of
71+ case _operationServers . operation $ oper of
70− [] -> env
72+ [] -> env
73instance Behavable 'OperationLevel 'PathFragmentLevel where
75instance Behavable 'OperationLevel 'PathFragmentLevel where
74 data Behave 'OperationLevel 'PathFragmentLevel
76 data Behave 'OperationLevel 'PathFragmentLevel
@@ -83,16 +85,42 @@ instance Behavable 'OperationLevel 'RequestLevel where
84instance Subtree MatchedOperation where
86instance Subtree MatchedOperation where
85 type SubtreeLevel MatchedOperation = 'OperationLevel
87 type SubtreeLevel MatchedOperation = 'OperationLevel
86− type CheckEnv MatchedOperation =
89+ CheckEnv MatchedOperation =
87− '[ ProdCons (Traced (Definitions Param))
90+ '[ ProdCons (Traced (Definitions Param))
88− , ProdCons (Traced (Definitions RequestBody))
91+ , ProdCons (Traced (Definitions RequestBody))
89− , ProdCons (Traced (Definitions SecurityScheme))
92+ , ProdCons (Traced (Definitions SecurityScheme))
90− , ProdCons (Traced (Definitions Response))
93+ , ProdCons (Traced (Definitions Response))
91− , ProdCons (Traced (Definitions Header))
94+ , ProdCons (Traced (Definitions Header))
92− , ProdCons (Traced (Definitions Schema))
95+ , ProdCons (Traced (Definitions Schema))
93− , ProdCons [Server]
96+ , ProdCons [Server]
97+ , ProdCons (Traced (Definitions Link))
99+ checkStructuralCompatibility env pc = do
100+ let pParams :: ProdCons [Param]
102+ defs <- extract <$> getH @(ProdCons (Traced (Definitions Param))) env
103+ op' <- _operationParameters . operation <$> pc
104+ pp <- fmap extract . pathParams <$> pc
106+ let o = M.fromList $ do
107+ param <- G.dereference defs <$> op'
108+ let key = paramKey param
112+ pure (paramKey param, param)
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
95− checkCompatibility env beh prodCons = do
123+ checkSemanticCompatibility env beh prodCons = do
96 checkParameters
124 checkParameters
97 checkRequestBodies
125 checkRequestBodies
98 checkResponses
126 checkResponses
@@ -102,97 +130,103 @@ instance Subtree MatchedOperation where
104 checkParameters = do
132 checkParameters = do
106− -- Merged parameters got from Operation and PathItem in one
133+ let -- Merged parameters got from Operation and PathItem in one
107− -- place. First element is path params, second is non-path params
134+ -- place. First element is path params, second is non-path params
108− tracedParams :: ProdCons ([Traced Param], [Traced Param])
135+ tracedParams :: ProdCons ([Traced Param], [Traced Param])
109− tracedParams = getParams <$> paramDefs <*> prodCons
136+ tracedParams = getParams <$> paramDefs <*> prodCons
110− getParams defs mp =
137+ getParams defs mp =
112− operationParamsMap :: Map ParamKey (Traced Param)
138+ let operationParamsMap :: Map ParamKey (Traced Param)
113− operationParamsMap = M.fromList $ do
139+ operationParamsMap = M.fromList $ do
114− paramRef <- tracedParameters mp
140+ paramRef <- tracedParameters mp
116− param = dereference defs paramRef
141+ let param = dereference defs paramRef
117− key = paramKey . extract $ param
142+ key = paramKey . extract $ param
118− pure (key, param)
143+ pure (key, param)
119− pathParamsMap :: Map ParamKey (Traced Param)
144+ pathParamsMap :: Map ParamKey (Traced Param)
120− pathParamsMap = M.fromList $ do
145+ pathParamsMap = M.fromList $ do
121− param <- pathParams . extract $ mp
146+ param <- pathParams . extract $ mp
122− pure (paramKey . extract $ param, param)
147+ pure (paramKey . extract $ param, param)
123− params = M.elems $ M.union operationParamsMap pathParamsMap -- We prefer params from Operation
148+ params = M.elems $ M.union operationParamsMap pathParamsMap -- We prefer params from Operation
124− splitted = L.partition
150+ L.partition
125− (\p -> (_paramIn . extract $ p) == ParamPath) params
151+ (\p -> (_paramIn . extract $ p) == ParamPath)
126− in splitted
153+ in splitted
127 checkNonPathParams $ snd <$> tracedParams
154 checkNonPathParams $ snd <$> tracedParams
128 checkPathParams $ fst <$> tracedParams
155 checkPathParams $ fst <$> tracedParams
130− checkNonPathParams :: ProdCons [Traced Param] -> CompatFormula ()
157+ checkNonPathParams :: ProdCons [Traced Param] -> SemanticCompatFormula ()
131 checkNonPathParams params = do
158 checkNonPathParams params = do
133− elements = getEls <$> params
159+ let elements = getEls <$> params
134− getEls params = M.fromList $ do
160+ getEls params = M.fromList $ do
135− p <- params
161+ p <- params
137− k = (_paramIn . extract $ p, _paramName . extract $ p)
162+ let k = (_paramIn . extract $ p, _paramName . extract $ p)
138− v = ProductLike
164+ ProductLike
139− { productValue = p
165+ { productValue = p
140− , required = fromMaybe False . _paramRequired . extract $ p
166+ , required = fromMaybe False . _paramRequired . extract $ p
142− pure (k, v)
168+ pure (k, v)
143− check (_, name) param = do
169+ check (_, name) param = do
144− checkCompatibility @Param (singletonH schemaDefs) (beh >>> step (InParam name)) param
170+ checkCompatibility @Param (singletonH schemaDefs) (beh >>> step (InParam name)) param
145 checkProducts beh (ParamNotMatched . snd) check elements
171 checkProducts beh (ParamNotMatched . snd) check elements
146− checkPathParams :: ProdCons [Traced Param] -> CompatFormula ()
172+ checkPathParams :: ProdCons [Traced Param] -> SemanticCompatFormula ()
147 checkPathParams pathParams = do
173 checkPathParams pathParams = do
149− fragments :: ProdCons [Traced PathFragmentParam]
174+ let fragments :: ProdCons [Traced PathFragmentParam]
150− fragments = getFragments <$> pathParams <*> prodCons
175+ fragments = getFragments <$> pathParams <*> prodCons
151− getFragments params mop = getPathFragments (extract mop) params
176+ getFragments params mop = getPathFragments (extract mop) params
152− -- Feed path parameters to the fragments getter
177+ -- Feed path parameters to the fragments getter
153− check idx frags = checkCompatibility @PathFragmentParam env (beh >>> step (InFragment idx)) frags
178+ check idx frags = checkCompatibility @PathFragmentParam env (beh >>> step (InFragment idx)) frags
154− elements = fragments <&> \frags -> M.fromList $ zip [0 :: Int ..] $ do
180+ fragments <&> \frags -> M.fromList $
181+ zip [0 :: Int ..] $ do
155− frag <- frags
182+ frag <- frags
156− pure $ ProductLike
184+ ProductLike
157− { productValue = frag
185+ { productValue = frag
158− , required = True }
186+ , required = True
159 checkProducts beh PathFragmentNotMatched check elements
188 checkProducts beh PathFragmentNotMatched check elements
160 checkRequestBodies = do
189 checkRequestBodies = do
162− check reqBody = checkCompatibility @RequestBody env (beh >>> step InRequest) reqBody
190+ let check reqBody = checkCompatibility @RequestBody env (beh >>> step InRequest) reqBody
163− elements = getReqBody <$> bodyDefs <*> prodCons
191+ elements = getReqBody <$> bodyDefs <*> prodCons
164− getReqBody bodyDef mop = M.fromList $ do
192+ getReqBody bodyDef mop = M.fromList $ do
165− bodyRef <- F.toList . tracedRequestBody $ mop
193+ bodyRef <- F.toList . tracedRequestBody $ mop
167− body = dereference bodyDef bodyRef
194+ let body = dereference bodyDef bodyRef
168− -- Single element map
195+ -- Single element map
169− pure ((), ProductLike
198+ , ProductLike
170− { productValue = body
199+ { productValue = body
171− , required = fromMaybe False . _requestBodyRequired . extract $ body
200+ , required = fromMaybe False . _requestBodyRequired . extract $ body
173 checkProducts beh (const NoRequestBody) (const check) elements
203 checkProducts beh (const NoRequestBody) (const check) elements
174 checkResponses = do
204 checkResponses = do
175− let
205+ let respEnv =
176− respEnv = HCons (swapProdCons respDefs)
206+ HCons (swapProdCons respDefs) $
177− $ HCons (swapProdCons headerDefs)
207+ HCons (swapProdCons headerDefs) $
178− $ HCons (swapProdCons schemaDefs) HNil
208+ HCons (swapProdCons schemaDefs) $
209+ HCons (swapProdCons linkDefs) HNil
179− resps = tracedResponses <$> prodCons
210+ resps = tracedResponses <$> prodCons
180 checkCompatibility respEnv beh $ swapProdCons resps
211 checkCompatibility respEnv beh $ swapProdCons resps
181 -- FIXME: https://github.com/typeable/openapi-diff/issues/27
212 -- FIXME: https://github.com/typeable/openapi-diff/issues/27
182 checkCallbacks = pure () -- (error "FIXME: not implemented")
213 checkCallbacks = pure () -- (error "FIXME: not implemented")
183 -- FIXME: https://github.com/typeable/openapi-diff/issues/28
214 -- FIXME: https://github.com/typeable/openapi-diff/issues/28
184 checkOperationSecurity = pure () -- (error "FIXME: not implemented")
215 checkOperationSecurity = pure () -- (error "FIXME: not implemented")
185 checkServers =
216 checkServers =
186− checkCompatibility env beh $
217+ checkCompatibility env beh $ do
187− tracedServers <$> getH @(ProdCons [Server]) env <*> prodCons
219+ se <- getH @(ProdCons [Server]) env
220+ pure $ Traced (ask x >>> step OperationServersStep) (getServers se (extract x))
188 bodyDefs = getH @(ProdCons (Traced (Definitions RequestBody))) env
221 bodyDefs = getH @(ProdCons (Traced (Definitions RequestBody))) env
189 respDefs = getH @(ProdCons (Traced (Definitions Response))) env
222 respDefs = getH @(ProdCons (Traced (Definitions Response))) env
190− headerDefs = getH @(ProdCons (Traced (Definitions Header))) env
223+ headerDefs = getH @(ProdCons (Traced (Definitions Header))) env
191 schemaDefs = getH @(ProdCons (Traced (Definitions Schema))) env
224 schemaDefs = getH @(ProdCons (Traced (Definitions Schema))) env
192 paramDefs = getH @(ProdCons (Traced (Definitions Param))) env
225 paramDefs = getH @(ProdCons (Traced (Definitions Param))) env
226+ linkDefs = getH @(ProdCons (Traced (Definitions Link))) env
194−data OperationMethod =
228+data OperationMethod
195− GetMethod
229+ = GetMethod
196 | PutMethod
230 | PutMethod
197 | PostMethod
231 | PostMethod
198 | DeleteMethod
232 | DeleteMethod