mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-06 04:21:09 +00:00
feat: add ref assertion (#675)
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"singleQuote": true,
|
"singleQuote": true
|
||||||
"trailingComma": "all"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ lint:
|
|||||||
rules:
|
rules:
|
||||||
assert/operation-get:
|
assert/operation-get:
|
||||||
context:
|
context:
|
||||||
- type: Operation
|
- type: Operation
|
||||||
matchParentKeys: [get]
|
matchParentKeys: [get]
|
||||||
subject: Operation
|
subject: Operation
|
||||||
property: operationId
|
property: operationId
|
||||||
message: Operation id for get operation should be camelCase
|
message: Operation id for get operation should be camelCase
|
||||||
@@ -17,4 +17,8 @@ lint:
|
|||||||
property: operationId
|
property: operationId
|
||||||
message: Operation id should be camelCase
|
message: Operation id should be camelCase
|
||||||
casing: camelCase
|
casing: camelCase
|
||||||
extends: []
|
assert/camel-case-on-value:
|
||||||
|
subject: NamedParameters
|
||||||
|
casing: camelCase
|
||||||
|
message: Named Parameters should be camelCase
|
||||||
|
extends: []
|
||||||
|
|||||||
@@ -35,3 +35,16 @@ paths:
|
|||||||
description: example description
|
description: example description
|
||||||
'404':
|
'404':
|
||||||
description: example description
|
description: example description
|
||||||
|
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
camelCase:
|
||||||
|
in: query
|
||||||
|
name: type
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
Wrong_casE:
|
||||||
|
in: query
|
||||||
|
name: per_page
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
|||||||
@@ -45,9 +45,23 @@ Operation id should be camelCase
|
|||||||
Error was generated by the operation-id-camel-case assertion rule.
|
Error was generated by the operation-id-camel-case assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
[4] openapi.yaml:46:5 at #/components/parameters/Wrong_casE
|
||||||
|
|
||||||
|
Named Parameters should be camelCase
|
||||||
|
|
||||||
|
44 | schema:
|
||||||
|
45 | type: string
|
||||||
|
46 | Wrong_casE:
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
47 | in: query
|
||||||
|
48 | name: per_page
|
||||||
|
|
||||||
|
Error was generated by the camel-case-on-value assertion rule.
|
||||||
|
|
||||||
|
|
||||||
/openapi.yaml: validated in <test>ms
|
/openapi.yaml: validated in <test>ms
|
||||||
|
|
||||||
❌ Validation failed with 3 errors.
|
❌ Validation failed with 4 errors.
|
||||||
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
|
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ lint:
|
|||||||
message: Only application/json can be used
|
message: Only application/json can be used
|
||||||
enum:
|
enum:
|
||||||
- application/json
|
- application/json
|
||||||
extends: []
|
extends: []
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
exports[`E2E lint assertions-enum-on-keys-error 1`] = `
|
exports[`E2E lint assertions-enum-on-keys-error 1`] = `
|
||||||
|
|
||||||
validating /openapi.yaml...
|
validating /openapi.yaml...
|
||||||
[1] openapi.yaml:28:11 at #/paths/~1pet~1findByStatus/get/responses/200/content
|
[1] openapi.yaml:34:13 at #/paths/~1pet~1findByStatus/get/responses/200/content/application~1xml
|
||||||
|
|
||||||
Only application/json can be used
|
Only application/json can be used
|
||||||
|
|
||||||
26 | '200':
|
32 | items:
|
||||||
27 | description: example description
|
33 | $ref: '#/components/schemas/Dog'
|
||||||
28 | content:
|
34 | application/xml:
|
||||||
| ^^^^^^^
|
| ^^^^^^^^^^^^^^^
|
||||||
29 | application/json:
|
35 | schema:
|
||||||
30 | schema:
|
36 | type: array
|
||||||
|
|
||||||
Error was generated by the media-type-application-json assertion rule.
|
Error was generated by the media-type-application-json assertion rule.
|
||||||
|
|
||||||
|
|||||||
@@ -124,16 +124,16 @@ Operation summary must have a maximum of 2 characters
|
|||||||
Error was generated by the operation-summary-max-length assertion rule.
|
Error was generated by the operation-summary-max-length assertion rule.
|
||||||
|
|
||||||
|
|
||||||
[9] openapi.yaml:31:9 at #/paths/~1pet~1findByStatus/put/requestBody/content
|
[9] openapi.yaml:32:11 at #/paths/~1pet~1findByStatus/put/requestBody/content/application~1json
|
||||||
|
|
||||||
Only application/pdf can be used
|
Only application/pdf can be used
|
||||||
|
|
||||||
29 | url: "https://example.com"
|
|
||||||
30 | requestBody:
|
30 | requestBody:
|
||||||
31 | content:
|
31 | content:
|
||||||
| ^^^^^^^
|
|
||||||
32 | application/json:
|
32 | application/json:
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
33 | schema:
|
33 | schema:
|
||||||
|
34 | type: 'object'
|
||||||
|
|
||||||
Error was generated by the media-type-pdf assertion rule.
|
Error was generated by the media-type-pdf assertion rule.
|
||||||
|
|
||||||
@@ -212,16 +212,16 @@ NamedExamples key must be in PascalCase
|
|||||||
Error was generated by the operation-id-camel-case assertion rule.
|
Error was generated by the operation-id-camel-case assertion rule.
|
||||||
|
|
||||||
|
|
||||||
[15] openapi.yaml:56:5 at #/paths/~1pet~1findByStatus/post
|
[15] openapi.yaml:78:7 at #/paths/~1pet~1findByStatus/post/x-code-samples
|
||||||
|
|
||||||
x-code-samples and x-internal must not be defined
|
x-code-samples and x-internal must not be defined
|
||||||
|
|
||||||
54 | type: string
|
76 | credit-file-identity-address:
|
||||||
55 | description: hooray
|
77 | summary: Credit file with fallback
|
||||||
56 | post:
|
78 | x-code-samples:
|
||||||
| ^^^^
|
| ^^^^^^^^^^^^^^
|
||||||
57 | operationId: EXAMPLE
|
79 | - lang: 'C#'
|
||||||
58 | summary: ''
|
80 | source: |
|
||||||
|
|
||||||
Error was generated by the operation-disallowed assertion rule.
|
Error was generated by the operation-disallowed assertion rule.
|
||||||
|
|
||||||
@@ -307,16 +307,16 @@ Operation object \`summary\` must be non-empty string.
|
|||||||
Error was generated by the operation-summary rule.
|
Error was generated by the operation-summary rule.
|
||||||
|
|
||||||
|
|
||||||
[21] openapi.yaml:62:11 at #/paths/~1pet~1findByStatus/post/responses/201/content
|
[21] openapi.yaml:63:13 at #/paths/~1pet~1findByStatus/post/responses/201/content/application~1pdf
|
||||||
|
|
||||||
Media type should not be pdf
|
Media type should not be pdf
|
||||||
|
|
||||||
60 | '201':
|
61 | description: Test description
|
||||||
61 | description: Test description
|
62 | content:
|
||||||
62 | content:
|
63 | application/pdf:
|
||||||
| ^^^^^^^
|
| ^^^^^^^^^^^^^^^
|
||||||
63 | application/pdf:
|
64 | schema:
|
||||||
64 | schema:
|
65 | type: 'object'
|
||||||
|
|
||||||
Error was generated by the operation-w-context assertion rule.
|
Error was generated by the operation-w-context assertion rule.
|
||||||
|
|
||||||
|
|||||||
18
__tests__/lint/assertions-ref-forbidden-error/.redocly.yaml
Normal file
18
__tests__/lint/assertions-ref-forbidden-error/.redocly.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apis:
|
||||||
|
main:
|
||||||
|
root: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
assert/ref-forbidden:
|
||||||
|
context:
|
||||||
|
- type: Response
|
||||||
|
subject: MediaType
|
||||||
|
property: schema
|
||||||
|
message: Response MediaType schema should NOT have a ref
|
||||||
|
ref: false
|
||||||
|
assert/ref-forbidden-no-property:
|
||||||
|
subject: PathItem
|
||||||
|
message: PathItems should NOT should have a ref
|
||||||
|
ref: false
|
||||||
|
extends: []
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
get:
|
||||||
|
summary: List all owners
|
||||||
|
operationId: listOwners
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: How many items to return at one time (max 100)
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int
|
||||||
|
responses:
|
||||||
|
404:
|
||||||
|
description: test
|
||||||
|
200:
|
||||||
|
description: An paged array of owners
|
||||||
|
headers:
|
||||||
|
x-next:
|
||||||
|
description: A link to the next page of responses
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
67
__tests__/lint/assertions-ref-forbidden-error/openapi.yaml
Normal file
67
__tests__/lint/assertions-ref-forbidden-error/openapi.yaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
openapi: '3.0.0'
|
||||||
|
info:
|
||||||
|
version: 1.0.0
|
||||||
|
title: Swagger Petstore
|
||||||
|
description: Information about Petstore
|
||||||
|
license:
|
||||||
|
name: MIT
|
||||||
|
url: https://opensource.org/licenses/MIT
|
||||||
|
servers:
|
||||||
|
- url: http://petstore.swagger.io/v1
|
||||||
|
paths:
|
||||||
|
/pets:
|
||||||
|
get:
|
||||||
|
summary: List all pets
|
||||||
|
operationId: listPets
|
||||||
|
tags:
|
||||||
|
- pets
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: How many items to return at one time (max 100)
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int
|
||||||
|
responses:
|
||||||
|
404:
|
||||||
|
description: test
|
||||||
|
200:
|
||||||
|
description: An paged array of pets
|
||||||
|
headers:
|
||||||
|
x-next:
|
||||||
|
description: A link to the next page of responses
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: ./components/schemas/Error.yaml
|
||||||
|
/owners:
|
||||||
|
$ref: components/paths/Owners.yaml
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Pet:
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
Pets:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Pet'
|
||||||
40
__tests__/lint/assertions-ref-forbidden-error/snapshot.js
Normal file
40
__tests__/lint/assertions-ref-forbidden-error/snapshot.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint assertions-ref-forbidden-error 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
[1] openapi.yaml:47:17 at #/paths/~1pets/get/responses/default/content/application~1json/schema
|
||||||
|
|
||||||
|
Response MediaType schema should NOT have a ref
|
||||||
|
|
||||||
|
45 | application/json:
|
||||||
|
46 | schema:
|
||||||
|
47 | $ref: ./components/schemas/Error.yaml
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
48 | /owners:
|
||||||
|
49 | $ref: components/paths/Owners.yaml
|
||||||
|
|
||||||
|
Error was generated by the ref-forbidden assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
[2] openapi.yaml:49:5 at #/paths/~1owners
|
||||||
|
|
||||||
|
PathItems should NOT should have a ref
|
||||||
|
|
||||||
|
47 | $ref: ./components/schemas/Error.yaml
|
||||||
|
48 | /owners:
|
||||||
|
49 | $ref: components/paths/Owners.yaml
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
50 | components:
|
||||||
|
51 | schemas:
|
||||||
|
|
||||||
|
Error was generated by the ref-forbidden-no-property assertion 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.
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
18
__tests__/lint/assertions-ref-pattern-error/.redocly.yaml
Normal file
18
__tests__/lint/assertions-ref-pattern-error/.redocly.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apis:
|
||||||
|
main:
|
||||||
|
root: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
assert/ref-pattern:
|
||||||
|
context:
|
||||||
|
- type: Response
|
||||||
|
subject: MediaType
|
||||||
|
property: schema
|
||||||
|
message: Response MediaType schema should contain ref to components/schemas folder
|
||||||
|
ref: ^(\.\/)?components\/schemas\/.*\.yaml$
|
||||||
|
assert/ref-pattern-no-properties:
|
||||||
|
subject: PathItem
|
||||||
|
message: PathItem should contain ref to components/paths folder
|
||||||
|
ref: ^(\.\/)?components\/paths\/.*\.yaml$
|
||||||
|
extends: []
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
get:
|
||||||
|
summary: List all Vets
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: How many items to return at one time (max 100)
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int
|
||||||
|
responses:
|
||||||
|
404:
|
||||||
|
description: test
|
||||||
|
200:
|
||||||
|
description: An paged array of Vets
|
||||||
|
headers:
|
||||||
|
x-next:
|
||||||
|
description: A link to the next page of responses
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
get:
|
||||||
|
summary: List all owners
|
||||||
|
operationId: listOwners
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: How many items to return at one time (max 100)
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int
|
||||||
|
responses:
|
||||||
|
404:
|
||||||
|
description: test
|
||||||
|
200:
|
||||||
|
description: An paged array of owners
|
||||||
|
headers:
|
||||||
|
x-next:
|
||||||
|
description: A link to the next page of responses
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
67
__tests__/lint/assertions-ref-pattern-error/openapi.yaml
Normal file
67
__tests__/lint/assertions-ref-pattern-error/openapi.yaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
openapi: '3.0.0'
|
||||||
|
info:
|
||||||
|
version: 1.0.0
|
||||||
|
title: Swagger Petstore
|
||||||
|
description: Information about Petstore
|
||||||
|
license:
|
||||||
|
name: MIT
|
||||||
|
url: https://opensource.org/licenses/MIT
|
||||||
|
servers:
|
||||||
|
- url: http://petstore.swagger.io/v1
|
||||||
|
paths:
|
||||||
|
/pets:
|
||||||
|
get:
|
||||||
|
summary: List all pets
|
||||||
|
operationId: listPets
|
||||||
|
tags:
|
||||||
|
- pets
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: How many items to return at one time (max 100)
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int
|
||||||
|
responses:
|
||||||
|
404:
|
||||||
|
description: test
|
||||||
|
200:
|
||||||
|
description: An paged array of pets
|
||||||
|
headers:
|
||||||
|
x-next:
|
||||||
|
description: A link to the next page of responses
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Pets'
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: ./components/schemas/Error.yaml
|
||||||
|
/owners:
|
||||||
|
$ref: components/paths/Owners.yaml
|
||||||
|
/vets:
|
||||||
|
$ref: components/Vets.yaml
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Pet:
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
Pets:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Pet'
|
||||||
82
__tests__/lint/assertions-ref-pattern-error/snapshot.js
Normal file
82
__tests__/lint/assertions-ref-pattern-error/snapshot.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint assertions-ref-pattern-error 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
[1] openapi.yaml:12:3 at #/paths/~1pets
|
||||||
|
|
||||||
|
PathItem should contain ref to components/paths folder
|
||||||
|
|
||||||
|
10 | - url: http://petstore.swagger.io/v1
|
||||||
|
11 | paths:
|
||||||
|
12 | /pets:
|
||||||
|
| ^^^^^
|
||||||
|
13 | get:
|
||||||
|
14 | summary: List all pets
|
||||||
|
|
||||||
|
Error was generated by the ref-pattern-no-properties assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
[2] openapi.yaml:39:17 at #/paths/~1pets/get/responses/200/content/application~1json/schema
|
||||||
|
|
||||||
|
Response MediaType schema should contain ref to components/schemas folder
|
||||||
|
|
||||||
|
37 | application/json:
|
||||||
|
38 | schema:
|
||||||
|
39 | $ref: '#/components/schemas/Pets'
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
40 | default:
|
||||||
|
41 | description: unexpected error
|
||||||
|
|
||||||
|
Error was generated by the ref-pattern assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
[3] components/paths/Owners.yaml:24:11 at #/get/responses/200/content/application~1json/schema
|
||||||
|
|
||||||
|
Response MediaType schema should contain ref to components/schemas folder
|
||||||
|
|
||||||
|
22 | content:
|
||||||
|
23 | application/json:
|
||||||
|
24 | schema:
|
||||||
|
| ^^^^^^
|
||||||
|
25 | properties:
|
||||||
|
26 | id:
|
||||||
|
|
||||||
|
Error was generated by the ref-pattern assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
[4] openapi.yaml:49:5 at #/paths/~1vets
|
||||||
|
|
||||||
|
PathItem should contain ref to components/paths folder
|
||||||
|
|
||||||
|
47 | $ref: components/paths/Owners.yaml
|
||||||
|
48 | /vets:
|
||||||
|
49 | $ref: components/Vets.yaml
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
50 | components:
|
||||||
|
51 | schemas:
|
||||||
|
|
||||||
|
Error was generated by the ref-pattern-no-properties assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
[5] components/Vets.yaml:23:11 at #/get/responses/200/content/application~1json/schema
|
||||||
|
|
||||||
|
Response MediaType schema should contain ref to components/schemas folder
|
||||||
|
|
||||||
|
21 | content:
|
||||||
|
22 | application/json:
|
||||||
|
23 | schema:
|
||||||
|
| ^^^^^^
|
||||||
|
24 | properties:
|
||||||
|
25 | id:
|
||||||
|
|
||||||
|
Error was generated by the ref-pattern assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
/openapi.yaml: validated in <test>ms
|
||||||
|
|
||||||
|
❌ Validation failed with 5 errors.
|
||||||
|
run \`openapi lint --generate-ignore-file\` to add all problems to the ignore file.
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
18
__tests__/lint/assertions-ref-required-error/.redocly.yaml
Normal file
18
__tests__/lint/assertions-ref-required-error/.redocly.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apis:
|
||||||
|
main:
|
||||||
|
root: ./openapi.yaml
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
assert/ref-required:
|
||||||
|
context:
|
||||||
|
- type: Response
|
||||||
|
subject: MediaType
|
||||||
|
property: schema
|
||||||
|
message: Response MediaType schema should have a ref
|
||||||
|
ref: true
|
||||||
|
assert/ref-required-no-property:
|
||||||
|
subject: PathItem
|
||||||
|
message: PathItems should have refs
|
||||||
|
ref: true
|
||||||
|
extends: []
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
get:
|
||||||
|
summary: List all owners
|
||||||
|
operationId: listOwners
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: How many items to return at one time (max 100)
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int
|
||||||
|
responses:
|
||||||
|
404:
|
||||||
|
description: test
|
||||||
|
200:
|
||||||
|
description: An paged array of owners
|
||||||
|
headers:
|
||||||
|
x-next:
|
||||||
|
description: A link to the next page of responses
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
required:
|
||||||
|
- code
|
||||||
|
- message
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
67
__tests__/lint/assertions-ref-required-error/openapi.yaml
Normal file
67
__tests__/lint/assertions-ref-required-error/openapi.yaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
openapi: '3.0.0'
|
||||||
|
info:
|
||||||
|
version: 1.0.0
|
||||||
|
title: Swagger Petstore
|
||||||
|
description: Information about Petstore
|
||||||
|
license:
|
||||||
|
name: MIT
|
||||||
|
url: https://opensource.org/licenses/MIT
|
||||||
|
servers:
|
||||||
|
- url: http://petstore.swagger.io/v1
|
||||||
|
paths:
|
||||||
|
/pets:
|
||||||
|
get:
|
||||||
|
summary: List all pets
|
||||||
|
operationId: listPets
|
||||||
|
tags:
|
||||||
|
- pets
|
||||||
|
parameters:
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: How many items to return at one time (max 100)
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int
|
||||||
|
responses:
|
||||||
|
404:
|
||||||
|
description: test
|
||||||
|
200:
|
||||||
|
description: An paged array of pets
|
||||||
|
headers:
|
||||||
|
x-next:
|
||||||
|
description: A link to the next page of responses
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
default:
|
||||||
|
description: unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: ./components/schemas/Error.yaml
|
||||||
|
/owners:
|
||||||
|
$ref: components/paths/Owners.yaml
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Pet:
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
Pets:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Pet'
|
||||||
54
__tests__/lint/assertions-ref-required-error/snapshot.js
Normal file
54
__tests__/lint/assertions-ref-required-error/snapshot.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`E2E lint assertions-ref-required-error 1`] = `
|
||||||
|
|
||||||
|
validating /openapi.yaml...
|
||||||
|
[1] openapi.yaml:12:3 at #/paths/~1pets
|
||||||
|
|
||||||
|
PathItems should have refs
|
||||||
|
|
||||||
|
10 | - url: http://petstore.swagger.io/v1
|
||||||
|
11 | paths:
|
||||||
|
12 | /pets:
|
||||||
|
| ^^^^^
|
||||||
|
13 | get:
|
||||||
|
14 | summary: List all pets
|
||||||
|
|
||||||
|
Error was generated by the ref-required-no-property assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
[2] openapi.yaml:38:15 at #/paths/~1pets/get/responses/200/content/application~1json/schema
|
||||||
|
|
||||||
|
Response MediaType schema should have a ref
|
||||||
|
|
||||||
|
36 | content:
|
||||||
|
37 | application/json:
|
||||||
|
38 | schema:
|
||||||
|
| ^^^^^^
|
||||||
|
39 | properties:
|
||||||
|
40 | id:
|
||||||
|
|
||||||
|
Error was generated by the ref-required assertion rule.
|
||||||
|
|
||||||
|
|
||||||
|
[3] components/paths/Owners.yaml:24:11 at #/get/responses/200/content/application~1json/schema
|
||||||
|
|
||||||
|
Response MediaType schema should have a ref
|
||||||
|
|
||||||
|
22 | content:
|
||||||
|
23 | application/json:
|
||||||
|
24 | schema:
|
||||||
|
| ^^^^^^
|
||||||
|
25 | properties:
|
||||||
|
26 | id:
|
||||||
|
|
||||||
|
Error was generated by the ref-required assertion 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.
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
@@ -12,7 +12,7 @@ lint:
|
|||||||
severity: error
|
severity: error
|
||||||
minLength: 13
|
minLength: 13
|
||||||
pattern: /\.$/
|
pattern: /\.$/
|
||||||
#property example
|
# property example
|
||||||
assert/path-item-get-defined:
|
assert/path-item-get-defined:
|
||||||
subject: PathItem
|
subject: PathItem
|
||||||
property: get
|
property: get
|
||||||
@@ -30,7 +30,7 @@ lint:
|
|||||||
- description
|
- description
|
||||||
message: Every tag must have a name and description.
|
message: Every tag must have a name and description.
|
||||||
defined: true
|
defined: true
|
||||||
#context example
|
# context example
|
||||||
assert/media-type-map-not-pdf:
|
assert/media-type-map-not-pdf:
|
||||||
subject:
|
subject:
|
||||||
- MediaTypeMap
|
- MediaTypeMap
|
||||||
@@ -40,9 +40,9 @@ lint:
|
|||||||
- put
|
- put
|
||||||
- type: Response
|
- type: Response
|
||||||
matchParentKeys: ['201', '200']
|
matchParentKeys: ['201', '200']
|
||||||
disallowed: [ 'application/pdf' ]
|
disallowed: ['application/pdf']
|
||||||
message: Media type should not be pdf
|
message: Media type should not be pdf
|
||||||
#enum example
|
# enum example
|
||||||
assert/media-type-map-enum:
|
assert/media-type-map-enum:
|
||||||
subject: MediaTypeMap
|
subject: MediaTypeMap
|
||||||
message: Only application/json and application/pdf can be used
|
message: Only application/json and application/pdf can be used
|
||||||
@@ -61,29 +61,29 @@ lint:
|
|||||||
enum:
|
enum:
|
||||||
- My resource
|
- My resource
|
||||||
- My collection
|
- My collection
|
||||||
#pattern example
|
# pattern example
|
||||||
assert/operation-summary-pattern:
|
assert/operation-summary-pattern:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
property: summary
|
property: summary
|
||||||
message: Summary should match a regex
|
message: Summary should match a regex
|
||||||
severity: error
|
severity: error
|
||||||
pattern: /resource/
|
pattern: /resource/
|
||||||
#casing
|
# casing
|
||||||
assert/operation-id-casing:
|
assert/operation-id-casing:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
property: operationId
|
property: operationId
|
||||||
message: NamedExamples key must be in PascalCase
|
message: NamedExamples key must be in PascalCase
|
||||||
severity: error
|
severity: error
|
||||||
casing: camelCase
|
casing: camelCase
|
||||||
#mutuallyExclusive example
|
# mutuallyExclusive example
|
||||||
assert/operation-mutually-exclusive:
|
assert/operation-mutually-exclusive:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
message: "Operation must not define both properties together: description and externalDocs"
|
message: 'Operation must not define both properties together: description and externalDocs'
|
||||||
severity: error
|
severity: error
|
||||||
mutuallyExclusive:
|
mutuallyExclusive:
|
||||||
- description
|
- description
|
||||||
- externalDocs
|
- externalDocs
|
||||||
#mutuallyRequired example
|
# mutuallyRequired example
|
||||||
assert/schema-properties-mutually-required:
|
assert/schema-properties-mutually-required:
|
||||||
subject: SchemaProperties
|
subject: SchemaProperties
|
||||||
context:
|
context:
|
||||||
@@ -93,27 +93,27 @@ lint:
|
|||||||
mutuallyRequired:
|
mutuallyRequired:
|
||||||
- created_at
|
- created_at
|
||||||
- updated_at
|
- updated_at
|
||||||
#mutuallyRequired example with context
|
# mutuallyRequired example with context
|
||||||
assert/response-map-required-with-context:
|
assert/response-map-required-with-context:
|
||||||
subject: ResponsesMap
|
subject: ResponsesMap
|
||||||
context:
|
context:
|
||||||
- type: Operation
|
- type: Operation
|
||||||
matchParentKeys:
|
matchParentKeys:
|
||||||
- put
|
- put
|
||||||
message: Must mutually define 200 and 201 responses for PUT requests.
|
message: Must mutually define 200 and 201 responses for PUT requests.
|
||||||
severity: error
|
severity: error
|
||||||
mutuallyRequired:
|
mutuallyRequired:
|
||||||
- '200'
|
- '200'
|
||||||
- '201'
|
- '201'
|
||||||
#requireAny example
|
# requireAny example
|
||||||
assert/operation-require-any-description-or-external:
|
assert/operation-require-any-description-or-external:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
message: "Operation must have one of properties defined: description or externalDocs"
|
message: 'Operation must have one of properties defined: description or externalDocs'
|
||||||
severity: error
|
severity: error
|
||||||
requireAny:
|
requireAny:
|
||||||
- description
|
- description
|
||||||
- externalDocs
|
- externalDocs
|
||||||
#disallowed example
|
# disallowed example
|
||||||
assert/operation-disallowed:
|
assert/operation-disallowed:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
message: x-code-samples and x-internal must not be defined
|
message: x-code-samples and x-internal must not be defined
|
||||||
@@ -121,14 +121,14 @@ lint:
|
|||||||
disallowed:
|
disallowed:
|
||||||
- x-code-samples
|
- x-code-samples
|
||||||
- x-internal
|
- x-internal
|
||||||
#defined example
|
# defined example
|
||||||
assert/operation-defined:
|
assert/operation-defined:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
property: x-codeSamples
|
property: x-codeSamples
|
||||||
message: x-codeSamples must be defined
|
message: x-codeSamples must be defined
|
||||||
severity: error
|
severity: error
|
||||||
defined: true
|
defined: true
|
||||||
# undefined example
|
# undefined example
|
||||||
assert/operation-undefined:
|
assert/operation-undefined:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
property: x-code-samples
|
property: x-code-samples
|
||||||
@@ -137,24 +137,30 @@ lint:
|
|||||||
- x-codeSamples instead of x-code-samples
|
- x-codeSamples instead of x-code-samples
|
||||||
severity: error
|
severity: error
|
||||||
undefined: true
|
undefined: true
|
||||||
#nonEmpty example
|
# nonEmpty example
|
||||||
assert/operation-non-empty:
|
assert/operation-non-empty:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
property: summary
|
property: summary
|
||||||
message: Operation summary should not be empty
|
message: Operation summary should not be empty
|
||||||
severity: error
|
severity: error
|
||||||
nonEmpty: true
|
nonEmpty: true
|
||||||
#minLength example
|
# minLength example
|
||||||
assert/operation-min-length:
|
assert/operation-min-length:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
property: summary
|
property: summary
|
||||||
message: Operation summary must have minimum of 2 chars length
|
message: Operation summary must have minimum of 2 chars length
|
||||||
severity: error
|
severity: error
|
||||||
minLength: 2
|
minLength: 2
|
||||||
#maxLength example
|
# maxLength example
|
||||||
assert/operation-max-length:
|
assert/operation-max-length:
|
||||||
subject: Operation
|
subject: Operation
|
||||||
property: summary
|
property: summary
|
||||||
message: Operation summary must have a maximum of 2 characters
|
message: Operation summary must have a maximum of 2 characters
|
||||||
severity: error
|
severity: error
|
||||||
maxLength: 20
|
maxLength: 20
|
||||||
|
# ref example
|
||||||
|
assert/ref:
|
||||||
|
subject: PathItem
|
||||||
|
message: No refs on Path Items
|
||||||
|
severity: error
|
||||||
|
ref: false
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ undefined | `boolean` | Asserts a property is undefined. See [undefined example]
|
|||||||
nonEmpty | `boolean` | Asserts a property is not empty. See [nonEmpty example](#nonempty-example).
|
nonEmpty | `boolean` | Asserts a property is not empty. See [nonEmpty example](#nonempty-example).
|
||||||
minLength | `integer` | Asserts a minimum length (inclusive) of a string or list (array). See [minLength example](#minlength-example).
|
minLength | `integer` | Asserts a minimum length (inclusive) of a string or list (array). See [minLength example](#minlength-example).
|
||||||
maxLength | `integer` | Asserts a maximum length (exclusive) of a string or list (array). See [maxLength example](#maxlength-example).
|
maxLength | `integer` | Asserts a maximum length (exclusive) of a string or list (array). See [maxLength example](#maxlength-example).
|
||||||
|
ref | `boolean | string` | Asserts a reference object presence in object's property. A boolean value of `true` means the property has a `$ref` defined. A boolean value of `false` means the property has not defined a `$ref` (it has an in-place value). A string value means that the `$ref` is defined and the unresolved value must match the pattern (for example, `'/paths\/.*\.yaml$/'`). See [ref example](#ref-example).|
|
||||||
|
|
||||||
## Context object
|
## Context object
|
||||||
|
|
||||||
@@ -435,7 +436,35 @@ lint:
|
|||||||
severity: error
|
severity: error
|
||||||
maxLength: 20
|
maxLength: 20
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `ref` example
|
||||||
|
|
||||||
|
The following example asserts that schema in MediaType contains a Reference object ($ref).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
assert/mediatype-schema-has-ref:
|
||||||
|
- subject: MediaType
|
||||||
|
property: schema
|
||||||
|
message: Ref is required.
|
||||||
|
ref: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, you can specify a Regular Expression to check if the reference object conforms to it:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
lint:
|
||||||
|
rules:
|
||||||
|
assert/mediatype-schema-ref-pattern:
|
||||||
|
- subject: MediaType
|
||||||
|
property: schema
|
||||||
|
message: Ref needs to point to components directory.
|
||||||
|
ref: /^(\.\/)?components\/.*\.yaml$/
|
||||||
|
```
|
||||||
|
|
||||||
Redocly CLI
|
Redocly CLI
|
||||||
|
|
||||||
## OpenAPI node types
|
## OpenAPI node types
|
||||||
|
|
||||||
Redocly defines a type tree based on the document type.
|
Redocly defines a type tree based on the document type.
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ module.exports = {
|
|||||||
'packages/core/': {
|
'packages/core/': {
|
||||||
statements: 77,
|
statements: 77,
|
||||||
branches: 69,
|
branches: 69,
|
||||||
functions: 65,
|
functions: 66,
|
||||||
lines: 77,
|
lines: 77,
|
||||||
},
|
},
|
||||||
'packages/cli/': {
|
'packages/cli/': {
|
||||||
statements: 30,
|
statements: 33,
|
||||||
branches: 27,
|
branches: 28,
|
||||||
functions: 29,
|
functions: 29,
|
||||||
lines: 32,
|
lines: 34,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
testMatch: ['**/__tests__/**/*.test.ts', '**/*.test.ts'],
|
testMatch: ['**/__tests__/**/*.test.ts', '**/*.test.ts'],
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import { Location } from '../../../../ref-utils';
|
||||||
|
import { Source } from '../../../../resolve';
|
||||||
import { asserts } from '../asserts';
|
import { asserts } from '../asserts';
|
||||||
|
|
||||||
|
let baseLocation = new Location(jest.fn() as any as Source, 'pointer');
|
||||||
|
|
||||||
describe('oas3 assertions', () => {
|
describe('oas3 assertions', () => {
|
||||||
describe('generic rules', () => {
|
describe('generic rules', () => {
|
||||||
const fakeNode = {
|
const fakeNode = {
|
||||||
@@ -10,232 +14,231 @@ describe('oas3 assertions', () => {
|
|||||||
|
|
||||||
describe('pattern', () => {
|
describe('pattern', () => {
|
||||||
it('value should match regex pattern', () => {
|
it('value should match regex pattern', () => {
|
||||||
expect(asserts.pattern('test string', '/test/')).toBeTruthy();
|
expect(asserts.pattern('test string', '/test/', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.pattern('test string', '/test me/')).toBeFalsy();
|
expect(asserts.pattern('test string', '/test me/', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.pattern(['test string', 'test me'], '/test/')).toBeTruthy();
|
expect(asserts.pattern(['test string', 'test me'], '/test/', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.pattern(['test string', 'test me'], '/test me/')).toBeFalsy();
|
expect(asserts.pattern(['test string', 'test me'], '/test me/', baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
|
expect(asserts.pattern('./components/smth/test.yaml', '/^(./)?components/.*.yaml$/', baseLocation)).toEqual({ isValid: true });
|
||||||
|
expect(asserts.pattern('./other.yaml', '/^(./)?components/.*.yaml$/', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ref', () => {
|
||||||
|
it('value should have ref', () => {
|
||||||
|
expect(asserts.ref({ $ref: 'text' }, true, baseLocation, { $ref: 'text' })).toEqual({ isValid: true, location: baseLocation });
|
||||||
|
expect(asserts.ref({}, true, baseLocation, {})).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('value should not have ref', () => {
|
||||||
|
expect(asserts.ref({ $ref: 'text' }, false, baseLocation, { $ref: 'text' })).toEqual({ isValid: false, location: baseLocation });
|
||||||
|
expect(asserts.ref({}, false, baseLocation, {})).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('value should match regex pattern', () => {
|
||||||
|
expect(asserts.ref({ $ref: 'test string' }, '/test/', baseLocation, { $ref: 'test string' })).toEqual({ isValid: true, location: baseLocation });
|
||||||
|
expect(asserts.ref({ $ref: 'test string' }, '/test me/', baseLocation, { $ref: 'test string' })).toEqual({ isValid: false, location: baseLocation });
|
||||||
|
expect(asserts.ref({ $ref: './components/smth/test.yaml' }, '/^(./)?components/.*.yaml$/', baseLocation, { $ref: './components/smth/test.yaml' })).toEqual({ isValid: true, location: baseLocation });
|
||||||
|
expect(asserts.ref({ $ref: './paths/smth/test.yaml' }, '/^(./)?components/.*.yaml$/', baseLocation, { $ref: './paths/smth/test.yaml' })).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('enum', () => {
|
describe('enum', () => {
|
||||||
it('value should be among predefined keys', () => {
|
it('value should be among predefined keys', () => {
|
||||||
expect(asserts.enum('test', ['test', 'example'])).toBeTruthy();
|
expect(asserts.enum('test', ['test', 'example'], baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.enum(['test'], ['test', 'example'])).toBeTruthy();
|
expect(asserts.enum(['test'], ['test', 'example'], baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.enum(['test', 'example'], ['test', 'example'])).toBeTruthy();
|
expect(asserts.enum(['test', 'example'], ['test', 'example'], baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.enum(['test', 'example', 'foo'], ['test', 'example'])).toBeFalsy();
|
expect(asserts.enum(['test', 'example', 'foo'], ['test', 'example'], baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo').key() });
|
||||||
expect(asserts.enum('test', ['foo', 'example'])).toBeFalsy();
|
expect(asserts.enum('test', ['foo', 'example'], baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.enum(['test', 'foo'], ['test', 'example'])).toBeFalsy();
|
expect(asserts.enum(['test', 'foo'], ['test', 'example'], baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo').key() });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('defined', () => {
|
describe('defined', () => {
|
||||||
it('value should be defined', () => {
|
it('value should be defined', () => {
|
||||||
expect(asserts.defined('test', true)).toBeTruthy();
|
expect(asserts.defined('test', true, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.defined(undefined, true)).toBeFalsy();
|
expect(asserts.defined(undefined, true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be undefined', () => {
|
it('value should be undefined', () => {
|
||||||
expect(asserts.defined(undefined, false)).toBeTruthy();
|
expect(asserts.defined(undefined, false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.defined('test', false)).toBeFalsy();
|
expect(asserts.defined('test', false, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('undefined', () => {
|
describe('undefined', () => {
|
||||||
it('value should be undefined', () => {
|
it('value should be undefined', () => {
|
||||||
expect(asserts.undefined(undefined, true)).toBeTruthy();
|
expect(asserts.undefined(undefined, true, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.undefined('test', true)).toBeFalsy();
|
expect(asserts.undefined('test', true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be defined', () => {
|
it('value should be defined', () => {
|
||||||
expect(asserts.undefined('test', false)).toBeTruthy();
|
expect(asserts.undefined('test', false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.undefined(undefined, false)).toBeFalsy();
|
expect(asserts.undefined(undefined, false, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('required', () => {
|
describe('required', () => {
|
||||||
it('values should be required', () => {
|
it('values should be required', () => {
|
||||||
expect(asserts.required(['one', 'two', 'three'], ['one', 'two'])).toBeTruthy();
|
expect(asserts.required(['one', 'two', 'three'], ['one', 'two'], baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.required(['one', 'two'], ['one', 'two', 'three'])).toBeFalsy();
|
expect(asserts.required(['one', 'two'], ['one', 'two', 'three'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('nonEmpty', () => {
|
describe('nonEmpty', () => {
|
||||||
it('value should not be empty', () => {
|
it('value should not be empty', () => {
|
||||||
expect(asserts.nonEmpty('test', true)).toBeTruthy();
|
expect(asserts.nonEmpty('test', true, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.nonEmpty('', true)).toBeFalsy();
|
expect(asserts.nonEmpty('', true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.nonEmpty(null, true)).toBeFalsy();
|
expect(asserts.nonEmpty(null, true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.nonEmpty(undefined, true)).toBeFalsy();
|
expect(asserts.nonEmpty(undefined, true, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be empty', () => {
|
it('value should be empty', () => {
|
||||||
expect(asserts.nonEmpty('', false)).toBeTruthy();
|
expect(asserts.nonEmpty('', false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.nonEmpty(null, false)).toBeTruthy();
|
expect(asserts.nonEmpty(null, false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.nonEmpty(undefined, false)).toBeTruthy();
|
expect(asserts.nonEmpty(undefined, false, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.nonEmpty('test', false)).toBeFalsy();
|
expect(asserts.nonEmpty('test', false, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('minLength', () => {
|
describe('minLength', () => {
|
||||||
it('value should have less or equal than 5 symbols length', () => {
|
it('value should have less or equal than 5 symbols length', () => {
|
||||||
expect(asserts.minLength('test', 5)).toBeFalsy();
|
expect(asserts.minLength('test', 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.minLength([1, 2, 3, 4], 5)).toBeFalsy();
|
expect(asserts.minLength([1, 2, 3, 4], 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.minLength([1, 2, 3, 4, 5], 5)).toBeTruthy();
|
expect(asserts.minLength([1, 2, 3, 4, 5], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.minLength([1, 2, 3, 4, 5, 6], 5)).toBeTruthy();
|
expect(asserts.minLength([1, 2, 3, 4, 5, 6], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.minLength('example', 5)).toBeTruthy();
|
expect(asserts.minLength('example', 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.minLength([], 5)).toBeFalsy();
|
expect(asserts.minLength([], 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.minLength('', 5)).toBeFalsy();
|
expect(asserts.minLength('', 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('maxLength', () => {
|
describe('maxLength', () => {
|
||||||
it('value should have more or equal than 5 symbols length', () => {
|
it('value should have more or equal than 5 symbols length', () => {
|
||||||
expect(asserts.maxLength('test', 5)).toBeTruthy();
|
expect(asserts.maxLength('test', 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.maxLength([1, 2, 3, 4], 5)).toBeTruthy();
|
expect(asserts.maxLength([1, 2, 3, 4], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.maxLength([1, 2, 3, 4, 5], 5)).toBeTruthy();
|
expect(asserts.maxLength([1, 2, 3, 4, 5], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.maxLength([1, 2, 3, 4, 5, 6], 5)).toBeFalsy();
|
expect(asserts.maxLength([1, 2, 3, 4, 5, 6], 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.maxLength('example', 5)).toBeFalsy();
|
expect(asserts.maxLength('example', 5, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.maxLength([], 5)).toBeTruthy();
|
expect(asserts.maxLength([], 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.maxLength('', 5)).toBeTruthy();
|
expect(asserts.maxLength('', 5, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('casing', () => {
|
describe('casing', () => {
|
||||||
it('value should be camelCase', () => {
|
it('value should be camelCase', () => {
|
||||||
expect(asserts.casing(['testExample', 'fooBar'], 'camelCase')).toBeTruthy();
|
expect(asserts.casing(['testExample', 'fooBar'], 'camelCase', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['testExample', 'FooBar'], 'camelCase')).toBeFalsy();
|
expect(asserts.casing(['testExample', 'FooBar'], 'camelCase', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('FooBar').key() });
|
||||||
expect(asserts.casing('testExample', 'camelCase')).toBeTruthy();
|
expect(asserts.casing('testExample', 'camelCase', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing('TestExample', 'camelCase')).toBeFalsy();
|
expect(asserts.casing('TestExample', 'camelCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test-example', 'camelCase')).toBeFalsy();
|
expect(asserts.casing('test-example', 'camelCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test_example', 'camelCase')).toBeFalsy();
|
expect(asserts.casing('test_example', 'camelCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be PascalCase', () => {
|
it('value should be PascalCase', () => {
|
||||||
expect(asserts.casing('TestExample', 'PascalCase')).toBeTruthy();
|
expect(asserts.casing('TestExample', 'PascalCase', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['TestExample', 'FooBar'], 'PascalCase')).toBeTruthy();
|
expect(asserts.casing(['TestExample', 'FooBar'], 'PascalCase', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['TestExample', 'fooBar'], 'PascalCase')).toBeFalsy();
|
expect(asserts.casing(['TestExample', 'fooBar'], 'PascalCase', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('fooBar').key() });
|
||||||
expect(asserts.casing('testExample', 'PascalCase')).toBeFalsy();
|
expect(asserts.casing('testExample', 'PascalCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test-example', 'PascalCase')).toBeFalsy();
|
expect(asserts.casing('test-example', 'PascalCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test_example', 'PascalCase')).toBeFalsy();
|
expect(asserts.casing('test_example', 'PascalCase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be kebab-case', () => {
|
it('value should be kebab-case', () => {
|
||||||
expect(asserts.casing('test-example', 'kebab-case')).toBeTruthy();
|
expect(asserts.casing('test-example', 'kebab-case', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['test-example', 'foo-bar'], 'kebab-case')).toBeTruthy();
|
expect(asserts.casing(['test-example', 'foo-bar'], 'kebab-case', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['test-example', 'foo_bar'], 'kebab-case')).toBeFalsy();
|
expect(asserts.casing(['test-example', 'foo_bar'], 'kebab-case', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo_bar').key() });
|
||||||
expect(asserts.casing('testExample', 'kebab-case')).toBeFalsy();
|
expect(asserts.casing('testExample', 'kebab-case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TestExample', 'kebab-case')).toBeFalsy();
|
expect(asserts.casing('TestExample', 'kebab-case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test_example', 'kebab-case')).toBeFalsy();
|
expect(asserts.casing('test_example', 'kebab-case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be snake_case', () => {
|
it('value should be snake_case', () => {
|
||||||
expect(asserts.casing('test_example', 'snake_case')).toBeTruthy();
|
expect(asserts.casing('test_example', 'snake_case', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['test_example', 'foo_bar'], 'snake_case')).toBeTruthy();
|
expect(asserts.casing(['test_example', 'foo_bar'], 'snake_case', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['test_example', 'foo-bar'], 'snake_case')).toBeFalsy();
|
expect(asserts.casing(['test_example', 'foo-bar'], 'snake_case', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo-bar').key() });
|
||||||
expect(asserts.casing('testExample', 'snake_case')).toBeFalsy();
|
expect(asserts.casing('testExample', 'snake_case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TestExample', 'snake_case')).toBeFalsy();
|
expect(asserts.casing('TestExample', 'snake_case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test-example', 'snake_case')).toBeFalsy();
|
expect(asserts.casing('test-example', 'snake_case', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be MACRO_CASE', () => {
|
it('value should be MACRO_CASE', () => {
|
||||||
expect(asserts.casing('TEST_EXAMPLE', 'MACRO_CASE')).toBeTruthy();
|
expect(asserts.casing('TEST_EXAMPLE', 'MACRO_CASE', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['TEST_EXAMPLE', 'FOO_BAR'], 'MACRO_CASE')).toBeTruthy();
|
expect(asserts.casing(['TEST_EXAMPLE', 'FOO_BAR'], 'MACRO_CASE', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['TEST_EXAMPLE', 'FOO-BAR'], 'MACRO_CASE')).toBeFalsy();
|
expect(asserts.casing(['TEST_EXAMPLE', 'FOO-BAR'], 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('FOO-BAR').key() });
|
||||||
expect(asserts.casing('TEST_EXAMPLE_', 'MACRO_CASE')).toBeFalsy();
|
expect(asserts.casing('TEST_EXAMPLE_', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('_TEST_EXAMPLE', 'MACRO_CASE')).toBeFalsy();
|
expect(asserts.casing('_TEST_EXAMPLE', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TEST__EXAMPLE', 'MACRO_CASE')).toBeFalsy();
|
expect(asserts.casing('TEST__EXAMPLE', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TEST-EXAMPLE', 'MACRO_CASE')).toBeFalsy();
|
expect(asserts.casing('TEST-EXAMPLE', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('testExample', 'MACRO_CASE')).toBeFalsy();
|
expect(asserts.casing('testExample', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TestExample', 'MACRO_CASE')).toBeFalsy();
|
expect(asserts.casing('TestExample', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test-example', 'MACRO_CASE')).toBeFalsy();
|
expect(asserts.casing('test-example', 'MACRO_CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be COBOL-CASE', () => {
|
it('value should be COBOL-CASE', () => {
|
||||||
expect(asserts.casing('TEST-EXAMPLE', 'COBOL-CASE')).toBeTruthy();
|
expect(asserts.casing('TEST-EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['TEST-EXAMPLE', 'FOO-BAR'], 'COBOL-CASE')).toBeTruthy();
|
expect(asserts.casing(['TEST-EXAMPLE', 'FOO-BAR'], 'COBOL-CASE', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['TEST-EXAMPLE', 'FOO_BAR'], 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing(['TEST-EXAMPLE', 'FOO_BAR'], 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('FOO_BAR').key() });
|
||||||
expect(asserts.casing('TEST-EXAMPLE-', 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing('TEST-EXAMPLE-', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('0TEST-EXAMPLE', 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing('0TEST-EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('-TEST-EXAMPLE', 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing('-TEST-EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TEST--EXAMPLE', 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing('TEST--EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TEST_EXAMPLE', 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing('TEST_EXAMPLE', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('testExample', 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing('testExample', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TestExample', 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing('TestExample', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test-example', 'COBOL-CASE')).toBeFalsy();
|
expect(asserts.casing('test-example', 'COBOL-CASE', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
it('value should be flatcase', () => {
|
it('value should be flatcase', () => {
|
||||||
expect(asserts.casing('testexample', 'flatcase')).toBeTruthy();
|
expect(asserts.casing('testexample', 'flatcase', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['testexample', 'foobar'], 'flatcase')).toBeTruthy();
|
expect(asserts.casing(['testexample', 'foobar'], 'flatcase', baseLocation)).toEqual({ isValid: true });
|
||||||
expect(asserts.casing(['testexample', 'foo_bar'], 'flatcase')).toBeFalsy();
|
expect(asserts.casing(['testexample', 'foo_bar'], 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation.child('foo_bar').key() });
|
||||||
expect(asserts.casing('testexample_', 'flatcase')).toBeFalsy();
|
expect(asserts.casing('testexample_', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('0testexample', 'flatcase')).toBeFalsy();
|
expect(asserts.casing('0testexample', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('testExample', 'flatcase')).toBeFalsy();
|
expect(asserts.casing('testExample', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('TestExample', 'flatcase')).toBeFalsy();
|
expect(asserts.casing('TestExample', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.casing('test-example', 'flatcase')).toBeFalsy();
|
expect(asserts.casing('test-example', 'flatcase', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.skip('sortOrder', () => {
|
describe.skip('sortOrder', () => {
|
||||||
it('value should be ordered in ASC direction', () => {
|
it('value should be ordered in ASC direction', () => {
|
||||||
expect(asserts.sortOrder(['example', 'foo', 'test'], 'asc')).toBeTruthy();
|
expect(asserts.sortOrder(['example', 'foo', 'test'], 'asc', baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.sortOrder(['example', 'foo', 'test'], { direction: 'asc' })).toBeTruthy();
|
expect(asserts.sortOrder(['example', 'foo', 'test'], { direction: 'asc' }, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.sortOrder(['example'], 'asc')).toBeTruthy();
|
expect(asserts.sortOrder(['example'], 'asc', baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.sortOrder(['example', 'test', 'foo'], 'asc')).toBeFalsy();
|
expect(asserts.sortOrder(['example', 'test', 'foo'], 'asc', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.sortOrder(['example', 'foo', 'test'], 'desc')).toBeFalsy();
|
expect(asserts.sortOrder(['example', 'foo', 'test'], 'desc', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(
|
expect(asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], { direction: 'asc', property: 'name' }, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], {
|
expect(asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], { direction: 'desc', property: 'name' }, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
direction: 'asc',
|
|
||||||
property: 'name',
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
asserts.sortOrder([{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }], {
|
|
||||||
direction: 'desc',
|
|
||||||
property: 'name',
|
|
||||||
}),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
it('value should be ordered in DESC direction', () => {
|
it('value should be ordered in DESC direction', () => {
|
||||||
expect(asserts.sortOrder(['test', 'foo', 'example'], 'desc')).toBeTruthy();
|
expect(asserts.sortOrder(['test', 'foo', 'example'], 'desc', baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.sortOrder(['test', 'foo', 'example'], { direction: 'desc' })).toBeTruthy();
|
expect(asserts.sortOrder(['test', 'foo', 'example'], { direction: 'desc' }, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.sortOrder(['example'], 'desc')).toBeTruthy();
|
expect(asserts.sortOrder(['example'], 'desc', baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
expect(asserts.sortOrder(['example', 'test', 'foo'], 'desc')).toBeFalsy();
|
expect(asserts.sortOrder(['example', 'test', 'foo'], 'desc', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(asserts.sortOrder(['test', 'foo', 'example'], 'asc')).toBeFalsy();
|
expect(asserts.sortOrder(['test', 'foo', 'example'], 'asc', baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
expect(
|
expect(asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], { direction: 'desc', property: 'name' }, baseLocation)).toEqual({ isValid: true, location: baseLocation });
|
||||||
asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], {
|
expect(asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], { direction: 'asc', property: 'name' }, baseLocation)).toEqual({ isValid: false, location: baseLocation });
|
||||||
direction: 'desc',
|
|
||||||
property: 'name',
|
|
||||||
}),
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
asserts.sortOrder([{ name: 'foo' }, { name: 'baz' }, { name: 'bar' }], {
|
|
||||||
direction: 'asc',
|
|
||||||
property: 'name',
|
|
||||||
}),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mutuallyExclusive', () => {
|
describe('mutuallyExclusive', () => {
|
||||||
it('node should not have more than one property from predefined list', () => {
|
it('node should not have more than one property from predefined list', () => {
|
||||||
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'test'])).toBeTruthy();
|
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'test'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), [])).toBeTruthy();
|
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), [], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar'])).toBeFalsy();
|
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
expect(
|
expect(asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar', 'test'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
asserts.mutuallyExclusive(Object.keys(fakeNode), ['foo', 'bar', 'test']),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mutuallyRequired', () => {
|
describe('mutuallyRequired', () => {
|
||||||
it('node should have all the properties from predefined list', () => {
|
it('node should have all the properties from predefined list', () => {
|
||||||
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar'])).toBeTruthy();
|
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'baz'])).toBeTruthy();
|
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'baz'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
expect(asserts.mutuallyRequired(Object.keys(fakeNode), [])).toBeTruthy();
|
expect(asserts.mutuallyRequired(Object.keys(fakeNode), [], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'test'])).toBeFalsy();
|
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'test'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'test'])).toBeFalsy();
|
expect(asserts.mutuallyRequired(Object.keys(fakeNode), ['foo', 'bar', 'test'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('requireAny', () => {
|
describe('requireAny', () => {
|
||||||
it('node must have at least one property from predefined list', () => {
|
it('node must have at least one property from predefined list', () => {
|
||||||
expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'test'])).toBeTruthy();
|
expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'test'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
expect(asserts.requireAny(Object.keys(fakeNode), ['test', 'bar'])).toBeTruthy();
|
expect(asserts.requireAny(Object.keys(fakeNode), ['test', 'bar'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
expect(asserts.requireAny(Object.keys(fakeNode), [])).toBeFalsy();
|
expect(asserts.requireAny(Object.keys(fakeNode), [], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
expect(asserts.requireAny(Object.keys(fakeNode), ['test', 'test1'])).toBeFalsy();
|
expect(asserts.requireAny(Object.keys(fakeNode), ['test', 'test1'], baseLocation)).toEqual({ isValid: false, location: baseLocation.key() });
|
||||||
expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'bar'])).toBeTruthy();
|
expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'bar'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'bar', 'test'])).toBeTruthy();
|
expect(asserts.requireAny(Object.keys(fakeNode), ['foo', 'bar', 'test'], baseLocation)).toEqual({ isValid: true, location: baseLocation.key() });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import { OrderOptions, OrderDirection, isOrdered, getIntersectionLength } from './utils';
|
import { Location } from '../../../ref-utils';
|
||||||
|
import { isString as runOnValue } from '../../../utils';
|
||||||
|
import {
|
||||||
|
OrderOptions,
|
||||||
|
OrderDirection,
|
||||||
|
isOrdered,
|
||||||
|
getIntersectionLength,
|
||||||
|
regexFromString,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
type Asserts = Record<string, (value: any, condition: any) => boolean>;
|
type AssertResult = { isValid: boolean; location?: Location };
|
||||||
|
type Asserts = Record<
|
||||||
|
string,
|
||||||
|
(value: any, condition: any, baseLocation: Location, rawValue?: any) => AssertResult
|
||||||
|
>;
|
||||||
|
|
||||||
export const runOnKeysSet = new Set([
|
export const runOnKeysSet = new Set([
|
||||||
'mutuallyExclusive',
|
'mutuallyExclusive',
|
||||||
@@ -13,6 +25,8 @@ export const runOnKeysSet = new Set([
|
|||||||
'sortOrder',
|
'sortOrder',
|
||||||
'disallowed',
|
'disallowed',
|
||||||
'required',
|
'required',
|
||||||
|
'requireAny',
|
||||||
|
'ref',
|
||||||
]);
|
]);
|
||||||
export const runOnValuesSet = new Set([
|
export const runOnValuesSet = new Set([
|
||||||
'pattern',
|
'pattern',
|
||||||
@@ -24,73 +38,82 @@ export const runOnValuesSet = new Set([
|
|||||||
'maxLength',
|
'maxLength',
|
||||||
'casing',
|
'casing',
|
||||||
'sortOrder',
|
'sortOrder',
|
||||||
|
'ref',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const asserts: Asserts = {
|
export const asserts: Asserts = {
|
||||||
pattern: (value: string | string[], condition: string): boolean => {
|
pattern: (value: string | string[], condition: string, baseLocation: Location) => {
|
||||||
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||||
const values = typeof value === 'string' ? [value] : value;
|
const values = runOnValue(value) ? [value] : value;
|
||||||
const regexOptions = condition.match(/(\b\/\b)(.+)/g) || ['/'];
|
const regx = regexFromString(condition);
|
||||||
condition = condition.slice(1).replace(regexOptions[0], '');
|
|
||||||
const regx = new RegExp(condition, regexOptions[0].slice(1));
|
|
||||||
for (let _val of values) {
|
for (let _val of values) {
|
||||||
if (!_val.match(regx)) {
|
if (!regx?.test(_val)) {
|
||||||
return false;
|
return { isValid: false, location: runOnValue(value) ? baseLocation : baseLocation.key() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return { isValid: true };
|
||||||
},
|
},
|
||||||
enum: (value: string | string[], condition: string[]): boolean => {
|
enum: (value: string | string[], condition: string[], baseLocation: Location) => {
|
||||||
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||||
const values = typeof value === 'string' ? [value] : value;
|
const values = runOnValue(value) ? [value] : value;
|
||||||
for (let _val of values) {
|
for (let _val of values) {
|
||||||
if (!condition.includes(_val)) {
|
if (!condition.includes(_val)) {
|
||||||
return false;
|
return {
|
||||||
|
isValid: false,
|
||||||
|
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return { isValid: true };
|
||||||
},
|
},
|
||||||
defined: (value: string | undefined, condition: boolean = true): boolean => {
|
defined: (value: string | undefined, condition: boolean = true, baseLocation: Location) => {
|
||||||
const isDefined = typeof value !== 'undefined';
|
const isDefined = typeof value !== 'undefined';
|
||||||
return condition ? isDefined : !isDefined;
|
return { isValid: condition ? isDefined : !isDefined, location: baseLocation };
|
||||||
},
|
},
|
||||||
required: (value: string[], keys: string[]): boolean => {
|
required: (value: string[], keys: string[], baseLocation: Location) => {
|
||||||
for (const requiredKey of keys) {
|
for (const requiredKey of keys) {
|
||||||
if (!value.includes(requiredKey)) {
|
if (!value.includes(requiredKey)) {
|
||||||
return false;
|
return { isValid: false, location: baseLocation.key() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return { isValid: true };
|
||||||
},
|
},
|
||||||
disallowed: (value: string | string[], condition: string[]): boolean => {
|
disallowed: (value: string | string[], condition: string[], baseLocation: Location) => {
|
||||||
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||||
const values = typeof value === 'string' ? [value] : value;
|
const values = runOnValue(value) ? [value] : value;
|
||||||
for (let _val of values) {
|
for (let _val of values) {
|
||||||
if (condition.includes(_val)) {
|
if (condition.includes(_val)) {
|
||||||
return false;
|
return {
|
||||||
|
isValid: false,
|
||||||
|
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return { isValid: true };
|
||||||
},
|
},
|
||||||
undefined: (value: any, condition: boolean = true): boolean => {
|
undefined: (value: any, condition: boolean = true, baseLocation: Location) => {
|
||||||
const isUndefined = typeof value === 'undefined';
|
const isUndefined = typeof value === 'undefined';
|
||||||
return condition ? isUndefined : !isUndefined;
|
return { isValid: condition ? isUndefined : !isUndefined, location: baseLocation };
|
||||||
},
|
},
|
||||||
nonEmpty: (value: string | undefined | null, condition: boolean = true): boolean => {
|
nonEmpty: (
|
||||||
|
value: string | undefined | null,
|
||||||
|
condition: boolean = true,
|
||||||
|
baseLocation: Location
|
||||||
|
) => {
|
||||||
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
const isEmpty = typeof value === 'undefined' || value === null || value === '';
|
||||||
return condition ? !isEmpty : isEmpty;
|
return { isValid: condition ? !isEmpty : isEmpty, location: baseLocation };
|
||||||
},
|
},
|
||||||
minLength: (value: string | any[], condition: number): boolean => {
|
minLength: (value: string | any[], condition: number, baseLocation: Location) => {
|
||||||
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||||
return value.length >= condition;
|
return { isValid: value.length >= condition, location: baseLocation };
|
||||||
},
|
},
|
||||||
maxLength: (value: string | any[], condition: number): boolean => {
|
maxLength: (value: string | any[], condition: number, baseLocation: Location) => {
|
||||||
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||||
return value.length <= condition;
|
return { isValid: value.length <= condition, location: baseLocation };
|
||||||
},
|
},
|
||||||
casing: (value: string | string[], condition: string): boolean => {
|
casing: (value: string | string[], condition: string, baseLocation: Location) => {
|
||||||
if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
|
if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||||
const values = typeof value === 'string' ? [value] : value;
|
const values: string[] = runOnValue(value) ? [value] : value;
|
||||||
for (let _val of values) {
|
for (let _val of values) {
|
||||||
let matchCase = false;
|
let matchCase = false;
|
||||||
switch (condition) {
|
switch (condition) {
|
||||||
@@ -117,24 +140,46 @@ export const asserts: Asserts = {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!matchCase) {
|
if (!matchCase) {
|
||||||
return false;
|
return {
|
||||||
|
isValid: false,
|
||||||
|
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return { isValid: true };
|
||||||
},
|
},
|
||||||
sortOrder: (value: any[], condition: OrderOptions | OrderDirection): boolean => {
|
sortOrder: (value: any[], condition: OrderOptions | OrderDirection, baseLocation: Location) => {
|
||||||
if (typeof value === 'undefined') return true;
|
if (typeof value === 'undefined') return { isValid: true };
|
||||||
return isOrdered(value, condition);
|
return { isValid: isOrdered(value, condition), location: baseLocation };
|
||||||
},
|
},
|
||||||
mutuallyExclusive: (value: string[], condition: string[]): boolean => {
|
mutuallyExclusive: (value: string[], condition: string[], baseLocation: Location) => {
|
||||||
return getIntersectionLength(value, condition) < 2;
|
return { isValid: getIntersectionLength(value, condition) < 2, location: baseLocation.key() };
|
||||||
},
|
},
|
||||||
mutuallyRequired: (value: string[], condition: string[]): boolean => {
|
mutuallyRequired: (value: string[], condition: string[], baseLocation: Location) => {
|
||||||
return getIntersectionLength(value, condition) > 0
|
return {
|
||||||
? getIntersectionLength(value, condition) === condition.length
|
isValid:
|
||||||
: true;
|
getIntersectionLength(value, condition) > 0
|
||||||
|
? getIntersectionLength(value, condition) === condition.length
|
||||||
|
: true,
|
||||||
|
location: baseLocation.key(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
requireAny: (value: string[], condition: string[]): boolean => {
|
requireAny: (value: string[], condition: string[], baseLocation: Location) => {
|
||||||
return getIntersectionLength(value, condition) >= 1;
|
return { isValid: getIntersectionLength(value, condition) >= 1, location: baseLocation.key() };
|
||||||
|
},
|
||||||
|
ref: (_value: any, condition: string | boolean, baseLocation, rawValue: any) => {
|
||||||
|
if (typeof rawValue === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
|
||||||
|
const hasRef = rawValue.hasOwnProperty('$ref');
|
||||||
|
if (typeof condition === 'boolean') {
|
||||||
|
return {
|
||||||
|
isValid: condition ? hasRef : !hasRef,
|
||||||
|
location: hasRef ? baseLocation : baseLocation.key(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const regex = regexFromString(condition);
|
||||||
|
return {
|
||||||
|
isValid: hasRef && regex?.test(rawValue['$ref']),
|
||||||
|
location: hasRef ? baseLocation : baseLocation.key(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isRef } from '../../../ref-utils';
|
import { isRef, Location } from '../../../ref-utils';
|
||||||
import { Problem, ProblemSeverity, UserContext } from '../../../walk';
|
import { Problem, ProblemSeverity, UserContext } from '../../../walk';
|
||||||
import { asserts } from './asserts';
|
import { asserts } from './asserts';
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export type AssertToApply = {
|
|||||||
export function buildVisitorObject(
|
export function buildVisitorObject(
|
||||||
subject: string,
|
subject: string,
|
||||||
context: Record<string, any>[],
|
context: Record<string, any>[],
|
||||||
subjectVisitor: any,
|
subjectVisitor: any
|
||||||
) {
|
) {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
return { [subject]: subjectVisitor };
|
return { [subject]: subjectVisitor };
|
||||||
@@ -46,7 +46,7 @@ export function buildVisitorObject(
|
|||||||
|
|
||||||
if (matchParentKeys && excludeParentKeys) {
|
if (matchParentKeys && excludeParentKeys) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`,
|
`Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,9 +75,12 @@ export function buildVisitorObject(
|
|||||||
export function buildSubjectVisitor(
|
export function buildSubjectVisitor(
|
||||||
properties: string | string[],
|
properties: string | string[],
|
||||||
asserts: AssertToApply[],
|
asserts: AssertToApply[],
|
||||||
context?: Record<string, any>[],
|
context?: Record<string, any>[]
|
||||||
) {
|
) {
|
||||||
return function (node: any, { report, location, key, type, resolve }: UserContext) {
|
return (
|
||||||
|
node: any,
|
||||||
|
{ report, location, rawLocation, key, type, resolve, rawNode }: UserContext
|
||||||
|
) => {
|
||||||
// We need to check context's last node if it has the same type as subject node;
|
// We need to check context's last node if it has the same type as subject node;
|
||||||
// if yes - that means we didn't create context's last node visitor,
|
// if yes - that means we didn't create context's last node visitor,
|
||||||
// so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
|
// so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
|
||||||
@@ -101,14 +104,28 @@ export function buildSubjectVisitor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const assert of asserts) {
|
for (const assert of asserts) {
|
||||||
|
const currentLocation = assert.name === 'ref' ? rawLocation : location;
|
||||||
if (properties) {
|
if (properties) {
|
||||||
for (const property of properties) {
|
for (const property of properties) {
|
||||||
// we can have resolvable scalar so need to resolve value here.
|
// we can have resolvable scalar so need to resolve value here.
|
||||||
const value = isRef(node[property]) ? resolve(node[property])?.node : node[property];
|
const value = isRef(node[property]) ? resolve(node[property])?.node : node[property];
|
||||||
runAssertion(value, assert, location.child(property), report);
|
runAssertion({
|
||||||
|
values: value,
|
||||||
|
rawValues: rawNode[property],
|
||||||
|
assert,
|
||||||
|
location: currentLocation.child(property),
|
||||||
|
report,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runAssertion(Object.keys(node), assert, location.key(), report);
|
const value = assert.name === 'ref' ? rawNode : Object.keys(node);
|
||||||
|
runAssertion({
|
||||||
|
values: Object.keys(node),
|
||||||
|
rawValues: value,
|
||||||
|
assert,
|
||||||
|
location: currentLocation,
|
||||||
|
report,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -148,20 +165,28 @@ export function isOrdered(value: any[], options: OrderOptions | OrderDirection):
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runAssertion(
|
type RunAssertionParams = {
|
||||||
values: string | string[],
|
values: string | string[];
|
||||||
assert: AssertToApply,
|
rawValues: any;
|
||||||
location: any,
|
assert: AssertToApply;
|
||||||
report: (problem: Problem) => void,
|
location: Location;
|
||||||
) {
|
report: (problem: Problem) => void;
|
||||||
const lintResult = asserts[assert.name](values, assert.conditions);
|
};
|
||||||
if (!lintResult) {
|
|
||||||
|
function runAssertion({ values, rawValues, assert, location, report }: RunAssertionParams) {
|
||||||
|
const lintResult = asserts[assert.name](values, assert.conditions, location, rawValues);
|
||||||
|
if (!lintResult.isValid) {
|
||||||
report({
|
report({
|
||||||
message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
|
message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
|
||||||
location,
|
location: lintResult.location || location,
|
||||||
forceSeverity: assert.severity,
|
forceSeverity: assert.severity,
|
||||||
suggest: assert.suggest,
|
suggest: assert.suggest,
|
||||||
ruleId: assert.assertId,
|
ruleId: assert.assertId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function regexFromString(input: string): RegExp | null {
|
||||||
|
const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
|
||||||
|
return matches && new RegExp(matches[1] || matches[3], matches[2]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,14 @@ import {
|
|||||||
VisitFunction,
|
VisitFunction,
|
||||||
} from './visitors';
|
} from './visitors';
|
||||||
|
|
||||||
import { ResolvedRefMap, Document, ResolveError, YamlParseError, Source, makeRefId } from './resolve';
|
import {
|
||||||
|
ResolvedRefMap,
|
||||||
|
Document,
|
||||||
|
ResolveError,
|
||||||
|
YamlParseError,
|
||||||
|
Source,
|
||||||
|
makeRefId,
|
||||||
|
} from './resolve';
|
||||||
import { pushStack, popStack } from './utils';
|
import { pushStack, popStack } from './utils';
|
||||||
import { OasVersion } from './oas-types';
|
import { OasVersion } from './oas-types';
|
||||||
import { NormalizedNodeType, isNamedType } from './types';
|
import { NormalizedNodeType, isNamedType } from './types';
|
||||||
@@ -19,14 +26,16 @@ export type ResolveResult<T extends NonUndefined> =
|
|||||||
|
|
||||||
export type ResolveFn<T> = (
|
export type ResolveFn<T> = (
|
||||||
node: Referenced<T>,
|
node: Referenced<T>,
|
||||||
from?: string,
|
from?: string
|
||||||
) => { location: Location; node: T } | { location: undefined; node: undefined };
|
) => { location: Location; node: T } | { location: undefined; node: undefined };
|
||||||
|
|
||||||
export type UserContext = {
|
export type UserContext = {
|
||||||
report(problem: Problem): void;
|
report(problem: Problem): void;
|
||||||
location: Location;
|
location: Location;
|
||||||
|
rawNode: any;
|
||||||
|
rawLocation: Location;
|
||||||
resolve<T>(
|
resolve<T>(
|
||||||
node: Referenced<T>,
|
node: Referenced<T>
|
||||||
): { location: Location; node: T } | { location: undefined; node: undefined };
|
): { location: Location; node: T } | { location: undefined; node: undefined };
|
||||||
parentLocations: Record<string, Location>;
|
parentLocations: Record<string, Location>;
|
||||||
type: NormalizedNodeType;
|
type: NormalizedNodeType;
|
||||||
@@ -121,8 +130,9 @@ export function walkDocument<T>(opts: {
|
|||||||
type: NormalizedNodeType,
|
type: NormalizedNodeType,
|
||||||
location: Location,
|
location: Location,
|
||||||
parent: any,
|
parent: any,
|
||||||
key: string | number,
|
key: string | number
|
||||||
) {
|
) {
|
||||||
|
const rawLocation = location;
|
||||||
let currentLocation = location;
|
let currentLocation = location;
|
||||||
const { node: resolvedNode, location: resolvedLocation, error } = resolve(node);
|
const { node: resolvedNode, location: resolvedLocation, error } = resolve(node);
|
||||||
const enteredContexts: Set<VisitorLevelContext> = new Set();
|
const enteredContexts: Set<VisitorLevelContext> = new Set();
|
||||||
@@ -138,15 +148,17 @@ export function walkDocument<T>(opts: {
|
|||||||
{
|
{
|
||||||
report,
|
report,
|
||||||
resolve,
|
resolve,
|
||||||
|
rawNode: node,
|
||||||
|
rawLocation,
|
||||||
location,
|
location,
|
||||||
type,
|
type,
|
||||||
parent,
|
parent,
|
||||||
key,
|
key,
|
||||||
parentLocations: {},
|
parentLocations: {},
|
||||||
oasVersion: ctx.oasVersion,
|
oasVersion: ctx.oasVersion,
|
||||||
getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
|
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
||||||
},
|
},
|
||||||
{ node: resolvedNode, location: resolvedLocation, error },
|
{ node: resolvedNode, location: resolvedLocation, error }
|
||||||
);
|
);
|
||||||
if (resolvedLocation?.source.absoluteRef && ctx.refTypes) {
|
if (resolvedLocation?.source.absoluteRef && ctx.refTypes) {
|
||||||
ctx.refTypes.set(resolvedLocation?.source.absoluteRef, type);
|
ctx.refTypes.set(resolvedLocation?.source.absoluteRef, type);
|
||||||
@@ -162,7 +174,7 @@ export function walkDocument<T>(opts: {
|
|||||||
|
|
||||||
const anyEnterVisitors = normalizedVisitors.any.enter;
|
const anyEnterVisitors = normalizedVisitors.any.enter;
|
||||||
const currentEnterVisitors = anyEnterVisitors.concat(
|
const currentEnterVisitors = anyEnterVisitors.concat(
|
||||||
normalizedVisitors[type.name]?.enter || [],
|
normalizedVisitors[type.name]?.enter || []
|
||||||
);
|
);
|
||||||
|
|
||||||
const activatedContexts: Array<VisitorSkippedLevelContext | VisitorLevelContext> = [];
|
const activatedContexts: Array<VisitorSkippedLevelContext | VisitorLevelContext> = [];
|
||||||
@@ -205,7 +217,7 @@ export function walkDocument<T>(opts: {
|
|||||||
while (ctx) {
|
while (ctx) {
|
||||||
ctx.activatedOn!.value.nextLevelTypeActivated = pushStack(
|
ctx.activatedOn!.value.nextLevelTypeActivated = pushStack(
|
||||||
ctx.activatedOn!.value.nextLevelTypeActivated,
|
ctx.activatedOn!.value.nextLevelTypeActivated,
|
||||||
type,
|
type
|
||||||
);
|
);
|
||||||
ctx = ctx.parent;
|
ctx = ctx.parent;
|
||||||
}
|
}
|
||||||
@@ -216,9 +228,10 @@ export function walkDocument<T>(opts: {
|
|||||||
const { ignoreNextVisitorsOnNode } = visitWithContext(
|
const { ignoreNextVisitorsOnNode } = visitWithContext(
|
||||||
visit,
|
visit,
|
||||||
resolvedNode,
|
resolvedNode,
|
||||||
|
node,
|
||||||
context,
|
context,
|
||||||
ruleId,
|
ruleId,
|
||||||
severity,
|
severity
|
||||||
);
|
);
|
||||||
if (ignoreNextVisitorsOnNode) break;
|
if (ignoreNextVisitorsOnNode) break;
|
||||||
}
|
}
|
||||||
@@ -282,7 +295,7 @@ export function walkDocument<T>(opts: {
|
|||||||
|
|
||||||
const anyLeaveVisitors = normalizedVisitors.any.leave;
|
const anyLeaveVisitors = normalizedVisitors.any.leave;
|
||||||
const currentLeaveVisitors = (normalizedVisitors[type.name]?.leave || []).concat(
|
const currentLeaveVisitors = (normalizedVisitors[type.name]?.leave || []).concat(
|
||||||
anyLeaveVisitors,
|
anyLeaveVisitors
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const context of activatedContexts.reverse()) {
|
for (const context of activatedContexts.reverse()) {
|
||||||
@@ -294,7 +307,7 @@ export function walkDocument<T>(opts: {
|
|||||||
let ctx: VisitorLevelContext | null = context.parent;
|
let ctx: VisitorLevelContext | null = context.parent;
|
||||||
while (ctx) {
|
while (ctx) {
|
||||||
ctx.activatedOn!.value.nextLevelTypeActivated = popStack(
|
ctx.activatedOn!.value.nextLevelTypeActivated = popStack(
|
||||||
ctx.activatedOn!.value.nextLevelTypeActivated,
|
ctx.activatedOn!.value.nextLevelTypeActivated
|
||||||
);
|
);
|
||||||
ctx = ctx.parent;
|
ctx = ctx.parent;
|
||||||
}
|
}
|
||||||
@@ -304,7 +317,7 @@ export function walkDocument<T>(opts: {
|
|||||||
|
|
||||||
for (const { context, visit, ruleId, severity } of currentLeaveVisitors) {
|
for (const { context, visit, ruleId, severity } of currentLeaveVisitors) {
|
||||||
if (!context.isSkippedLevel && enteredContexts.has(context)) {
|
if (!context.isSkippedLevel && enteredContexts.has(context)) {
|
||||||
visitWithContext(visit, resolvedNode, context, ruleId, severity);
|
visitWithContext(visit, resolvedNode, node, context, ruleId, severity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,15 +334,17 @@ export function walkDocument<T>(opts: {
|
|||||||
{
|
{
|
||||||
report,
|
report,
|
||||||
resolve,
|
resolve,
|
||||||
|
rawNode: node,
|
||||||
|
rawLocation,
|
||||||
location,
|
location,
|
||||||
type,
|
type,
|
||||||
parent,
|
parent,
|
||||||
key,
|
key,
|
||||||
parentLocations: {},
|
parentLocations: {},
|
||||||
oasVersion: ctx.oasVersion,
|
oasVersion: ctx.oasVersion,
|
||||||
getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
|
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
||||||
},
|
},
|
||||||
{ node: resolvedNode, location: resolvedLocation, error },
|
{ node: resolvedNode, location: resolvedLocation, error }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,37 +353,42 @@ export function walkDocument<T>(opts: {
|
|||||||
// returns true ignores all the next visitors on the specific node
|
// returns true ignores all the next visitors on the specific node
|
||||||
function visitWithContext(
|
function visitWithContext(
|
||||||
visit: VisitFunction<any>,
|
visit: VisitFunction<any>,
|
||||||
|
resolvedNode: any,
|
||||||
node: any,
|
node: any,
|
||||||
context: VisitorLevelContext,
|
context: VisitorLevelContext,
|
||||||
ruleId: string,
|
ruleId: string,
|
||||||
severity: ProblemSeverity,
|
severity: ProblemSeverity
|
||||||
) {
|
) {
|
||||||
const report = reportFn.bind(undefined, ruleId, severity);
|
const report = reportFn.bind(undefined, ruleId, severity);
|
||||||
let ignoreNextVisitorsOnNode = false;
|
let ignoreNextVisitorsOnNode = false;
|
||||||
|
|
||||||
visit(
|
visit(
|
||||||
node,
|
resolvedNode,
|
||||||
{
|
{
|
||||||
report,
|
report,
|
||||||
resolve,
|
resolve,
|
||||||
|
rawNode: node,
|
||||||
location: currentLocation,
|
location: currentLocation,
|
||||||
|
rawLocation,
|
||||||
type,
|
type,
|
||||||
parent,
|
parent,
|
||||||
key,
|
key,
|
||||||
parentLocations: collectParentsLocations(context),
|
parentLocations: collectParentsLocations(context),
|
||||||
oasVersion: ctx.oasVersion,
|
oasVersion: ctx.oasVersion,
|
||||||
ignoreNextVisitorsOnNode: () => { ignoreNextVisitorsOnNode = true; },
|
ignoreNextVisitorsOnNode: () => {
|
||||||
|
ignoreNextVisitorsOnNode = true;
|
||||||
|
},
|
||||||
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
|
||||||
},
|
},
|
||||||
collectParents(context),
|
collectParents(context),
|
||||||
context,
|
context
|
||||||
);
|
);
|
||||||
return { ignoreNextVisitorsOnNode };
|
return { ignoreNextVisitorsOnNode };
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolve<T>(
|
function resolve<T>(
|
||||||
ref: Referenced<T>,
|
ref: Referenced<T>,
|
||||||
from: string = currentLocation.source.absoluteRef,
|
from: string = currentLocation.source.absoluteRef
|
||||||
): ResolveResult<T> {
|
): ResolveResult<T> {
|
||||||
if (!isRef(ref)) return { location, node: ref };
|
if (!isRef(ref)) return { location, node: ref };
|
||||||
const refId = makeRefId(from, ref.$ref);
|
const refId = makeRefId(from, ref.$ref);
|
||||||
|
|||||||
Reference in New Issue
Block a user