mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-09 20:57:44 +00:00
fix: schema property:example should be validated (#375)
* fix: schema property:example should be validated * chore: schema-example separate rule * test: example-schema: string, number, int * chore: example object in schema test + minor rule fix * chore: support v3.1.0 * chore: rewrite schema-example-type rule using ajv * update tests for 3.1 * chore: no-invalid-parameter-examples rule * fix: reporting * chore: added disallowAdditionalProperties * docs: rules no-invalid parameter & schema examples
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
apiDefinitions:
|
||||||
|
main: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
no-invalid-schema-examples: error
|
||||||
|
extends: []
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
openapi: '3.0.0'
|
||||||
|
info:
|
||||||
|
version: v0
|
||||||
|
title: my_Api
|
||||||
|
description: my_api
|
||||||
|
contact:
|
||||||
|
name: my_api
|
||||||
|
license:
|
||||||
|
name: Proprietary
|
||||||
|
url: https://my_api.com
|
||||||
|
servers:
|
||||||
|
- url: https://my_api.com
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/my_post:
|
||||||
|
post:
|
||||||
|
operationId: my_post
|
||||||
|
description: my_post
|
||||||
|
summary: my_post
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
my_list:
|
||||||
|
type: array
|
||||||
|
uniqueItems: true
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
test
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: My 200 response
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint no-invalid-schema-examples-array-error 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
[1] openapi.yaml:34:21 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/my_list/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be array.
|
||||||
|
|
||||||
|
32 | type: string
|
||||||
|
33 | example:
|
||||||
|
34 | test
|
||||||
|
| ^^^^
|
||||||
|
35 | responses:
|
||||||
|
36 | '200':
|
||||||
|
|
||||||
|
referenced from openapi.yaml:29:19
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
/openapi.yaml: validated in <test>ms
|
||||||
|
|
||||||
|
❌ Validation failed with 1 error.
|
||||||
|
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiDefinitions:
|
||||||
|
main: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
no-invalid-schema-examples: error
|
||||||
|
extends: []
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
openapi: '3.0.0'
|
||||||
|
info:
|
||||||
|
version: v0
|
||||||
|
title: my_Api
|
||||||
|
description: my_api
|
||||||
|
contact:
|
||||||
|
name: my_api
|
||||||
|
license:
|
||||||
|
name: Proprietary
|
||||||
|
url: https://my_api.com
|
||||||
|
servers:
|
||||||
|
- url: https://my_api.com
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/pet:
|
||||||
|
parameters:
|
||||||
|
- name: Test
|
||||||
|
schema:
|
||||||
|
example:
|
||||||
|
property: 42
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
property:
|
||||||
|
type: string
|
||||||
|
/my_post:
|
||||||
|
post:
|
||||||
|
operationId: my_post
|
||||||
|
description: my post
|
||||||
|
summary: my_post
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: ID
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Test'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: successful operation
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Test:
|
||||||
|
type: object
|
||||||
|
example: test example
|
||||||
|
properties:
|
||||||
|
my_list:
|
||||||
|
type: string
|
||||||
|
example: 50
|
||||||
|
nested:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Dog'
|
||||||
|
- type: object
|
||||||
|
example: dog
|
||||||
|
properties:
|
||||||
|
huntingSkill:
|
||||||
|
type: string
|
||||||
|
example: 100
|
||||||
|
nested_schema:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/Category'
|
||||||
|
Dog:
|
||||||
|
type: object
|
||||||
|
example: test dog example
|
||||||
|
properties:
|
||||||
|
my_list:
|
||||||
|
type: string
|
||||||
|
example: 32
|
||||||
|
Category:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
description: Category ID
|
||||||
|
example: category example
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint no-invalid-schema-examples-nested-error 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
[1] openapi.yaml:20:23 at #/paths/~1pet/parameters/0/schema/example/property
|
||||||
|
|
||||||
|
Example value must conform to the schema: \`property\` property type must be string.
|
||||||
|
|
||||||
|
18 | schema:
|
||||||
|
19 | example:
|
||||||
|
20 | property: 42
|
||||||
|
| ^^
|
||||||
|
21 | type: object
|
||||||
|
22 | properties:
|
||||||
|
|
||||||
|
referenced from openapi.yaml:19:11
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[2] openapi.yaml:53:20 at #/components/schemas/Test/properties/my_list/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be string.
|
||||||
|
|
||||||
|
51 | my_list:
|
||||||
|
52 | type: string
|
||||||
|
53 | example: 50
|
||||||
|
| ^^
|
||||||
|
54 | nested:
|
||||||
|
55 | allOf:
|
||||||
|
|
||||||
|
referenced from openapi.yaml:52:11
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[3] openapi.yaml:72:20 at #/components/schemas/Dog/properties/my_list/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be string.
|
||||||
|
|
||||||
|
70 | my_list:
|
||||||
|
71 | type: string
|
||||||
|
72 | example: 32
|
||||||
|
| ^^
|
||||||
|
73 | Category:
|
||||||
|
74 | type: object
|
||||||
|
|
||||||
|
referenced from openapi.yaml:71:11
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[4] openapi.yaml:68:16 at #/components/schemas/Dog/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be object.
|
||||||
|
|
||||||
|
66 | Dog:
|
||||||
|
67 | type: object
|
||||||
|
68 | example: test dog example
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
69 | properties:
|
||||||
|
70 | my_list:
|
||||||
|
|
||||||
|
referenced from openapi.yaml:67:7
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[5] openapi.yaml:62:28 at #/components/schemas/Test/properties/nested/allOf/1/properties/huntingSkill/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be string.
|
||||||
|
|
||||||
|
60 | huntingSkill:
|
||||||
|
61 | type: string
|
||||||
|
62 | example: 100
|
||||||
|
| ^^^
|
||||||
|
63 | nested_schema:
|
||||||
|
64 | oneOf:
|
||||||
|
|
||||||
|
referenced from openapi.yaml:61:19
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[6] openapi.yaml:79:20 at #/components/schemas/Category/properties/id/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be number.
|
||||||
|
|
||||||
|
77 | type: number
|
||||||
|
78 | description: Category ID
|
||||||
|
79 | example: category example
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
80 |
|
||||||
|
|
||||||
|
referenced from openapi.yaml:77:11
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[7] openapi.yaml:58:24 at #/components/schemas/Test/properties/nested/allOf/1/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be object.
|
||||||
|
|
||||||
|
56 | - $ref: '#/components/schemas/Dog'
|
||||||
|
57 | - type: object
|
||||||
|
58 | example: dog
|
||||||
|
| ^^^
|
||||||
|
59 | properties:
|
||||||
|
60 | huntingSkill:
|
||||||
|
|
||||||
|
referenced from openapi.yaml:57:15
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[8] openapi.yaml:49:16 at #/components/schemas/Test/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be object.
|
||||||
|
|
||||||
|
47 | Test:
|
||||||
|
48 | type: object
|
||||||
|
49 | example: test example
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
50 | properties:
|
||||||
|
51 | my_list:
|
||||||
|
|
||||||
|
referenced from openapi.yaml:48:7
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
/openapi.yaml: validated in <test>ms
|
||||||
|
|
||||||
|
❌ Validation failed with 8 errors.
|
||||||
|
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiDefinitions:
|
||||||
|
main: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
no-invalid-schema-examples: error
|
||||||
|
extends: []
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
openapi: '3.1.0'
|
||||||
|
info:
|
||||||
|
version: v0
|
||||||
|
title: my_Api
|
||||||
|
description: my_api
|
||||||
|
contact:
|
||||||
|
name: my_api
|
||||||
|
license:
|
||||||
|
name: Proprietary
|
||||||
|
url: https://my_api.com
|
||||||
|
servers:
|
||||||
|
- url: https://my_api.com
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/my_post:
|
||||||
|
post:
|
||||||
|
operationId: my_post
|
||||||
|
description: my_post
|
||||||
|
summary: my_post
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
one:
|
||||||
|
description: type array
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example:
|
||||||
|
- test
|
||||||
|
two:
|
||||||
|
description: two
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
examples:
|
||||||
|
- test
|
||||||
|
- 25
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: My 200 response
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint no-invalid-schema-examples-oas3.1-error 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
[1] openapi.yaml:34:21 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/one/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be integer,string.
|
||||||
|
|
||||||
|
32 | - string
|
||||||
|
33 | example:
|
||||||
|
34 | - test
|
||||||
|
| ^^^^^^
|
||||||
|
35 | two:
|
||||||
|
| ^
|
||||||
|
36 | description: two
|
||||||
|
37 | type:
|
||||||
|
|
||||||
|
referenced from openapi.yaml:29:19
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[2] openapi.yaml:41:23 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/two/examples/1
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be string.
|
||||||
|
|
||||||
|
39 | examples:
|
||||||
|
40 | - test
|
||||||
|
41 | - 25
|
||||||
|
| ^^
|
||||||
|
42 | responses:
|
||||||
|
43 | '200':
|
||||||
|
|
||||||
|
referenced from openapi.yaml:36:19
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
/openapi.yaml: validated in <test>ms
|
||||||
|
|
||||||
|
❌ Validation failed with 2 errors.
|
||||||
|
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiDefinitions:
|
||||||
|
main: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
no-invalid-schema-examples: error
|
||||||
|
extends: []
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
openapi: '3.1.0'
|
||||||
|
info:
|
||||||
|
version: v0
|
||||||
|
title: my_Api
|
||||||
|
description: my_api
|
||||||
|
contact:
|
||||||
|
name: my_api
|
||||||
|
license:
|
||||||
|
name: Proprietary
|
||||||
|
url: https://my_api.com
|
||||||
|
servers:
|
||||||
|
- url: https://my_api.com
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/my_post:
|
||||||
|
post:
|
||||||
|
operationId: my_post
|
||||||
|
description: my_post
|
||||||
|
summary: my_post
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
one:
|
||||||
|
description: type array
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: test
|
||||||
|
two:
|
||||||
|
description: two
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
examples:
|
||||||
|
- test
|
||||||
|
- 25
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: My 200 response
|
||||||
11
__tests__/lint/no-invalid-schema-examples-oas3.1/snapshot.js
Normal file
11
__tests__/lint/no-invalid-schema-examples-oas3.1/snapshot.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint no-invalid-schema-examples-oas3.1 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
/openapi.yaml: validated in <test>ms
|
||||||
|
|
||||||
|
Woohoo! Your OpenAPI definition is valid. 🎉
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiDefinitions:
|
||||||
|
main: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
no-invalid-schema-examples: error
|
||||||
|
extends: []
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
openapi: '3.0.0'
|
||||||
|
info:
|
||||||
|
version: v0
|
||||||
|
title: my_Api
|
||||||
|
description: my_api
|
||||||
|
contact:
|
||||||
|
name: my_api
|
||||||
|
license:
|
||||||
|
name: Proprietary
|
||||||
|
url: https://my_api.com
|
||||||
|
servers:
|
||||||
|
- url: https://my_api.com
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/my_post:
|
||||||
|
post:
|
||||||
|
operationId: my_post
|
||||||
|
description: my_post
|
||||||
|
summary: my_post
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
prop_string:
|
||||||
|
type: string
|
||||||
|
example: 56
|
||||||
|
prop_number:
|
||||||
|
type: number
|
||||||
|
example: test
|
||||||
|
prop_integer:
|
||||||
|
type: integer
|
||||||
|
example: 5.34
|
||||||
|
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: My 200 response
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint no-invalid-schema-examples-string-number-error 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
[1] openapi.yaml:30:28 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/prop_string/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be string.
|
||||||
|
|
||||||
|
28 | prop_string:
|
||||||
|
29 | type: string
|
||||||
|
30 | example: 56
|
||||||
|
| ^^
|
||||||
|
31 | prop_number:
|
||||||
|
32 | type: number
|
||||||
|
|
||||||
|
referenced from openapi.yaml:29:19
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[2] openapi.yaml:33:28 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/prop_number/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be number.
|
||||||
|
|
||||||
|
31 | prop_number:
|
||||||
|
32 | type: number
|
||||||
|
33 | example: test
|
||||||
|
| ^^^^
|
||||||
|
34 | prop_integer:
|
||||||
|
35 | type: integer
|
||||||
|
|
||||||
|
referenced from openapi.yaml:32:19
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
[3] openapi.yaml:36:28 at #/paths/~1my_post/post/requestBody/content/application~1json/schema/properties/prop_integer/example
|
||||||
|
|
||||||
|
Example value must conform to the schema: type must be integer.
|
||||||
|
|
||||||
|
34 | prop_integer:
|
||||||
|
35 | type: integer
|
||||||
|
36 | example: 5.34
|
||||||
|
| ^^^^
|
||||||
|
37 |
|
||||||
|
38 | responses:
|
||||||
|
|
||||||
|
referenced from openapi.yaml:35:19
|
||||||
|
|
||||||
|
Error was generated by the no-invalid-schema-examples rule.
|
||||||
|
|
||||||
|
|
||||||
|
/openapi.yaml: validated in <test>ms
|
||||||
|
|
||||||
|
❌ Validation failed with 3 errors.
|
||||||
|
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
7
__tests__/lint/no-invalid-schema-examples/.redocly.yaml
Normal file
7
__tests__/lint/no-invalid-schema-examples/.redocly.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiDefinitions:
|
||||||
|
main: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
no-invalid-schema-examples: error
|
||||||
|
extends: []
|
||||||
79
__tests__/lint/no-invalid-schema-examples/openapi.yaml
Normal file
79
__tests__/lint/no-invalid-schema-examples/openapi.yaml
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
openapi: '3.0.0'
|
||||||
|
info:
|
||||||
|
version: v0
|
||||||
|
title: my_Api
|
||||||
|
description: my_api
|
||||||
|
contact:
|
||||||
|
name: my_api
|
||||||
|
license:
|
||||||
|
name: Proprietary
|
||||||
|
url: https://my_api.com
|
||||||
|
servers:
|
||||||
|
- url: https://my_api.com
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/pet:
|
||||||
|
parameters:
|
||||||
|
- name: Test
|
||||||
|
schema:
|
||||||
|
example:
|
||||||
|
property: prop
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
property:
|
||||||
|
type: string
|
||||||
|
/my_post:
|
||||||
|
post:
|
||||||
|
operationId: my_post
|
||||||
|
description: my post
|
||||||
|
summary: my_post
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: ID
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Test'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: successful operation
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Test:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
my_list:
|
||||||
|
type: string
|
||||||
|
example: list
|
||||||
|
nested:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Dog'
|
||||||
|
- type: object
|
||||||
|
example:
|
||||||
|
pet: dog
|
||||||
|
properties:
|
||||||
|
huntingSkill:
|
||||||
|
type: string
|
||||||
|
example: skill
|
||||||
|
nested_schema:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/Category'
|
||||||
|
Dog:
|
||||||
|
type: object
|
||||||
|
example:
|
||||||
|
status: 400
|
||||||
|
properties:
|
||||||
|
my_list:
|
||||||
|
type: string
|
||||||
|
example: my list
|
||||||
|
Category:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
description: Category ID
|
||||||
|
example: 45.78
|
||||||
11
__tests__/lint/no-invalid-schema-examples/snapshot.js
Normal file
11
__tests__/lint/no-invalid-schema-examples/snapshot.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint no-invalid-schema-examples 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
/openapi.yaml: validated in <test>ms
|
||||||
|
|
||||||
|
Woohoo! Your OpenAPI definition is valid. 🎉
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`E2E spec-error-if-minimum-not-correct 1`] = `
|
exports[`E2E lint spec-error-if-minimum-not-correct 1`] = `
|
||||||
|
|
||||||
No configurations were defined in extends -- using built in recommended configuration by default.
|
No configurations were defined in extends -- using built in recommended configuration by default.
|
||||||
|
|
||||||
|
|||||||
@@ -242,6 +242,29 @@ lint:
|
|||||||
- application/json
|
- application/json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### no-invalid-schema-examples
|
||||||
|
|
||||||
|
Verifies that schema example value conforms to the schema. Disallows additional properties by default.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
lint:
|
||||||
|
no-invalid-schema-examples:
|
||||||
|
severity: error
|
||||||
|
disallowAdditionalProperties: false
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### no-invalid-parameter-examples
|
||||||
|
|
||||||
|
Verifies that parameter example value conforms to the schema. Disallows additional properties by default.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
lint:
|
||||||
|
no-invalid-parameter-examples:
|
||||||
|
severity: error
|
||||||
|
disallowAdditionalProperties: false
|
||||||
|
```
|
||||||
|
|
||||||
## Recommended config
|
## Recommended config
|
||||||
|
|
||||||
There are three built-in configurations:
|
There are three built-in configurations:
|
||||||
|
|||||||
@@ -24,8 +24,7 @@
|
|||||||
"preview": "npm run cli preview-docs resources/pets.yaml",
|
"preview": "npm run cli preview-docs resources/pets.yaml",
|
||||||
"benchmark": "node --expose-gc --noconcurrent_sweeping --predictable packages/core/src/benchmark/benchmark.js",
|
"benchmark": "node --expose-gc --noconcurrent_sweeping --predictable packages/core/src/benchmark/benchmark.js",
|
||||||
"webpack-bundle": "webpack --config webpack.config.ts",
|
"webpack-bundle": "webpack --config webpack.config.ts",
|
||||||
"upload": "node scripts/archive-and-upload-bundle.js",
|
"upload": "node scripts/archive-and-upload-bundle.js"
|
||||||
"lint_test": "npm run cli lint -- --config resources/config.yaml --format stylish"
|
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@@ -82,4 +81,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export default {
|
|||||||
},
|
},
|
||||||
'request-mime-type': 'error',
|
'request-mime-type': 'error',
|
||||||
spec: 'error',
|
spec: 'error',
|
||||||
|
'no-invalid-schema-examples': 'error',
|
||||||
|
'no-invalid-parameter-examples': 'error',
|
||||||
},
|
},
|
||||||
oas3_0Rules: {
|
oas3_0Rules: {
|
||||||
'no-invalid-media-type-examples': 'error',
|
'no-invalid-media-type-examples': 'error',
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { UserContext } from '../../walk';
|
||||||
|
import { Oas3Parameter } from '../../typings/openapi';
|
||||||
|
import { validateExample } from '../utils';
|
||||||
|
|
||||||
|
export const NoInvalidParameterExamples: any = (opts: any) => {
|
||||||
|
const disallowAdditionalProperties = opts.disallowAdditionalProperties ?? true;
|
||||||
|
return {
|
||||||
|
Parameter: {
|
||||||
|
leave(parameter: Oas3Parameter, ctx: UserContext) {
|
||||||
|
if (parameter.example) {
|
||||||
|
validateExample(
|
||||||
|
parameter.example,
|
||||||
|
parameter.schema!,
|
||||||
|
ctx.location.child('example'),
|
||||||
|
ctx,
|
||||||
|
disallowAdditionalProperties,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter.examples) {
|
||||||
|
for (const [key, example] of Object.entries(parameter.examples)) {
|
||||||
|
if ('value' in example) {
|
||||||
|
validateExample(
|
||||||
|
example.value,
|
||||||
|
parameter.schema!,
|
||||||
|
ctx.location.child(['examples', key]),
|
||||||
|
ctx,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
27
packages/core/src/rules/common/no-invalid-schema-examples.ts
Normal file
27
packages/core/src/rules/common/no-invalid-schema-examples.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { UserContext } from '../../walk';
|
||||||
|
import { Oas3_1Schema } from '../../typings/openapi';
|
||||||
|
import { validateExample } from '../utils';
|
||||||
|
|
||||||
|
export const NoInvalidSchemaExamples: any = (opts: any) => {
|
||||||
|
const disallowAdditionalProperties = opts.disallowAdditionalProperties ?? true;
|
||||||
|
return {
|
||||||
|
Schema: {
|
||||||
|
leave(schema: Oas3_1Schema, ctx: UserContext) {
|
||||||
|
if (schema.examples) {
|
||||||
|
for (const example of schema.examples) {
|
||||||
|
validateExample(
|
||||||
|
example,
|
||||||
|
schema,
|
||||||
|
ctx.location.child(['examples', schema.examples.indexOf(example)]),
|
||||||
|
ctx,
|
||||||
|
disallowAdditionalProperties,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schema.example) {
|
||||||
|
validateExample(schema.example, schema, ctx.location.child('example'), ctx, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { OasSpec } from '../common/spec';
|
import { OasSpec } from '../common/spec';
|
||||||
|
import { NoInvalidSchemaExamples } from '../common/no-invalid-schema-examples';
|
||||||
|
import { NoInvalidParameterExamples } from '../common/no-invalid-parameter-examples';
|
||||||
import { InfoDescription } from '../common/info-description';
|
import { InfoDescription } from '../common/info-description';
|
||||||
import { InfoContact } from '../common/info-contact';
|
import { InfoContact } from '../common/info-contact';
|
||||||
import { InfoLicense } from '../common/info-license-url';
|
import { InfoLicense } from '../common/info-license-url';
|
||||||
@@ -41,6 +43,8 @@ import { InfoDescriptionOverride } from '../common/info-description-override';
|
|||||||
|
|
||||||
export const rules = {
|
export const rules = {
|
||||||
spec: OasSpec as Oas2Rule,
|
spec: OasSpec as Oas2Rule,
|
||||||
|
'no-invalid-schema-examples': NoInvalidSchemaExamples,
|
||||||
|
'no-invalid-parameter-examples': NoInvalidParameterExamples,
|
||||||
'info-description': InfoDescription as Oas2Rule,
|
'info-description': InfoDescription as Oas2Rule,
|
||||||
'info-contact': InfoContact as Oas2Rule,
|
'info-contact': InfoContact as Oas2Rule,
|
||||||
'info-license': InfoLicense as Oas2Rule,
|
'info-license': InfoLicense as Oas2Rule,
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import { OperationDescriptionOverride } from '../common/operation-description-ov
|
|||||||
import { TagDescriptionOverride } from '../common/tag-description-override';
|
import { TagDescriptionOverride } from '../common/tag-description-override';
|
||||||
import { InfoDescriptionOverride } from '../common/info-description-override';
|
import { InfoDescriptionOverride } from '../common/info-description-override';
|
||||||
import { PathExcludesPatterns } from '../common/path-excludes-patterns';
|
import { PathExcludesPatterns } from '../common/path-excludes-patterns';
|
||||||
|
import { NoInvalidSchemaExamples } from '../common/no-invalid-schema-examples';
|
||||||
|
import { NoInvalidParameterExamples } from '../common/no-invalid-parameter-examples';
|
||||||
|
|
||||||
export const rules = {
|
export const rules = {
|
||||||
spec: OasSpec,
|
spec: OasSpec,
|
||||||
@@ -93,6 +95,8 @@ export const rules = {
|
|||||||
'request-mime-type': RequestMimeType,
|
'request-mime-type': RequestMimeType,
|
||||||
'response-mime-type': ResponseMimeType,
|
'response-mime-type': ResponseMimeType,
|
||||||
'path-segment-plural': PathSegmentPlural,
|
'path-segment-plural': PathSegmentPlural,
|
||||||
|
'no-invalid-schema-examples': NoInvalidSchemaExamples,
|
||||||
|
'no-invalid-parameter-examples': NoInvalidParameterExamples,
|
||||||
} as Oas3RuleSet;
|
} as Oas3RuleSet;
|
||||||
|
|
||||||
export const preprocessors = {};
|
export const preprocessors = {};
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import { Oas3Rule } from '../../visitors';
|
import { Oas3Rule } from '../../visitors';
|
||||||
import { validateJsonSchema } from '../ajv';
|
|
||||||
import { Location, isRef } from '../../ref-utils';
|
import { Location, isRef } from '../../ref-utils';
|
||||||
import { Oas3Example } from '../../typings/openapi';
|
import { Oas3Example } from '../../typings/openapi';
|
||||||
|
import { validateExample } from '../utils';
|
||||||
|
import { UserContext } from '../../walk';
|
||||||
|
|
||||||
export const ValidContentExamples: Oas3Rule = (opts) => {
|
export const ValidContentExamples: Oas3Rule = (opts) => {
|
||||||
const disallowAdditionalProperties = opts.disallowAdditionalProperties ?? true;
|
const disallowAdditionalProperties = opts.disallowAdditionalProperties ?? true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
MediaType: {
|
MediaType: {
|
||||||
leave(mediaType, { report, location, resolve }) {
|
leave(mediaType, ctx: UserContext) {
|
||||||
|
const { location, resolve } = ctx;
|
||||||
if (!mediaType.schema) return;
|
if (!mediaType.schema) return;
|
||||||
|
|
||||||
if (mediaType.example) {
|
if (mediaType.example) {
|
||||||
validateExample(mediaType.example, location.child('example'));
|
validateExample(
|
||||||
|
mediaType.example,
|
||||||
|
mediaType.schema,
|
||||||
|
location.child('example'),
|
||||||
|
ctx,
|
||||||
|
disallowAdditionalProperties,
|
||||||
|
);
|
||||||
} else if (mediaType.examples) {
|
} else if (mediaType.examples) {
|
||||||
for (const exampleName of Object.keys(mediaType.examples)) {
|
for (const exampleName of Object.keys(mediaType.examples)) {
|
||||||
let example = mediaType.examples[exampleName];
|
let example = mediaType.examples[exampleName];
|
||||||
@@ -23,40 +30,13 @@ export const ValidContentExamples: Oas3Rule = (opts) => {
|
|||||||
dataLoc = resolved.location.child('value');
|
dataLoc = resolved.location.child('value');
|
||||||
example = resolved.node;
|
example = resolved.node;
|
||||||
}
|
}
|
||||||
|
validateExample(
|
||||||
validateExample(example.value, dataLoc);
|
example.value,
|
||||||
}
|
mediaType.schema,
|
||||||
}
|
dataLoc,
|
||||||
|
ctx,
|
||||||
function validateExample(example: any, dataLoc: Location) {
|
|
||||||
try {
|
|
||||||
const { valid, errors } = validateJsonSchema(
|
|
||||||
example,
|
|
||||||
mediaType.schema!,
|
|
||||||
location.child('schema'),
|
|
||||||
dataLoc.pointer,
|
|
||||||
resolve,
|
|
||||||
disallowAdditionalProperties,
|
disallowAdditionalProperties,
|
||||||
);
|
);
|
||||||
if (!valid) {
|
|
||||||
for (let error of errors) {
|
|
||||||
report({
|
|
||||||
message: `Example value must conform to the schema: ${error.message}.`,
|
|
||||||
location: {
|
|
||||||
...new Location(dataLoc.source, error.instancePath),
|
|
||||||
reportOnKey: error.keyword === 'additionalProperties',
|
|
||||||
},
|
|
||||||
from: location,
|
|
||||||
suggest: error.suggest,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
report({
|
|
||||||
message: `Example validation errored: ${e.message}.`,
|
|
||||||
location: location.child('schema'),
|
|
||||||
from: location
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import levenshtein = require('js-levenshtein');
|
import levenshtein = require('js-levenshtein');
|
||||||
import { UserContext } from '../walk';
|
import { UserContext } from '../walk';
|
||||||
|
import { Location } from '../ref-utils';
|
||||||
|
import { validateJsonSchema } from './ajv';
|
||||||
|
import { Oas3Schema, Referenced } from '../typings/openapi';
|
||||||
|
|
||||||
export function oasTypeOf(value: unknown) {
|
export function oasTypeOf(value: unknown) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
@@ -20,7 +23,7 @@ export function oasTypeOf(value: unknown) {
|
|||||||
*/
|
*/
|
||||||
export function matchesJsonSchemaType(value: unknown, type: string, nullable: boolean): boolean {
|
export function matchesJsonSchemaType(value: unknown, type: string, nullable: boolean): boolean {
|
||||||
if (nullable && value === null) {
|
if (nullable && value === null) {
|
||||||
return value === null
|
return value === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -79,4 +82,42 @@ export function getSuggest(given: string, variants: string[]): string[] {
|
|||||||
|
|
||||||
// if (bestMatch.distance <= 4) return bestMatch.string;
|
// if (bestMatch.distance <= 4) return bestMatch.string;
|
||||||
return distances.map((d) => d.variant);
|
return distances.map((d) => d.variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateExample(
|
||||||
|
example: any,
|
||||||
|
schema: Referenced<Oas3Schema>,
|
||||||
|
dataLoc: Location,
|
||||||
|
{ resolve, location, report }: UserContext,
|
||||||
|
disallowAdditionalProperties: boolean,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { valid, errors } = validateJsonSchema(
|
||||||
|
example,
|
||||||
|
schema,
|
||||||
|
location.child('schema'),
|
||||||
|
dataLoc.pointer,
|
||||||
|
resolve,
|
||||||
|
disallowAdditionalProperties,
|
||||||
|
);
|
||||||
|
if (!valid) {
|
||||||
|
for (let error of errors) {
|
||||||
|
report({
|
||||||
|
message: `Example value must conform to the schema: ${error.message}.`,
|
||||||
|
location: {
|
||||||
|
...new Location(dataLoc.source, error.instancePath),
|
||||||
|
reportOnKey: error.keyword === 'additionalProperties',
|
||||||
|
},
|
||||||
|
from: location,
|
||||||
|
suggest: error.suggest,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
report({
|
||||||
|
message: `Example validation errored: ${e.message}.`,
|
||||||
|
location: location.child('schema'),
|
||||||
|
from: location,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -150,6 +150,10 @@ export interface Oas3Schema {
|
|||||||
xml?: Oas3Xml;
|
xml?: Oas3Xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Oas3_1Schema extends Oas3Schema {
|
||||||
|
examples?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Oas3Discriminator {
|
export interface Oas3Discriminator {
|
||||||
propertyName: string;
|
propertyName: string;
|
||||||
mapping?: { [name: string]: string };
|
mapping?: { [name: string]: string };
|
||||||
|
|||||||
Reference in New Issue
Block a user