Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(oas): account for deep $ref pointers when reducing an API def #926

Merged
merged 1 commit into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of course you can do that 🙃

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we love it here, folks

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}`) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(non-blocking) i feel like i've seen ref syntax that uses #components and not #/components — is that valid? should we account for that here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have also seen that before but it is not valid. our parser doesn't pick up on it now, but should at some point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i had a note about this in one of my gitto PRs over the past two weeks but can't link those here.

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) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol wut

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this snuck in in a commit i made this morning where i added that skipped failing test. console.logx is a tool i use to dump full objects.

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
Loading