Skip to content

Commit 42f45b9

Browse files
authored
Merge pull request #793 from postmanlabs/release/v4.21.0
Release version v4.21.0
2 parents d366111 + a2c3651 commit 42f45b9

8 files changed

+461
-82
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## [Unreleased]
44

5+
## [v4.21.0] - 2024-05-17
6+
7+
### Added
8+
9+
- Added support for simplified request and response body matching in case of multiple examples.
10+
511
## [v4.20.1] - 2024-03-27
612

713
### Fixed
@@ -614,7 +620,9 @@ Newer releases follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0
614620

615621
- Base release
616622

617-
[Unreleased]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.20.1...HEAD
623+
[Unreleased]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.21.0...HEAD
624+
625+
[v4.21.0]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.20.1...v4.21.0
618626

619627
[v4.20.1]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.20.0...v4.20.1
620628

libV2/schemaUtils.js

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,16 @@ let QUERYPARAM = 'query',
10821082

10831083
/**
10841084
* Generates postman equivalent examples which contains request and response mappings of
1085-
* each example based on examples mentioned ind definition
1085+
* each example based on examples mentioned in definition
1086+
*
1087+
* This matching between request bodies and response bodies are done in following order.
1088+
* 1. Try matching keys from request and response examples
1089+
* 2. If any key matching is found, we'll generate example from it and ignore non-matching keys
1090+
* 3. If no matching key is found, we'll generate examples based on positional matching.
1091+
*
1092+
* Positional matching means first example in request body will be matched with first example
1093+
* in response body and so on. Any left over request or response body for which
1094+
* positional matching is not found, we'll use first req/res example.
10861095
*
10871096
* @param {Object} context - Global context object
10881097
* @param {Object} responseExamples - Examples defined in the response
@@ -1092,8 +1101,51 @@ let QUERYPARAM = 'query',
10921101
* @returns {Array} Examples for corresponding operation
10931102
*/
10941103
generateExamples = (context, responseExamples, requestBodyExamples, responseBodySchema, isXMLExample) => {
1095-
const pmExamples = [];
1104+
const pmExamples = [],
1105+
responseExampleKeys = _.map(responseExamples, 'key'),
1106+
requestBodyExampleKeys = _.map(requestBodyExamples, 'key'),
1107+
matchedKeys = _.intersectionBy(responseExampleKeys, requestBodyExampleKeys, _.toLower),
1108+
usedRequestExamples = _.fill(Array(requestBodyExamples.length), false),
1109+
exampleKeyComparator = (example, key) => {
1110+
return _.toLower(example.key) === _.toLower(key);
1111+
};
1112+
1113+
// Do keys matching first and ignore any leftover req/res body for which matching is not found
1114+
if (matchedKeys.length) {
1115+
_.forEach(matchedKeys, (key) => {
1116+
const matchedRequestExamples = _.filter(requestBodyExamples, (example) => {
1117+
return exampleKeyComparator(example, key);
1118+
}),
1119+
responseExample = _.find(responseExamples, (example) => {
1120+
return exampleKeyComparator(example, key);
1121+
});
1122+
1123+
let requestExample = _.find(matchedRequestExamples, ['contentType', _.get(responseExample, 'contentType')]),
1124+
responseExampleData;
1125+
1126+
if (!requestExample) {
1127+
requestExample = _.head(matchedRequestExamples);
1128+
}
1129+
1130+
responseExampleData = getExampleData(context, { [responseExample.key]: responseExample.value });
1131+
1132+
if (isXMLExample) {
1133+
responseExampleData = getXMLExampleData(context, responseExampleData, responseBodySchema);
1134+
}
1135+
1136+
pmExamples.push({
1137+
request: getExampleData(context, { [requestExample.key]: requestExample.value }),
1138+
response: responseExampleData,
1139+
name: _.get(responseExample, 'value.summary') ||
1140+
(responseExample.key !== '_default' && responseExample.key) ||
1141+
_.get(requestExample, 'value.summary') || requestExample.key || 'Example'
1142+
});
1143+
});
1144+
1145+
return pmExamples;
1146+
}
10961147

1148+
// No key matching between req and res were found, so perform positional matching now
10971149
_.forEach(responseExamples, (responseExample, index) => {
10981150

10991151
if (!_.isObject(responseExample)) {
@@ -1115,46 +1167,12 @@ let QUERYPARAM = 'query',
11151167
return;
11161168
}
11171169

1118-
requestExample = _.find(requestBodyExamples, (example, index) => {
1119-
if (
1120-
example.contentType === responseExample.contentType &&
1121-
_.toLower(example.key) === _.toLower(responseExample.key)
1122-
) {
1123-
requestBodyExamples[index].isUsed = true;
1124-
return true;
1125-
}
1126-
return false;
1127-
});
1128-
1129-
// If exact content type is not matching, pick first content type with same example key
1130-
if (!requestExample) {
1131-
requestExample = _.find(requestBodyExamples, (example, index) => {
1132-
if (_.toLower(example.key) === _.toLower(responseExample.key)) {
1133-
requestBodyExamples[index].isUsed = true;
1134-
return true;
1135-
}
1136-
return false;
1137-
});
1170+
if (requestBodyExamples[index] && !usedRequestExamples[index]) {
1171+
requestExample = requestBodyExamples[index];
1172+
usedRequestExamples[index] = true;
11381173
}
1139-
1140-
if (!requestExample) {
1141-
if (requestBodyExamples[index] && !requestBodyExamples[index].isUsed) {
1142-
requestExample = requestBodyExamples[index];
1143-
requestBodyExamples[index].isUsed = true;
1144-
}
1145-
else {
1146-
for (let i = 0; i < requestBodyExamples.length; i++) {
1147-
if (!requestBodyExamples[i].isUsed) {
1148-
requestExample = requestBodyExamples[i];
1149-
requestBodyExamples[i].isUsed = true;
1150-
break;
1151-
}
1152-
}
1153-
1154-
if (!requestExample) {
1155-
requestExample = requestBodyExamples[0];
1156-
}
1157-
}
1174+
else {
1175+
requestExample = requestBodyExamples[0];
11581176
}
11591177

11601178
pmExamples.push({
@@ -1168,9 +1186,10 @@ let QUERYPARAM = 'query',
11681186
let responseExample,
11691187
responseExampleData;
11701188

1189+
// Add any left over request body examples with first response body as matching
11711190
for (let i = 0; i < requestBodyExamples.length; i++) {
11721191

1173-
if (!requestBodyExamples[i].isUsed || pmExamples.length === 0) {
1192+
if (!usedRequestExamples[i] || pmExamples.length === 0) {
11741193
if (!responseExample) {
11751194
responseExample = _.head(responseExamples);
11761195

@@ -1359,7 +1378,15 @@ let QUERYPARAM = 'query',
13591378
};
13601379
});
13611380
}
1362-
return generateExamples(context, responseExamples, requestBodyExamples, requestBodySchema, isBodyTypeXML);
1381+
1382+
let matchedRequestBodyExamples = _.filter(requestBodyExamples, ['contentType', bodyType]);
1383+
1384+
// If content-types are not matching, match with any present content-types
1385+
if (_.isEmpty(matchedRequestBodyExamples)) {
1386+
matchedRequestBodyExamples = requestBodyExamples;
1387+
}
1388+
1389+
return generateExamples(context, responseExamples, matchedRequestBodyExamples, requestBodySchema, isBodyTypeXML);
13631390
}
13641391

13651392
return [{ [bodyKey]: bodyData }];

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openapi-to-postmanv2",
3-
"version": "4.20.1",
3+
"version": "4.21.0",
44
"description": "Convert a given OpenAPI specification to Postman Collection v2.0",
55
"homepage": "https://github.com/postmanlabs/openapi-to-postman",
66
"bugs": "https://github.com/postmanlabs/openapi-to-postman/issues",
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
{
2+
"openapi": "3.0.0",
3+
"info": {
4+
"version": "1.0.0",
5+
"title": "Swagger Petstore",
6+
"license": {
7+
"name": "MIT"
8+
}
9+
},
10+
"servers": [{
11+
"url": "http://petstore.swagger.io/v1"
12+
}],
13+
"paths": {
14+
"/pets": {
15+
"post": {
16+
"summary": "List all pets",
17+
"operationId": "pets - updated",
18+
"tags": [
19+
"pets"
20+
],
21+
"parameters": [{
22+
"name": "limit1",
23+
"in": "query",
24+
"description": "How many items to return at one time (max 100)",
25+
"schema": {
26+
"type": "integer",
27+
"format": "int32"
28+
}
29+
}],
30+
"requestBody": {
31+
"content": {
32+
"application/json": {
33+
"schema": {
34+
"$ref": "#/components/schemas/Error"
35+
},
36+
"examples": {
37+
"ok_example": {
38+
"value": {
39+
"message": "ok"
40+
}
41+
},
42+
"not_ok_example": {
43+
"value": {
44+
"message": "fail"
45+
}
46+
}
47+
}
48+
},
49+
"application/xml": {
50+
"schema": {
51+
"$ref": "#/components/schemas/Error"
52+
},
53+
"examples": {
54+
"ok_example": {
55+
"value": {
56+
"message": "ok"
57+
}
58+
},
59+
"not_ok_example": {
60+
"value": {
61+
"message": "fail"
62+
}
63+
}
64+
}
65+
}
66+
}
67+
},
68+
"responses": {
69+
"default": {
70+
"description": "unexpected error",
71+
"content": {
72+
"application/json": {
73+
"schema": {
74+
"$ref": "#/components/schemas/Error"
75+
},
76+
"example": {
77+
"message": "Not Found"
78+
}
79+
},
80+
"application/xml": {
81+
"schema": {
82+
"$ref": "#/components/schemas/Error"
83+
},
84+
"example": {
85+
"message": "Not Found"
86+
}
87+
}
88+
}
89+
},
90+
"200": {
91+
"description": "Ok",
92+
"content": {
93+
"application/json": {
94+
"schema": {
95+
"$ref": "#/components/schemas/Error"
96+
},
97+
"example": {
98+
"message": "Found",
99+
"code": 200123
100+
}
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}
107+
},
108+
"components": {
109+
"schemas": {
110+
"Error": {
111+
"required": [
112+
"code",
113+
"message"
114+
],
115+
"properties": {
116+
"code": {
117+
"type": "integer",
118+
"format": "int32"
119+
},
120+
"message": {
121+
"type": "string"
122+
}
123+
}
124+
}
125+
}
126+
}
127+
}

test/data/valid_openapi/multiExampleMatchingRequestResponse.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ paths:
2222
value:
2323
includedFields:
2424
- user
25+
extra-value:
26+
value:
27+
includedFields:
28+
- eyeColor
2529
responses:
2630
200:
2731
description: None
@@ -44,6 +48,10 @@ paths:
4448
{
4549
"user": 1
4650
}
51+
extra-value-2:
52+
value:
53+
includedFields:
54+
- eyeColor
4755
components:
4856
schemas:
4957
World:

0 commit comments

Comments
 (0)