Skip to content

Commit bc7ebb1

Browse files
authored
fix: do not resolve $ref in examples.value (#2392)
1 parent 2fc95f4 commit bc7ebb1

File tree

12 files changed

+344
-4
lines changed

12 files changed

+344
-4
lines changed

.changeset/loud-mails-hang.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@redocly/openapi-core": patch
3+
"@redocly/cli": patch
4+
---
5+
6+
Fixed an issue where the content of `$ref`s inside example values was erroneously resolved during bundling and linting.

docs/@v2/configuration/reference/resolve.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ One HTTP header is supported for each URL resolved.
2121

2222
- doNotResolveExamples
2323
- boolean
24-
- When running `lint`, set this option to `true` to avoid resolving `$ref` fields in examples. Resolving `$ref`s in other parts of the API is unaffected.
24+
- Set this option to `true` to prevent resolving `$ref` fields in singular `example` properties. This affects both `lint` and `bundle` commands. Resolving `$ref`s in other parts of the API description is unaffected.
2525

2626
---
2727

docs/@v2/configuration/resolve.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ properties:
1212
doNotResolveExamples:
1313
type: boolean
1414
description: >-
15-
You can stop `lint` from resolving `$refs` in examples by setting this configuration option to `true`.
15+
Set this option to `true` to prevent resolving `$ref` fields in singular `example` properties.
16+
This affects both `lint` and `bundle` commands.
1617
This does not affect `$ref` resolution in other parts of the API description.
1718
default: false
1819
http:
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import outdent from 'outdent';
2+
import * as path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
import { bundleDocument } from '../bundle/bundle-document.js';
5+
import { parseYamlToDocument, yamlSerializer } from '../../__tests__/utils.js';
6+
import { createConfig } from '../config/index.js';
7+
import { BaseResolver } from '../resolve.js';
8+
import { AsyncApi3Types, Oas3Types } from '../index.js';
9+
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
11+
12+
describe('Bundle Examples $ref Resolution', () => {
13+
expect.addSnapshotSerializer(yamlSerializer);
14+
15+
describe('OpenAPI 3.0/3.1 Examples', () => {
16+
it('should resolve $ref in Example field', async () => {
17+
const testDocument = parseYamlToDocument(
18+
outdent`
19+
openapi: 3.0.0
20+
info:
21+
title: Test API
22+
version: 1.0.0
23+
paths:
24+
/test:
25+
get:
26+
responses:
27+
'200':
28+
content:
29+
application/json:
30+
schema:
31+
type: object
32+
properties:
33+
id:
34+
type: integer
35+
name:
36+
type: string
37+
example:
38+
$ref: ${path.join(__dirname, 'fixtures/example-data.json')}
39+
`,
40+
''
41+
);
42+
43+
const { bundle: result, problems } = await bundleDocument({
44+
document: testDocument,
45+
externalRefResolver: new BaseResolver(),
46+
config: await createConfig({}),
47+
types: Oas3Types,
48+
});
49+
50+
expect(problems).toHaveLength(0);
51+
expect(result.parsed).toMatchInlineSnapshot(`
52+
openapi: 3.0.0
53+
info:
54+
title: Test API
55+
version: 1.0.0
56+
paths:
57+
/test:
58+
get:
59+
responses:
60+
'200':
61+
content:
62+
application/json:
63+
schema:
64+
type: object
65+
properties:
66+
id:
67+
type: integer
68+
name:
69+
type: string
70+
example:
71+
id: 123
72+
name: Test User
73+
components: {}
74+
`);
75+
});
76+
77+
it('should NOT resolve $ref in Example.value field', async () => {
78+
const testDocument = parseYamlToDocument(
79+
outdent`
80+
openapi: 3.1.0
81+
info:
82+
title: Test API
83+
version: 1.0.0
84+
paths:
85+
/test:
86+
get:
87+
responses:
88+
'200':
89+
content:
90+
application/json:
91+
examples:
92+
test-example:
93+
value:
94+
$ref: ./example-data.json
95+
`,
96+
''
97+
);
98+
99+
const { bundle: result, problems } = await bundleDocument({
100+
document: testDocument,
101+
externalRefResolver: new BaseResolver(),
102+
config: await createConfig({}),
103+
types: Oas3Types,
104+
});
105+
106+
expect(problems).toHaveLength(0);
107+
expect(result.parsed).toMatchInlineSnapshot(`
108+
openapi: 3.1.0
109+
info:
110+
title: Test API
111+
version: 1.0.0
112+
paths:
113+
/test:
114+
get:
115+
responses:
116+
'200':
117+
content:
118+
application/json:
119+
examples:
120+
test-example:
121+
value:
122+
$ref: ./example-data.json
123+
components: {}
124+
`);
125+
});
126+
});
127+
128+
describe('OpenAPI 3.2 Examples', () => {
129+
it('should NOT resolve $ref in Example.dataValue field', async () => {
130+
const testDocument = parseYamlToDocument(
131+
outdent`
132+
openapi: 3.2.0
133+
info:
134+
title: Test API
135+
version: 1.0.0
136+
paths:
137+
/test:
138+
get:
139+
responses:
140+
'200':
141+
content:
142+
application/json:
143+
examples:
144+
test-example:
145+
dataValue:
146+
$ref: ./example-data.json
147+
`,
148+
''
149+
);
150+
151+
const { bundle: result, problems } = await bundleDocument({
152+
document: testDocument,
153+
externalRefResolver: new BaseResolver(),
154+
config: await createConfig({}),
155+
types: Oas3Types,
156+
});
157+
158+
expect(problems).toHaveLength(0);
159+
expect(result.parsed).toMatchInlineSnapshot(`
160+
openapi: 3.2.0
161+
info:
162+
title: Test API
163+
version: 1.0.0
164+
paths:
165+
/test:
166+
get:
167+
responses:
168+
'200':
169+
content:
170+
application/json:
171+
examples:
172+
test-example:
173+
dataValue:
174+
$ref: ./example-data.json
175+
components: {}
176+
`);
177+
});
178+
});
179+
180+
describe('AsyncAPI 3.0 Examples', () => {
181+
it('should NOT resolve $ref in Example.value field', async () => {
182+
const testDocument = parseYamlToDocument(
183+
outdent`
184+
asyncapi: 3.0.0
185+
info:
186+
title: Test AsyncAPI
187+
version: 1.0.0
188+
operations:
189+
sendUserSignedup:
190+
action: send
191+
messages:
192+
- payload:
193+
type: object
194+
examples:
195+
test-example:
196+
value:
197+
$ref: ./example-data.json
198+
`,
199+
''
200+
);
201+
202+
const { bundle: result, problems } = await bundleDocument({
203+
document: testDocument,
204+
externalRefResolver: new BaseResolver(),
205+
config: await createConfig({}),
206+
types: AsyncApi3Types,
207+
});
208+
209+
expect(problems).toHaveLength(0);
210+
expect(result.parsed).toMatchInlineSnapshot(`
211+
asyncapi: 3.0.0
212+
info:
213+
title: Test AsyncAPI
214+
version: 1.0.0
215+
operations:
216+
sendUserSignedup:
217+
action: send
218+
messages:
219+
- payload:
220+
type: object
221+
examples:
222+
test-example:
223+
value:
224+
$ref: ./example-data.json
225+
components: {}
226+
`);
227+
});
228+
});
229+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"id": 123,
3+
"name": "Test User"
4+
}

packages/core/src/rules/oas3/__tests__/struct/referenceableScalars.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,38 @@ describe('Referenceable scalars', () => {
6969
});
7070
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
7171
});
72+
73+
it('should not report example value with $ref', async () => {
74+
const document = parseYamlToDocument(
75+
outdent`
76+
openapi: 3.2.0
77+
info:
78+
title: Test
79+
version: '1.0'
80+
paths:
81+
'/test':
82+
get:
83+
parameters:
84+
- name: test
85+
schema:
86+
type: object
87+
examples:
88+
test:
89+
value:
90+
$ref: not $ref, example
91+
`,
92+
__dirname + '/foobar.yaml'
93+
);
94+
95+
const results = await lintDocument({
96+
externalRefResolver: new BaseResolver(),
97+
document,
98+
config: await createConfig({
99+
rules: {
100+
'no-unresolved-refs': 'error',
101+
},
102+
}),
103+
});
104+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
105+
});
72106
});

