Skip to content

Commit 0427866

Browse files
authored
Merge pull request #797 from postmanlabs/feature/fix-multi-example-response-code-matching
Added support for response code based multi example matching.
2 parents cfa5aa9 + eb0f269 commit 0427866

File tree

3 files changed

+386
-6
lines changed

3 files changed

+386
-6
lines changed

libV2/schemaUtils.js

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,13 @@ let QUERYPARAM = 'query',
10861086
*
10871087
* This matching between request bodies and response bodies are done in following order.
10881088
* 1. Try matching keys from request and response examples
1089+
*
1090+
* (We'll also be considering any request body example with response code as key
1091+
* that's matching default response body example if present
1092+
* See fro example - test/data/valid_openapi/multiExampleResponseCodeMatching.json)
1093+
*
10891094
* 2. If any key matching is found, we'll generate example from it and ignore non-matching keys
1095+
*
10901096
* 3. If no matching key is found, we'll generate examples based on positional matching.
10911097
*
10921098
* Positional matching means first example in request body will be matched with first example
@@ -1104,19 +1110,34 @@ let QUERYPARAM = 'query',
11041110
const pmExamples = [],
11051111
responseExampleKeys = _.map(responseExamples, 'key'),
11061112
requestBodyExampleKeys = _.map(requestBodyExamples, 'key'),
1107-
matchedKeys = _.intersectionBy(responseExampleKeys, requestBodyExampleKeys, _.toLower),
11081113
usedRequestExamples = _.fill(Array(requestBodyExamples.length), false),
11091114
exampleKeyComparator = (example, key) => {
11101115
return _.toLower(example.key) === _.toLower(key);
11111116
};
11121117

1118+
let matchedKeys = _.intersectionBy(responseExampleKeys, requestBodyExampleKeys, _.toLower),
1119+
isResponseCodeMatching = false;
1120+
1121+
// Only match in case of default response example matching with any request body example
1122+
if (!matchedKeys.length && responseExamples.length === 1 && responseExamples[0].key === '_default') {
1123+
const responseCodes = _.map(responseExamples, 'responseCode');
1124+
1125+
matchedKeys = _.intersectionBy(responseCodes, requestBodyExampleKeys, _.toLower);
1126+
isResponseCodeMatching = matchedKeys.length > 0;
1127+
}
1128+
11131129
// Do keys matching first and ignore any leftover req/res body for which matching is not found
11141130
if (matchedKeys.length) {
11151131
_.forEach(matchedKeys, (key) => {
11161132
const matchedRequestExamples = _.filter(requestBodyExamples, (example) => {
11171133
return exampleKeyComparator(example, key);
11181134
}),
11191135
responseExample = _.find(responseExamples, (example) => {
1136+
// If there is a response code key-matching, then only match with keys based on response code
1137+
if (isResponseCodeMatching) {
1138+
return example.responseCode === key;
1139+
}
1140+
11201141
return exampleKeyComparator(example, key);
11211142
});
11221143

@@ -1183,6 +1204,7 @@ let QUERYPARAM = 'query',
11831204
});
11841205
});
11851206

1207+
// eslint-disable-next-line one-var
11861208
let responseExample,
11871209
responseExampleData;
11881210

