Skip to content

Commit f20ac79

Browse files
authored
Filter: preserve inverse flag filtering with stripFlags (#198)
* tests: inverse with strip * Filter: preserve inverse flag filtering with stripFlags (#193) * Filter: preserve inverse flag filtering with stripFlags
1 parent 6359be9 commit f20ac79

9 files changed

Lines changed: 174 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [1.29.5] - 2026-02-28
44

5+
- Filter: preserve inverse flag filtering with stripFlags (#193)
56
- Types: Update Typescript typings (#194)
67

78
## [1.29.4] - 2026-02-23

openapi-format.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -794,10 +794,36 @@ async function openapiFilter(oaObj, options) {
794794
options.unusedDepth === 0 ||
795795
(stripUnused.length > 0 && unusedComp.meta.total > 0 && options.unusedDepth <= 10)
796796
) {
797-
options.unusedDepth++;
798-
const resultObj = await openapiFilter(jsonObj, options);
797+
const stripFlagsSet = new Set(stripFlags);
798+
// If a flag is both inverse-kept and stripped in the same pass, recursive
799+
// filtering would otherwise remove previously matched operations.
800+
const hasInverseFlagsStripConflict = inverseFilterFlags.some(flag => stripFlagsSet.has(flag));
801+
// Same conflict check for inverseFlagValues, based on the flag key of each object.
802+
const hasInverseFlagValuesStripConflict = inverseFilterFlagValues.some(flagObj =>
803+
stripFlagsSet.has(Object.keys(flagObj || {})[0])
804+
);
805+
806+
// Recurse with inverse flag filters disabled only for this conflict case.
807+
// This keeps the first-pass inverse selection intact while still allowing
808+
// cleanup recursion (unused components, empty objects, etc.).
809+
const recurseOptions =
810+
hasInverseFlagsStripConflict || hasInverseFlagValuesStripConflict
811+
? {
812+
...options,
813+
filterSet: {
814+
...(options.filterSet || {}),
815+
inverseFlags: [],
816+
inverseFlagValues: []
817+
}
818+
}
819+
: options;
820+
821+
// Preserve recursion depth semantics regardless of whether we cloned options.
822+
recurseOptions.unusedDepth = (recurseOptions.unusedDepth || 0) + 1;
823+
const resultObj = await openapiFilter(jsonObj, recurseOptions);
799824
jsonObj = resultObj.data;
800-
unusedComp = JSON.parse(JSON.stringify(options.unusedComp));
825+
// Carry forward unused component tracking from the recurse options object.
826+
unusedComp = JSON.parse(JSON.stringify(recurseOptions.unusedComp));
801827
}
802828

803829
// Prepare totalComp for the final result

test/filtering.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,16 @@ describe('openapi-format CLI filtering tests', () => {
310310
});
311311
});
312312

313+
describe('yaml-filter-inverse-flags-stripFlags', () => {
314+
it('yaml-filter-inverse-flags-stripFlags - should match expected output', async () => {
315+
const testName = 'yaml-filter-inverse-flags-stripFlags';
316+
const {result, outputBefore, outputAfter} = await testUtils.loadTest(testName);
317+
expect(result.code).toBe(0);
318+
expect(result.stdout).toContain('formatted successfully');
319+
expect(outputAfter).toStrictEqual(outputBefore);
320+
});
321+
});
322+
313323
describe('isUsedComp', () => {
314324
it('returns false for non-object input', () => {
315325
expect(isUsedComp(null, 'schemas')).toBe(false);

test/openapi-core.test.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,70 @@ describe('openapi-format core API', () => {
2626
expect(result.data.tags).toEqual([{name: 'pets'}]);
2727
});
2828

29+
it('openapiFilter should keep inverseFlags-matched operations when stripFlags removes the same key', async () => {
30+
const doc = {
31+
openapi: '3.0.0',
32+
info: {title: 'API', version: '1.0.0'},
33+
paths: {
34+
'/pets': {
35+
get: {'x-public': true, responses: {200: {description: 'ok'}}},
36+
post: {responses: {200: {description: 'ok'}}}
37+
}
38+
}
39+
};
40+
41+
const onlyInverse = await openapiFilter(doc, {filterSet: {inverseFlags: ['x-public']}});
42+
expect(onlyInverse.data.paths).toHaveProperty('/pets.get');
43+
44+
const inverseAndStrip = await openapiFilter(doc, {
45+
filterSet: {inverseFlags: ['x-public'], stripFlags: ['x-public']}
46+
});
47+
expect(inverseAndStrip.data.paths).toHaveProperty('/pets.get');
48+
expect(inverseAndStrip.data.paths['/pets'].get['x-public']).toBeUndefined();
49+
expect(inverseAndStrip.data.paths['/pets'].post).toBeUndefined();
50+
});
51+
52+
it('adding responses to unusedComponents should not remove still-referenced schemas', async () => {
53+
const doc = {
54+
openapi: '3.0.0',
55+
info: {title: 'API', version: '1.0.0'},
56+
paths: {
57+
'/pets': {
58+
get: {
59+
'x-public': true,
60+
responses: {
61+
200: {description: 'ok', content: {'application/json': {schema: {$ref: '#/components/schemas/Pet'}}}}
62+
}
63+
},
64+
post: {
65+
responses: {
66+
200: {description: 'ok', content: {'application/json': {schema: {$ref: '#/components/schemas/Pet'}}}}
67+
}
68+
}
69+
}
70+
},
71+
components: {
72+
schemas: {
73+
Pet: {type: 'object'}
74+
}
75+
}
76+
};
77+
78+
const base = {
79+
inverseFlags: ['x-public'],
80+
stripFlags: ['x-public'],
81+
unusedComponents: ['schemas', 'parameters', 'examples', 'headers', 'requestBodies']
82+
};
83+
const withResponses = {...base, unusedComponents: [...base.unusedComponents, 'responses']};
84+
85+
const resultBase = await openapiFilter(doc, {filterSet: base});
86+
const resultWithResponses = await openapiFilter(doc, {filterSet: withResponses});
87+
88+
expect(resultBase.data).toEqual(resultWithResponses.data);
89+
expect(resultWithResponses.data.paths).toHaveProperty('/pets.get');
90+
expect(resultWithResponses.data.components.schemas).toHaveProperty('Pet');
91+
});
92+
2993
it('openapiChangeCase should apply summary, description and securitySchemes ref casing', async () => {
3094
const doc = {
3195
openapi: '3.0.0',

test/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const tests = !localTesting
1616
? fs.readdirSync(__dirname).filter(file => {
1717
return fs.statSync(path.join(__dirname, file)).isDirectory() && !file.startsWith('_');
1818
})
19-
: ['yaml-sort-component-props'];
19+
: ['yaml-filter-inverse-flags-stripFlags'];
2020

2121
describe('openapi-format tests', () => {
2222
let consoleLogSpy, consoleWarnSpy;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
inverseFlags:
2+
- x-public
3+
stripFlags:
4+
- x-public
5+
unusedComponents:
6+
- schemas
7+
- parameters
8+
- examples
9+
- headers
10+
- requestBodies
11+
- responses
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
openapi: 3.0.0
2+
info:
3+
version: 1.0.0
4+
title: Swagger Petstore
5+
paths:
6+
/pets:
7+
get:
8+
operationId: findPets
9+
x-public: true
10+
responses:
11+
'200':
12+
description: pet response
13+
content:
14+
application/json:
15+
schema:
16+
$ref: '#/components/schemas/Pet'
17+
post:
18+
operationId: addPet
19+
responses:
20+
'200':
21+
description: pet response
22+
content:
23+
application/json:
24+
schema:
25+
$ref: '#/components/schemas/Pet'
26+
components:
27+
schemas:
28+
Pet:
29+
type: object
30+
properties:
31+
id:
32+
type: integer
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
verbose: true
2+
no-sort: true
3+
output: output.yaml
4+
filterFile: customFilter.yaml
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
openapi: 3.0.0
2+
info:
3+
version: 1.0.0
4+
title: Swagger Petstore
5+
paths:
6+
/pets:
7+
get:
8+
operationId: findPets
9+
responses:
10+
'200':
11+
description: pet response
12+
content:
13+
application/json:
14+
schema:
15+
$ref: '#/components/schemas/Pet'
16+
components:
17+
schemas:
18+
Pet:
19+
type: object
20+
properties:
21+
id:
22+
type: integer

0 commit comments

Comments
 (0)