mirror of
https://github.com/LukeHagar/redocly-cli.git
synced 2025-12-06 04:21:09 +00:00
feat: spot-arazzo rules (#1670)
This commit is contained in:
6
.changeset/famous-doors-chew.md
Normal file
6
.changeset/famous-doors-chew.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@redocly/openapi-core": minor
|
||||
"@redocly/cli": minor
|
||||
---
|
||||
|
||||
Updated the Arazzo validation types for workflows input, parameter objects, and criteria to match the specification.
|
||||
6
.changeset/late-bats-fly.md
Normal file
6
.changeset/late-bats-fly.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@redocly/openapi-core": minor
|
||||
"@redocly/cli": minor
|
||||
---
|
||||
|
||||
Added Arazzo rulesets so that users can customize their linting rules for this format.
|
||||
@@ -68,9 +68,36 @@ arazzo/museum-api.arazzo.yaml: validated in 14ms
|
||||
run `redocly lint --generate-ignore-file` to add all problems to the ignore file.
|
||||
```
|
||||
|
||||
{% admonition type="info" name="Validation only" %}
|
||||
No additional rules or configuration are available for Arazzo in the current version of Redocly CLI; the tool merely checks that the file meets the specification.
|
||||
{% /admonition %}
|
||||
## Configure the linting rules
|
||||
|
||||
Choose from the ready-made rulesets (`minimal`, `recommended` or `recommended-strict`), or go one better and configure the rules that suit your use case.
|
||||
The rules available for linting Arazzo are:
|
||||
|
||||
- `parameters-not-in-body`: the `in` section inside `parameters` must not contain a `body`.
|
||||
- `sourceDescription-type`: the `type` property of the `sourceDescription` object must be either `openapi` or `arazzo`.
|
||||
- `version-enum`: the `version` property must be one of the supported values.
|
||||
- `workflowId-unique`: the `workflowId` property must be unique across all workflows.
|
||||
- `stepId-unique`: the `stepId` must be unique amongst all steps described in the workflow.
|
||||
- `sourceDescription-name-unique`: the `name` property of the `sourceDescription` object must be unique across all source descriptions.
|
||||
- `workflow-dependsOn`: the items in the `workflow` `dependsOn` property must exist and be unique.
|
||||
- `parameters-unique`: the `parameters` list must not include duplicate parameters.
|
||||
- `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.
|
||||
|
||||
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:
|
||||
|
||||
```yaml
|
||||
extends:
|
||||
- minimal
|
||||
|
||||
arazzoRules:
|
||||
sourceDescription-name-unique: warn
|
||||
version-enum: error
|
||||
```
|
||||
|
||||
The configuration shown here gives some good entry-level linting using the `minimal` standard, and adds checks that we're using a supported version of Arazzo, and warns if each source description doesn't have a unique name.
|
||||
|
||||
## Choose output format
|
||||
|
||||
@@ -114,13 +141,6 @@ With this action in place, the intentional errors I added to the Arazzo descript
|
||||
|
||||

|
||||
|
||||
## Arazzo rules
|
||||
|
||||
To expand the linting checks for an Arazzo description, start by enabling
|
||||
some of the built-in rules. The currently-supported rules are:
|
||||
|
||||
- `parameters-no-body-inside-in`: the `in` section inside `parameters` must not contain a `body`.
|
||||
|
||||
## Participate in Redocly CLI
|
||||
|
||||
Redocly CLI is an open source project, so we invite you to check out the [code on GitHub](https://github.com/Redocly/redocly-cli/), and open issues to report problems or request features.
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { slash } from '@redocly/openapi-core';
|
||||
import { pluralize } from '@redocly/openapi-core/lib/utils';
|
||||
import { green, yellow } from 'colorette';
|
||||
import {
|
||||
exitWithError,
|
||||
HandledError,
|
||||
pluralize,
|
||||
printExecutionTime,
|
||||
} from '../../utils/miscellaneous';
|
||||
import { exitWithError, HandledError, printExecutionTime } from '../../utils/miscellaneous';
|
||||
import { handlePushStatus } from './push-status';
|
||||
import { ReuniteApiClient, getDomain, getApiKeys } from '../api';
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
lintConfig,
|
||||
} from '@redocly/openapi-core';
|
||||
import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
|
||||
import { pluralize } from '@redocly/openapi-core/lib/utils';
|
||||
import {
|
||||
checkIfRulesetExist,
|
||||
exitWithError,
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
getFallbackApisOrExit,
|
||||
handleError,
|
||||
notifyAboutIncompatibleConfigOptions,
|
||||
pluralize,
|
||||
printConfigLintTotals,
|
||||
printLintTotals,
|
||||
printUnusedWarnings,
|
||||
|
||||
@@ -13,12 +13,12 @@ import {
|
||||
getMergedConfig,
|
||||
getProxyAgent,
|
||||
} from '@redocly/openapi-core';
|
||||
import { pluralize } from '@redocly/openapi-core/lib/utils';
|
||||
import {
|
||||
exitWithError,
|
||||
printExecutionTime,
|
||||
getFallbackApisOrExit,
|
||||
dumpBundle,
|
||||
pluralize,
|
||||
} from '../utils/miscellaneous';
|
||||
import { promptClientToken } from './login';
|
||||
import { handlePush as handleCMSPush } from '../cms/commands/push';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as pluralizeOne from 'pluralize';
|
||||
import { basename, dirname, extname, join, resolve, relative, isAbsolute } from 'path';
|
||||
import { blue, gray, green, red, yellow } from 'colorette';
|
||||
import { performance } from 'perf_hooks';
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
loadConfig,
|
||||
RedoclyClient,
|
||||
} from '@redocly/openapi-core';
|
||||
import { isEmptyObject, isPlainObject } from '@redocly/openapi-core/lib/utils';
|
||||
import { isEmptyObject, isPlainObject, pluralize } from '@redocly/openapi-core/lib/utils';
|
||||
import { ConfigValidationError } from '@redocly/openapi-core/lib/config';
|
||||
import { deprecatedRefDocsSchema } from '@redocly/config/lib/reference-docs-config-schema';
|
||||
import { outputExtensions } from '../types';
|
||||
@@ -277,13 +276,6 @@ export function getAndValidateFileExtension(fileName: string): NonNullable<Outpu
|
||||
return 'yaml';
|
||||
}
|
||||
|
||||
export function pluralize(sentence: string, count?: number, inclusive?: boolean) {
|
||||
return sentence
|
||||
.split(' ')
|
||||
.map((word) => pluralizeOne(word, count, inclusive))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export function handleError(e: Error, ref: string) {
|
||||
switch (e.constructor) {
|
||||
case HandledError: {
|
||||
|
||||
@@ -5,8 +5,18 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
|
||||
"arazzoDecorators": {},
|
||||
"arazzoPreprocessors": {},
|
||||
"arazzoRules": {
|
||||
"parameters-no-body-inside-in": "off",
|
||||
"parameters-not-in-body": "warn",
|
||||
"parameters-unique": "error",
|
||||
"requestBody-replacements-unique": "warn",
|
||||
"sourceDescription-name-unique": "error",
|
||||
"sourceDescription-type": "error",
|
||||
"spec": "error",
|
||||
"step-onFailure-unique": "warn",
|
||||
"step-onSuccess-unique": "warn",
|
||||
"stepId-unique": "error",
|
||||
"version-enum": "warn",
|
||||
"workflow-dependsOn": "error",
|
||||
"workflowId-unique": "error",
|
||||
},
|
||||
"async2Decorators": {},
|
||||
"async2Preprocessors": {},
|
||||
@@ -137,8 +147,18 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
|
||||
"arazzoDecorators": {},
|
||||
"arazzoPreprocessors": {},
|
||||
"arazzoRules": {
|
||||
"parameters-no-body-inside-in": "off",
|
||||
"parameters-not-in-body": "warn",
|
||||
"parameters-unique": "error",
|
||||
"requestBody-replacements-unique": "warn",
|
||||
"sourceDescription-name-unique": "error",
|
||||
"sourceDescription-type": "error",
|
||||
"spec": "error",
|
||||
"step-onFailure-unique": "warn",
|
||||
"step-onSuccess-unique": "warn",
|
||||
"stepId-unique": "error",
|
||||
"version-enum": "warn",
|
||||
"workflow-dependsOn": "error",
|
||||
"workflowId-unique": "error",
|
||||
},
|
||||
"async2Decorators": {},
|
||||
"async2Preprocessors": {},
|
||||
|
||||
@@ -126,7 +126,20 @@ const all: PluginStyleguideConfig<'built-in'> = {
|
||||
'channels-kebab-case': 'error',
|
||||
'no-channel-trailing-slash': 'error',
|
||||
},
|
||||
arazzoRules: { spec: 'error', 'parameters-no-body-inside-in': 'off' },
|
||||
arazzoRules: {
|
||||
spec: 'error',
|
||||
'parameters-not-in-body': 'error',
|
||||
'sourceDescription-type': 'error',
|
||||
'version-enum': 'error',
|
||||
'workflowId-unique': 'error',
|
||||
'stepId-unique': 'error',
|
||||
'sourceDescription-name-unique': 'error',
|
||||
'workflow-dependsOn': 'error',
|
||||
'parameters-unique': 'error',
|
||||
'step-onSuccess-unique': 'error',
|
||||
'step-onFailure-unique': 'error',
|
||||
'requestBody-replacements-unique': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
export default all;
|
||||
|
||||
@@ -110,7 +110,17 @@ const minimal: PluginStyleguideConfig<'built-in'> = {
|
||||
},
|
||||
arazzoRules: {
|
||||
spec: 'error',
|
||||
'parameters-no-body-inside-in': 'off',
|
||||
'parameters-not-in-body': 'off',
|
||||
'sourceDescription-type': 'off',
|
||||
'version-enum': 'warn',
|
||||
'workflowId-unique': 'error',
|
||||
'stepId-unique': 'error',
|
||||
'sourceDescription-name-unique': 'off',
|
||||
'workflow-dependsOn': 'off',
|
||||
'parameters-unique': 'off',
|
||||
'step-onSuccess-unique': 'off',
|
||||
'step-onFailure-unique': 'off',
|
||||
'requestBody-replacements-unique': 'off',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,17 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
|
||||
},
|
||||
arazzoRules: {
|
||||
spec: 'error',
|
||||
'parameters-no-body-inside-in': 'off',
|
||||
'parameters-not-in-body': 'error',
|
||||
'sourceDescription-type': 'error',
|
||||
'version-enum': 'error',
|
||||
'workflowId-unique': 'error',
|
||||
'stepId-unique': 'error',
|
||||
'sourceDescription-name-unique': 'error',
|
||||
'workflow-dependsOn': 'error',
|
||||
'parameters-unique': 'error',
|
||||
'step-onSuccess-unique': 'error',
|
||||
'step-onFailure-unique': 'error',
|
||||
'requestBody-replacements-unique': 'error',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,17 @@ const recommended: PluginStyleguideConfig<'built-in'> = {
|
||||
},
|
||||
arazzoRules: {
|
||||
spec: 'error',
|
||||
'parameters-no-body-inside-in': 'off',
|
||||
'parameters-not-in-body': 'warn',
|
||||
'sourceDescription-type': 'error',
|
||||
'version-enum': 'warn',
|
||||
'workflowId-unique': 'error',
|
||||
'stepId-unique': 'error',
|
||||
'sourceDescription-name-unique': 'error',
|
||||
'workflow-dependsOn': 'error',
|
||||
'parameters-unique': 'error',
|
||||
'step-onSuccess-unique': 'warn',
|
||||
'step-onFailure-unique': 'warn',
|
||||
'requestBody-replacements-unique': 'warn',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AsyncApi2Types } from './types/asyncapi2';
|
||||
import { AsyncApi3Types } from './types/asyncapi3';
|
||||
import { ArazzoTypes } from './types/arazzo';
|
||||
import { isPlainObject } from './utils';
|
||||
import { VERSION_PATTERN } from './typings/arazzo';
|
||||
|
||||
import type {
|
||||
BuiltInAsync2RuleId,
|
||||
@@ -132,7 +133,7 @@ export function detectSpec(root: unknown): SpecVersion {
|
||||
throw new Error(`Unsupported AsyncAPI version: ${root.asyncapi}`);
|
||||
}
|
||||
|
||||
if (typeof root.arazzo === 'string' && root.arazzo.startsWith('1.')) {
|
||||
if (typeof root.arazzo === 'string' && VERSION_PATTERN.test(root.arazzo)) {
|
||||
return SpecVersion.Arazzo;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,8 @@ import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
import { StyleguideConfig } from '../../../config';
|
||||
import { ArazzoRule } from '../../../visitors';
|
||||
|
||||
describe('Arazzo parameters-no-body-inside-in', () => {
|
||||
describe('Spot parameters-not-in-body', () => {
|
||||
const document = parseYamlToDocument(
|
||||
outdent`
|
||||
arazzo: '1.0.0'
|
||||
@@ -51,7 +49,7 @@ describe('Arazzo parameters-no-body-inside-in', () => {
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'parameters-no-body-inside-in': 'error' },
|
||||
arazzoRules: { 'parameters-not-in-body': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -66,7 +64,7 @@ describe('Arazzo parameters-no-body-inside-in', () => {
|
||||
},
|
||||
],
|
||||
"message": "The \`body\` value of the \`in\` property is not supported by Spot.",
|
||||
"ruleId": "parameters-no-body-inside-in",
|
||||
"ruleId": "parameters-not-in-body",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
@@ -0,0 +1,114 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo parameters-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==
|
||||
- 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
|
||||
parameters:
|
||||
- in: header
|
||||
name: Secret
|
||||
value: Basic Og==
|
||||
- in: header
|
||||
name: Secret
|
||||
value: Basic Og==
|
||||
- reference: $components.parameters.notify
|
||||
value: 12
|
||||
- reference: $components.parameters.notify
|
||||
value: 12
|
||||
successCriteria:
|
||||
- condition: $statusCode == 200
|
||||
`,
|
||||
'arazzo.yaml'
|
||||
);
|
||||
|
||||
it('should not report on `parameters` duplication', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({ rules: {} }),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
|
||||
it('should report on `parameters` duplication', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'parameters-unique': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/parameters/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The parameter \`name\` must be unique amongst listed parameters.",
|
||||
"ruleId": "parameters-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/parameters/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The parameter \`name\` must be unique amongst listed parameters.",
|
||||
"ruleId": "parameters-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/parameters/3",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The parameter \`reference\` must be unique amongst listed parameters.",
|
||||
"ruleId": "parameters-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo requestBody-replacements-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:
|
||||
replacements:
|
||||
- target: $randomString
|
||||
value: $randomString
|
||||
- target: $randomString
|
||||
value: $randomString
|
||||
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: jsonpath
|
||||
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 `replacement` is not unique amongst all `replacements` in the RequestBody', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'requestBody-replacements-unique': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/requestBody/replacements/1/target",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "Every \`replacement\` in \`requestBody\` must be unique.",
|
||||
"ruleId": "requestBody-replacements-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report when the `replacement` is not unique amongst all `replacements` in the RequestBody', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo sourceDescription-type', () => {
|
||||
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
|
||||
- name: api
|
||||
type: none
|
||||
x-serverUrl: 'http://localhost/api'
|
||||
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: 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 on sourceDescription with type `none`', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'sourceDescription-type': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/sourceDescriptions/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The \`type\` property of the \`sourceDescription\` object must be either \`openapi\` or \`arazzo\`.",
|
||||
"ruleId": "sourceDescription-type",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report on sourceDescription with type `none`', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'sourceDescription-type': 'off' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo sourceDescription-name-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
|
||||
- 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: 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 an error when sourceDescription `name` is not unique among all sourceDescriptions', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'sourceDescription-name-unique': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/sourceDescriptions/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The \`name\` must be unique amongst all SourceDescriptions.",
|
||||
"ruleId": "sourceDescription-name-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report an error when sourceDescription `name` is not unique among all sourceDescriptions', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo step-onFailure-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: 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
|
||||
onFailure:
|
||||
- name: test
|
||||
workflowId: events-crud
|
||||
type: goto
|
||||
- name: test
|
||||
workflowId: events-crud
|
||||
type: goto
|
||||
- reference: $steps.test.outputs.createdEventId
|
||||
- reference: $steps.test.outputs.createdEventId
|
||||
- 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 action `name` or `reference` is not unique amongst all onFailure actions in the step', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'step-onFailure-unique': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/onFailure/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The action \`name\` must be unique amongst listed \`onFailure\` actions.",
|
||||
"ruleId": "step-onFailure-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/onFailure/3",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The action \`reference\` must be unique amongst listed \`onFailure\` actions.",
|
||||
"ruleId": "step-onFailure-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report when the action `name` or `reference` is not unique amongst all onFailure actions in the step', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo step-onSuccess-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: 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
|
||||
onSuccess:
|
||||
- name: test
|
||||
workflowId: events-crud
|
||||
type: goto
|
||||
- name: test
|
||||
workflowId: events-crud
|
||||
type: goto
|
||||
- reference: $steps.test.outputs.createdEventId
|
||||
- reference: $steps.test.outputs.createdEventId
|
||||
- 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 action `name` or `reference` is not unique amongst all onSuccess actions in the step', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'step-onSuccess-unique': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/onSuccess/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The action \`name\` must be unique amongst listed \`onSuccess\` actions.",
|
||||
"ruleId": "step-onSuccess-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/steps/0/onSuccess/3",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The action \`reference\` must be unique amongst listed \`onSuccess\` actions.",
|
||||
"ruleId": "step-onSuccess-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report when the action `name` or `reference` is not unique amongst all onSuccess actions in the step', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo stepId-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: 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
|
||||
- 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
|
||||
- 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 `stepId` is not unique amongst all steps described in the workflow', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'stepId-unique': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/1/steps/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "The \`stepId\` must be unique amongst all steps described in the workflow.",
|
||||
"ruleId": "stepId-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report when the `stepId` is not unique amongst all steps described in the workflow', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo version-enum', () => {
|
||||
const document = parseYamlToDocument(
|
||||
outdent`
|
||||
arazzo: '1.0.1'
|
||||
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: 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 on arazzo version error', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'version-enum': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/arazzo",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "Only 1.0.0 Arazzo version is supported by Spot.",
|
||||
"ruleId": "version-enum",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report on arazzo version error', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,212 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo workflow-dependsOn', () => {
|
||||
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.
|
||||
dependsOn:
|
||||
- get-museum-hours-2
|
||||
- get-museum-hours-3
|
||||
- get-museum-hours-2
|
||||
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
|
||||
- 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
|
||||
- workflowId: get-museum-hours-3
|
||||
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'
|
||||
);
|
||||
|
||||
const documentWithNotExistingWorkflows = parseYamlToDocument(
|
||||
outdent`
|
||||
arazzo: 1.0.0
|
||||
info:
|
||||
title: Redocly Museum API Test Workflow
|
||||
description: >-
|
||||
Use the Museum API with Arazzo as an example of describing multi-step workflows.
|
||||
Built with love by Redocly.
|
||||
version: 1.0.0
|
||||
|
||||
sourceDescriptions:
|
||||
- name: museum-api
|
||||
type: openapi
|
||||
url: ../openapi.yaml
|
||||
- name: tickets-from-museum-api
|
||||
type: arazzo
|
||||
url: museum-tickets.arazzo.yaml
|
||||
|
||||
workflows:
|
||||
- workflowId: get-museum-hours
|
||||
dependsOn:
|
||||
- events-crud
|
||||
- events-crus
|
||||
- $sourceDescriptions.tickets-from-museum-apis.workflows.get-museum-tickets
|
||||
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
|
||||
outputs:
|
||||
schedule: $response.body
|
||||
- stepId: buy-ticket
|
||||
description: >-
|
||||
Buy a ticket for the museum by calling an external workflow from another Arazzo file.
|
||||
workflowId: $sourceDescriptions.tickets-from-museum-api.workflows.get-museum-tickets
|
||||
outputs:
|
||||
ticketId: $outputs.ticketId
|
||||
- workflowId: events-crud
|
||||
description: >-
|
||||
This workflow demonstrates how to list, create, update, and delete special events at the museum.
|
||||
parameters:
|
||||
- in: header
|
||||
name: Authorization
|
||||
value: Basic Og==
|
||||
steps:
|
||||
- stepId: list-events
|
||||
description: >-
|
||||
Request the list of events.
|
||||
operationPath: $sourceDescriptions.museum-api#/paths/~1special-events/get
|
||||
outputs:
|
||||
events: $response.body
|
||||
`,
|
||||
'arazzo.yaml'
|
||||
);
|
||||
|
||||
it('should report on dependsOn unique violation', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'workflow-dependsOn': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/dependsOn",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "Every workflow in dependsOn must be unique.",
|
||||
"ruleId": "workflow-dependsOn",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report on dependsOn unique violation', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'workflow-dependsOn': 'off' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
|
||||
it('should report on not existing workflows in dependsOn', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document: documentWithNotExistingWorkflows,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'workflow-dependsOn': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/dependsOn/1",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "Workflow events-crus must be defined in workflows.",
|
||||
"ruleId": "workflow-dependsOn",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/0/dependsOn/2",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "SourceDescription tickets-from-museum-apis must be defined in sourceDescriptions.",
|
||||
"ruleId": "workflow-dependsOn",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { outdent } from 'outdent';
|
||||
import { lintDocument } from '../../../lint';
|
||||
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
||||
import { BaseResolver } from '../../../resolve';
|
||||
|
||||
describe('Arazzo workflowId-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: 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
|
||||
- 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: 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 on workflowId unique violation', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'workflowId-unique': 'error' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"location": [
|
||||
{
|
||||
"pointer": "#/workflows/1/get-museum-hours",
|
||||
"reportOnKey": false,
|
||||
"source": "arazzo.yaml",
|
||||
},
|
||||
],
|
||||
"message": "Every workflow must have a unique \`workflowId\`.",
|
||||
"ruleId": "workflowId-unique",
|
||||
"severity": "error",
|
||||
"suggest": [],
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not report on workflowId unique violation', async () => {
|
||||
const results = await lintDocument({
|
||||
externalRefResolver: new BaseResolver(),
|
||||
document,
|
||||
config: await makeConfig({
|
||||
rules: {},
|
||||
arazzoRules: { 'workflowId-unique': 'off' },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,16 @@
|
||||
import { Spec } from '../common/spec';
|
||||
import { Assertions } from '../common/assertions';
|
||||
import { ParametersNoBodyInsideIn } from '../spot/parameters-no-body-inside-in';
|
||||
import { ParametersNotInBody } from '../spot/parameters-not-in-body';
|
||||
import { SourceDescriptionType } from '../arazzo/source-description-type';
|
||||
import { VersionEnum } from '../spot/version-enum';
|
||||
import { WorkflowIdUnique } from './workflowId-unique';
|
||||
import { StepIdUnique } from './stepId-unique';
|
||||
import { SourceDescriptionsNameUnique } from './sourceDescriptions-name-unique';
|
||||
import { WorkflowDependsOn } from './workflow-dependsOn';
|
||||
import { ParametersUnique } from './parameters-unique';
|
||||
import { StepOnSuccessUnique } from './step-onSuccess-unique';
|
||||
import { StepOnFailureUnique } from './step-onFailure-unique';
|
||||
import { RequestBodyReplacementsUnique } from './requestBody-replacements-unique';
|
||||
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { ArazzoRuleSet } from '../../oas-types';
|
||||
@@ -8,7 +18,17 @@ import type { ArazzoRuleSet } from '../../oas-types';
|
||||
export const rules: ArazzoRuleSet<'built-in'> = {
|
||||
spec: Spec as ArazzoRule,
|
||||
assertions: Assertions as ArazzoRule,
|
||||
'parameters-no-body-inside-in': ParametersNoBodyInsideIn as ArazzoRule,
|
||||
'parameters-not-in-body': ParametersNotInBody as ArazzoRule,
|
||||
'sourceDescription-type': SourceDescriptionType as ArazzoRule,
|
||||
'version-enum': VersionEnum as ArazzoRule,
|
||||
'workflowId-unique': WorkflowIdUnique as ArazzoRule,
|
||||
'stepId-unique': StepIdUnique as ArazzoRule,
|
||||
'sourceDescription-name-unique': SourceDescriptionsNameUnique as ArazzoRule,
|
||||
'workflow-dependsOn': WorkflowDependsOn as ArazzoRule,
|
||||
'parameters-unique': ParametersUnique as ArazzoRule,
|
||||
'step-onSuccess-unique': StepOnSuccessUnique as ArazzoRule,
|
||||
'step-onFailure-unique': StepOnFailureUnique as ArazzoRule,
|
||||
'requestBody-replacements-unique': RequestBodyReplacementsUnique as ArazzoRule,
|
||||
};
|
||||
|
||||
export const preprocessors = {};
|
||||
|
||||
33
packages/core/src/rules/arazzo/parameters-unique.ts
Normal file
33
packages/core/src/rules/arazzo/parameters-unique.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const ParametersUnique: ArazzoRule = () => {
|
||||
return {
|
||||
Parameters: {
|
||||
enter(parameters, { report, location }: UserContext) {
|
||||
if (!parameters) return;
|
||||
const seenParameters = new Set();
|
||||
|
||||
for (const parameter of parameters) {
|
||||
if (seenParameters.has(parameter?.name)) {
|
||||
report({
|
||||
message: 'The parameter `name` must be unique amongst listed parameters.',
|
||||
location: location.child([parameters.indexOf(parameter)]),
|
||||
});
|
||||
}
|
||||
|
||||
if (seenParameters.has(parameter?.reference)) {
|
||||
report({
|
||||
message: 'The parameter `reference` must be unique amongst listed parameters.',
|
||||
location: location.child([parameters.indexOf(parameter)]),
|
||||
});
|
||||
}
|
||||
|
||||
parameter?.name
|
||||
? seenParameters.add(parameter.name)
|
||||
: seenParameters.add(parameter.reference);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const RequestBodyReplacementsUnique: ArazzoRule = () => {
|
||||
const seenReplacements = new Set();
|
||||
|
||||
return {
|
||||
RequestBody: {
|
||||
enter(requestBody, { report, location }: UserContext) {
|
||||
if (!requestBody.replacements) return;
|
||||
|
||||
for (const replacement of requestBody.replacements) {
|
||||
if (seenReplacements.has(replacement.target)) {
|
||||
report({
|
||||
message: 'Every `replacement` in `requestBody` must be unique.',
|
||||
location: location.child([
|
||||
'replacements',
|
||||
requestBody.replacements.indexOf(replacement),
|
||||
`target`,
|
||||
]),
|
||||
});
|
||||
}
|
||||
seenReplacements.add(replacement.target);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
20
packages/core/src/rules/arazzo/source-description-type.ts
Normal file
20
packages/core/src/rules/arazzo/source-description-type.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const SourceDescriptionType: ArazzoRule = () => {
|
||||
return {
|
||||
SourceDescriptions: {
|
||||
enter(SourceDescriptions, { report, location }: UserContext) {
|
||||
for (const sourceDescription of SourceDescriptions) {
|
||||
if (!['openapi', 'arazzo'].includes(sourceDescription?.type)) {
|
||||
report({
|
||||
message:
|
||||
'The `type` property of the `sourceDescription` object must be either `openapi` or `arazzo`.',
|
||||
location: location.child([SourceDescriptions.indexOf(sourceDescription)]),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const SourceDescriptionsNameUnique: ArazzoRule = () => {
|
||||
const seenSourceDescriptions = new Set();
|
||||
|
||||
return {
|
||||
SourceDescriptions: {
|
||||
enter(sourceDescriptions, { report, location }: UserContext) {
|
||||
if (!sourceDescriptions.length) return;
|
||||
for (const sourceDescription of sourceDescriptions) {
|
||||
if (seenSourceDescriptions.has(sourceDescription.name)) {
|
||||
report({
|
||||
message: 'The `name` must be unique amongst all SourceDescriptions.',
|
||||
location: location.child([sourceDescriptions.indexOf(sourceDescription)]),
|
||||
});
|
||||
}
|
||||
seenSourceDescriptions.add(sourceDescription.name);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
33
packages/core/src/rules/arazzo/step-onFailure-unique.ts
Normal file
33
packages/core/src/rules/arazzo/step-onFailure-unique.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const StepOnFailureUnique: ArazzoRule = () => {
|
||||
return {
|
||||
OnFailureActionList: {
|
||||
enter(onFailureActionList, { report, location }: UserContext) {
|
||||
if (!onFailureActionList) return;
|
||||
const seenFailureActions = new Set();
|
||||
|
||||
for (const onFailureAction of onFailureActionList) {
|
||||
if (seenFailureActions.has(onFailureAction?.name)) {
|
||||
report({
|
||||
message: 'The action `name` must be unique amongst listed `onFailure` actions.',
|
||||
location: location.child([onFailureActionList.indexOf(onFailureAction)]),
|
||||
});
|
||||
}
|
||||
|
||||
if (seenFailureActions.has(onFailureAction?.reference)) {
|
||||
report({
|
||||
message: 'The action `reference` must be unique amongst listed `onFailure` actions.',
|
||||
location: location.child([onFailureActionList.indexOf(onFailureAction)]),
|
||||
});
|
||||
}
|
||||
|
||||
onFailureAction?.name
|
||||
? seenFailureActions.add(onFailureAction.name)
|
||||
: seenFailureActions.add(onFailureAction.reference);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
33
packages/core/src/rules/arazzo/step-onSuccess-unique.ts
Normal file
33
packages/core/src/rules/arazzo/step-onSuccess-unique.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const StepOnSuccessUnique: ArazzoRule = () => {
|
||||
return {
|
||||
OnSuccessActionList: {
|
||||
enter(onSuccessActionList, { report, location }: UserContext) {
|
||||
if (!onSuccessActionList) return;
|
||||
const seenSuccessActions = new Set();
|
||||
|
||||
for (const onSuccessAction of onSuccessActionList) {
|
||||
if (seenSuccessActions.has(onSuccessAction?.name)) {
|
||||
report({
|
||||
message: 'The action `name` must be unique amongst listed `onSuccess` actions.',
|
||||
location: location.child([onSuccessActionList.indexOf(onSuccessAction)]),
|
||||
});
|
||||
}
|
||||
|
||||
if (seenSuccessActions.has(onSuccessAction?.reference)) {
|
||||
report({
|
||||
message: 'The action `reference` must be unique amongst listed `onSuccess` actions.',
|
||||
location: location.child([onSuccessActionList.indexOf(onSuccessAction)]),
|
||||
});
|
||||
}
|
||||
|
||||
onSuccessAction?.name
|
||||
? seenSuccessActions.add(onSuccessAction.name)
|
||||
: seenSuccessActions.add(onSuccessAction.reference);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
24
packages/core/src/rules/arazzo/stepId-unique.ts
Normal file
24
packages/core/src/rules/arazzo/stepId-unique.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const StepIdUnique: ArazzoRule = () => {
|
||||
return {
|
||||
Workflow: {
|
||||
enter(workflow, { report, location }: UserContext) {
|
||||
if (!workflow.steps) return;
|
||||
const seenSteps = new Set();
|
||||
|
||||
for (const step of workflow.steps) {
|
||||
if (!step.stepId) return;
|
||||
if (seenSteps.has(step.stepId)) {
|
||||
report({
|
||||
message: 'The `stepId` must be unique amongst all steps described in the workflow.',
|
||||
location: location.child(['steps', workflow.steps.indexOf(step)]),
|
||||
});
|
||||
}
|
||||
seenSteps.add(step.stepId);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
56
packages/core/src/rules/arazzo/workflow-dependsOn.ts
Normal file
56
packages/core/src/rules/arazzo/workflow-dependsOn.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const WorkflowDependsOn: ArazzoRule = () => {
|
||||
const seenWorkflow = new Set();
|
||||
const existingSourceDescriptions = new Set();
|
||||
const existingWorkflowIds = new Set();
|
||||
|
||||
return {
|
||||
SourceDescriptions: {
|
||||
enter(sourceDescriptions) {
|
||||
for (const sourceDescription of sourceDescriptions) {
|
||||
existingSourceDescriptions.add(sourceDescription.name);
|
||||
}
|
||||
},
|
||||
},
|
||||
Workflows: {
|
||||
enter(workflows) {
|
||||
for (const workflow of workflows) {
|
||||
existingWorkflowIds.add(workflow.workflowId);
|
||||
}
|
||||
},
|
||||
},
|
||||
Workflow: {
|
||||
leave(workflow, { report, location }: UserContext) {
|
||||
if (!workflow.dependsOn) return;
|
||||
|
||||
for (const item of workflow.dependsOn) {
|
||||
// Possible dependsOn workflow pattern: $sourceDescriptions.<name>.<workflowId>
|
||||
if (item.startsWith('$sourceDescriptions.')) {
|
||||
const sourceDescriptionName = item.split('.')[1];
|
||||
if (!existingSourceDescriptions.has(sourceDescriptionName)) {
|
||||
report({
|
||||
message: `SourceDescription ${sourceDescriptionName} must be defined in sourceDescriptions.`,
|
||||
location: location.child([`dependsOn`, workflow.dependsOn.indexOf(item)]),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!item.startsWith('$sourceDescriptions') && !existingWorkflowIds.has(item)) {
|
||||
report({
|
||||
message: `Workflow ${item} must be defined in workflows.`,
|
||||
location: location.child([`dependsOn`, workflow.dependsOn.indexOf(item)]),
|
||||
});
|
||||
}
|
||||
if (seenWorkflow.has(item)) {
|
||||
report({
|
||||
message: 'Every workflow in dependsOn must be unique.',
|
||||
location: location.child([`dependsOn`]),
|
||||
});
|
||||
}
|
||||
seenWorkflow.add(item);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
21
packages/core/src/rules/arazzo/workflowId-unique.ts
Normal file
21
packages/core/src/rules/arazzo/workflowId-unique.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const WorkflowIdUnique: ArazzoRule = () => {
|
||||
const seenWorkflow = new Set();
|
||||
|
||||
return {
|
||||
Workflow: {
|
||||
enter(workflow, { report, location }: UserContext) {
|
||||
if (!workflow.workflowId) return;
|
||||
if (seenWorkflow.has(workflow.workflowId)) {
|
||||
report({
|
||||
message: 'Every workflow must have a unique `workflowId`.',
|
||||
location: location.child([workflow.workflowId]),
|
||||
});
|
||||
}
|
||||
seenWorkflow.add(workflow.workflowId);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const ParametersNoBodyInsideIn: ArazzoRule = () => {
|
||||
export const ParametersNotInBody: ArazzoRule = () => {
|
||||
return {
|
||||
Parameter: {
|
||||
enter(parameter, { report, location }: UserContext) {
|
||||
24
packages/core/src/rules/spot/version-enum.ts
Normal file
24
packages/core/src/rules/spot/version-enum.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ARAZZO_VERSIONS_SUPPORTED_BY_SPOT } from '../../typings/arazzo';
|
||||
import { pluralize } from '../../utils';
|
||||
|
||||
import type { ArazzoRule } from '../../visitors';
|
||||
import type { UserContext } from '../../walk';
|
||||
|
||||
export const VersionEnum: ArazzoRule = () => {
|
||||
const supportedVersions = ARAZZO_VERSIONS_SUPPORTED_BY_SPOT.join(', ');
|
||||
return {
|
||||
Root: {
|
||||
enter(root, { report, location }: UserContext) {
|
||||
if (!ARAZZO_VERSIONS_SUPPORTED_BY_SPOT.includes(root.arazzo)) {
|
||||
report({
|
||||
message: `Only ${supportedVersions} Arazzo ${pluralize(
|
||||
'version is',
|
||||
ARAZZO_VERSIONS_SUPPORTED_BY_SPOT.length
|
||||
)} supported by Spot.`,
|
||||
location: location.child('arazzo'),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import { Discriminator, DiscriminatorMapping, ExternalDocs, Xml } from './oas3';
|
||||
|
||||
const Root: NodeType = {
|
||||
properties: {
|
||||
arazzo: { type: 'string', enum: ['1.0.0'] },
|
||||
arazzo: { type: 'string' },
|
||||
info: 'Info',
|
||||
sourceDescriptions: 'SourceDescriptions',
|
||||
'x-parameters': 'Parameters',
|
||||
@@ -89,9 +89,7 @@ const ArazzoSourceDescription: NodeType = {
|
||||
const ReusableObject: NodeType = {
|
||||
properties: {
|
||||
reference: { type: 'string' },
|
||||
value: {
|
||||
type: 'string',
|
||||
},
|
||||
value: {}, // any
|
||||
},
|
||||
required: ['reference'],
|
||||
extensionsPrefix: 'x-',
|
||||
@@ -108,10 +106,10 @@ const Parameter: NodeType = {
|
||||
const Parameters: NodeType = {
|
||||
properties: {},
|
||||
items: (value: any) => {
|
||||
if (value?.in) {
|
||||
return 'Parameter';
|
||||
} else {
|
||||
if (value?.reference) {
|
||||
return 'ReusableObject';
|
||||
} else {
|
||||
return 'Parameter';
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -122,7 +120,7 @@ const Workflow: NodeType = {
|
||||
description: { type: 'string' },
|
||||
parameters: 'Parameters',
|
||||
dependsOn: { type: 'array', items: { type: 'string' } },
|
||||
inputs: 'NamedInputs',
|
||||
inputs: 'Schema',
|
||||
outputs: 'Outputs',
|
||||
steps: 'Steps',
|
||||
successActions: 'OnSuccessActionList',
|
||||
@@ -234,7 +232,7 @@ const SuccessActionObject: NodeType = {
|
||||
type: { type: 'string', enum: ['goto', 'end'] },
|
||||
stepId: { type: 'string' },
|
||||
workflowId: { type: 'string' },
|
||||
criteria: 'CriterionObject',
|
||||
criteria: listOf('CriterionObject'),
|
||||
},
|
||||
required: ['type', 'name'],
|
||||
};
|
||||
@@ -256,7 +254,7 @@ const FailureActionObject: NodeType = {
|
||||
stepId: { type: 'string' },
|
||||
retryAfter: { type: 'number' },
|
||||
retryLimit: { type: 'number' },
|
||||
criteria: 'CriterionObject',
|
||||
criteria: listOf('CriterionObject'),
|
||||
},
|
||||
required: ['type', 'name'],
|
||||
};
|
||||
|
||||
@@ -109,7 +109,20 @@ export type BuiltInAsync2RuleId = typeof builtInAsync2Rules[number];
|
||||
|
||||
export type BuiltInAsync3RuleId = typeof builtInAsync3Rules[number];
|
||||
|
||||
const builtInArazzoRules = ['spec', 'parameters-no-body-inside-in'] as const;
|
||||
const builtInArazzoRules = [
|
||||
'spec',
|
||||
'parameters-not-in-body',
|
||||
'sourceDescription-type',
|
||||
'version-enum',
|
||||
'workflowId-unique',
|
||||
'stepId-unique',
|
||||
'sourceDescription-name-unique',
|
||||
'workflow-dependsOn',
|
||||
'parameters-unique',
|
||||
'step-onSuccess-unique',
|
||||
'step-onFailure-unique',
|
||||
'requestBody-replacements-unique',
|
||||
] as const;
|
||||
|
||||
export type BuiltInArazzoRuleId = typeof builtInArazzoRules[number];
|
||||
|
||||
|
||||
@@ -170,3 +170,7 @@ export interface ArazzoDefinition {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const VERSION_PATTERN = /^1\.0\.\d+(-.+)?$/;
|
||||
|
||||
export const ARAZZO_VERSIONS_SUPPORTED_BY_SPOT = ['1.0.0'];
|
||||
|
||||
@@ -6,6 +6,7 @@ import { parseYaml } from './js-yaml';
|
||||
import { env } from './env';
|
||||
import { logger, colorize } from './logger';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import * as pluralizeOne from 'pluralize';
|
||||
|
||||
import type { HttpResolveConfig } from './config';
|
||||
import type { UserContext } from './walk';
|
||||
@@ -23,6 +24,13 @@ export function pushStack<T, P extends Stack<T> = Stack<T>>(head: P, value: T) {
|
||||
return { prev: head, value };
|
||||
}
|
||||
|
||||
export function pluralize(sentence: string, count?: number, inclusive?: boolean) {
|
||||
return sentence
|
||||
.split(' ')
|
||||
.map((word) => pluralizeOne(word, count, inclusive))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export function popStack<T, P extends Stack<T>>(head: P) {
|
||||
return head?.prev ?? null;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ workflows:
|
||||
operationPath: $sourceDescriptions.museum-api#/paths/~1special-events~1{eventId}/get
|
||||
parameters:
|
||||
- name: eventId
|
||||
in: body
|
||||
in: path
|
||||
value: $steps.create-event.outputs.createdEventId
|
||||
successCriteria:
|
||||
- context: $statusCode
|
||||
|
||||
Reference in New Issue
Block a user