@@ -1221,10 +1243,13 @@ let QUERYPARAM = 'query',
12211243
* @param {Object} requestBodySchema - Schema of the request / response body
12221244
* @param {String} bodyType - Content type of the body
12231245
* @param {Boolean} isExampleBody - Whether the body is example body
1246+
* @param {String} responseCode - Response code
12241247
* @param {Object} requestBodyExamples - Examples defined in the request body
12251248
* @returns {Array} Request / Response body data
12261249
*/
1227-
resolveBodyData = (context, requestBodySchema, bodyType, isExampleBody = false, requestBodyExamples) => {
1250+
resolveBodyData = (context, requestBodySchema, bodyType, isExampleBody = false,
1251+
responseCode = null, requestBodyExamples = {}
1252+
) => {
12281253
let { parametersResolution, indentCharacter } = context.computedOptions,
12291254
headerFamily = getHeaderFamily(bodyType),
12301255
bodyData = '',
@@ -1366,7 +1391,8 @@ let QUERYPARAM = 'query',
13661391
responseExamples = [{
13671392
key: '_default',
13681393
value: bodyData,
1369-
contentType: bodyType
1394+
contentType: bodyType,
1395+
responseCode
13701396
}];
13711397

13721398
if (!_.isEmpty(examples)) {
@@ -1839,9 +1865,10 @@ let QUERYPARAM = 'query',
18391865
* @param {Object} context - Global context object
18401866
* @param {Object} responseBody - Response body schema
18411867
* @param {Object} requestBodyExamples - Examples defined in the request body of corresponding operation
1868+
* @param {String} code - Response code
18421869
* @returns {Array} - Postman examples
18431870
*/
1844-
resolveResponseBody = (context, responseBody = {}, requestBodyExamples) => {
1871+
resolveResponseBody = (context, responseBody = {}, requestBodyExamples = {}, code = null) => {
18451872
let responseContent,
18461873
bodyType,
18471874
allBodyData,
@@ -1868,7 +1895,7 @@ let QUERYPARAM = 'query',
18681895
bodyType = getRawBodyType(responseContent);
18691896
headerFamily = getHeaderFamily(bodyType);
18701897

1871-
allBodyData = resolveBodyData(context, responseContent[bodyType], bodyType, true, requestBodyExamples);
1898+
allBodyData = resolveBodyData(context, responseContent[bodyType], bodyType, true, code, requestBodyExamples);
18721899

18731900
return _.map(allBodyData, (bodyData) => {
18741901
let requestBodyData = bodyData.request,
@@ -2081,7 +2108,7 @@ let QUERYPARAM = 'query',
20812108
let responseSchema = _.has(responseObj, '$ref') ? resolveSchema(context, responseObj) : responseObj,
20822109
{ includeAuthInfoInExample } = context.computedOptions,
20832110
auth = request.auth,
2084-
resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples) || {},
2111+
resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples, code) || {},
20852112
headers = resolveResponseHeaders(context, responseSchema.headers);
20862113

20872114
_.forOwn(resolvedExamples, (resolvedExample = {}) => {
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
{
2+
"x-generator": "NSwag v13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v13.0.0.0))",
3+
"openapi": "3.0.0",
4+
"info": {
5+
"title": "Postman Example API",
6+
"description": "postman Test. \r\n\r\n © Copyright 2024.",
7+
"version": "v1"
8+
},
9+
"servers": [
10+
{
11+
"url": "https://localhost:1234"
12+
}
13+
],
14+
"paths": {
15+
"/addUser": {
16+
"post": {
17+
"tags": [
18+
"PostmanExample"
19+
],
20+
"summary": "Add User",
21+
"description": "Add new user to system and define his access.",
22+
"operationId": "PostmanExample_AddUser",
23+
"requestBody": {
24+
"x-name": "command",
25+
"content": {
26+
"application/json": {
27+
"schema": {
28+
"$ref": "#/components/schemas/AddUserCommand"
29+
},
30+
"examples": {
31+
"200": {
32+
"value": {
33+
"userDetail": {
34+
"roleId": 1,
35+
"department": "Admin 1",
36+
"email": "123@gmail.com"
37+
}
38+
}
39+
},
40+
"400": {
41+
"value": {
42+
"userDetail": {
43+
"roleId": null,
44+
"department": "Admin 1",
45+
"email": ""
46+
}
47+
}
48+
},
49+
"404": {
50+
"value": {
51+
"userDetail": {
52+
"roleId": 0,
53+
"department": "Admin 0",
54+
"email": "123@gmail.com"
55+
}
56+
}
57+
},
58+
"409": {
59+
"value": {
60+
"userDetail": {
61+
"roleId": 1,
62+
"department": "Admin 1",
63+
"email": "123@gmail.com"
64+
}
65+
}
66+
},
67+
"500": {
68+
"value": {
69+
"userDetail": {
70+
"roleId": 0,
71+
"department": null,
72+
"email": null
73+
}
74+
}
75+
}
76+
}
77+
}
78+
},
79+
"required": true,
80+
"x-position": 1
81+
},
82+
"responses": {
83+
"200": {
84+
"description": "",
85+
"content": {
86+
"application/json": {
87+
"schema": {
88+
"$ref": "#/components/schemas/UserResponse"
89+
},
90+
"example": {
91+
"userId": 12
92+
}
93+
}
94+
}
95+
},
96+
"400": {
97+
"description": "",
98+
"content": {
99+
"application/json": {
100+
"schema": {
101+
"$ref": "#/components/schemas/BadRequestResponse"
102+
},
103+
"example": {
104+
"hasErrorMessage": true,
105+
"errorMessage": "Bad Request",
106+
"validationsErrors": [
107+
{
108+
"propertyName": "RoleID",
109+
"errorMessage": "Can not be null"
110+
}
111+
]
112+
}
113+
}
114+
}
115+
},
116+
"404": {
117+
"description": "",
118+
"content": {
119+
"application/json": {
120+
"schema": {
121+
"$ref": "#/components/schemas/NotFoundResponse"
122+
},
123+
"example": {
124+
"message": "AddUserDetailsCommand : User Role Not Found"
125+
}
126+
}
127+
}
128+
},
129+
"409": {
130+
"description": "",
131+
"content": {
132+
"application/json": {
133+
"schema": {
134+
"$ref": "#/components/schemas/ConflictErrorResponse"
135+
},
136+
"example": {
137+
"message": "AddUserDetailsCommand : Duplicate"
138+
}
139+
}
140+
}
141+
},
142+
"500": {
143+
"description": "",
144+
"content": {
145+
"application/json": {
146+
"schema": {
147+
"$ref": "#/components/schemas/UnexpectedErrorResponse"
148+
},
149+
"example": {
150+
"message": "AddUserDetailsCommand : System Error message"
151+
}
152+
}
153+
}
154+
}
155+
}
156+
}
157+
}
158+
},
159+
"components": {
160+
"schemas": {
161+
"UserResponse": {
162+
"allOf": [
163+
{
164+
"$ref": "#/components/schemas/Response"
165+
},
166+
{
167+
"type": "object",
168+
"additionalProperties": false,
169+
"properties": {
170+
"userId": {
171+
"type": "integer",
172+
"format": "int32",
173+
"nullable": true
174+
}
175+
}
176+
}
177+
]
178+
},
179+
"Response": {
180+
"type": "object",
181+
"additionalProperties": false
182+
},
183+
"BadRequestResponse": {
184+
"type": "object",
185+
"additionalProperties": false,
186+
"properties": {
187+
"hasErrorMessage": {
188+
"type": "boolean"
189+
},
190+
"errorMessage": {
191+
"type": "string",
192+
"nullable": true
193+
},
194+
"validationsErrors": {
195+
"type": "array",
196+
"items": {
197+
"$ref": "#/components/schemas/ValidationError"
198+
}
199+
}
200+
}
201+
},
202+
"ValidationError": {
203+
"type": "object",
204+
"additionalProperties": false,
205+
"properties": {
206+
"propertyName": {
207+
"type": "string"
208+
},
209+
"errorMessage": {
210+
"type": "string"
211+
}
212+
}
213+
},
214+
"NotFoundResponse": {
215+
"type": "object",
216+
"additionalProperties": false,
217+
"properties": {
218+
"message": {
219+
"type": "string"
220+
}
221+
}
222+
},
223+
"ConflictErrorResponse": {
224+
"type": "object",
225+
"additionalProperties": false,
226+
"properties": {
227+
"message": {
228+
"type": "string"
229+
}
230+
}
231+
},
232+
"UnexpectedErrorResponse": {
233+
"type": "object",
234+
"additionalProperties": false,
235+
"properties": {
236+
"message": {
237+
"type": "string"
238+
}
239+
}
240+
},
241+
"AddUserCommand": {
242+
"allOf": [
243+
{
244+
"$ref": "#/components/schemas/AddUserCommandResponse"
245+
},
246+
{
247+
"type": "object",
248+
"additionalProperties": false,
249+
"properties": {
250+
"userDetail": {
251+
"$ref": "#/components/schemas/UserInformationDto"
252+
}
253+
}
254+
}
255+
]
256+
},
257+
"UserInformationDto": {
258+
"type": "object",
259+
"additionalProperties": false,
260+
"properties": {
261+
"roleId": {
262+
"type": "integer",
263+
"format": "int32",
264+
"nullable": true
265+
},
266+
"department": {
267+
"type": "string",
268+
"nullable": true
269+
},
270+
"email": {
271+
"type": "string",
272+
"nullable": true
273+
}
274+
}
275+
},
276+
"AddUserCommandResponse": {
277+
"type": "object",
278+
"additionalProperties": false
279+
}
280+
}
281+
}
282+
}

0 commit comments

Comments
 (0)