Skip to content

Commit

Permalink
fix(oas): account for deep $ref pointers when reducing an API def (#…
Browse files Browse the repository at this point in the history
…926)

| 🚥 Resolves #925 |
| :------------------- |

## 🧰 Changes

This updates `oas/reducer` to account for deep `$ref` pointers like
`#/components/examples/event-min/value`. Normally when we're running
through components to remove we only look at
`#/components/examples/event-min`, however when a schema like this is
deeply referenced we won't pick up that it's used and end up removing
`event-min` from the components block, resulting in a corrupted schema
that will no longer validate.
  • Loading branch information
erunion authored Jan 23, 2025
1 parent 2448b3c commit 84b7fe6
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 17 deletions.
19 changes: 18 additions & 1 deletion packages/oas/src/reducer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ function accumulateUsedRefs(schema: Record<string, unknown>, $refs: Set<string>,
}

getUsedRefs($refSchema).forEach(({ value: currRef }) => {
// Because it's possible to have a parameter named `$ref`, which our lookup would pick up as a
// false positive, we want to exclude that from `$ref` matching as it's not really a reference.
if (typeof currRef !== 'string') {
return;
}

// If we've already processed this $ref don't send us into an infinite loop.
if ($refs.has(currRef)) {
return;
Expand Down Expand Up @@ -178,7 +184,18 @@ export default function reducer(definition: OASDocument, opts: ReducerOptions =
if ('components' in reduced) {
Object.keys(reduced.components).forEach((componentType: keyof ComponentsObject) => {
Object.keys(reduced.components[componentType]).forEach(component => {
if (!$refs.has(`#/components/${componentType}/${component}`)) {
// If our `$ref` either is a full, or deep match, then we should preserve it.
const refIsUsed =
$refs.has(`#/components/${componentType}/${component}`) ||
Array.from($refs).some(ref => {
// Because you can have a `$ref` like `#/components/examples/event-min/value`, which
// would be accumulated via our `$refs` query, we want to make sure we account for them.
// If we don't look for these then we'll end up removing them from the overall reduced
// definition, resulting in data loss and schema corruption.
return ref.startsWith(`#/components/${componentType}/${component}/`);
});

if (!refIsUsed) {
delete reduced.components[componentType][component];
}
});
Expand Down
17 changes: 1 addition & 16 deletions packages/oas/test/reducer/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { OASDocument } from '../../src/types.js';

import { inspect } from 'node:util';

import swagger from '@readme/oas-examples/2.0/json/petstore.json';
import parametersCommon from '@readme/oas-examples/3.0/json/parameters-common.json';
import petstore from '@readme/oas-examples/3.0/json/petstore.json';
Expand All @@ -17,16 +15,6 @@ import reduceQuirks from '../__datasets__/reduce-quirks.json';
import securityRootLevel from '../__datasets__/security-root-level.json';
import tagQuirks from '../__datasets__/tag-quirks.json';

declare global {
interface Console {
logx: any;
}
}

console.logx = (obj: any) => {
console.log(inspect(obj, false, null, true));
};

describe('reducer', () => {
it('should not do anything if no reducers are supplied', () => {
expect(reducer(petstore as any)).toStrictEqual(petstore as any);
Expand Down Expand Up @@ -207,10 +195,7 @@ describe('reducer', () => {
expect(Object.keys(reduced.paths['/anything'])).toStrictEqual(['get', 'post']);
});

/**
* @see {@link https://github.com/readmeio/oas/issues/925}
*/
it.skip('should preserved deeply nested `example` refs', () => {
it('should preserved deeply nested `example` refs', () => {
const reduced = reducer(reduceQuirks as any, {
paths: {
'/events': ['get'],
Expand Down

0 comments on commit 84b7fe6

Please sign in to comment.