@redocly/openapi-core 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @redocly/openapi-core
2
2
 
3
+ ## 1.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Made `redocly.yaml` validation consistent with the general Redocly config.
8
+
9
+ ### Patch Changes
10
+
11
+ - Fixed `no-invalid-media-type-examples`, `no-invalid-parameter-examples`, and `no-invalid-schema-examples` rules which allowed falsy example values to pass for any schema.
12
+
13
+ ## 1.14.0
14
+
15
+ ### Minor Changes
16
+
17
+ - Added the ability to exclude some operations or entire paths from the `security-defined` rule.
18
+
3
19
  ## 1.13.0
4
20
 
5
21
  ### Minor Changes
@@ -8,7 +8,7 @@ const NoInvalidParameterExamples = (opts) => {
8
8
  return {
9
9
  Parameter: {
10
10
  leave(parameter, ctx) {
11
- if (parameter.example) {
11
+ if (parameter.example !== undefined) {
12
12
  (0, utils_1.validateExample)(parameter.example, parameter.schema, ctx.location.child('example'), ctx, allowAdditionalProperties);
13
13
  }
14
14
  if (parameter.examples) {
@@ -13,7 +13,7 @@ const NoInvalidSchemaExamples = (opts) => {
13
13
  (0, utils_1.validateExample)(example, schema, ctx.location.child(['examples', schema.examples.indexOf(example)]), ctx, allowAdditionalProperties);
14
14
  }
15
15
  }
16
- if (schema.example) {
16
+ if (schema.example !== undefined) {
17
17
  (0, utils_1.validateExample)(schema.example, schema, ctx.location.child('example'), ctx, true);
18
18
  }
19
19
  },
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SecurityDefined = void 0;
4
- const SecurityDefined = () => {
4
+ const SecurityDefined = (opts) => {
5
5
  const referencedSchemes = new Map();
6
6
  const operationsWithoutSecurity = [];
7
7
  let eachOperationHasSecurity = true;
8
+ let path;
8
9
  return {
9
10
  Root: {
10
11
  leave(root, { report }) {
@@ -46,11 +47,22 @@ const SecurityDefined = () => {
46
47
  }
47
48
  }
48
49
  },
49
- Operation(operation, { location }) {
50
- if (!(operation === null || operation === void 0 ? void 0 : operation.security)) {
51
- eachOperationHasSecurity = false;
52
- operationsWithoutSecurity.push(location);
53
- }
50
+ PathItem: {
51
+ enter(pathItem, { key }) {
52
+ path = key;
53
+ },
54
+ Operation(operation, { location, key }) {
55
+ var _a;
56
+ const isException = (_a = opts.exceptions) === null || _a === void 0 ? void 0 : _a.some((item) => {
57
+ var _a;
58
+ return item.path === path &&
59
+ (!item.methods || ((_a = item.methods) === null || _a === void 0 ? void 0 : _a.some((method) => method.toLowerCase() === key)));
60
+ });
61
+ if (!(operation === null || operation === void 0 ? void 0 : operation.security) && !isException) {
62
+ eachOperationHasSecurity = false;
63
+ operationsWithoutSecurity.push(location);
64
+ }
65
+ },
54
66
  },
55
67
  };
56
68
  };
@@ -12,7 +12,7 @@ const ValidContentExamples = (opts) => {
12
12
  const { location, resolve } = ctx;
13
13
  if (!mediaType.schema)
14
14
  return;
15
- if (mediaType.example) {
15
+ if (mediaType.example !== undefined) {
16
16
  resolveAndValidateExample(mediaType.example, location.child('example'));
17
17
  }
18
18
  else if (mediaType.examples) {
@@ -19,7 +19,6 @@ export type Oas3_1NodeType = typeof oas3_1NodeTypesList[number];
19
19
  export declare const createConfigTypes: (extraSchemas: JSONSchema) => {
20
20
  ConfigRoot: NodeType;
21
21
  ConfigApisProperties: NodeType;
22
- ConfigRootTheme: NodeType;
23
22
  };
24
23
  export declare const ConfigTypes: Record<string, NodeType>;
25
24
  export {};
@@ -220,7 +220,7 @@ const ConfigStyleguide = {
220
220
  async2Decorators: { type: 'object' },
221
221
  },
222
222
  };
223
- const createConfigRoot = (nodeTypes) => (Object.assign(Object.assign({}, nodeTypes.rootRedoclyConfigSchema), { properties: Object.assign(Object.assign(Object.assign({}, nodeTypes.rootRedoclyConfigSchema.properties), ConfigStyleguide.properties), { apis: 'ConfigApis', theme: 'ConfigRootTheme', 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', organization: { type: 'string' }, region: { enum: ['us', 'eu'] }, telemetry: { enum: ['on', 'off'] }, resolve: {
223
+ const createConfigRoot = (nodeTypes) => (Object.assign(Object.assign({}, nodeTypes.rootRedoclyConfigSchema), { properties: Object.assign(Object.assign(Object.assign({}, nodeTypes.rootRedoclyConfigSchema.properties), ConfigStyleguide.properties), { apis: 'ConfigApis', 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', organization: { type: 'string' }, region: { enum: ['us', 'eu'] }, telemetry: { enum: ['on', 'off'] }, resolve: {
224
224
  properties: {
225
225
  http: 'ConfigHTTP',
226
226
  doNotResolveExamples: { type: 'boolean' },
@@ -259,10 +259,6 @@ const ConfigHTTP = {
259
259
  },
260
260
  },
261
261
  };
262
- const createConfigRootTheme = (nodeTypes) => {
263
- var _a;
264
- return (Object.assign(Object.assign({}, nodeTypes['rootRedoclyConfigSchema.theme']), { properties: Object.assign(Object.assign({}, (_a = nodeTypes['rootRedoclyConfigSchema.theme']) === null || _a === void 0 ? void 0 : _a.properties), { openapi: 'ConfigReferenceDocs' }) }));
265
- };
266
262
  const Rules = {
267
263
  properties: {},
268
264
  additionalProperties: (value, key) => {
@@ -765,8 +761,8 @@ const GenerateCodeSamples = {
765
761
  },
766
762
  required: ['languages'],
767
763
  };
764
+ // TODO: deprecated
768
765
  const ConfigReferenceDocs = {
769
- // TODO: partially invalid @Viacheslav
770
766
  properties: {
771
767
  theme: 'ConfigTheme',
772
768
  corsProxyUrl: { type: 'string' },
@@ -904,7 +900,7 @@ const ConfigMockServer = {
904
900
  const createConfigTypes = (extraSchemas) => {
905
901
  // Create types based on external schemas
906
902
  const nodeTypes = (0, json_schema_adapter_1.getNodeTypesFromJSONSchema)('rootRedoclyConfigSchema', extraSchemas);
907
- return Object.assign(Object.assign(Object.assign({}, CoreConfigTypes), { ConfigRoot: createConfigRoot(nodeTypes), ConfigApisProperties: createConfigApisProperties(nodeTypes), ConfigRootTheme: createConfigRootTheme(nodeTypes) }), nodeTypes);
903
+ return Object.assign(Object.assign(Object.assign({}, CoreConfigTypes), { ConfigRoot: createConfigRoot(nodeTypes), ConfigApisProperties: createConfigApisProperties(nodeTypes) }), nodeTypes);
908
904
  };
909
905
  exports.createConfigTypes = createConfigTypes;
910
906
  const CoreConfigTypes = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "@redocly/ajv": "^8.11.0",
38
- "@redocly/config": "^0.5.0",
38
+ "@redocly/config": "^0.6.0",
39
39
  "colorette": "^1.2.0",
40
40
  "js-levenshtein": "^1.1.6",
41
41
  "js-yaml": "^4.1.0",
@@ -369,26 +369,8 @@ describe('lint', () => {
369
369
  min: 3
370
370
  theme:
371
371
  openapi:
372
- showConsole: true
373
- layout:
374
- scope: section
375
- routingStrategy: browser
376
- theme:
377
- rightPanel:
378
- backgroundColor: '#263238'
379
- links:
380
- color: '#6CC496'
381
- theme:
382
- openapi:
383
- showConsole: true
384
- layout:
385
- scope: section
386
- routingStrategy: browser
387
- theme:
388
- rightPanel:
389
- backgroundColor: '#263238'
390
- links:
391
- color: '#6CC496'
372
+ showConsole: true # Not expected anymore
373
+ layout: wrong-option
392
374
  `,
393
375
  ''
394
376
  );
@@ -414,12 +396,12 @@ describe('lint', () => {
414
396
  "from": undefined,
415
397
  "location": [
416
398
  {
417
- "pointer": "#/theme/openapi/layout",
418
- "reportOnKey": false,
399
+ "pointer": "#/theme/openapi/showConsole",
400
+ "reportOnKey": true,
419
401
  "source": "",
420
402
  },
421
403
  ],
422
- "message": "\`layout\` can be one of the following only: "stacked", "three-panel".",
404
+ "message": "Property \`showConsole\` is not expected here.",
423
405
  "ruleId": "configuration spec",
424
406
  "severity": "error",
425
407
  "suggest": [],
@@ -428,18 +410,15 @@ describe('lint', () => {
428
410
  "from": undefined,
429
411
  "location": [
430
412
  {
431
- "pointer": "#/theme/openapi/theme/theme",
432
- "reportOnKey": true,
413
+ "pointer": "#/theme/openapi/layout",
414
+ "reportOnKey": false,
433
415
  "source": "",
434
416
  },
435
417
  ],
436
- "message": "Property \`theme\` is not expected here.",
418
+ "message": "\`layout\` can be one of the following only: "stacked", "three-panel".",
437
419
  "ruleId": "configuration spec",
438
420
  "severity": "error",
439
- "suggest": [
440
- "schema",
441
- "shape",
442
- ],
421
+ "suggest": [],
443
422
  },
444
423
  ]
445
424
  `);
@@ -806,12 +785,12 @@ describe('lint', () => {
806
785
  "from": undefined,
807
786
  "location": [
808
787
  {
809
- "pointer": "#/apis/with-theme/theme/openapi",
810
- "reportOnKey": false,
788
+ "pointer": "#/apis/with-theme/theme/not-expected",
789
+ "reportOnKey": true,
811
790
  "source": "",
812
791
  },
813
792
  ],
814
- "message": "Expected type \`object\` but got \`string\`.",
793
+ "message": "Property \`not-expected\` is not expected here.",
815
794
  "ruleId": "configuration spec",
816
795
  "severity": "error",
817
796
  "suggest": [],
@@ -820,12 +799,12 @@ describe('lint', () => {
820
799
  "from": undefined,
821
800
  "location": [
822
801
  {
823
- "pointer": "#/apis/with-theme/theme/not-expected",
824
- "reportOnKey": true,
802
+ "pointer": "#/apis/with-theme/theme/openapi",
803
+ "reportOnKey": false,
825
804
  "source": "",
826
805
  },
827
806
  ],
828
- "message": "Property \`not-expected\` is not expected here.",
807
+ "message": "Expected type \`rootRedoclyConfigSchema.apis_additionalProperties.theme.openapi\` (object) but got \`string\`",
829
808
  "ruleId": "configuration spec",
830
809
  "severity": "error",
831
810
  "suggest": [],
@@ -149,19 +149,6 @@ describe('getConfig', () => {
149
149
  "severity": "warn",
150
150
  "suggest": [],
151
151
  },
152
- {
153
- "location": [
154
- {
155
- "pointer": "#/theme",
156
- "reportOnKey": false,
157
- "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
158
- },
159
- ],
160
- "message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
161
- "ruleId": "configuration no-unresolved-refs",
162
- "severity": "warn",
163
- "suggest": [],
164
- },
165
152
  {
166
153
  "from": {
167
154
  "pointer": "#/rules",
@@ -179,6 +166,19 @@ describe('getConfig', () => {
179
166
  "severity": "warn",
180
167
  "suggest": [],
181
168
  },
169
+ {
170
+ "location": [
171
+ {
172
+ "pointer": "#/theme",
173
+ "reportOnKey": false,
174
+ "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
175
+ },
176
+ ],
177
+ "message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
178
+ "ruleId": "configuration no-unresolved-refs",
179
+ "severity": "warn",
180
+ "suggest": [],
181
+ },
182
182
  ]
183
183
  `);
184
184
  });
@@ -0,0 +1,53 @@
1
+ import { outdent } from 'outdent';
2
+ import { lintDocument } from '../../../lint';
3
+ import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
4
+ import { BaseResolver } from '../../../resolve';
5
+
6
+ describe('no-invalid-parameter-examples', () => {
7
+ it('should report on invalid falsy example', async () => {
8
+ const document = parseYamlToDocument(
9
+ outdent`
10
+ openapi: 3.1.0
11
+ paths:
12
+ /results:
13
+ get:
14
+ parameters:
15
+ - name: username
16
+ in: query
17
+ schema:
18
+ type: string
19
+ maxLength: 15
20
+ example: false
21
+ `,
22
+ 'foobar.yaml'
23
+ );
24
+
25
+ const results = await lintDocument({
26
+ externalRefResolver: new BaseResolver(),
27
+ document,
28
+ config: await makeConfig({ 'no-invalid-parameter-examples': 'error' }),
29
+ });
30
+
31
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
32
+ [
33
+ {
34
+ "from": {
35
+ "pointer": "#/paths/~1results/get/parameters/0",
36
+ "source": "foobar.yaml",
37
+ },
38
+ "location": [
39
+ {
40
+ "pointer": "#/paths/~1results/get/parameters/0/example",
41
+ "reportOnKey": false,
42
+ "source": "foobar.yaml",
43
+ },
44
+ ],
45
+ "message": "Example value must conform to the schema: type must be string.",
46
+ "ruleId": "no-invalid-parameter-examples",
47
+ "severity": "error",
48
+ "suggest": [],
49
+ },
50
+ ]
51
+ `);
52
+ });
53
+ });
@@ -0,0 +1,51 @@
1
+ import { outdent } from 'outdent';
2
+ import { lintDocument } from '../../../lint';
3
+ import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
4
+ import { BaseResolver } from '../../../resolve';
5
+
6
+ describe('no-invalid-schema-examples', () => {
7
+ it('should report on invalid falsy example', async () => {
8
+ const document = parseYamlToDocument(
9
+ outdent`
10
+ openapi: 3.1.0
11
+ components:
12
+ schemas:
13
+ Car:
14
+ type: object
15
+ properties:
16
+ color:
17
+ type: string
18
+ example: 0
19
+ `,
20
+ 'foobar.yaml'
21
+ );
22
+
23
+ const results = await lintDocument({
24
+ externalRefResolver: new BaseResolver(),
25
+ document,
26
+ config: await makeConfig({ 'no-invalid-schema-examples': 'error' }),
27
+ });
28
+
29
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
30
+ [
31
+ {
32
+ "from": {
33
+ "pointer": "#/components/schemas/Car/properties/color",
34
+ "source": "foobar.yaml",
35
+ },
36
+ "location": [
37
+ {
38
+ "pointer": "#/components/schemas/Car/properties/color/example",
39
+ "reportOnKey": false,
40
+ "source": "foobar.yaml",
41
+ },
42
+ ],
43
+ "message": "Example value must conform to the schema: type must be string.",
44
+ "ruleId": "no-invalid-schema-examples",
45
+ "severity": "error",
46
+ "suggest": [],
47
+ },
48
+ ]
49
+ `);
50
+ });
51
+ });
@@ -172,4 +172,107 @@ describe('Oas3 security-defined', () => {
172
172
 
173
173
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
174
174
  });
175
+
176
+ it('should not report if a pathItem is explicitly excluded in the option', async () => {
177
+ const document = parseYamlToDocument(
178
+ outdent`
179
+ openapi: 3.1.0
180
+ paths:
181
+ /excluded:
182
+ get:
183
+ description: Should be skipped.
184
+ post:
185
+ description: Should be skipped.`,
186
+ 'foobar.yaml'
187
+ );
188
+
189
+ const results = await lintDocument({
190
+ externalRefResolver: new BaseResolver(),
191
+ document,
192
+ config: await makeConfig({
193
+ 'security-defined': { exceptions: [{ path: '/excluded' }] },
194
+ }),
195
+ });
196
+
197
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
198
+ });
199
+
200
+ it('should report only those operations without security defined that are not excluded in the options', async () => {
201
+ const document = parseYamlToDocument(
202
+ outdent`
203
+ openapi: 3.1.0
204
+ paths:
205
+ /partially-excluded:
206
+ get:
207
+ description: Should be skipped.
208
+ post:
209
+ description: Has security.
210
+ security: []
211
+ delete:
212
+ description: Should have security defined.`,
213
+ 'foobar.yaml'
214
+ );
215
+
216
+ const results = await lintDocument({
217
+ externalRefResolver: new BaseResolver(),
218
+ document,
219
+ config: await makeConfig({
220
+ 'security-defined': { exceptions: [{ path: '/partially-excluded', methods: ['GET'] }] },
221
+ }),
222
+ });
223
+
224
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
225
+ [
226
+ {
227
+ "location": [
228
+ {
229
+ "pointer": "#/paths/~1partially-excluded/delete",
230
+ "reportOnKey": true,
231
+ "source": "foobar.yaml",
232
+ },
233
+ ],
234
+ "message": "Every operation should have security defined on it or on the root level.",
235
+ "ruleId": "security-defined",
236
+ "severity": "error",
237
+ "suggest": [],
238
+ },
239
+ ]
240
+ `);
241
+ });
242
+
243
+ it('should report operations from path items that are not excluded', async () => {
244
+ const document = parseYamlToDocument(
245
+ outdent`
246
+ openapi: 3.1.0
247
+ paths:
248
+ /not-excluded:
249
+ get:
250
+ summary: Should have security defined.`,
251
+ 'foobar.yaml'
252
+ );
253
+
254
+ const results = await lintDocument({
255
+ externalRefResolver: new BaseResolver(),
256
+ document,
257
+ config: await makeConfig({ 'security-defined': { exceptions: [{ path: '/excluded' }] } }),
258
+ });
259
+
260
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
261
+ [
262
+ {
263
+ "location": [
264
+ {
265
+ "pointer": "#/paths/~1not-excluded/get",
266
+ "reportOnKey": true,
267
+ "source": "foobar.yaml",
268
+ },
269
+ ],
270
+ "message": "Every operation should have security defined on it or on the root level.",
271
+ "ruleId": "security-defined",
272
+ "severity": "error",
273
+ "suggest": [],
274
+ },
275
+ ]
276
+ `);
277
+ });
175
278
  });
@@ -7,7 +7,7 @@ export const NoInvalidParameterExamples: any = (opts: any) => {
7
7
  return {
8
8
  Parameter: {
9
9
  leave(parameter: Oas3Parameter, ctx: UserContext) {
10
- if (parameter.example) {
10
+ if (parameter.example !== undefined) {
11
11
  validateExample(
12
12
  parameter.example,
13
13
  parameter.schema!,
@@ -18,7 +18,7 @@ export const NoInvalidSchemaExamples: any = (opts: any) => {
18
18
  );
19
19
  }
20
20
  }
21
- if (schema.example) {
21
+ if (schema.example !== undefined) {
22
22
  validateExample(schema.example, schema, ctx.location.child('example'), ctx, true);
23
23
  }
24
24
  },
@@ -1,10 +1,22 @@
1
1
  import { Oas3Rule, Oas2Rule } from '../../visitors';
2
2
  import { Location } from '../../ref-utils';
3
3
  import { UserContext } from '../../walk';
4
- import { Oas2Definition, Oas2Operation, Oas2SecurityScheme } from '../../typings/swagger';
5
- import { Oas3Definition, Oas3Operation, Oas3SecurityScheme } from '../../typings/openapi';
4
+ import {
5
+ Oas2Definition,
6
+ Oas2Operation,
7
+ Oas2PathItem,
8
+ Oas2SecurityScheme,
9
+ } from '../../typings/swagger';
10
+ import {
11
+ Oas3Definition,
12
+ Oas3Operation,
13
+ Oas3PathItem,
14
+ Oas3SecurityScheme,
15
+ } from '../../typings/openapi';
6
16
 
7
- export const SecurityDefined: Oas3Rule | Oas2Rule = () => {
17
+ export const SecurityDefined: Oas3Rule | Oas2Rule = (opts: {
18
+ exceptions?: { path: string; methods?: string[] }[];
19
+ }) => {
8
20
  const referencedSchemes = new Map<
9
21
  string,
10
22
  {
@@ -15,6 +27,7 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = () => {
15
27
 
16
28
  const operationsWithoutSecurity: Location[] = [];
17
29
  let eachOperationHasSecurity: boolean = true;
30
+ let path: string | undefined;
18
31
 
19
32
  return {
20
33
  Root: {
@@ -55,11 +68,21 @@ export const SecurityDefined: Oas3Rule | Oas2Rule = () => {
55
68
  }
56
69
  }
57
70
  },
58
- Operation(operation: Oas2Operation | Oas3Operation, { location }: UserContext) {
59
- if (!operation?.security) {
60
- eachOperationHasSecurity = false;
61
- operationsWithoutSecurity.push(location);
62
- }
71
+ PathItem: {
72
+ enter(pathItem: Oas2PathItem | Oas3PathItem, { key }: UserContext) {
73
+ path = key as string;
74
+ },
75
+ Operation(operation: Oas2Operation | Oas3Operation, { location, key }: UserContext) {
76
+ const isException = opts.exceptions?.some(
77
+ (item) =>
78
+ item.path === path &&
79
+ (!item.methods || item.methods?.some((method) => method.toLowerCase() === key))
80
+ );
81
+ if (!operation?.security && !isException) {
82
+ eachOperationHasSecurity = false;
83
+ operationsWithoutSecurity.push(location);
84
+ }
85
+ },
63
86
  },
64
87
  };
65
88
  };
@@ -137,6 +137,58 @@ describe('no-invalid-media-type-examples', () => {
137
137
  `);
138
138
  });
139
139
 
140
+ it('should report on invalid example with a falsy value', async () => {
141
+ const document = parseYamlToDocument(
142
+ outdent`
143
+ openapi: 3.1.0
144
+ paths:
145
+ /test:
146
+ get:
147
+ responses:
148
+ '200':
149
+ content:
150
+ application/json:
151
+ schema:
152
+ type: string
153
+ example: false
154
+ `,
155
+ 'foobar.yaml'
156
+ );
157
+
158
+ const results = await lintDocument({
159
+ externalRefResolver: new BaseResolver(),
160
+ document,
161
+ config: await makeConfig({
162
+ 'no-invalid-media-type-examples': {
163
+ severity: 'error',
164
+ allowAdditionalProperties: false,
165
+ },
166
+ }),
167
+ });
168
+
169
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
170
+ [
171
+ {
172
+ "from": {
173
+ "pointer": "#/paths/~1test/get/responses/200/content/application~1json",
174
+ "source": "foobar.yaml",
175
+ },
176
+ "location": [
177
+ {
178
+ "pointer": "#/paths/~1test/get/responses/200/content/application~1json/example",
179
+ "reportOnKey": false,
180
+ "source": "foobar.yaml",
181
+ },
182
+ ],
183
+ "message": "Example value must conform to the schema: type must be string.",
184
+ "ruleId": "no-invalid-media-type-examples",
185
+ "severity": "error",
186
+ "suggest": [],
187
+ },
188
+ ]
189
+ `);
190
+ });
191
+
140
192
  it('should not report on valid example with allowAdditionalProperties', async () => {
141
193
  const document = parseYamlToDocument(
142
194
  outdent`
@@ -12,7 +12,7 @@ export const ValidContentExamples: Oas3Rule = (opts) => {
12
12
  leave(mediaType, ctx: UserContext) {
13
13
  const { location, resolve } = ctx;
14
14
  if (!mediaType.schema) return;
15
- if (mediaType.example) {
15
+ if (mediaType.example !== undefined) {
16
16
  resolveAndValidateExample(mediaType.example, location.child('example'));
17
17
  } else if (mediaType.examples) {
18
18
  for (const exampleName of Object.keys(mediaType.examples)) {