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-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.
|
- `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.
|
- `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`.
|
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:
|
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": {},
|
"arazzoDecorators": {},
|
||||||
"arazzoPreprocessors": {},
|
"arazzoPreprocessors": {},
|
||||||
"arazzoRules": {
|
"arazzoRules": {
|
||||||
|
"criteria-unique": "warn",
|
||||||
|
"no-actions-type-end": "warn",
|
||||||
|
"no-criteria-xpath": "warn",
|
||||||
"parameters-not-in-body": "warn",
|
"parameters-not-in-body": "warn",
|
||||||
"parameters-unique": "error",
|
"parameters-unique": "error",
|
||||||
"requestBody-replacements-unique": "warn",
|
"requestBody-replacements-unique": "warn",
|
||||||
@@ -147,6 +150,9 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
|
|||||||
"arazzoDecorators": {},
|
"arazzoDecorators": {},
|
||||||
"arazzoPreprocessors": {},
|
"arazzoPreprocessors": {},
|
||||||
"arazzoRules": {
|
"arazzoRules": {
|
||||||
|
"criteria-unique": "warn",
|
||||||
|
"no-actions-type-end": "warn",
|
||||||
|
"no-criteria-xpath": "warn",
|
||||||
"parameters-not-in-body": "warn",
|
"parameters-not-in-body": "warn",
|
||||||
"parameters-unique": "error",
|
"parameters-unique": "error",
|
||||||
"requestBody-replacements-unique": "warn",
|
"requestBody-replacements-unique": "warn",
|
||||||
|
|||||||
@@ -139,6 +139,9 @@ const all: PluginStyleguideConfig<'built-in'> = {
|
|||||||
'step-onSuccess-unique': 'error',
|
'step-onSuccess-unique': 'error',
|
||||||
'step-onFailure-unique': 'error',
|
'step-onFailure-unique': 'error',
|
||||||
'requestBody-replacements-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-onSuccess-unique': 'off',
|
||||||
'step-onFailure-unique': 'off',
|
'step-onFailure-unique': 'off',
|
||||||
'requestBody-replacements-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-onSuccess-unique': 'error',
|
||||||
'step-onFailure-unique': 'error',
|
'step-onFailure-unique': 'error',
|
||||||
'requestBody-replacements-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-onSuccess-unique': 'warn',
|
||||||
'step-onFailure-unique': 'warn',
|
'step-onFailure-unique': 'warn',
|
||||||
'requestBody-replacements-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 { StepOnSuccessUnique } from './step-onSuccess-unique';
|
||||||
import { StepOnFailureUnique } from './step-onFailure-unique';
|
import { StepOnFailureUnique } from './step-onFailure-unique';
|
||||||
import { RequestBodyReplacementsUnique } from './requestBody-replacements-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 { ArazzoRule } from '../../visitors';
|
||||||
import type { ArazzoRuleSet } from '../../oas-types';
|
import type { ArazzoRuleSet } from '../../oas-types';
|
||||||
@@ -29,6 +32,9 @@ export const rules: ArazzoRuleSet<'built-in'> = {
|
|||||||
'step-onSuccess-unique': StepOnSuccessUnique as ArazzoRule,
|
'step-onSuccess-unique': StepOnSuccessUnique as ArazzoRule,
|
||||||
'step-onFailure-unique': StepOnFailureUnique as ArazzoRule,
|
'step-onFailure-unique': StepOnFailureUnique as ArazzoRule,
|
||||||
'requestBody-replacements-unique': RequestBodyReplacementsUnique 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 = {};
|
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;
|
return undefined;
|
||||||
} else if (typeof value === 'string') {
|
} else if (typeof value === 'string') {
|
||||||
return { enum: ['regex', 'jsonpath', 'simple', 'xpath'] };
|
return { enum: ['regex', 'jsonpath', 'simple', 'xpath'] };
|
||||||
} else if (value.type === 'jsonpath') {
|
} else if (value?.type === 'jsonpath') {
|
||||||
return 'JSONPathCriterion';
|
return 'JSONPathCriterion';
|
||||||
} else {
|
} else {
|
||||||
return 'XPathCriterion';
|
return 'XPathCriterion';
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ const builtInArazzoRules = [
|
|||||||
'step-onSuccess-unique',
|
'step-onSuccess-unique',
|
||||||
'step-onFailure-unique',
|
'step-onFailure-unique',
|
||||||
'requestBody-replacements-unique',
|
'requestBody-replacements-unique',
|
||||||
|
'no-criteria-xpath',
|
||||||
|
'no-actions-type-end',
|
||||||
|
'criteria-unique',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type BuiltInArazzoRuleId = typeof builtInArazzoRules[number];
|
export type BuiltInArazzoRuleId = typeof builtInArazzoRules[number];
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ workflows:
|
|||||||
- condition: $statusCode == 201
|
- condition: $statusCode == 201
|
||||||
- context: $response.body
|
- context: $response.body
|
||||||
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
||||||
type: jsonpath
|
type:
|
||||||
|
type: jsonpath
|
||||||
|
version: draft-goessner-dispatch-jsonpath-00
|
||||||
outputs:
|
outputs:
|
||||||
createdEventId: $response.body.eventId
|
createdEventId: $response.body.eventId
|
||||||
name: $response.body.name
|
name: $response.body.name
|
||||||
|
|||||||
Reference in New Issue
Block a user