diff --git a/.spectral.yaml b/.spectral.yaml new file mode 100644 index 000000000..9119fae3b --- /dev/null +++ b/.spectral.yaml @@ -0,0 +1 @@ +extends: ["spectral:asyncapi"] diff --git a/docs/getting-started/1-concepts.md b/docs/getting-started/1-concepts.md index 7318a7d5b..8a2c6e80e 100644 --- a/docs/getting-started/1-concepts.md +++ b/docs/getting-started/1-concepts.md @@ -8,7 +8,7 @@ To achieve this, Spectral has three key concepts: - **Functions** accept a value and return issues if the value is incorrect. - **Rulesets** act as a container for rules and functions. -Spectral comes bundled with a [set of core functions](../reference/functions.md) and rulesets for working with [OpenAPI v2 and v3](./4-openapi.md) and [AsyncAPI v2](./5-asyncapi.md) that you can chose to use or extend, but Spectral is about far more than just checking your OpenAPI/AsyncAPI documents are valid. +Spectral comes bundled with a [set of core functions](../reference/functions.md) and rulesets for working with [OpenAPI v2 and v3](./4-openapi.md) and [AsyncAPI v2 and v3](./5-asyncapi.md) that you can chose to use or extend, but Spectral is about far more than just checking your OpenAPI/AsyncAPI documents are valid. By far the most popular use-case of Spectral is automating [API Style Guides](https://stoplight.io/api-style-guides-guidelines-and-best-practices?utm_source=github&utm_medium=spectral&utm_campaign=docs), implementing rules that your Architecture, DevOps, API Governance or "Center of Excellence" teams have decided upon. Companies generally write these style guides as wiki pages, and several can be found on [API Stylebook](http://apistylebook.com/), but most of these rules could be automated with Spectral. For example: diff --git a/docs/getting-started/3-rulesets.md b/docs/getting-started/3-rulesets.md index 59a018b04..9dc2b997a 100644 --- a/docs/getting-started/3-rulesets.md +++ b/docs/getting-started/3-rulesets.md @@ -13,7 +13,7 @@ The fastest way to create a ruleset is to use the `extends` property to leverage Spectral comes with two built-in rulesets: - `spectral:oas` - [OpenAPI v2/v3 rules](./4-openapi.md) -- `spectral:asyncapi` - [AsyncAPI v2 rules](./5-asyncapi.md) +- `spectral:asyncapi` - [AsyncAPI v2/v3 rules](./5-asyncapi.md) To create a ruleset that extends both rulesets, open your terminal and run: @@ -21,7 +21,7 @@ To create a ruleset that extends both rulesets, open your terminal and run: echo 'extends: ["spectral:oas", "spectral:asyncapi"]' > .spectral.yaml ``` -The newly created ruleset file can then be used to lint any OpenAPI v2/v3 or AsyncAPI descriptions using the `spectral lint` command: +The newly created ruleset file can then be used to lint any OpenAPI v2/v3 or AsyncAPI v2/v3 descriptions using the `spectral lint` command: ```bash spectral lint myapifile.yaml diff --git a/docs/reference/asyncapi-rules.md b/docs/reference/asyncapi-rules.md index 6934f1cad..88f6bfbce 100644 --- a/docs/reference/asyncapi-rules.md +++ b/docs/reference/asyncapi-rules.md @@ -161,6 +161,7 @@ specifications that reference those objects). **Recommended:** Yes ## AsyncAPI v2 + The following rules ONLY apply to AsyncAPI v2 documents. ### asyncapi-server-security @@ -721,12 +722,6 @@ Operation objects should have a description. **Recommended:** Yes -### asyncapi-3-operation-operationId - -This operation ID is essentially a reference for the operation. Tools may use it for defining function names, class method names, and even URL hashes in documentation systems. - -**Recommended:** Yes - ### asyncapi-3-payload-unsupported-schemaFormat AsyncAPI can support various `schemaFormat` values. When unspecified, one of the following will be assumed: diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-3-operation-operationId.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-3-operation-operationId.test.ts deleted file mode 100644 index 283a98a7a..000000000 --- a/packages/rulesets/src/asyncapi/__tests__/asyncapi-3-operation-operationId.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { DiagnosticSeverity } from '@stoplight/types'; -import testRule from './__helpers__/tester'; - -testRule('asyncapi-3-operation-operationId', [ - { - name: 'valid case', - document: { - asyncapi: '3.0.0', - operations: { - SomeOperation: { - operationId: 'onePubId', - }, - }, - }, - errors: [], - }, - { - name: `operations.SomeOperation.operationId property is missing`, - document: { - asyncapi: '3.0.0', - operations: { - SomeOperation: {}, - }, - }, - errors: [ - { - message: 'Operation must have "operationId".', - path: ['operations', 'SomeOperation'], - severity: DiagnosticSeverity.Error, - }, - ], - }, -]); diff --git a/packages/rulesets/src/asyncapi/index.ts b/packages/rulesets/src/asyncapi/index.ts index 35e2d0c74..192a61773 100644 --- a/packages/rulesets/src/asyncapi/index.ts +++ b/packages/rulesets/src/asyncapi/index.ts @@ -119,6 +119,7 @@ export default { message: '{{error}}', severity: 'error', recommended: true, + formats: [aas2, aas3], given: ['$.channels.*', '$.components.channels.*'], then: { function: asyncApiChannelParameters, @@ -141,6 +142,7 @@ export default { recommended: true, resolved: false, // We use the JSON pointer to match the channel. given: '$.channels.*', + formats: [aas3], then: { field: '$.servers.*.$ref', function: pattern, @@ -209,6 +211,7 @@ export default { description: 'Contact object must have "name", "url" and "email".', recommended: true, given: '$.info.contact', + formats: [aas2, aas3], then: [ { field: 'name', @@ -227,6 +230,7 @@ export default { 'asyncapi-info-contact': { description: 'Info object must have "contact" object.', recommended: true, + formats: [aas2, aas3], given: '$', then: { field: 'info.contact', @@ -237,6 +241,7 @@ export default { description: 'Info "description" must be present and non-empty string.', recommended: true, given: '$', + formats: [aas2, aas3], then: { field: 'info.description', function: truthy, @@ -246,6 +251,7 @@ export default { description: 'License object must include "url".', recommended: false, given: '$', + formats: [aas2, aas3], then: { field: 'info.license.url', function: truthy, @@ -255,6 +261,7 @@ export default { description: 'Info object must have "license" object.', recommended: true, given: '$', + formats: [aas2, aas3], then: { field: 'info.license', function: truthy, @@ -266,6 +273,7 @@ export default { recommended: true, severity: 'info', given: '$.asyncapi', + formats: [aas2, aas3], then: { function: schema, functionOptions: { @@ -351,17 +359,6 @@ export default { function: truthy, }, }, - 'asyncapi-3-operation-operationId': { - description: 'Operation must have "operationId".', - severity: 'error', - recommended: true, - formats: [aas3], - given: '$.operations.*', - then: { - field: 'operationId', - function: truthy, - }, - }, 'asyncapi-operation-security': { description: 'Operation have to reference a defined security schemes.', message: '{{error}}', @@ -393,6 +390,7 @@ export default { 'asyncapi-parameter-description': { description: 'Parameter objects must have "description".', recommended: false, + formats: [aas2, aas3], given: ['$.components.parameters.*', '$.channels.*.parameters.*'], then: { field: 'description', @@ -513,6 +511,7 @@ export default { message: '{{error}}', severity: 'error', recommended: true, + formats: [aas2, aas3], given: '$', then: { function: asyncApiDocumentSchema, @@ -618,6 +617,7 @@ export default { 'asyncapi-servers': { description: 'AsyncAPI object must have non-empty "servers" object.', recommended: true, + formats: [aas2, aas3], given: '$', then: { field: 'servers', @@ -771,6 +771,7 @@ export default { description: 'Potentially unused components schema has been detected.', recommended: true, resolved: false, + formats: [aas2, aas3], given: '$.components.schemas', then: { function: unreferencedReusableObject, @@ -783,6 +784,7 @@ export default { description: 'Potentially unused components server has been detected.', recommended: true, resolved: false, + formats: [aas2, aas3], given: '$.components.servers', then: { function: unreferencedReusableObject, diff --git a/test-harness/scenarios/asyncapi3-streetlights.scenario b/test-harness/scenarios/asyncapi3-streetlights.scenario new file mode 100644 index 000000000..34c0a9c16 --- /dev/null +++ b/test-harness/scenarios/asyncapi3-streetlights.scenario @@ -0,0 +1,44 @@ +====test==== +Validate simple AsyncAPI 3.0 sample +====document==== +asyncapi: 3.0.0 +info: + title: Account Service + version: 1.0.0 + description: This service is in charge of processing user signups +channels: + userSignedup: + address: user/signedup + messages: + UserSignedUp: + payload: + type: object + properties: + displayName: + type: string + description: Name of the user + email: + type: string + format: email + description: Email of the user +operations: + sendUserSignedup: + action: send + channel: + $ref: '#/channels/userSignedup' +====asset:ruleset==== +const { asyncapi } = require('@stoplight/spectral-rulesets'); +module.exports = asyncapi; +====command==== +{bin} lint {document} --ruleset "{asset:ruleset}" +====stdout==== +{document} + 1:1 warning asyncapi-tags AsyncAPI object must have non-empty "tags" array. + 1:11 information asyncapi-latest-version The latest version is not used. You should update to the "2.6.0" version. asyncapi + 2:6 warning asyncapi-info-contact Info object must have "contact" object. info + 45:13 warning asyncapi-operation-description Operation "description" must be present and non-empty string. channels.smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured.publish + 57:15 warning asyncapi-operation-description Operation "description" must be present and non-empty string. channels.smartylighting/streetlights/1/0/action/{streetlightId}/turn/on.subscribe + 68:15 warning asyncapi-operation-description Operation "description" must be present and non-empty string. channels.smartylighting/streetlights/1/0/action/{streetlightId}/turn/off.subscribe + 79:15 warning asyncapi-operation-description Operation "description" must be present and non-empty string. channels.smartylighting/streetlights/1/0/action/{streetlightId}/dim.subscribe + +✖ 7 problems (0 errors, 6 warnings, 1 info, 0 hints)