packages/core/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type ScalarSchema = {
44
items?: ScalarSchema;
55
enum?: string[];
66
isExample?: boolean;
7+
resolvable?: boolean;
78
directResolveAs?: string;
89
minimum?: number;
910
};

packages/core/src/types/oas3.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ const MediaType: NodeType = {
233233

234234
const Example: NodeType = {
235235
properties: {
236-
value: { isExample: true },
236+
value: { resolvable: false },
237237
summary: { type: 'string' },
238238
description: { type: 'string' },
239239
externalValue: { type: 'string' },

packages/core/src/types/oas3_2.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ const Example: NodeType = {
194194
...Oas3_1Types.Example,
195195
properties: {
196196
...Oas3_1Types.Example.properties,
197-
dataValue: { isExample: true },
197+
dataValue: { resolvable: false },
198198
serializedValue: { type: 'string' },
199199
},
200200
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
openapi: 3.2.0
2+
info:
3+
title: Test API
4+
version: 1.0.0
5+
paths:
6+
/test:
7+
get:
8+
responses:
9+
'200':
10+
content:
11+
application/json:
12+
schema:
13+
type: object
14+
properties:
15+
$ref:
16+
type: string
17+
example:
18+
$ref: example.json # should NOT be resolved
19+
examples:
20+
Test:
21+
summary: Example with $ref in value (should NOT resolve)
22+
value:
23+
$ref: example.json # should NOT be resolved
24+
25+
TestDataValue:
26+
summary: Example with $ref in dataValue (should NOT resolve)
27+
dataValue:
28+
$ref: example.json # should NOT be resolved

0 commit comments

Comments
 (0)