Skip to content

Commit 7ef9885

Browse files
authored
CLI: convert to OpenAPI 3.2 (#181)
* CLI: convert to OpenAPI 3.2
1 parent fe448b3 commit 7ef9885

File tree

20 files changed

+969
-23
lines changed

20 files changed

+969
-23
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## unreleased
22

3+
- CLI: convert an OpenAPI 3.0 document to an OpenAPI version 3.2
4+
- CLI: convert an OpenAPI 3.1 document to an OpenAPI version 3.2
5+
36
## [1.28.0] - 2025-09-12
47

58
- Overlay: Support "extends" for referencing OpenAPI documents (#176)

bin/__snapshots__/cli.test.js.snap

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@ OpenAPI-Format CLI settings:
1010
"
1111
`;
1212

13+
exports[`openapi-format CLI command should convert 3.1 to OpenAPI 3.2 1`] = `
14+
"================================================================================
15+
OpenAPI-Format CLI settings:
16+
- Input file: test/yaml-convert-3.1-3.2/input.yaml
17+
- OAS version converted to: "3.2"
18+
================================================================================
19+
20+
OpenAPI-Format CLI options:
21+
|---------------------|-------|
22+
| sort | false |
23+
| keepComments | false |
24+
| sortComponentsProps | false |
25+
| lineWidth | -1 |
26+
| bundle | true |
27+
| split | false |
28+
| convertTo | 3.2 |
29+
| verbose | 3 |
30+
|---------------------|-------|
31+
32+
================================================================================
33+
✅ OpenAPI formatted successfully
34+
================================================================================
35+
"
36+
`;
37+
1338
exports[`openapi-format CLI command should keep the comments for YAML 1`] = `
1439
"================================================================================
1540
OpenAPI-Format CLI settings:
@@ -95,7 +120,7 @@ Options:
95120
--sortComponentsProps sort properties within schema components alphabetically (default: false)
96121
--lineWidth <lineWidth> max line width of YAML output (default: -1)
97122
--rename <oaTitle> overwrite the title in the OpenAPI document
98-
--convertTo <oaVersion> convert the OpenAPI document to OpenAPI version 3.1
123+
--convertTo <oaVersion> convert the OpenAPI document to OpenAPI version 3.1 or 3.2
99124
--no-bundle don't bundle the local and remote $ref in the OpenAPI document
100125
--split split the OpenAPI document into a multi-file structure (default: false)
101126
--json print the file to stdout as JSON
@@ -225,7 +250,7 @@ OpenAPI-Format CLI settings:
225250
exports[`openapi-format CLI command should use the convert version 1`] = `
226251
"================================================================================
227252
OpenAPI-Format CLI settings:
228-
- Input file: test/yaml-convert-3.1/input.yaml
253+
- Input file: test/yaml-convert-3.0-3.1/input.yaml
229254
- OAS version converted to: "3.1"
230255
================================================================================
231256

bin/cli.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const openapiFormat = require('../openapi-format');
44
const program = require('commander');
55
const {infoTable, infoOut, logOut, debugOut} = require('../utils/logging');
66
const {stringify} = require('../openapi-format');
7+
const {resolveConvertTargetVersion} = require('../utils/convert');
78
const fs = require('fs');
89
const path = require('path');
910

@@ -29,7 +30,7 @@ program
2930
.option('--sortComponentsProps', 'sort properties within schema components alphabetically', false)
3031
.option('--lineWidth <lineWidth>', 'max line width of YAML output', -1)
3132
.option('--rename <oaTitle>', 'overwrite the title in the OpenAPI document')
32-
.option('--convertTo <oaVersion>', 'convert the OpenAPI document to OpenAPI version 3.1')
33+
.option('--convertTo <oaVersion>', 'convert the OpenAPI document to OpenAPI version 3.1 or 3.2')
3334
.option('--no-bundle', `don't bundle the local and remote $ref in the OpenAPI document`, false)
3435
.option('--split', 'split the OpenAPI document into a multi-file structure', false)
3536
.option('--json', 'print the file to stdout as JSON')
@@ -324,14 +325,12 @@ async function run(oaFile, options) {
324325
if (resFormat.data) resObj = resFormat.data;
325326
}
326327

327-
// Convert the OpenAPI document to OpenAPI 3.1
328-
if (
329-
(options.convertTo && options.convertTo.toString() === '3.1') ||
330-
(options.convertToVersion && options.convertToVersion === 3.1)
331-
) {
328+
// Convert the OpenAPI document to a supported OpenAPI version
329+
const convertVersionInfo = resolveConvertTargetVersion(options);
330+
if (convertVersionInfo) {
332331
const resVersion = await openapiFormat.openapiConvertVersion(resObj, options);
333332
if (resVersion.data) resObj = resVersion.data;
334-
debugOut(`- OAS version converted to: "${options.convertTo}"`, options.verbose); // LOG - Conversion title
333+
debugOut(`- OAS version converted to: "${convertVersionInfo.label}"`, options.verbose); // LOG - Conversion title
335334
}
336335

337336
// Rename title OpenAPI document

bin/cli.test.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ describe('openapi-format CLI command', () => {
381381
});
382382

383383
it('should use the convert version', async () => {
384-
const path = `test/yaml-convert-3.1`;
384+
const path = `test/yaml-convert-3.0-3.1`;
385385
const inputFile = `${path}/input.yaml`;
386386
const outputFile = `${path}/output.yaml`;
387387
const output = await getLocalFile(outputFile);
@@ -395,6 +395,20 @@ describe('openapi-format CLI command', () => {
395395
expect(sanitize(result.stderr)).toStrictEqual(sanitize(output));
396396
});
397397

398+
it('should convert 3.1 to OpenAPI 3.2', async () => {
399+
const path = `test/yaml-convert-3.1-3.2`;
400+
const inputFile = `${path}/input.yaml`;
401+
const outputFile = `${path}/output.yaml`;
402+
const output = await getLocalFile(outputFile);
403+
404+
let result = await testUtils.cli([inputFile, `--convertTo "3.2"`, `--no-sort`, `-vvv`], '.');
405+
expect(result.code).toBe(0);
406+
expect(result.stdout).toContain('formatted successfully');
407+
expect(result.stdout).toContain('OAS version converted to: "3.2"');
408+
expect(result.stdout).toMatchSnapshot();
409+
expect(sanitize(result.stderr)).toStrictEqual(sanitize(output));
410+
});
411+
398412
it('should use the sortComponentsProps option', async () => {
399413
const path = `test/yaml-sort-component-props`;
400414
const inputFile = `${path}/input.yaml`;

openapi-format.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ const {
2626
convertConst,
2727
convertExclusiveMinimum,
2828
convertExclusiveMaximum,
29-
setInObject
29+
setInObject,
30+
convertTagDisplayName,
31+
convertTagGroups,
32+
resolveConvertTargetVersion
3033
} = require('./utils/convert');
3134
const {parseFile, writeFile, stringify, detectFormat, parseString, analyzeOpenApi, readFile} = require('./utils/file');
3235
const {parseTpl, getOperation} = require('./utils/parseTpl');
@@ -1107,7 +1110,15 @@ async function openapiConvertVersion(oaObj, options) {
11071110
// let debugConvertVersionStep = '' // uncomment // debugConvertVersionStep below to see which sort part is triggered
11081111

11091112
// Change OpenAPI version
1110-
jsonObj.openapi = '3.1.0';
1113+
const targetVersionInfo = resolveConvertTargetVersion(options) || {label: '3.1', normalized: '3.1.0'};
1114+
const targetVersionLabel = targetVersionInfo.label;
1115+
jsonObj.openapi = targetVersionInfo.normalized;
1116+
1117+
const isTargetVersion32 = targetVersionLabel === '3.2';
1118+
1119+
if (isTargetVersion32) {
1120+
jsonObj = convertTagGroups(jsonObj);
1121+
}
11111122

11121123
// Change x-webhooks to webhooks
11131124
if (jsonObj['x-webhooks']) {
@@ -1136,6 +1147,10 @@ async function openapiConvertVersion(oaObj, options) {
11361147
// Change type > single enum
11371148
node = convertConst(node);
11381149
}
1150+
1151+
if (isTargetVersion32) {
1152+
node = convertTagDisplayName(node);
1153+
}
11391154
this.update(node);
11401155

11411156
// Change components/schemas - schema

readme.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Additional features include powerful filtering options based on flags, tags, met
1414
To quickly standardize OpenAPI documents there is support for generating the operationIds and apply casing rules for consistency.
1515

1616
The CLI can split large OpenAPI documents into modular, multi-file structures for easier management.
17-
For upgrades, the openapi-format CLI offers the option to convert an OpenAPI 3.0 document to OpenAPI 3.1.
17+
For upgrades, the openapi-format CLI offers the option to convert an OpenAPI 3.0 or 3.1 document to OpenAPI 3.1 or 3.2.
1818

1919
With the newly added OpenAPI Overlay support, users can overlay changes onto existing OpenAPI documents, to extend and customize the OpenAPI document.
2020

@@ -79,7 +79,8 @@ Postman collections, test suites, ...
7979
- [x] Generate OpenAPI elements for consistency
8080
- [x] Bundle local and remote references in the OpenAPI document
8181
- [x] Split the OpenAPI document into a multi-file structure
82-
- [x] Convert OpenAPI 3.0 documents to OpenAPI 3.1
82+
- [x] Convert OpenAPI 3.0 documents to OpenAPI 3.1 or 3.2
83+
- [x] Convert OpenAPI 3.1 documents to OpenAPI 3.2
8384
- [x] Rename the OpenAPI title
8485
- [x] Support OpenAPI documents in JSON format
8586
- [x] Support OpenAPI documents in YAML format
@@ -165,7 +166,7 @@ Options:
165166
166167
--rename Rename the OpenAPI title [string]
167168
168-
--convertTo convert the OpenAPI document to OpenAPI version 3.1 [string]
169+
--convertTo convert the OpenAPI document to OpenAPI version 3.1 or 3.2 [string]
169170
170171
--configFile The file with the OpenAPI-format CLI options [path]
171172
@@ -198,7 +199,7 @@ Options:
198199
| --no-bundle | | don't bundle the local and remote $ref in the OpenAPI document | boolean | FALSE | optional |
199200
| --split | | split the OpenAPI document into a multi-file structure | boolean | FALSE | optional |
200201
| --rename | | rename the OpenAPI title | string | | optional |
201-
| --convertTo | | convert the OpenAPI document to OpenAPI version 3.1 | string | | optional |
202+
| --convertTo | | convert the OpenAPI document to OpenAPI version 3.1 or 3.2 | string | | optional |
202203
| --configFile | -c | the file with all the format config options | path to file | | optional |
203204
| --lineWidth | | max line width of YAML output | number | -1 (Infinity) | optional |
204205
| --json | | prints the file to stdout as JSON | | FALSE | optional |
@@ -1424,15 +1425,16 @@ which results in
14241425

14251426
> 🏗 BETA NOTICE: This feature is considered BETA since we are investigating the configuration syntax and extra formatting/casing capabilities.
14261427

1427-
- Format & convert the OpenAPI document to OpenAPI version 3.1
1428+
- Format & convert the OpenAPI document to OpenAPI version 3.1 or 3.2
14281429

1429-
openapi-format can help you to upgrade your current OpenAPI 3.0.x document to the latest version OpenAPI 3.1.
1430+
openapi-format can help you to upgrade your current OpenAPI 3.0.x document to OpenAPI 3.1 or 3.2.
14301431

14311432
```shell
14321433
$ openapi-format openapi.json -o openapi-3.1.json --convertTo "3.1"
1434+
$ openapi-format openapi.json -o openapi-3.2.json --convertTo "3.2"
14331435
```
14341436

1435-
which results in all the changes described in the [migration guide from Phil Sturgeon](https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0)
1437+
Using `3.1` results in all the changes described in the [migration guide from Phil Sturgeon](https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0), while the `3.2` target aligns with the new capabilities highlighted in [OpenAPI 3.2 is here](https://quobix.com/articles/openapi-3.2/).
14361438

14371439
**before**
14381440

@@ -1452,6 +1454,8 @@ which results in all the changes described in the [migration guide from Phil Stu
14521454
"title": "OpenAPI Petstore - OpenAPI",
14531455
```
14541456

1457+
When converting to 3.2 the `openapi` field will be set to `3.2.0`, preparing the document for features like hierarchical tags, the `QUERY` HTTP method, and reusable media types introduced in the 3.2 release.
1458+
14551459
## CLI configuration usage
14561460

14571461
The openapi-format CLI supports bundling all options in a single configuration file. This can simplify management, especially for CI/CD pipelines where configurations are stored in version control systems.

test/converting.test.js

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ const {
99
convertConst,
1010
convertImageBase64,
1111
convertMultiPartBinary,
12+
convertTagDisplayName,
13+
convertTagGroups,
1214
setInObject
1315
} = require('../utils/convert');
1416
const {describe, it, expect} = require('@jest/globals');
1517

1618
describe('openapi-format CLI converting tests', () => {
17-
describe('yaml-convert to 3.1', () => {
18-
it('yaml-convert-3.1 - should match expected output', async () => {
19-
const testName = 'yaml-convert-3.1';
19+
describe('yaml-convert 3.0 to 3.1', () => {
20+
it('yaml-convert-3.0-3.1 - should match expected output', async () => {
21+
const testName = 'yaml-convert-3.0-3.1';
2022
const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName);
2123
// console.log('result',result)
2224
expect(result.code).toBe(0);
@@ -25,6 +27,26 @@ describe('openapi-format CLI converting tests', () => {
2527
});
2628
});
2729

30+
describe('yaml-convert 3.0 to 3.2', () => {
31+
it('yaml-convert-3.0-3.2 - should match expected output', async () => {
32+
const testName = 'yaml-convert-3.0-3.2';
33+
const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName);
34+
expect(result.code).toBe(0);
35+
expect(result.stdout).toContain('formatted successfully');
36+
expect(outputAfter).toStrictEqual(outputBefore);
37+
});
38+
});
39+
40+
describe('yaml-convert 3.1 to 3.2', () => {
41+
it('yaml-convert-3.1-3.2 - should match expected output', async () => {
42+
const testName = 'yaml-convert-3.1-3.2';
43+
const {result, input, outputBefore, outputAfter} = await testUtils.loadTest(testName);
44+
expect(result.code).toBe(0);
45+
expect(result.stdout).toContain('formatted successfully');
46+
expect(outputAfter).toStrictEqual(outputBefore);
47+
});
48+
});
49+
2850
describe('util-convert', () => {
2951
it('convertNullable - should convert nullable into a type', async () => {
3052
const obj = {
@@ -185,6 +207,52 @@ describe('openapi-format CLI converting tests', () => {
185207
});
186208
});
187209

210+
it('convertTagDisplayName - should convert x-displayName to summary', async () => {
211+
const tag = {
212+
name: 'products',
213+
'x-displayName': 'Product APIs',
214+
description: 'All product operations'
215+
};
216+
const res = convertTagDisplayName(tag);
217+
expect(res).toStrictEqual({
218+
name: 'products',
219+
summary: 'Product APIs',
220+
description: 'All product operations'
221+
});
222+
});
223+
224+
it('convertTagGroups - should convert x-tagGroups to native tag relationships', async () => {
225+
const doc = {
226+
tags: [
227+
{name: 'products'},
228+
{name: 'books', description: 'Books operations'},
229+
{name: 'cds'}
230+
],
231+
'x-tagGroups': [
232+
{
233+
name: 'Products',
234+
description: 'All product operations',
235+
tags: ['books', 'cds']
236+
}
237+
]
238+
};
239+
240+
const res = convertTagGroups(doc);
241+
expect(res['x-tagGroups']).toBeUndefined();
242+
const productsTag = res.tags.find(tag => tag.name === 'products');
243+
const booksTag = res.tags.find(tag => tag.name === 'books');
244+
const cdsTag = res.tags.find(tag => tag.name === 'cds');
245+
246+
expect(productsTag).toMatchObject({
247+
name: 'products',
248+
summary: 'Products',
249+
description: 'All product operations',
250+
kind: 'nav'
251+
});
252+
expect(booksTag).toMatchObject({name: 'books', parent: 'products', kind: 'nav'});
253+
expect(cdsTag).toMatchObject({name: 'cds', parent: 'products', kind: 'nav'});
254+
});
255+
188256
it('convertImageBase64 - should convert an image upload with base64 encoding', async () => {
189257
const obj = {
190258
schema: {
@@ -264,6 +332,10 @@ describe('openapi-format CLI converting tests', () => {
264332
expect(resExclusiveMinimum).toStrictEqual(obj);
265333
const resExclusiveMaximum = convertExclusiveMaximum(obj);
266334
expect(resExclusiveMaximum).toStrictEqual(obj);
335+
const resTagDisplayName = convertTagDisplayName(obj);
336+
expect(resTagDisplayName).toStrictEqual(obj);
337+
const resTagGroups = convertTagGroups(obj);
338+
expect(resTagGroups).toStrictEqual(obj);
267339
});
268340
});
269341
});
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)