Skip to content

Commit 320c9f7

Browse files
committed
Filter: preserve inverse flag filtering with stripFlags (#193)
1 parent 71e276f commit 320c9f7

5 files changed

Lines changed: 67 additions & 10 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/openapi-core.test.js

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

29-
it('openapiFilter inverseFlags + stripFlags currently removes previously kept operations after recurse', async () => {
29+
it('openapiFilter should keep inverseFlags-matched operations when stripFlags removes the same key', async () => {
3030
const doc = {
3131
openapi: '3.0.0',
3232
info: {title: 'API', version: '1.0.0'},
@@ -42,17 +42,28 @@ describe('openapi-format core API', () => {
4242
expect(onlyInverse.data.paths).toHaveProperty('/pets.get');
4343

4444
const inverseAndStrip = await openapiFilter(doc, {filterSet: {inverseFlags: ['x-public'], stripFlags: ['x-public']}});
45-
expect(inverseAndStrip.data.paths).toBeUndefined();
45+
expect(inverseAndStrip.data.paths).toHaveProperty('/pets.get');
46+
expect(inverseAndStrip.data.paths['/pets'].get['x-public']).toBeUndefined();
47+
expect(inverseAndStrip.data.paths['/pets'].post).toBeUndefined();
4648
});
4749

48-
it('adding responses to unusedComponents does not change inverseFlags+stripFlags outcome', async () => {
50+
it('adding responses to unusedComponents should not remove still-referenced schemas', async () => {
4951
const doc = {
5052
openapi: '3.0.0',
5153
info: {title: 'API', version: '1.0.0'},
5254
paths: {
5355
'/pets': {
54-
get: {'x-public': true, responses: {'200': {description: 'ok'}}},
55-
post: {responses: {'200': {description: 'ok'}}}
56+
get: {
57+
'x-public': true,
58+
responses: {
59+
'200': {description: 'ok', content: {'application/json': {schema: {$ref: '#/components/schemas/Pet'}}}}
60+
}
61+
},
62+
post: {
63+
responses: {
64+
'200': {description: 'ok', content: {'application/json': {schema: {$ref: '#/components/schemas/Pet'}}}}
65+
}
66+
}
5667
}
5768
},
5869
components: {
@@ -73,7 +84,8 @@ describe('openapi-format core API', () => {
7384
const resultWithResponses = await openapiFilter(doc, {filterSet: withResponses});
7485

7586
expect(resultBase.data).toEqual(resultWithResponses.data);
76-
expect(resultWithResponses.data.paths).toBeUndefined();
87+
expect(resultWithResponses.data.paths).toHaveProperty('/pets.get');
88+
expect(resultWithResponses.data.components.schemas).toHaveProperty('Pet');
7789
});
7890

7991
it('openapiChangeCase should apply summary, description and securitySchemes ref casing', async () => {

test/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const {run} = require('../bin/cli');
88
const {parseFile, stringify, writeFile} = require('../openapi-format');
99

1010
// SELECTIVE TESTING DEBUG
11-
const localTesting = true;
11+
const localTesting = false;
1212
const destroyOutput = false;
1313

1414
// Load tests

test/yaml-filter-inverse-flags-stripFlags/output.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,21 @@ openapi: 3.0.0
22
info:
33
version: 1.0.0
44
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)