git.iko.soy/typeable/compaREST
on master help sign in
Non-schema golden tests (#40)

* req body media * refac * wip * fix * wip * response media type object * parameters stuff * fix code * param schema * fix * Prods * Sums * wip * wip (compiles not works) * wip * rework responses * wip operations * PathItem done * path fragments done * matching done * fixups * path matching and other stuff * comment out * better error * Nope * tests ok * fix bug with responses * all works as usual * all tests ok * warnings * fix tests

commit1d27fd17f32cd6f7361e5bbf3640d7159b981c9f authorAlexey Uimanov <s9gf4ult@gmail.com> dateMon 3 May 2021, 23:45 (5 years ago) parent95f82e1
83 files changed+1698−380
openapi-diff.cabal+2 src/OpenAPI/Checker/Orphans.hs+3 src/OpenAPI/Checker/Validate/MediaTypeObject.hs+27−24 src/OpenAPI/Checker/Validate/Operation.hs+150−92 src/OpenAPI/Checker/Validate/Param.hs+70−18 src/OpenAPI/Checker/Validate/PathFragment.hs+35−60 src/OpenAPI/Checker/Validate/ProcessedPathItem.hs+148−129 src/OpenAPI/Checker/Validate/Products.hs+71 src/OpenAPI/Checker/Validate/RequestBody.hs+16−10 src/OpenAPI/Checker/Validate/Responses.hs+50−41 src/OpenAPI/Checker/Validate/Sums.hs+28 test/golden/common/maximum-lowered/trace-tree.yaml+2−2 test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/a.yaml+19 test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/b.yaml+19 test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/trace-tree.yaml+6 test/golden/common/pathItem/operation/parameters/allowEmptyValue/set/a.yaml+19 test/golden/common/pathItem/operation/parameters/allowEmptyValue/set/b.yaml+19 test/golden/common/pathItem/operation/parameters/allowEmptyValue/set/trace-tree.yaml+1 test/golden/common/pathItem/operation/parameters/change/a.yaml+20 test/golden/common/pathItem/operation/parameters/change/b.yaml+20 test/golden/common/pathItem/operation/parameters/change/trace-tree.yaml+8 test/golden/common/pathItem/operation/parameters/required/false/add/a.yaml+18 test/golden/common/pathItem/operation/parameters/required/false/add/b.yaml+20 test/golden/common/pathItem/operation/parameters/required/false/add/trace-tree.yaml+1 test/golden/common/pathItem/operation/parameters/required/false/del/a.yaml+20 test/golden/common/pathItem/operation/parameters/required/false/del/b.yaml+18 test/golden/common/pathItem/operation/parameters/required/false/del/trace-tree.yaml+1 test/golden/common/pathItem/operation/parameters/required/reset/a.yaml+19 test/golden/common/pathItem/operation/parameters/required/reset/b.yaml+19 test/golden/common/pathItem/operation/parameters/required/reset/trace-tree.yaml+1 test/golden/common/pathItem/operation/parameters/required/set/a.yaml+19 test/golden/common/pathItem/operation/parameters/required/set/b.yaml+19 test/golden/common/pathItem/operation/parameters/required/set/trace-tree.yaml+6 test/golden/common/pathItem/operation/parameters/required/true/add/a.yaml+18 test/golden/common/pathItem/operation/parameters/required/true/add/b.yaml+21 test/golden/common/pathItem/operation/parameters/required/true/add/trace-tree.yaml+4 test/golden/common/pathItem/operation/parameters/required/true/del/a.yaml+21 test/golden/common/pathItem/operation/parameters/required/true/del/b.yaml+18 test/golden/common/pathItem/operation/parameters/required/true/del/trace-tree.yaml+1 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/add/a.yaml+20 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/add/b.yaml+23 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/add/trace-tree.yaml+1 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/a.yaml+20 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/b.yaml+20 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/trace-tree.yaml+9 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/a.yaml+23 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/b.yaml+20 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/trace-tree.yaml+6 test/golden/common/pathItem/operation/requestBody/required/reset/a.yaml+21 test/golden/common/pathItem/operation/requestBody/required/reset/b.yaml+21 test/golden/common/pathItem/operation/requestBody/required/reset/trace-tree.yaml+1 test/golden/common/pathItem/operation/requestBody/required/set/a.yaml+21 test/golden/common/pathItem/operation/requestBody/required/set/b.yaml+21 test/golden/common/pathItem/operation/requestBody/required/set/trace-tree.yaml+6 test/golden/common/pathItem/operation/responses/add/a.yaml+21 test/golden/common/pathItem/operation/responses/add/b.yaml+23 test/golden/common/pathItem/operation/responses/add/trace-tree.yaml+5 test/golden/common/pathItem/operation/responses/change/headers/mandatory/add/a.yaml+25 test/golden/common/pathItem/operation/responses/change/headers/mandatory/add/b.yaml+28 test/golden/common/pathItem/operation/responses/change/headers/mandatory/add/trace-tree.yaml+1 test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/a.yaml+28 test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/b.yaml+25 test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/trace-tree.yaml+7 test/golden/common/pathItem/operation/responses/change/headers/optional/add/a.yaml+24 test/golden/common/pathItem/operation/responses/change/headers/optional/add/b.yaml+26 test/golden/common/pathItem/operation/responses/change/headers/optional/add/trace-tree.yaml+1 test/golden/common/pathItem/operation/responses/change/headers/optional/del/a.yaml+26 test/golden/common/pathItem/operation/responses/change/headers/optional/del/b.yaml+24 test/golden/common/pathItem/operation/responses/change/headers/optional/del/trace-tree.yaml+1 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/a.yaml+25 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/b.yaml+28 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/trace-tree.yaml+7 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/a.yaml+25 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/b.yaml+25 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/trace-tree.yaml+10 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/del/a.yaml+28 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/del/b.yaml+25 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/del/trace-tree.yaml+1 test/golden/common/pathItem/operation/responses/del/a.yaml+23 test/golden/common/pathItem/operation/responses/del/b.yaml+21 test/golden/common/pathItem/operation/responses/del/trace-tree.yaml+1 test/golden/common/property-removed/trace-tree.yaml+2−2 test/golden/common/property-required/trace-tree.yaml+2−2
openapi-diff.cabal+2
@@ -112,11 +112,13 @@ library
112112 , OpenAPI.Checker.Validate.Param
113113 , OpenAPI.Checker.Validate.PathFragment
114114 , OpenAPI.Checker.Validate.ProcessedPathItem
115+ , OpenAPI.Checker.Validate.Products
115116 , OpenAPI.Checker.Validate.RequestBody
116117 , OpenAPI.Checker.Validate.Responses
117118 , OpenAPI.Checker.Validate.Schema
118119 , OpenAPI.Checker.Validate.SecurityRequirement
119120 , OpenAPI.Checker.Validate.Server
121+ , OpenAPI.Checker.Validate.Sums
120122
121123executable openapi-diff
122124 import: common-options
src/OpenAPI/Checker/Orphans.hs+3
@@ -21,7 +21,10 @@ deriving stock instance Ord AdditionalProperties
2121deriving stock instance Ord Discriminator
2222deriving stock instance Ord Xml
2323deriving stock instance Ord OpenApiType
24+deriving stock instance Ord Style
2425deriving stock instance Ord OpenApiItems
26+deriving stock instance Ord ParamLocation
27+
2528
2629instance (Ord k, Ord v) => Ord (IOHM.InsOrdHashMap k v) where
2730 compare xs ys = compare (IOHM.toList xs) (IOHM.toList ys)
src/OpenAPI/Checker/Validate/MediaTypeObject.hs+27−24
@@ -3,13 +3,16 @@
33module OpenAPI.Checker.Validate.MediaTypeObject () where
44
55import Data.Foldable as F
6+import Data.Functor
67import Data.HList
78import Data.HashMap.Strict.InsOrd as IOHM
9+import Data.Map.Strict as M
810import Data.OpenApi
911import Data.Text (Text)
1012import Network.HTTP.Media (MediaType, mainType, subType)
1113import OpenAPI.Checker.Subtree
1214import OpenAPI.Checker.Trace
15+import OpenAPI.Checker.Validate.Products
1316import OpenAPI.Checker.Validate.Schema ()
1417
1518instance Subtree MediaTypeObject where
@@ -18,37 +21,37 @@ instance Subtree MediaTypeObject where
1821 , ProdCons (Definitions Schema)
1922 ]
2023 data CheckIssue MediaTypeObject
21− = MediaEncodingMissing
24+ = MediaEncodingMissing Text
2225 | MediaEncodingIncompat
2326 | MediaTypeSchemaRequired
2427 deriving (Eq, Ord, Show)
25− checkCompatibility env (ProdCons p c) = do
26− tryCheckEncoding
27− checkSchema
28− pure ()
29− where
30− mediaType = getH @MediaType env
31− tryCheckEncoding =
32− if | "multipart" == mainType mediaType -> checkEncoding
33− | "application" == mainType mediaType &&
34− "x-www-form-urlencoded" == subType mediaType -> checkEncoding
35− | otherwise -> pure ()
36− where
37− -- Each parameter encoded by the producer must be parsed by the
38− -- consumer
39− checkEncoding = for_ (IOHM.toList $ _mediaTypeObjectEncoding p) $ \(paramName, prodEncoding) ->
40− case IOHM.lookup paramName $ _mediaTypeObjectEncoding c of
41− Nothing -> issueAt consumer MediaEncodingMissing
42− Just consEncoding -> localStep (MediaTypeParamEncoding paramName)
43− $ checkCompatibility HNil
44− $ ProdCons prodEncoding consEncoding
45− -- If consumer requires schema then producer must produce compatible
46− -- request
47− checkSchema = for_ (_mediaTypeObjectSchema c) $ \consRef ->
28+ checkCompatibility env prodCons@(ProdCons p c) = do
29+ if | "multipart" == mainType mediaType -> checkEncoding
30+ | "application" == mainType mediaType &&
31+ "x-www-form-urlencoded" == subType mediaType -> checkEncoding
32+ | otherwise -> pure ()
33+ -- If consumer requires schema then producer must produce compatible
34+ -- request
35+ for_ (_mediaTypeObjectSchema c) $ \consRef ->
4836 case _mediaTypeObjectSchema p of
4937 Nothing -> issueAt producer MediaTypeSchemaRequired
5038 Just prodRef -> localStep MediaTypeSchema
5139 $ checkCompatibility env $ ProdCons prodRef consRef
40+ pure ()
41+ where
42+ mediaType = getH @MediaType env
43+ checkEncoding =
44+ let
45+ -- Parameters of the media type are product-like entities
46+ getEncoding mt = M.fromList
47+ $ (IOHM.toList $ _mediaTypeObjectEncoding mt) <&> \(k, enc) ->
48+ ( k
49+ , ProductLike
50+ { traced = Traced (step $ MediaTypeParamEncoding k) enc
51+ , required = True } )
52+ encProdCons = getEncoding <$> prodCons
53+ in checkProducts MediaEncodingMissing
54+ (const $ checkCompatibility HNil) encProdCons
5255
5356instance Subtree Encoding where
5457 type CheckEnv Encoding = '[]
src/OpenAPI/Checker/Validate/Operation.hs+150−92
@@ -1,123 +1,181 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
2+{-# OPTIONS_GHC -Wno-name-shadowing #-}
23
3−module OpenAPI.Checker.Validate.Operation (Step (..)) where
4+module OpenAPI.Checker.Validate.Operation
5+ ( MatchedOperation(..)
6+ ) where
47
5−import Data.Foldable
8+
9+import Data.Foldable as F
10+import Data.Functor
611import Data.HList
7−import qualified Data.HashMap.Strict.InsOrd as IOHM
12+import qualified Data.List as L
13+import Data.Map.Strict as M
14+import Data.Maybe
815import Data.OpenApi
916import Data.Text (Text)
1017import OpenAPI.Checker.References
1118import OpenAPI.Checker.Subtree
1219import OpenAPI.Checker.Trace
1320import OpenAPI.Checker.Validate.Param ()
21+import OpenAPI.Checker.Validate.PathFragment
22+import OpenAPI.Checker.Validate.Products
1423import OpenAPI.Checker.Validate.RequestBody ()
1524import OpenAPI.Checker.Validate.Responses ()
1625import OpenAPI.Checker.Validate.SecurityRequirement ()
1726import OpenAPI.Checker.Validate.Server ()
1827
19−instance Subtree Operation where
20− type
21− CheckEnv Operation =
22− '[ ProdCons (Definitions Param)
23− , ProdCons (Definitions RequestBody)
24− , ProdCons (Definitions SecurityScheme)
25− , ProdCons (Definitions Response)
26− , ProdCons (Definitions Header)
27− , ProdCons (Definitions Schema)
28− ]
29− data CheckIssue Operation
30− = ParamNotMatched Text -- Param name
28+-- data ParamKey
29+
30+data MatchedOperation = MatchedOperation
31+ { operation :: !Operation
32+ , pathParams :: ![Traced OpenApi Param]
33+ -- ^ Params from the PathItem
34+ , getPathFragments :: !([Traced OpenApi Param] -> [Traced OpenApi PathFragmentParam])
35+ -- ^ Path fragments traced from PathItem. Takes full list of
36+ -- operation-specific parameters
37+ }
38+
39+type ParamKey = (ParamLocation, Text)
40+
41+paramKey :: Param -> ParamKey
42+paramKey param = (_paramIn param, _paramName param)
43+
44+instance Subtree MatchedOperation where
45+ type CheckEnv MatchedOperation =
46+ '[ ProdCons (Definitions Param)
47+ , ProdCons (Definitions RequestBody)
48+ , ProdCons (Definitions SecurityScheme)
49+ , ProdCons (Definitions Response)
50+ , ProdCons (Definitions Header)
51+ , ProdCons (Definitions Schema)
52+ ]
53+ 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
3158 | NoRequestBody
3259 | CallbacksNotSupported
3360 | SecurityRequirementNotMet Int -- security indexs
3461 | ServerNotConsumed Int -- server index
3562 deriving (Eq, Ord, Show)
36− checkCompatibility env prodCons = do
37− let ProdCons {producer = pNonPathParams, consumer = cNonPathParams} = do
38− op <- _operationParameters <$> prodCons
39− defParams <- getH @(ProdCons (Definitions Param)) env
40− pure $
41− filter ((/= ParamPath) . _paramIn . getTraced)
42− . fmap (dereference defParams)
43− $ op
44− reqBody = do
45− op <- _operationRequestBody <$> prodCons
46− reqDefs <- getH @(ProdCons (Definitions RequestBody)) env
47− pure $ fmap (dereference reqDefs) op
48− for_ pNonPathParams $ \p@(Traced _ param) ->
49− anyOfAt
50− producer
51− (ParamNotMatched $ _paramName param)
52− [ checkProdCons HNil . fmap (retrace (step OperationParamsStep)) $ ProdCons p c
53− | c <- cNonPathParams
54− ]
55− case reqBody of
56− ProdCons Nothing Nothing -> pure ()
57− ProdCons (Just pBody) (Just cBody) ->
58− localStep OperationRequestBodyStep $
59− checkProdCons env (ProdCons pBody cBody)
60− ProdCons Nothing (Just _) -> issueAt producer NoRequestBody
61− ProdCons (Just _) Nothing -> issueAt consumer NoRequestBody
62− localStep OperationResponsesStep $
63− checkCompatibility env $ _operationResponses <$> prodCons
64− -- FIXME: https://github.com/typeable/openapi-diff/issues/27
65− case IOHM.null . _operationCallbacks <$> prodCons of
66− (ProdCons True True) -> pure ()
67− (ProdCons False _) -> issueAt producer CallbacksNotSupported
68− (ProdCons _ False) -> issueAt consumer CallbacksNotSupported
69− -- FIXME: https://github.com/typeable/openapi-diff/issues/28
70− sequenceA_
71− [ anyOfAt
72− producer
73− (SecurityRequirementNotMet i)
74− [ localStep OperationSecurityRequirementStep $
75− checkCompatibility env $ ProdCons prodSecurity consSecurity
76− | consSecurity <- _operationSecurity . consumer $ prodCons
77− ]
78− | (i, prodSecurity) <- zip [0 ..] . _operationSecurity . producer $ prodCons
79− ]
80− -- FIXME: https://github.com/typeable/openapi-diff/issues/29s
81− sequenceA_
82− [ anyOfAt
83− producer
84− (ServerNotConsumed i)
85− [ localStep OperationServerStep $
86− checkCompatibility env $ ProdCons pServer cServer
87− | cServer <- _operationServers . consumer $ prodCons
88− ]
89− | (i, pServer) <- zip [0 ..] . _operationServers . producer $ prodCons
90− ]
63+ checkCompatibility env prodCons = withTrace $ \myTrace -> do
64+ checkParameters myTrace
65+ checkRequestBodies
66+ checkResponses
67+ checkCallbacks
68+ checkOperationSecurity
69+ checkServers
9170 pure ()
71+ where
72+ checkParameters myTrace = do
73+ let
74+ -- Merged parameters got from Operation and PathItem in one
75+ -- place. First element is path params, second is non-path params
76+ tracedParams :: ProdCons ([Traced OpenApi Param], [Traced OpenApi Param])
77+ tracedParams = getParams <$> myTrace <*> paramDefs <*> prodCons
78+ getParams root defs mp =
79+ let
80+ operationParamsMap :: Map ParamKey (Traced OpenApi Param)
81+ operationParamsMap = M.fromList $ do
82+ paramRef <- _operationParameters $ operation mp
83+ let
84+ tracedParam = retrace root
85+ $ dereferenceTraced defs
86+ $ Traced (step $ OperationParamsStep) paramRef
87+ key = paramKey $ getTraced tracedParam
88+ pure (key, tracedParam)
89+ pathParamsMap :: Map ParamKey (Traced OpenApi Param)
90+ pathParamsMap = M.fromList $ do
91+ param <- pathParams mp
92+ pure (paramKey $ getTraced param, param)
93+ params = M.elems $ M.union operationParamsMap pathParamsMap
94+ -- We prefer params from Operation
95+ splitted = L.partition
96+ (\p -> (_paramIn $ getTraced p) == ParamPath) params
97+ in splitted
98+ checkNonPathParams $ snd <$> tracedParams
99+ checkPathParams $ fst <$> tracedParams
100+ pure ()
101+ checkNonPathParams :: ProdCons [Traced OpenApi Param] -> CompatFormula MatchedOperation ()
102+ checkNonPathParams params = do
103+ let
104+ elements = getEls <$> params
105+ getEls traced = M.fromList $ do
106+ p <- traced
107+ let
108+ param = getTraced p
109+ k = (_paramIn param, _paramName param)
110+ v = ProductLike
111+ { traced = p
112+ , required = fromMaybe False $ _paramRequired param
113+ }
114+ pure (k, v)
115+ check _ param = do
116+ checkCompatibility @Param (singletonH schemaDefs) param
117+ checkProducts' (uncurry ParamNotMatched) check elements
118+ checkPathParams pathParams = do
119+ let
120+ fragments :: ProdCons [Traced OpenApi PathFragmentParam]
121+ fragments = getFragments <$> pathParams <*> prodCons
122+ getFragments params mop = (getPathFragments mop) params
123+ -- Feed path parameters to the fragments getter
124+ check _ frags = checkCompatibility @PathFragmentParam env frags
125+ elements = fragments <&> \frags -> M.fromList $ zip [0..] $ do
126+ frag <- frags
127+ pure $ ProductLike
128+ { traced = frag
129+ , required = True }
130+ checkProducts' PathFragmentNotMatched check elements
131+ checkRequestBodies = do
132+ let
133+ check _ reqBody = checkCompatibility @RequestBody env reqBody
134+ elements = getReqBody <$> bodyDefs <*> prodCons
135+ getReqBody bodyDef mop = M.fromList $ do
136+ bodyRef <- F.toList $ _operationRequestBody $ operation mop
137+ let
138+ traced = dereferenceTraced bodyDef
139+ $ Traced (step $ OperationRequestBodyStep) bodyRef
140+ required = fromMaybe False
141+ $ _requestBodyRequired $ getTraced traced
142+ elt = ProductLike { traced, required }
143+ -- Single element map
144+ pure ((), elt)
145+ checkProducts (const NoRequestBody) check elements
146+ checkResponses = do
147+ let
148+ resps = (_operationResponses . operation) <$> prodCons
149+ respEnv = HCons (swapProdCons respDefs)
150+ $ HCons (swapProdCons headerDefs)
151+ $ HCons (swapProdCons schemaDefs) HNil
152+ localStep OperationResponsesStep
153+ $ swapRoles $ checkCompatibility respEnv $ swapProdCons resps
154+ checkCallbacks = pure () -- (error "FIXME: not implemented")
155+ checkOperationSecurity = pure () -- (error "FIXME: not implemented")
156+ checkServers = pure () -- (error "FIXME: not implemented")
157+ bodyDefs = getH @(ProdCons (Definitions RequestBody)) env
158+ respDefs = getH @(ProdCons (Definitions Response)) env
159+ headerDefs = getH @(ProdCons (Definitions Header)) env
160+ schemaDefs = getH @(ProdCons (Definitions Schema)) env
161+ paramDefs = getH @(ProdCons (Definitions Param)) env
92162
93−instance Steppable PathItem Operation where
94− data Step PathItem Operation
95− = GetStep
96− | PutStep
97− | PostStep
98− | DeleteStep
99− | OptionsStep
100− | HeadStep
101− | PatchStep
102− | TraceStep
103− deriving (Eq, Ord, Show)
104−
105−instance Steppable Operation (Referenced Param) where
106− data Step Operation (Referenced Param) = OperationParamsStep
163+instance Steppable MatchedOperation (Referenced Param) where
164+ data Step MatchedOperation (Referenced Param) = OperationParamsStep
107165 deriving (Eq, Ord, Show)
108166
109−instance Steppable Operation (Referenced RequestBody) where
110− data Step Operation (Referenced RequestBody) = OperationRequestBodyStep
167+instance Steppable MatchedOperation (Referenced RequestBody) where
168+ data Step MatchedOperation (Referenced RequestBody) = OperationRequestBodyStep
111169 deriving (Eq, Ord, Show)
112170
113−instance Steppable Operation Responses where
114− data Step Operation Responses = OperationResponsesStep
171+instance Steppable MatchedOperation Responses where
172+ data Step MatchedOperation Responses = OperationResponsesStep
115173 deriving (Eq, Ord, Show)
116174
117−instance Steppable Operation SecurityRequirement where
118− data Step Operation SecurityRequirement = OperationSecurityRequirementStep
175+instance Steppable MatchedOperation SecurityRequirement where
176+ data Step MatchedOperation SecurityRequirement = OperationSecurityRequirementStep
119177 deriving (Eq, Ord, Show)
120178
121−instance Steppable Operation Server where
122− data Step Operation Server = OperationServerStep
179+instance Steppable MatchedOperation Server where
180+ data Step MatchedOperation Server = OperationServerStep
123181 deriving (Eq, Ord, Show)
src/OpenAPI/Checker/Validate/Param.hs+70−18
@@ -1,31 +1,83 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
2+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
23
34module OpenAPI.Checker.Validate.Param () where
45
5−import Data.OpenApi
6+import Control.Monad
67import Data.Maybe
8+import Data.OpenApi
9+import OpenAPI.Checker.Orphans
710import OpenAPI.Checker.Subtree
11+import OpenAPI.Checker.Trace
12+import OpenAPI.Checker.Validate.Schema ()
13+
14+-- | The type is normalized encoding style of the parameter. If two encoding
15+-- styles are equal then parameters are compatible with their encoding style
16+data EncodingStyle = EncodingStyle
17+ { style :: Style
18+ , explode :: Bool
19+ , allowReserved :: Maybe Bool
20+ -- ^ Nothing when @in@ parameter is not @query@
21+ } deriving (Eq, Ord, Show)
22+
23+paramEncoding :: Param -> EncodingStyle
24+paramEncoding p = EncodingStyle
25+ { style, explode, allowReserved }
26+ where
27+ style = fromMaybe defaultStyle $ _paramStyle p
28+ defaultStyle = case _paramIn p of
29+ ParamQuery -> StyleForm
30+ ParamPath -> StyleSimple
31+ ParamHeader -> StyleSimple
32+ ParamCookie -> StyleForm
33+ explode = fromMaybe defaultExplode $ _paramExplode p
34+ defaultExplode = case style of
35+ StyleForm -> True
36+ _ -> False
37+ allowReserved = case _paramIn p of
38+ ParamQuery -> Just $ fromMaybe False $ _paramAllowReserved p
39+ _ -> Nothing
840
941instance Subtree Param where
10− type CheckEnv Param = '[]
42+ type CheckEnv Param = '[ProdCons (Definitions Schema)]
1143 data CheckIssue Param
1244 = ParamNameMismatch
1345 -- ^ Params have different names
14− | ParamOptionalityIncompatible
15− -- ^ Consumer requires non-empty param, but producer gives optional
46+ | ParamEmptinessIncompatible
47+ -- ^ Consumer requires non-empty param, but producer gives emptyable
48+ | ParamRequired
49+ -- ^ Consumer requires mandatory parm, but producer optional
1650 | ParamPlaceIncompatible
51+ | ParamStyleMismatch
52+ -- ^ Params encoded in different styles
53+ | ParamSchemaMismatch
54+ -- ^ One of schemas not presented
55+ 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
63+ (ParamQuery, ParamQuery) -> do
64+ -- Emptiness is only for query params
65+ when ((fromMaybe False $ _paramAllowEmptyValue p)
66+ && not (fromMaybe False $ _paramAllowEmptyValue c))
67+ $ issueAt producer ParamEmptinessIncompatible
68+ (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 ()
78+ -- If consumer doesn't care then why we should?
79+ pure ()
80+
81+instance Steppable Param (Referenced Schema) where
82+ data Step Param (Referenced Schema) = ParamSchema
1783 deriving (Eq, Ord, Show)
18− checkCompatibility _ (ProdCons p c) = case ( _paramIn p, _paramIn c) of
19− (ParamQuery, ParamQuery) ->
20− namesCompatible $
21− if (fromMaybe False $ _paramAllowEmptyValue p)
22− && not (fromMaybe False $ _paramAllowEmptyValue c)
23− then issueAt producer ParamOptionalityIncompatible
24− else pure ()
25− (a, b) | a == b -> namesCompatible $ pure ()
26− _ -> issueAt producer ParamPlaceIncompatible
27− where
28− namesCompatible next =
29− if _paramName p /= _paramName c
30− then issueAt producer ParamNameMismatch
31− else next
src/OpenAPI/Checker/Validate/PathFragment.hs+35−60
@@ -1,96 +1,71 @@
11module OpenAPI.Checker.Validate.PathFragment
2− ( PathParamRefs
3− , TracedReferences
4− , getPathParamRefs
5− , parsePath
2+ ( parsePath
63 , PathFragment (..)
4+ , PathFragmentParam
75 )
86where
97
10−import Control.Lens
11−import Control.Monad.Reader
128import qualified Data.Aeson as A
13−import Data.HList
14−import qualified Data.List.NonEmpty as NE
15−import Data.Map.Strict (Map)
16−import qualified Data.Map.Strict as M
17−import Data.Maybe
189import Data.OpenApi
1910import Data.Text (Text)
2011import qualified Data.Text as T
21−import OpenAPI.Checker.References
12+import Data.Typeable
2213import OpenAPI.Checker.Subtree
2314import OpenAPI.Checker.Trace
2415import OpenAPI.Checker.Validate.Param ()
2516
26−getPathParamRefs
27− :: Definitions Param
28− -> [Referenced Param]
29− -> Map Reference (Traced (Referenced Param) Param)
30−getPathParamRefs defs xs =
31− M.fromList $ do
32− x <- xs
33− let (Traced t param) = dereference defs x
34− guard (_paramIn param == ParamPath)
35− return (Reference $ _paramName param, Traced t param)
36−
3717-- TODO: templates can be only part of the PathFragment. Currently only supports templates as full PathFragment.
3818-- https://github.com/typeable/openapi-diff/issues/23
39−parsePath :: FilePath -> [PathFragment]
19+parsePath :: FilePath -> [PathFragment Text]
4020parsePath = fmap partition . T.splitOn "/" . T.pack
4121 where
42− partition :: Text -> PathFragment
22+ partition :: Text -> PathFragment Text
4323 partition t
4424 | Just ('{', rest) <- T.uncons t
4525 , Just (ref, '}') <- T.unsnoc rest =
46− DynamicPath $ Reference ref
26+ DynamicPath ref
4727 partition t = StaticPath t
4828
49−data PathFragment
29+-- | Fragment parameterized by parameter. The dynamic part may be either
30+-- reference to some parameter (in context of operation) or dereferenced
31+-- parameter itself.
32+data PathFragment param
5033 = StaticPath Text
51− | DynamicPath Reference
34+ | DynamicPath param
5235 deriving stock (Eq, Ord)
5336
54−instance Steppable PathFragment Param where
55− data Step PathFragment Param = StaticPathParam
56− deriving (Eq, Ord, Show)
57−
58−type PathParamRefs = TracedReferences PathFragment Param
37+type PathFragmentParam = PathFragment (Traced OpenApi Param)
5938
60−instance Subtree PathFragment where
61− type CheckEnv PathFragment = '[ProdCons PathParamRefs]
62− data CheckIssue PathFragment = PathFragmentsDontMatch Text Text
39+instance (Typeable param) => Steppable (PathFragment param) Param where
40+ data Step (PathFragment param) Param = StaticPathParam Text
6341 deriving (Eq, Ord, Show)
6442
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)
6549 -- This case isn't strictly needed. It is here for optimization.
6650 checkCompatibility _ ProdCons {producer = (StaticPath x), consumer = (StaticPath y)} =
6751 if x == y
6852 then pure ()
6953 else issueAt consumer (PathFragmentsDontMatch x y)
70− checkCompatibility env prodCons = do
71− let (t, param) =
72− fsplit . fmap deTraced $
73− dePathFragment
74− <$> (singletonH <$> getH @(ProdCons PathParamRefs) env)
75− <*> prodCons
76− localTrace t $ checkCompatibility env param
77−
78−-- | A clearer name for 'NE.unzip' that can be used without qualifying it.
79−fsplit :: Functor f => f (a, b) -> (f a, f b)
80−fsplit = NE.unzip
81−
82−dePathFragment :: Has PathParamRefs xs => HList xs -> PathFragment -> Traced PathFragment Param
83−dePathFragment (getH @PathParamRefs -> params) = \case
84− (StaticPath s) ->
85− Traced (step StaticPathParam) $
86− mempty
87− { _paramRequired = Just True
88− , _paramIn = ParamPath
89− , _paramAllowEmptyValue = Just False
90− , _paramAllowReserved = Just False
91− , _paramSchema = Just $ Inline $ staticStringSchema s
92− }
93− (DynamicPath ref) -> M.lookup ref params & fromMaybe (error $ show ref <> " not found.")
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
9469
9570staticStringSchema :: Text -> Schema
9671staticStringSchema t =
src/OpenAPI/Checker/Validate/ProcessedPathItem.hs+148−129
@@ -1,4 +1,5 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
2+{-# OPTIONS_GHC -Wno-name-shadowing #-}
23
34module OpenAPI.Checker.Validate.ProcessedPathItem
45 ( ProcessedPathItem (..)
@@ -8,16 +9,21 @@ module OpenAPI.Checker.Validate.ProcessedPathItem
89 )
910where
1011
11−import Data.Foldable
12+import Control.Monad
13+import Data.Foldable as F
14+import Data.Functor
1215import Data.HList
16+import qualified Data.List as L
17+import Data.Map.Strict as M
1318import Data.Maybe
1419import Data.OpenApi
15−import Generic.Data
20+import Data.Text as T
1621import OpenAPI.Checker.References
1722import OpenAPI.Checker.Subtree
1823import OpenAPI.Checker.Trace
1924import OpenAPI.Checker.Validate.Operation
2025import OpenAPI.Checker.Validate.PathFragment
26+import OpenAPI.Checker.Validate.Sums
2127
2228-- FIXME: There's probably a better name for this, but `PathItem` is already taken ;(
2329data ProcessedPathItem = ProcessedPathItem
@@ -28,7 +34,8 @@ data ProcessedPathItem = ProcessedPathItem
2834processPathItems :: [(FilePath, PathItem)] -> ProcessedPathItems
2935processPathItems = ProcessedPathItems . fmap (uncurry ProcessedPathItem)
3036
31−newtype ProcessedPathItems = ProcessedPathItems {unProcessedPathItems :: [ProcessedPathItem]}
37+newtype ProcessedPathItems =
38+ ProcessedPathItems {unProcessedPathItems :: [ProcessedPathItem]}
3239
3340instance Subtree ProcessedPathItems where
3441 type
@@ -40,48 +47,54 @@ instance Subtree ProcessedPathItems where
4047 , ProdCons (Definitions Header)
4148 , ProdCons (Definitions Schema)
4249 ]
43− data CheckIssue ProcessedPathItems = NoPathsMatched | WrongNumberOfFragments
50+ data CheckIssue ProcessedPathItems
51+ = NoPathsMatched FilePath
52+ | AllPathsFailed FilePath
53+ -- When several paths match given but all checks failed
4454 deriving (Eq, Ord, Show)
45− checkCompatibility env prodCons = do
46− let ProdCons {producer = p, consumer = c} =
47− (\paramDefs -> fmap (processPathItem paramDefs) . unProcessedPathItems)
48− <$> getH @(ProdCons (Definitions Param)) env
49− <*> prodCons
50− sequenceA_
51− [ anyOf'
52− [ localTrace (step <$> ProdCons pSPath cSPath) $ do
53− -- make sure every path fragment is compatible
54− sequenceA_
55− [ localTrace (pure . step $ PathFragmentStep i) $
56− checkCompatibility (singletonH $ ProdCons pPathFragmentParams cPathFragmentParams) pair
57− | (i, pair) <- zip [0 ..] pathFragments
58− ]
59− -- make sure the operation is compatible.
60− localTrace (pure . step $ getter stepProcessedPathItem) $
61− checkCompatibility env $ ProdCons pOperation cOperation
62− pure ()
63− | (cSPath, cPath, cPathItem) <- c
64− , -- ... and try to match it with every endpoint in the consumer.
65− --
66− -- This is required because the meaning of path fragments can change on
67− -- a per-method basis even within the same 'PathItem'
68− --
69− -- Here we only need to look for the method that the current producer
70− -- endpoint is using.
71− (cParams, cOperation) <- maybeToList $ getter cPathItem
72− , let cPathFragmentParams = retrace (step PathFragmentParentStep) <$> cParams
73− , -- make sure the paths are the same length
74− pathFragments <- maybeToList $ zipAllWith ProdCons pPath cPath
75− ]
76− | (pSPath, pPath, pPathItem) <- p
77− , -- look at every endpoint in the producer ...
78− (ProcessedPathItemGetter getter, (pParams, pOperation)) <-
79− toList (fmap . (,) <$> processedPathItemGetters <*> pPathItem) >>= maybeToList
80− , let pPathFragmentParams = retrace (step PathFragmentParentStep) <$> pParams
81− ]
55+ checkCompatibility env (ProdCons p c) = do
56+ -- Each path generated by producer must be handled by consumer with exactly
57+ -- one way
58+ for_ (unProcessedPathItems p) $ \ prodItem -> do
59+ let
60+ prodPath = path prodItem
61+ matchedItems = do
62+ consItem <- unProcessedPathItems c
63+ matched <- F.toList $ matchingPathItems $ ProdCons prodItem consItem
64+ return matched
65+ 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
72+ match <- matches
73+ let trace = matchedTrace <$> match
74+ pure $ localTrace trace $ checkCompatibility env match
8275 where
83− anyOf' [x] = x -- preserve errors if there's only one choice
84− anyOf' xs = anyOfAt producer NoPathsMatched xs
76+ matchedTrace :: MatchedPathItem -> Trace ProcessedPathItems MatchedPathItem
77+ matchedTrace mpi = step $ MatchedPathStep $ matchedPath mpi
78+
79+-- | Preliminary checks two paths for compatibility. Returns Nothing if two
80+-- paths obviously do not match: static parts differ or count of path elements
81+-- is not equal
82+matchingPathItems :: ProdCons ProcessedPathItem -> Maybe (ProdCons MatchedPathItem)
83+matchingPathItems prodCons = do
84+ let frags = parsePath . path <$> prodCons
85+ guard $ fragsMatch frags
86+ let
87+ mkMatchedItems frag ppi = MatchedPathItem
88+ { pathItem = item ppi
89+ , matchedPath = path ppi
90+ , pathFragments = frag }
91+ return $ mkMatchedItems <$> frags <*> prodCons
92+
93+fragsMatch :: ProdCons [PathFragment Text] -> Bool
94+fragsMatch (ProdCons p c) = maybe False and $ zipAllWith check p c
95+ where
96+ check (StaticPath s1) (StaticPath s2) = s1 == s2
97+ check _ _ = True
8598
8699zipAllWith :: (a -> b -> c) -> [a] -> [b] -> Maybe [c]
87100zipAllWith _ [] [] = Just []
@@ -89,96 +102,102 @@ zipAllWith f (x : xs) (y : ys) = (f x y :) <$> zipAllWith f xs ys
89102zipAllWith _ (_ : _) [] = Nothing
90103zipAllWith _ [] (_ : _) = Nothing
91104
92−processPathItem
93− :: Definitions Param -- ^ from components
94− -> ProcessedPathItem
95− -> ( Step ProcessedPathItems PathItem
96− , [PathFragment]
97− , ForeachOperation (Maybe (TracedReferences PathItem Param, Operation))
98− )
99−processPathItem componentParams ProcessedPathItem {path = pathS, item = pathItem} =
100− let path = parsePath pathS
101− commonPathParams =
102− retrace (step PathItemParametersStep)
103− <$> getPathParamRefs componentParams (_pathItemParameters pathItem)
104− processOperation (s :: Step PathItem Operation) op =
105− let operationParams =
106− retrace (Root `Snoc` s `Snoc` OperationParamsStep)
107− <$> getPathParamRefs componentParams (_operationParameters op)
108− pathParams =
109− operationParams <> commonPathParams
110− in (pathParams, op)
111− in ( PathStep pathS
112− , path
113− , fmap . processOperation
114− <$> stepProcessedPathItem
115− <*> ForeachOperation
116− { processedPathItemGet = _pathItemGet pathItem
117− , processedPathItemPut = _pathItemPut pathItem
118− , processedPathItemPost = _pathItemPost pathItem
119− , processedPathItemDelete = _pathItemDelete pathItem
120− , processedPathItemOptions = _pathItemOptions pathItem
121− , processedPathItemHead = _pathItemHead pathItem
122− , processedPathItemPatch = _pathItemPatch pathItem
123− , processedPathItemTrace = _pathItemTrace pathItem
124− }
125− )
126−
127−instance Steppable ProcessedPathItems PathItem where
128− data Step ProcessedPathItems PathItem = PathStep FilePath
129− deriving (Eq, Ord, Show)
105+data MatchedPathItem = MatchedPathItem
106+ { pathItem :: !PathItem
107+ , matchedPath :: !FilePath
108+ , pathFragments :: ![PathFragment Text]
109+ -- ^ Pre-parsed path from PathItem
110+ }
130111
131−instance Steppable PathItem PathFragment where
132− data Step PathItem PathFragment
133− = -- | The index of the path item
134− PathFragmentStep Int
112+instance Subtree MatchedPathItem where
113+ type CheckEnv MatchedPathItem =
114+ '[ ProdCons (Definitions Param)
115+ , ProdCons (Definitions RequestBody)
116+ , ProdCons (Definitions SecurityScheme)
117+ , ProdCons (Definitions Response)
118+ , ProdCons (Definitions Header)
119+ , ProdCons (Definitions Schema)
120+ ]
121+ data CheckIssue MatchedPathItem
122+ = OperationMissing (Step MatchedPathItem MatchedOperation)
123+ deriving (Eq, Ord, Show)
124+ checkCompatibility env prodCons = withTrace $ \rootTrace -> do
125+ let
126+ paramDefs = getH @(ProdCons (Definitions Param)) env
127+ pathTracedParams = getPathParams <$> rootTrace <*> paramDefs <*> prodCons
128+ 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 =
142+ -- operationParams will be known on Operation check stage, so we give a
143+ -- function, returning fragments
144+ let
145+ paramsMap :: Map Text (Traced OpenApi Param)
146+ paramsMap = M.fromList $ do
147+ tracedParam <- operationParams
148+ let pname = _paramName $ getTraced tracedParam
149+ pure (pname, tracedParam)
150+ fragments :: [PathFragmentParam]
151+ fragments = (pathFragments mpi) <&> \case
152+ StaticPath t -> StaticPath t
153+ DynamicPath pname -> DynamicPath
154+ $ fromMaybe (error $ "Param not found " <> T.unpack pname)
155+ $ 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
160+ operations = getOperations <$> pathTracedParams <*> pathTracedFragments <*> prodCons
161+ 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
172+ -- 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
176+ -- Operations are sum-like entities. Use step to operation as key because
177+ -- why not
178+ checkSums OperationMissing check operations
179+
180+
181+instance Steppable ProcessedPathItems MatchedPathItem where
182+ data Step ProcessedPathItems MatchedPathItem = MatchedPathStep FilePath
135183 deriving (Eq, Ord, Show)
136184
137−instance Steppable PathFragment PathItem where
138− data Step PathFragment PathItem = PathFragmentParentStep
185+instance 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
139195 deriving (Eq, Ord, Show)
140196
141−instance Steppable PathItem (Referenced Param) where
142− data Step PathItem (Referenced Param) = PathItemParametersStep
197+instance Steppable MatchedPathItem (Referenced Param) where
198+ data Step MatchedPathItem (Referenced Param) = PathItemParam
143199 deriving (Eq, Ord, Show)
144200
145−data ForeachOperation a = ForeachOperation
146− { processedPathItemGet :: a
147− , processedPathItemPut :: a
148− , processedPathItemPost :: a
149− , processedPathItemDelete :: a
150− , processedPathItemOptions :: a
151− , processedPathItemHead :: a
152− , processedPathItemPatch :: a
153− , processedPathItemTrace :: a
154− }
155− deriving stock (Functor, Generic1)
156− deriving (Applicative, Foldable) via Generically1 ForeachOperation
157−
158−newtype ProcessedPathItemGetter = ProcessedPathItemGetter (forall a. ForeachOperation a -> a)
159−
160−processedPathItemGetters :: ForeachOperation ProcessedPathItemGetter
161−processedPathItemGetters =
162− ForeachOperation
163− { processedPathItemGet = ProcessedPathItemGetter processedPathItemGet
164− , processedPathItemPut = ProcessedPathItemGetter processedPathItemPut
165− , processedPathItemPost = ProcessedPathItemGetter processedPathItemPost
166− , processedPathItemDelete = ProcessedPathItemGetter processedPathItemDelete
167− , processedPathItemOptions = ProcessedPathItemGetter processedPathItemOptions
168− , processedPathItemHead = ProcessedPathItemGetter processedPathItemHead
169− , processedPathItemPatch = ProcessedPathItemGetter processedPathItemPatch
170− , processedPathItemTrace = ProcessedPathItemGetter processedPathItemTrace
171− }
172−
173−stepProcessedPathItem :: ForeachOperation (Step PathItem Operation)
174−stepProcessedPathItem =
175− ForeachOperation
176− { processedPathItemGet = GetStep
177− , processedPathItemPut = PutStep
178− , processedPathItemPost = PostStep
179− , processedPathItemDelete = DeleteStep
180− , processedPathItemOptions = OptionsStep
181− , processedPathItemHead = HeadStep
182− , processedPathItemPatch = PatchStep
183− , processedPathItemTrace = TraceStep
184− }
201+instance Steppable MatchedPathItem PathFragmentParam where
202+ data Step MatchedPathItem PathFragmentParam = PathFragmentStep Int
203+ deriving (Eq, Ord, Show)
src/OpenAPI/Checker/Validate/Products.hsnew file+71
@@ -0,0 +1,71 @@
1+{- | Checks product-like entities. The key is some identificator for the product
2+element. Each element may be required or optional.
3+
4+One example of product is request parameters. There are optional and required
5+parameters. The client and server have possibly different set of
6+parameters. What we must check is if server requires some request parameter,
7+then this parameter must be presented by client and their schemas must match.
8+
9+So when we checking products we are checking from the server's (consumer)
10+perspective, ensuring that all parameters are provided by the client (producer)
11+and their schemas match.
12+
13+This module abstracts this logic for arbitrary elements -}
14+
15+module OpenAPI.Checker.Validate.Products
16+ ( checkProducts
17+ , checkProducts'
18+ , ProductLike(..)
19+ ) where
20+
21+import Data.Foldable
22+import Data.Functor
23+import Data.Map.Strict (Map)
24+import qualified Data.Map.Strict as M
25+import Data.OpenApi.Internal
26+import OpenAPI.Checker.Subtree
27+import OpenAPI.Checker.Trace
28+
29+-- | Some entity which is product-like
30+data ProductLike root a = ProductLike
31+ { traced :: Traced root a
32+ , required :: Bool
33+ }
34+
35+checkProducts'
36+ :: forall k root t
37+ . (Subtree root, Ord k)
38+ => (k -> CheckIssue root)
39+ -- ^ 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) ->
44+ case M.lookup key p of
45+ Nothing -> case required consElt of
46+ True -> issueAt producer $ noElt key
47+ False -> pure ()
48+ Just prodElt -> do
49+ let
50+ elts :: ProdCons (ProductLike OpenApi t)
51+ 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
src/OpenAPI/Checker/Validate/RequestBody.hs+16−10
@@ -5,15 +5,17 @@ module OpenAPI.Checker.Validate.RequestBody
55 )
66where
77
8−import Data.Foldable as F
8+import Data.Functor
99import Data.HList
1010import Data.HashMap.Strict.InsOrd as IOHM
11+import Data.Map.Strict as M
1112import Data.Maybe
1213import Data.OpenApi
1314import Network.HTTP.Media (MediaType)
1415import OpenAPI.Checker.Subtree
1516import OpenAPI.Checker.Trace
1617import OpenAPI.Checker.Validate.MediaTypeObject ()
18+import OpenAPI.Checker.Validate.Sums
1719
1820instance Subtree RequestBody where
1921 type CheckEnv RequestBody =
@@ -22,19 +24,23 @@ instance Subtree RequestBody where
2224 = RequestBodyRequired
2325 | RequestMediaTypeNotFound MediaType
2426 deriving (Eq, Ord, Show)
25− checkCompatibility env (ProdCons p c) =
27+ checkCompatibility env prodCons@(ProdCons p c) =
2628 if not (fromMaybe False $ _requestBodyRequired p)
2729 && (fromMaybe False $ _requestBodyRequired c)
2830 then issueAt producer RequestBodyRequired
2931 else
30− -- Each media type generated by the producer must be parsed by the
31− -- consumer
32− for_ (IOHM.toList $ _requestBodyContent p) $ \(mediaType, prodMedia) ->
33− case IOHM.lookup mediaType $ _requestBodyContent c of
34− Nothing -> issueAt consumer (RequestMediaTypeNotFound mediaType)
35− Just consMedia -> localStep (MediaTypeStep mediaType) $
36− checkCompatibility (HCons mediaType env) (ProdCons prodMedia consMedia)
32+ -- Media type object are sums-like entities.
33+ let
34+ check mediaType pc = checkCompatibility @MediaTypeObject (HCons mediaType env) pc
35+ 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
3743
3844instance Steppable RequestBody MediaTypeObject where
39− data Step RequestBody MediaTypeObject = MediaTypeStep MediaType
45+ data Step RequestBody MediaTypeObject = RequestMediaTypeObject MediaType
4046 deriving (Eq, Ord, Show)
src/OpenAPI/Checker/Validate/Responses.hs+50−41
@@ -1,4 +1,5 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
2+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
23
34module OpenAPI.Checker.Validate.Responses
45 (
@@ -8,6 +9,7 @@ where
89import Data.Foldable
910import Data.HList
1011import Data.HashMap.Strict.InsOrd as IOHM
12+import Data.Map.Strict as M
1113import Data.Maybe
1214import Data.OpenApi
1315import Network.HTTP.Media (MediaType)
@@ -15,7 +17,9 @@ import OpenAPI.Checker.References
1517import OpenAPI.Checker.Subtree
1618import OpenAPI.Checker.Trace
1719import OpenAPI.Checker.Validate.MediaTypeObject ()
20+import OpenAPI.Checker.Validate.Products
1821import OpenAPI.Checker.Validate.Schema ()
22+import OpenAPI.Checker.Validate.Sums
1923
2024instance Subtree Responses where
2125 type CheckEnv Responses =
@@ -25,19 +29,21 @@ instance Subtree Responses where
2529 ]
2630 data CheckIssue Responses = ResponseCodeNotFound HttpStatusCode
2731 deriving (Eq, Ord, Show)
28− -- Here we are checking responses, so, the consumer and producer swap their
29− -- roles. The consumer now generates the response and producer consumes
30− -- it. So, the logic is swapped.
31− checkCompatibility env (ProdCons p c) = do
32− let defs = getH @(ProdCons (Definitions Response)) env
33− for_ (IOHM.toList $ _responsesResponses c) $ \(prodStatus, consRef) ->
34− case IOHM.lookup prodStatus $ _responsesResponses p of
35− Nothing -> issueAt producer $ ResponseCodeNotFound prodStatus
36− Just prodRef -> do
37− let tracedRefs = dereference <$> defs <*> ProdCons prodRef consRef
38− localStep (ResponseCodeStep prodStatus)
39− $ checkProdCons env tracedRefs
40− -- FIXME: Do we need to check "default" fields somehow here?
32+ -- Roles are already swapped. Producer is a server and consumer is a
33+ -- client. Response codes are sum-like entity because we can answer with only
34+ -- one element
35+ checkCompatibility env prodCons = do
36+ let
37+ defs = getH @(ProdCons (Definitions Response)) env
38+ check _ resps = checkCompatibility @Response env resps
39+ elements = getEls <$> defs <*> prodCons
40+ 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
4147
4248instance Subtree Response where
4349 type CheckEnv Response =
@@ -48,37 +54,40 @@ instance Subtree Response where
4854 = ResponseMediaTypeMissing MediaType
4955 | ResponseHeaderMissing HeaderName
5056 deriving (Eq, Ord, Show)
51− -- Here we are checking responses, so, the consumer and producer swap their
52− -- roles. The consumer now generates the response and producer consumes
53− -- it. So, the logic is swapped.
54− checkCompatibility env (ProdCons p c) = do
57+ checkCompatibility env prodCons = do
58+ -- Roles are already swapped. Producer is a server and consumer is a client
5559 checkMediaTypes
5660 checkHeaders
5761 pure ()
5862 where
59− -- Each response type, generated by the consumer must be parseable by the
60− -- producer
6163 checkMediaTypes = do
62− for_ (IOHM.toList $ _responseContent c) $ \ (mediaType, consMediaObject) ->
63− case IOHM.lookup mediaType $ _responseContent p of
64− Nothing -> issueAt producer $ ResponseMediaTypeMissing mediaType
65− Just prodMediaObject -> localStep (ResponseMediaObject mediaType)
66− $ swapRoles
67− $ checkCompatibility @MediaTypeObject (mediaType `HCons` swapProdCons schemaRefs `HCons` HNil)
68− $ ProdCons consMediaObject prodMediaObject
69− -- Each header expected by the producer must be provided by the
70− -- consumer. Assume, that extra consumer headers are ignored.
64+ -- Media types are sum-like entity
65+ let
66+ check mediaType mtObj =
67+ let mtEnv = HCons mediaType $ env
68+ in checkCompatibility @MediaTypeObject mtEnv mtObj
69+ 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
75+
7176 checkHeaders = do
72− for_ (IOHM.toList $ _responseHeaders p) $ \ (hname, prodRef) ->
73− case IOHM.lookup hname $ _responseHeaders c of
74− Nothing -> issueAt consumer $ ResponseHeaderMissing hname
75− Just consRef -> do
76− let headerRefs = dereference <$> headerDefs <*> ProdCons prodRef consRef
77− localStep (ResponseHeader hname)
78− $ swapRoles
79− $ checkProdCons (singletonH $ swapProdCons schemaRefs) $ swapProdCons headerRefs
77+ -- Headers are product-like entities
78+ let
79+ check _hname hdrs = checkCompatibility @Header env hdrs
80+ elements = getEls <$> headerDefs <*> prodCons
81+ 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
8090 headerDefs = getH @(ProdCons (Definitions Header)) env
81− schemaRefs = getH @(ProdCons (Definitions Schema)) env
8291
8392instance Subtree Header where
8493 type CheckEnv Header = '[ProdCons (Definitions Schema)]
@@ -100,6 +109,10 @@ instance Subtree Header where
100109 $ checkCompatibility env $ ProdCons prodRef consRef
101110 pure ()
102111
112+instance Steppable Responses (Referenced Response) where
113+ data Step Responses (Referenced Response) = ResponseCodeStep HttpStatusCode
114+ deriving (Eq, Ord, Show)
115+
103116instance Steppable Header (Referenced Schema) where
104117 data Step Header (Referenced Schema) = HeaderSchema
105118 deriving (Eq, Ord, Show)
@@ -111,7 +124,3 @@ instance Steppable Response (Referenced Header) where
111124instance Steppable Response MediaTypeObject where
112125 data Step Response MediaTypeObject = ResponseMediaObject MediaType
113126 deriving (Eq, Ord, Show)
114−
115−instance Steppable Responses (Referenced Response) where
116− data Step Responses (Referenced Response) = ResponseCodeStep HttpStatusCode
117− deriving (Eq, Ord, Show)
src/OpenAPI/Checker/Validate/Sums.hsnew file+28
@@ -0,0 +1,28 @@
1+module OpenAPI.Checker.Validate.Sums
2+ ( checkSums
3+ ) where
4+
5+import Data.Foldable
6+import Data.Map.Strict (Map)
7+import qualified Data.Map.Strict as M
8+import OpenAPI.Checker.Subtree
9+import OpenAPI.Checker.Trace
10+
11+
12+checkSums
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 ()
19+checkSums noElt check (ProdCons p c) = for_ (M.toList p) $ \(key, prodElt) ->
20+ case M.lookup key c of
21+ Nothing -> issueAt consumer $ noElt key
22+ Just consElt ->
23+ let
24+ sumElts :: ProdCons (Traced root t)
25+ sumElts = ProdCons prodElt consElt
26+ trace = getTrace <$> sumElts
27+ elements = getTraced <$> sumElts
28+ in localTrace trace $ check key elements
test/golden/common/maximum-lowered/trace-tree.yaml+2−2
@@ -1,10 +1,10 @@
11Left:
22 OpenApiPathsStep:
3− PathStep "/test":
3+ MatchedPathStep "/test":
44 PostStep:
55 OperationRequestBodyStep:
66 InlineStep:
7− MediaTypeStep application/json:
7+ RequestMediaTypeObject application/json:
88 MediaTypeSchema:
99 ReferencedStep (Reference {getReference = "Test"}):
1010 MaximumFields: MatchingMaximumWeak (Inclusive 2.0) (Inclusive 3.0)
test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/a.yamlnew file+19
@@ -0,0 +1,19 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ allowEmptyValue: true
16+ responses:
17+ "200":
18+ description: "Test"
19+components: {}
test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/b.yamlnew file+19
@@ -0,0 +1,19 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ allowEmptyValue: false
16+ responses:
17+ "200":
18+ description: "Test"
19+components: {}
test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/trace-tree.yamlnew file+6
@@ -0,0 +1,6 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationParamsStep:
6+ InlineStep: ParamEmptinessIncompatible
test/golden/common/pathItem/operation/parameters/allowEmptyValue/set/a.yamlnew file+19
@@ -0,0 +1,19 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ allowEmptyValue: false
16+ responses:
17+ "200":
18+ description: "Test"
19+components: {}
test/golden/common/pathItem/operation/parameters/allowEmptyValue/set/b.yamlnew file+19
@@ -0,0 +1,19 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ allowEmptyValue: true
16+ responses:
17+ "200":
18+ description: "Test"
19+components: {}
test/golden/common/pathItem/operation/parameters/allowEmptyValue/set/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/parameters/change/a.yamlnew file+20
@@ -0,0 +1,20 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test
14+ in: query
15+ schema:
16+ type: "string"
17+ responses:
18+ "200":
19+ description: "Test"
20+components: {}
test/golden/common/pathItem/operation/parameters/change/b.yamlnew file+20
@@ -0,0 +1,20 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test
14+ in: query
15+ schema:
16+ type: "number"
17+ responses:
18+ "200":
19+ description: "Test"
20+components: {}
test/golden/common/pathItem/operation/parameters/change/trace-tree.yamlnew file+8
@@ -0,0 +1,8 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationParamsStep:
6+ InlineStep:
7+ ParamSchema:
8+ InlineStep: NoContradiction
test/golden/common/pathItem/operation/parameters/required/false/add/a.yamlnew file+18
@@ -0,0 +1,18 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ responses:
16+ "200":
17+ description: "Test"
18+components: {}
test/golden/common/pathItem/operation/parameters/required/false/add/b.yamlnew file+20
@@ -0,0 +1,20 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ - name: test2
16+ in: query
17+ responses:
18+ "200":
19+ description: "Test"
20+components: {}
test/golden/common/pathItem/operation/parameters/required/false/add/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/parameters/required/false/del/a.yamlnew file+20
@@ -0,0 +1,20 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ - name: test2
16+ in: query
17+ responses:
18+ "200":
19+ description: "Test"
20+components: {}
test/golden/common/pathItem/operation/parameters/required/false/del/b.yamlnew file+18
@@ -0,0 +1,18 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ responses:
16+ "200":
17+ description: "Test"
18+components: {}
test/golden/common/pathItem/operation/parameters/required/false/del/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/parameters/required/reset/a.yamlnew file+19
@@ -0,0 +1,19 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ required: true
16+ responses:
17+ "200":
18+ description: "Test"
19+components: {}
test/golden/common/pathItem/operation/parameters/required/reset/b.yamlnew file+19
@@ -0,0 +1,19 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ required: false
16+ responses:
17+ "200":
18+ description: "Test"
19+components: {}
test/golden/common/pathItem/operation/parameters/required/reset/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/parameters/required/set/a.yamlnew file+19
@@ -0,0 +1,19 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ required: false
16+ responses:
17+ "200":
18+ description: "Test"
19+components: {}
test/golden/common/pathItem/operation/parameters/required/set/b.yamlnew file+19
@@ -0,0 +1,19 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ required: true
16+ responses:
17+ "200":
18+ description: "Test"
19+components: {}
test/golden/common/pathItem/operation/parameters/required/set/trace-tree.yamlnew file+6
@@ -0,0 +1,6 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationParamsStep:
6+ InlineStep: ParamRequired
test/golden/common/pathItem/operation/parameters/required/true/add/a.yamlnew file+18
@@ -0,0 +1,18 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ responses:
16+ "200":
17+ description: "Test"
18+components: {}
test/golden/common/pathItem/operation/parameters/required/true/add/b.yamlnew file+21
@@ -0,0 +1,21 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ - name: test2
16+ in: query
17+ required: true
18+ responses:
19+ "200":
20+ description: "Test"
21+components: {}
test/golden/common/pathItem/operation/parameters/required/true/add/trace-tree.yamlnew file+4
@@ -0,0 +1,4 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep: ParamNotMatched ParamQuery "test2"
test/golden/common/pathItem/operation/parameters/required/true/del/a.yamlnew file+21
@@ -0,0 +1,21 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ - name: test2
16+ in: query
17+ required: true
18+ responses:
19+ "200":
20+ description: "Test"
21+components: {}
test/golden/common/pathItem/operation/parameters/required/true/del/b.yamlnew file+18
@@ -0,0 +1,18 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ parameters:
13+ - name: test1
14+ in: query
15+ responses:
16+ "200":
17+ description: "Test"
18+components: {}
test/golden/common/pathItem/operation/parameters/required/true/del/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/add/a.yamlnew file+20
@@ -0,0 +1,20 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ responses:
18+ "200":
19+ description: "Test"
20+components: {}
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/add/b.yamlnew file+23
@@ -0,0 +1,23 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ "application/x-www-form-urlencoded":
18+ schema:
19+ type: "string"
20+ responses:
21+ "200":
22+ description: "Test"
23+components: {}
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/add/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/a.yamlnew file+20
@@ -0,0 +1,20 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ responses:
18+ "200":
19+ description: "Test"
20+components: {}
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/b.yamlnew file+20
@@ -0,0 +1,20 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "number"
17+ responses:
18+ "200":
19+ description: "Test"
20+components: {}
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/trace-tree.yamlnew file+9
@@ -0,0 +1,9 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationRequestBodyStep:
6+ InlineStep:
7+ RequestMediaTypeObject application/json:
8+ MediaTypeSchema:
9+ InlineStep: NoContradiction
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/a.yamlnew file+23
@@ -0,0 +1,23 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ "application/x-www-form-urlencoded":
18+ schema:
19+ type: "string"
20+ responses:
21+ "200":
22+ description: "Test"
23+components: {}
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/b.yamlnew file+20
@@ -0,0 +1,20 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ responses:
18+ "200":
19+ description: "Test"
20+components: {}
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/trace-tree.yamlnew file+6
@@ -0,0 +1,6 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationRequestBodyStep:
6+ InlineStep: RequestMediaTypeNotFound application/x-www-form-urlencoded
test/golden/common/pathItem/operation/requestBody/required/reset/a.yamlnew file+21
@@ -0,0 +1,21 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: true
18+ responses:
19+ "200":
20+ description: "Test"
21+components: {}
test/golden/common/pathItem/operation/requestBody/required/reset/b.yamlnew file+21
@@ -0,0 +1,21 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+components: {}
test/golden/common/pathItem/operation/requestBody/required/reset/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/requestBody/required/set/a.yamlnew file+21
@@ -0,0 +1,21 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+components: {}
test/golden/common/pathItem/operation/requestBody/required/set/b.yamlnew file+21
@@ -0,0 +1,21 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: true
18+ responses:
19+ "200":
20+ description: "Test"
21+components: {}
test/golden/common/pathItem/operation/requestBody/required/set/trace-tree.yamlnew file+6
@@ -0,0 +1,6 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationRequestBodyStep:
6+ InlineStep: RequestBodyRequired
test/golden/common/pathItem/operation/responses/add/a.yamlnew file+21
@@ -0,0 +1,21 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+components: {}
test/golden/common/pathItem/operation/responses/add/b.yamlnew file+23
@@ -0,0 +1,23 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ "500":
22+ description: "Added"
23+components: {}
test/golden/common/pathItem/operation/responses/add/trace-tree.yamlnew file+5
@@ -0,0 +1,5 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationResponsesStep: ResponseCodeNotFound 500
test/golden/common/pathItem/operation/responses/change/headers/mandatory/add/a.yamlnew file+25
@@ -0,0 +1,25 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ headers:
22+ "Test":
23+ description: "Test header"
24+ required: true
25+components: {}
test/golden/common/pathItem/operation/responses/change/headers/mandatory/add/b.yamlnew file+28
@@ -0,0 +1,28 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ headers:
22+ "Test":
23+ description: "Test header"
24+ required: true
25+ "Test2":
26+ description: "Test2 header"
27+ required: true
28+components: {}
test/golden/common/pathItem/operation/responses/change/headers/mandatory/add/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/a.yamlnew file+28
@@ -0,0 +1,28 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ headers:
22+ "Test":
23+ description: "Test header"
24+ required: true
25+ "Test2":
26+ description: "Test2 header"
27+ required: true
28+components: {}
test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/b.yamlnew file+25
@@ -0,0 +1,25 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ headers:
22+ "Test":
23+ description: "Test header"
24+ required: true
25+components: {}
test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/trace-tree.yamlnew file+7
@@ -0,0 +1,7 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationResponsesStep:
6+ ResponseCodeStep 200:
7+ InlineStep: ResponseHeaderMissing "Test2"
test/golden/common/pathItem/operation/responses/change/headers/optional/add/a.yamlnew file+24
@@ -0,0 +1,24 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ headers:
22+ "Test":
23+ description: "Test header"
24+components: {}
test/golden/common/pathItem/operation/responses/change/headers/optional/add/b.yamlnew file+26
@@ -0,0 +1,26 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ headers:
22+ "Test":
23+ description: "Test header"
24+ "Test2":
25+ description: "Test2 header"
26+components: {}
test/golden/common/pathItem/operation/responses/change/headers/optional/add/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/responses/change/headers/optional/del/a.yamlnew file+26
@@ -0,0 +1,26 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ headers:
22+ "Test":
23+ description: "Test header"
24+ "Test2":
25+ description: "Test2 header"
26+components: {}
test/golden/common/pathItem/operation/responses/change/headers/optional/del/b.yamlnew file+24
@@ -0,0 +1,24 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ headers:
22+ "Test":
23+ description: "Test header"
24+components: {}
test/golden/common/pathItem/operation/responses/change/headers/optional/del/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/a.yamlnew file+25
@@ -0,0 +1,25 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ content:
22+ "application/json":
23+ schema:
24+ type: "string"
25+components: {}
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/b.yamlnew file+28
@@ -0,0 +1,28 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ content:
22+ "application/json":
23+ schema:
24+ type: "string"
25+ "application/x-www-form-urlencoded":
26+ schema:
27+ type: "string"
28+components: {}
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/trace-tree.yamlnew file+7
@@ -0,0 +1,7 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationResponsesStep:
6+ ResponseCodeStep 200:
7+ InlineStep: ResponseMediaTypeMissing application/x-www-form-urlencoded
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/a.yamlnew file+25
@@ -0,0 +1,25 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ content:
22+ "application/json":
23+ schema:
24+ type: "string"
25+components: {}
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/b.yamlnew file+25
@@ -0,0 +1,25 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ content:
22+ "application/json":
23+ schema:
24+ type: "number"
25+components: {}
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/trace-tree.yamlnew file+10
@@ -0,0 +1,10 @@
1+Left:
2+ OpenApiPathsStep:
3+ MatchedPathStep "/test":
4+ PostStep:
5+ OperationResponsesStep:
6+ ResponseCodeStep 200:
7+ InlineStep:
8+ ResponseMediaObject application/json:
9+ MediaTypeSchema:
10+ InlineStep: NoContradiction
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/del/a.yamlnew file+28
@@ -0,0 +1,28 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ content:
22+ "application/json":
23+ schema:
24+ type: "string"
25+ "application/x-www-form-urlencoded":
26+ schema:
27+ type: "string"
28+components: {}
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/del/b.yamlnew file+25
@@ -0,0 +1,25 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ content:
22+ "application/json":
23+ schema:
24+ type: "string"
25+components: {}
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/del/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/pathItem/operation/responses/del/a.yamlnew file+23
@@ -0,0 +1,23 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+ "500":
22+ description: "Added"
23+components: {}
test/golden/common/pathItem/operation/responses/del/b.yamlnew file+21
@@ -0,0 +1,21 @@
1+openapi: "3.0.0"
2+info:
3+ version: 1.0.0
4+ title: Swagger Petstore
5+ license:
6+ name: MIT
7+servers:
8+ - url: http://petstore.swagger.io/v1
9+paths:
10+ /test:
11+ post:
12+ requestBody:
13+ content:
14+ "application/json" :
15+ schema:
16+ type: "string"
17+ required: false
18+ responses:
19+ "200":
20+ description: "Test"
21+components: {}
test/golden/common/pathItem/operation/responses/del/trace-tree.yamlnew file+1
@@ -0,0 +1 @@
1+Right: []
test/golden/common/property-removed/trace-tree.yaml+2−2
@@ -1,6 +1,6 @@
11Left:
22 OpenApiPathsStep:
3− PathStep "/test":
3+ MatchedPathStep "/test":
44 PostStep:
55 OperationResponsesStep:
66 ResponseCodeStep 200:
@@ -11,7 +11,7 @@ Left:
1111 PropertiesFields: PropertyNowRequired "property2"
1212 OperationRequestBodyStep:
1313 InlineStep:
14− MediaTypeStep application/json:
14+ RequestMediaTypeObject application/json:
1515 MediaTypeSchema:
1616 ReferencedStep (Reference {getReference = "Test"}):
1717 PropertiesFields: UnexpectedProperty "property2"
test/golden/common/property-required/trace-tree.yaml+2−2
@@ -1,10 +1,10 @@
11Left:
22 OpenApiPathsStep:
3− PathStep "/test":
3+ MatchedPathStep "/test":
44 PostStep:
55 OperationRequestBodyStep:
66 InlineStep:
7− MediaTypeStep application/json:
7+ RequestMediaTypeObject application/json:
88 MediaTypeSchema:
99 ReferencedStep (Reference {getReference = "Test"}):
1010 PropertiesFields: PropertyNowRequired "property2"