Skip to content
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
15 changes: 11 additions & 4 deletions packages/downgrader/src/v3.1-to-v3.0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,17 +293,24 @@ const DOCUMENT_FIELDS = defineFields({
// or `additionalProperties`, which the conversion can replace with another
// schema, is replaced by its target, unless it already dangles. So is one
// that percent-encodes a character, which `find` decodes, as it can hide
// those segments, such as `#/%24defs/Pet`.
// those segments, such as `#/%24defs/Pet`. The segment after
// `#/components/<type>/` is a name, so a component named `items` is not one.
function isInlined(ref: string, ctx: Context): boolean {
let keywords = ref
if (ref.startsWith('#/components/')) {
const name = ref.indexOf('/', 13) + 1
const end = ref.indexOf('/', name)
keywords = name === 0 || end === -1 ? '' : ref.slice(end)
}
return (
(ref.includes('%') ||
ref.includes('/$defs/') ||
ref.includes('/definitions/') ||
keywords.includes('/$defs/') ||
keywords.includes('/definitions/') ||
// The removed parts, spelled out, as a callback for each `$ref` costs
// about 3% of the instructions of a conversion.
ref.startsWith('#/webhooks/') ||
ref.startsWith('#/components/pathItems/') ||
INTO_REPLACED.test(ref)) &&
INTO_REPLACED.test(keywords)) &&
resolvePointer(ref, ctx) !== undefined
)
}
Expand Down
27 changes: 27 additions & 0 deletions packages/downgrader/tests/v3.1-to-v3.0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2932,6 +2932,33 @@ describe('downgradeSchemaV31ToV30', () => {
expect(out?.Count).toEqual(ref('Bag/properties/itemsLeft'))
})

it('keeps a $ref to a component named like a keyword it replaces', () => {
// Celigo names a recursive mapping row `items`: replacing the $ref cut
// the recursion to {} one level down.
const ref = (path: string) => ({ $ref: `#/components/schemas/${path}` })
const schemas = {
items: { type: 'object', properties: { rows: { type: 'array', items: ref('items') } } },
additionalProperties: { type: 'string' },
$defs: { type: 'object', properties: { a: { type: 'integer' } } },
definitions: { type: 'object', properties: { b: { type: 'integer' } } },
Map: { type: 'object', additionalProperties: ref('additionalProperties') },
Defs: ref('$defs/properties/a'),
Definitions: ref('definitions/properties/b'),
// A keyword after the name still counts.
Row: ref('items/properties/rows/items'),
} as const
const out = convert({
components: { schemas, responses: { items: { description: 'Rows' } } },
paths: {
'/rows': { get: { responses: { 200: { $ref: '#/components/responses/items' } } } },
},
})
expect(out.components?.schemas).toEqual({ ...schemas, Row: ref('items') })
expect(out.paths?.['/rows']?.get?.responses?.[200]).toEqual({
$ref: '#/components/responses/items',
})
})

it('replaces a percent-encoded $ref with its target', () => {
// find decodes it, so it can point into $defs, a removed part, or items
// that the conversion replaces, unseen.
Expand Down
Loading