mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-06 04:21:09 +00:00
feat: adding spot arazzo rules (#1713)
This commit is contained in:
6
.changeset/green-penguins-press.md
Normal file
6
.changeset/green-penguins-press.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@redocly/openapi-core": minor
|
||||
"@redocly/cli": minor
|
||||
---
|
||||
|
||||
Added Spot and Arazzo rules: `no-criteria-xpath`, `no-actions-type-end`, `criteria-unique`.
|
||||
@@ -84,6 +84,9 @@ The rules available for linting Arazzo are:
|
||||
- `step-onSuccess-unique`: the `onSuccess` actions of the `step` object must be unique.
|
||||
- `step-onFailure-unique`: the `onFailure` actions of the `step` object must be unique.
|
||||
- `requestBody-replacements-unique`: the `replacements` of the `requestBody` object must be unique.
|
||||
- `no-criteria-xpath`: the `xpath` type criteria is not supported by Spot.
|
||||
- `no-actions-type-end`: the `end` type action is not supported by Spot.
|
||||
- `criteria-unique`: the criteria list must not contain duplicated assertions.
|
||||
|
||||
Add the rules to `redocly.yaml`, but for Arazzo specifications, the rules go in their own configuration section called `arazzoRules`.
|
||||
The following example shows configuration for the minimal ruleset with some additional rules configuration:
|
||||
|
||||
60
docs/rules/arazzo/criteria-unique.md
Normal file
60
docs/rules/arazzo/criteria-unique.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
slug: /docs/cli/rules/arazzo/criteria-unique
|
||||
---
|
||||
|
||||
# criteria-unique
|
||||
|
||||
The criteria list must not contain duplicated assertions.
|
||||
|
||||
| Arazzo | Compatibility |
|
||||
| ------ | ------------- |
|
||||
| 1.0.0 | ✅ |
|
||||
|
||||
## API design principles
|
||||
|
||||
The criteria list must not contain duplicated assertions.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Option | Type | Description |
|
||||
| -------- | ------ | ------------------------------------------------------- |
|
||||
| severity | string | Possible values: `off`, `warn`, `error`. Default `off`. |
|
||||
|
||||
An example configuration:
|
||||
|
||||
```yaml
|
||||
arazzoRules:
|
||||
criteria-unique: error
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Given the following configuration:
|
||||
|
||||
```yaml
|
||||
arazzoRules:
|
||||
criteria-unique: error
|
||||
```
|
||||
|
||||
Example of a criteria list:
|
||||
|
||||
```yaml Object example
|
||||
successCriteria:
|
||||
- condition: $statusCode == 200
|
||||
onSuccess:
|
||||
- name: 'onSuccessActionName'
|
||||
type: 'goto'
|
||||
stepId: 'buy-ticket'
|
||||
criteria:
|
||||
- condition: $response.body.open == true
|
||||
onFailure:
|
||||
- name: 'onFailureActionName'
|
||||
type: 'goto'
|
||||
stepId: 'buy-ticket'
|
||||
criteria:
|
||||
- condition: $response.body.open == true
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Rule source](https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/rules/arazzo/criteria-unique.ts)
|
||||
55
docs/rules/spot/no-actions-type-end.test.md
Normal file
55
docs/rules/spot/no-actions-type-end.test.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
slug: /docs/cli/rules/spot/no-actions-type-end
|
||||
---
|
||||
|
||||
# no-actions-type-end
|
||||
|
||||
The `end` type action is not supported by Spot.
|
||||
|
||||
| Arazzo | Compatibility |
|
||||
| ------ | ------------- |
|
||||
| 1.0.0 | ✅ |
|
||||
|
||||
## API design principles
|
||||
|
||||
This is a `Spot`-specific rule.
|
||||
The `end` type action is not supported by Spot.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Option | Type | Description |
|
||||
| -------- | ------ | ------------------------------------------------------- |
|
||||
| severity | string | Possible values: `off`, `warn`, `error`. Default `off`. |
|
||||
|
||||
An example configuration:
|
||||
|
||||
```yaml
|
||||
arazzoRules:
|
||||
no-actions-type-end: error
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Given the following configuration:
|
||||
|
||||
```yaml
|
||||
arazzoRules:
|
||||
no-actions-type-end: error
|
||||
```
|
||||
|
||||
Example of an action:
|
||||
|
||||
```yaml Object example
|
||||
onSuccess:
|
||||
- name: 'onSuccessActionName'
|
||||
type: 'goto'
|
||||
stepId: 'buy-ticket'
|
||||
onFailure:
|
||||
- name: 'onFailureActionName'
|
||||
type: 'goto'
|
||||
stepId: 'buy-ticket'
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Rule source](https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/rules/spot/no-actions-type-end.ts)
|
||||
54
docs/rules/spot/no-criteria-xpath.md
Normal file
54
docs/rules/spot/no-criteria-xpath.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
slug: /docs/cli/rules/spot/no-criteria-xpath
|
||||
---
|
||||
|
||||
# no-criteria-xpath
|
||||
|
||||
The `xpath` type criteria is not supported by Spot.
|
||||
|
||||
| Arazzo | Compatibility |
|
||||
| ------ | ------------- |
|
||||
| 1.0.0 | ✅ |
|
||||
|
||||
## API design principles
|
||||
|
||||
This is `Spot` specific rule.
|
||||
The `xpath` type criteria is not supported by Spot.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Option | Type | Description |
|
||||
| -------- | ------ | ------------------------------------------------------- |
|
||||
| severity | string | Possible values: `off`, `warn`, `error`. Default `off`. |
|
||||
|
||||
An example configuration:
|
||||
|
||||
```yaml
|
||||
arazzoRules:
|
||||
no-criteria-xpath: error
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Given the following configuration:
|
||||
|
||||
```yaml
|
||||
arazzoRules:
|
||||
no-criteria-xpath: error
|
||||
```
|
||||
|
||||
Example of criteria:
|
||||
|
||||
```yaml Object example
|
||||
successCriteria:
|
||||
- condition: $statusCode == 201
|
||||
- context: $response.body
|
||||
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
||||
type:
|
||||
type: jsonpath
|
||||
version: draft-goessner-dispatch-jsonpath-00
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Rule source](https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/rules/spot/no-criteria-xpath.ts)
|
||||
@@ -5,6 +5,9 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
|
||||
"arazzoDecorators": {},
|
||||
"arazzoPreprocessors": {},
|
||||
"arazzoRules": {
|
||||
"criteria-unique": "warn",
|
||||
"no-actions-type-end": "warn",
|
||||
"no-criteria-xpath": "warn",
|
||||
"parameters-not-in-body": "warn",
|
||||
"parameters-unique": "error",
|
||||
"requestBody-replacements-unique": "warn",
|
||||
@@ -147,6 +150,9 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
|
||||
"arazzoDecorators": {},
|
||||
"arazzoPreprocessors": {},
|
||||
"arazzoRules": {
|
||||
"criteria-unique": "warn",
|
||||
"no-actions-type-end": "warn",
|
||||
"no-criteria-xpath": "warn",
|
||||
"parameters-not-in-body": "warn",
|
||||
"parameters-unique": "error",
|
||||
"requestBody-replacements-unique": "warn",
|
||||
|
||||
@@ -139,6 +139,9 @@ const all: PluginStyleguideConfig<'built-in'> = {
|
||||
'step-onSuccess-unique': 'error',
|
||||
'step-onFailure-unique': 'error',
|
||||
'requestBody-replacements-unique': 'error',
|
||||
'no-criteria-xpath': 'error',
|
||||
'no-actions-type-end': 'error',
|
||||
'criteria-unique': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -121,6 +121,9 @@ const minimal: PluginStyleguideConfig<'built-in'> = {
|
||||
'step-onSuccess-unique': 'off',
|
||||
'step-onFailure-unique': 'off',
|
||||
'requestBody-replacements-unique': 'off',
|
||||
'no-criteria-xpath': 'off',
|
||||
'no-actions-type-end': 'off',
|
||||
'criteria-unique': 'off',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -121,6 +121,9 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
|
||||
'step-onSuccess-unique': 'error',
|
||||
'step-onFailure-unique': 'error',
|
||||
'requestBody-replacements-unique': 'error',
|
||||
'no-criteria-xpath': 'error',
|
||||
'no-actions-type-end': 'error',
|
||||
'criteria-unique': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -121,6 +121,9 @@ const recommended: PluginStyleguideConfig<'built-in'> = {
|
||||
'step-onSuccess-unique': 'warn',
|
||||
'step-onFailure-unique': 'warn',
|
||||
'requestBody-replacements-unique': 'warn',
|
||||
'no-criteria-xpath': 'warn',
|
||||
'no-actions-type-end': 'warn',
|
||||
'criteria-unique': 'warn',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
161
packages/core/src/rules/arazzo/__tests__/criteria-unique.test.ts
Normal file
161
packages/core/src/rules/arazzo/__tests__/criteria-unique.test.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo criteria-unique', () => {
|
||||
const document = parseYamlToDocument(
|
||||
outdent`
|
||||
arazzo: '1.0.0'
|
||||
info:
|
||||
title: Cool API
|
||||
version: 1.0.0
|
||||
description: A cool API
|
||||
sourceDescriptions:
|
||||
- name: museum-api
|
||||
type: openapi
|
||||
url: openapi.yaml
|
||||
workflows:
|
||||
- workflowId: get-museum-hours
|
||||
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
value: Basic Og==
|
||||
steps:
|
||||
- stepId: create-event
|
||||
description: >-
|
||||
Create a new special event.
|
||||
operationPath: $sourceDescriptions.museum-api#/paths/~1special-events/post
|
||||
requestBody:
|
||||
payload:
|
||||
name: 'Mermaid Treasure Identification and Analysis'
|
||||
location: 'Under the seaaa 🦀 🎶 🌊.'
|
||||
eventDescription: 'Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.'
|
||||
dates:
|
||||
- '2023-09-05'
|
||||
- '2023-09-08'
|
||||
price: 0
|
||||
successCriteria:
|
||||
- condition: $statusCode == 200
|
||||
- condition: $statusCode == 200
|
||||
- context: $response.body
|
||||
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
||||
type: jsonpath
|
||||
- context: $response.body
|
||||
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
||||
type: jsonpath
|
||||
onSuccess:
|
||||
- name: 'onSuccessActionName'
|
||||
type: 'goto'
|
||||
stepId: 'buy-ticket'
|
||||
criteria:
|
||||
- condition: $response.body.open == true
|
||||
- condition: $response.body.open == true
|
||||
onFailure:
|
||||
- name: 'onFailureActionName'
|
||||
type: 'goto'
|
||||
stepId: 'buy-ticket'
|
||||
criteria:
|
||||
- condition: $response.body.open == true
|
||||
- condition: $response.body.open == true
|
||||
outputs:
|
||||
createdEventId: $response.body.eventId
|
||||
name: $response.body.name
|
||||
- workflowId: get-museum-hours-2
|
||||
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
value: Basic Og==
|
||||
steps:
|
||||
- stepId: get-museum-hours
|
||||
description: >-
|
||||
Get museum hours by resolving request details with getMuseumHours operationId from openapi.yaml description.
|
||||
operationId: museum-api.getMuseumHours
|
||||
successCriteria:
|
||||
- condition: $statusCode == 200
|
||||
`,
|
||||
'arazzo.yaml'
|
||||
);
|
||||
|
||||
it('should report when the duplicated criteria exists', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'criteria-unique': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/successCriteria/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The Step SuccessCriteria items must be unique.",
|
||||
"ruleId": "criteria-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/successCriteria/3",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The Step SuccessCriteria items must be unique.",
|
||||
"ruleId": "criteria-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/onSuccess/0/criteria/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The SuccessAction criteria items must be unique.",
|
||||
"ruleId": "criteria-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/onFailure/0/criteria/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The FailureAction criteria items must be unique.",
|
||||
"ruleId": "criteria-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report when the duplicated criteria exists', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo no-actions-type-end', () => {
|
||||
const document = parseYamlToDocument(
|
||||
outdent`
|
||||
arazzo: '1.0.0'
|
||||
info:
|
||||
title: Cool API
|
||||
version: 1.0.0
|
||||
description: A cool API
|
||||
sourceDescriptions:
|
||||
- name: museum-api
|
||||
type: openapi
|
||||
url: openapi.yaml
|
||||
workflows:
|
||||
- workflowId: get-museum-hours
|
||||
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
value: Basic Og==
|
||||
steps:
|
||||
- stepId: create-event
|
||||
description: >-
|
||||
Create a new special event.
|
||||
operationPath: $sourceDescriptions.museum-api#/paths/~1special-events/post
|
||||
requestBody:
|
||||
payload:
|
||||
name: 'Mermaid Treasure Identification and Analysis'
|
||||
location: 'Under the seaaa 🦀 🎶 🌊.'
|
||||
eventDescription: 'Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.'
|
||||
dates:
|
||||
- '2023-09-05'
|
||||
- '2023-09-08'
|
||||
price: 0
|
||||
successCriteria:
|
||||
- condition: $statusCode == 201
|
||||
onSuccess:
|
||||
- name: 'onSuccessActionName'
|
||||
type: 'end'
|
||||
stepId: 'buy-ticket'
|
||||
onFailure:
|
||||
- name: 'onFailureActionName'
|
||||
type: 'end'
|
||||
stepId: 'buy-ticket'
|
||||
outputs:
|
||||
createdEventId: $response.body.eventId
|
||||
name: $response.body.name
|
||||
- workflowId: get-museum-hours-2
|
||||
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
value: Basic Og==
|
||||
steps:
|
||||
- stepId: get-museum-hours
|
||||
description: >-
|
||||
Get museum hours by resolving request details with getMuseumHours operationId from openapi.yaml description.
|
||||
operationId: museum-api.getMuseumHours
|
||||
successCriteria:
|
||||
- condition: $statusCode == 200
|
||||
`,
|
||||
'arazzo.yaml'
|
||||
);
|
||||
|
||||
it('should report when the type `end` action exists', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'no-actions-type-end': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/onSuccess/0/type",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The \`end\` type action is not supported by Spot.",
|
||||
"ruleId": "no-actions-type-end",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/onFailure/0/type",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The \`end\` type action is not supported by Spot.",
|
||||
"ruleId": "no-actions-type-end",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report when the type `end` action exists', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo no-criteria-xpath', () => {
|
||||
const document = parseYamlToDocument(
|
||||
outdent`
|
||||
arazzo: '1.0.0'
|
||||
info:
|
||||
title: Cool API
|
||||
version: 1.0.0
|
||||
description: A cool API
|
||||
sourceDescriptions:
|
||||
- name: museum-api
|
||||
type: openapi
|
||||
url: openapi.yaml
|
||||
workflows:
|
||||
- workflowId: get-museum-hours
|
||||
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
value: Basic Og==
|
||||
steps:
|
||||
- stepId: create-event
|
||||
description: >-
|
||||
Create a new special event.
|
||||
operationPath: $sourceDescriptions.museum-api#/paths/~1special-events/post
|
||||
requestBody:
|
||||
payload:
|
||||
name: 'Mermaid Treasure Identification and Analysis'
|
||||
location: 'Under the seaaa 🦀 🎶 🌊.'
|
||||
eventDescription: 'Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.'
|
||||
dates:
|
||||
- '2023-09-05'
|
||||
- '2023-09-08'
|
||||
price: 0
|
||||
successCriteria:
|
||||
- condition: $statusCode == 201
|
||||
- context: $response.body
|
||||
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
||||
type:
|
||||
type: jsonpath
|
||||
version: draft-goessner-dispatch-jsonpath-00
|
||||
- context: $response.body
|
||||
condition: $.name == 'Orca Identification and Analysis'
|
||||
type: xpath
|
||||
- context: $response.body
|
||||
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
||||
type:
|
||||
type: xpath
|
||||
version: xpath-30
|
||||
outputs:
|
||||
createdEventId: $response.body.eventId
|
||||
name: $response.body.name
|
||||
- workflowId: get-museum-hours-2
|
||||
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
value: Basic Og==
|
||||
steps:
|
||||
- stepId: get-museum-hours
|
||||
description: >-
|
||||
Get museum hours by resolving request details with getMuseumHours operationId from openapi.yaml description.
|
||||
operationId: museum-api.getMuseumHours
|
||||
successCriteria:
|
||||
- condition: $statusCode == 200
|
||||
`,
|
||||
'arazzo.yaml'
|
||||
);
|
||||
|
||||
it('should report when the `xpath` criteria exists', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'no-criteria-xpath': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/successCriteria/2/type",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The \`xpath\` type criteria is not supported by Spot.",
|
||||
"ruleId": "no-criteria-xpath",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/successCriteria/3/type",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The \`xpath\` type criteria is not supported by Spot.",
|
||||
"ruleId": "no-criteria-xpath",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report when the `xpath` criteria exists', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
63
packages/core/src/rules/arazzo/criteria-unique.ts
Normal file
63
packages/core/src/rules/arazzo/criteria-unique.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const CriteriaUnique: ArazzoRule = () => {
|
||||
return {
|
||||
FailureActionObject: {
|
||||
enter(action, { report, location }: UserContext) {
|
||||
const criterias = action.criteria;
|
||||
const seen = new Set<string>();
|
||||
for (const criteria of criterias) {
|
||||
const key = JSON.stringify(criteria);
|
||||
if (seen.has(key)) {
|
||||
report({
|
||||
message: 'The FailureAction criteria items must be unique.',
|
||||
location: location.child(['criteria', criterias.indexOf(criteria)]),
|
||||
});
|
||||
} else {
|
||||
seen.add(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
SuccessActionObject: {
|
||||
enter(action, { report, location }: UserContext) {
|
||||
const criterias = action.criteria;
|
||||
const seen = new Set<string>();
|
||||
for (const criteria of criterias) {
|
||||
const key = JSON.stringify(criteria);
|
||||
if (seen.has(key)) {
|
||||
report({
|
||||
message: 'The SuccessAction criteria items must be unique.',
|
||||
location: location.child(['criteria', criterias.indexOf(criteria)]),
|
||||
});
|
||||
} else {
|
||||
seen.add(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
Step: {
|
||||
enter(step, { report, location }: UserContext) {
|
||||
if (!step.successCriteria) {
|
||||
return;
|
||||
}
|
||||
|
||||
const successCriterias = step.successCriteria;
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const criteria of successCriterias) {
|
||||
const key = JSON.stringify(criteria);
|
||||
if (seen.has(key)) {
|
||||
report({
|
||||
message: 'The Step SuccessCriteria items must be unique.',
|
||||
location: location.child(['successCriteria', successCriterias.indexOf(criteria)]),
|
||||
});
|
||||
} else {
|
||||
seen.add(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -11,6 +11,9 @@ import { ParametersUnique } from './parameters-unique';
|
||||
import { StepOnSuccessUnique } from './step-onSuccess-unique';
|
||||
import { StepOnFailureUnique } from './step-onFailure-unique';
|
||||
import { RequestBodyReplacementsUnique } from './requestBody-replacements-unique';
|
||||
import { NoCriteriaXpath } from '../spot/no-criteria-xpath';
|
||||
import { NoActionsTypeEnd } from '../spot/no-actions-type-end';
|
||||
import { CriteriaUnique } from './criteria-unique';
|
||||
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { ArazzoRuleSet } from '../../oas-types';
|
||||
@@ -29,6 +32,9 @@ export const rules: ArazzoRuleSet<'built-in'> = {
|
||||
'step-onSuccess-unique': StepOnSuccessUnique as ArazzoRule,
|
||||
'step-onFailure-unique': StepOnFailureUnique as ArazzoRule,
|
||||
'requestBody-replacements-unique': RequestBodyReplacementsUnique as ArazzoRule,
|
||||
'no-criteria-xpath': NoCriteriaXpath as ArazzoRule,
|
||||
'no-actions-type-end': NoActionsTypeEnd as ArazzoRule,
|
||||
'criteria-unique': CriteriaUnique as ArazzoRule,
|
||||
};
|
||||
|
||||
export const preprocessors = {};
|
||||
|
||||
27
packages/core/src/rules/spot/no-actions-type-end.ts
Normal file
27
packages/core/src/rules/spot/no-actions-type-end.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const NoActionsTypeEnd: ArazzoRule = () => {
|
||||
return {
|
||||
FailureActionObject: {
|
||||
enter(action, { report, location }: UserContext) {
|
||||
if (action.type === 'end') {
|
||||
report({
|
||||
message: 'The `end` type action is not supported by Spot.',
|
||||
location: location.child(['type']),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
SuccessActionObject: {
|
||||
enter(action, { report, location }: UserContext) {
|
||||
if (action.type === 'end') {
|
||||
report({
|
||||
message: 'The `end` type action is not supported by Spot.',
|
||||
location: location.child(['type']),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
20
packages/core/src/rules/spot/no-criteria-xpath.ts
Normal file
20
packages/core/src/rules/spot/no-criteria-xpath.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const NoCriteriaXpath: ArazzoRule = () => {
|
||||
return {
|
||||
CriterionObject: {
|
||||
enter(criteria, { report, location }: UserContext) {
|
||||
if (!criteria.type) {
|
||||
return;
|
||||
}
|
||||
if (criteria?.type?.type === 'xpath' || criteria?.type === 'xpath') {
|
||||
report({
|
||||
message: 'The `xpath` type criteria is not supported by Spot.',
|
||||
location: location.child(['type']),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -205,7 +205,7 @@ const CriterionObject: NodeType = {
|
||||
return undefined;
|
||||
} else if (typeof value === 'string') {
|
||||
return { enum: ['regex', 'jsonpath', 'simple', 'xpath'] };
|
||||
} else if (value.type === 'jsonpath') {
|
||||
} else if (value?.type === 'jsonpath') {
|
||||
return 'JSONPathCriterion';
|
||||
} else {
|
||||
return 'XPathCriterion';
|
||||
|
||||
@@ -122,6 +122,9 @@ const builtInArazzoRules = [
|
||||
'step-onSuccess-unique',
|
||||
'step-onFailure-unique',
|
||||
'requestBody-replacements-unique',
|
||||
'no-criteria-xpath',
|
||||
'no-actions-type-end',
|
||||
'criteria-unique',
|
||||
] as const;
|
||||
|
||||
export type BuiltInArazzoRuleId = typeof builtInArazzoRules[number];
|
||||
|
||||
@@ -68,7 +68,9 @@ workflows:
|
||||
- condition: $statusCode == 201
|
||||
- context: $response.body
|
||||
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
||||
type:
|
||||
type: jsonpath
|
||||
version: draft-goessner-dispatch-jsonpath-00
|
||||
outputs:
|
||||
createdEventId: $response.body.eventId
|
||||
name: $response.body.name
|
||||
|
||||
Reference in New Issue
Block a user