Skip to content

Commit

Permalink
feat(rulesets): add rules for validation uniqueness of tag names (#2104)
Browse files Browse the repository at this point in the history

Co-authored-by: Jakub Rożek <jakub@rozek.tech>
  • Loading branch information
magicmatatjahu and P0lip committed May 30, 2022
1 parent 9acc633 commit 4447d81
Show file tree
Hide file tree
Showing 10 changed files with 441 additions and 1 deletion.
22 changes: 22 additions & 0 deletions docs/reference/asyncapi-rules.md
Expand Up @@ -353,6 +353,28 @@ tags:

**Recommended:** No

### asyncapi-tags-uniqueness

Tags must not have duplicate names (identifiers).

**Recommended:** Yes

**Bad Example**

```yaml
tags:
- name: "Badger"
- name: "Badger"
```

**Good Example**

```yaml
tags:
- name: "Aardvark"
- name: "Badger"
```

### asyncapi-tags

AsyncAPI object should have non-empty `tags` array.
Expand Down
22 changes: 22 additions & 0 deletions docs/reference/openapi-rules.md
Expand Up @@ -250,6 +250,28 @@ tags:
- name: "Badger"
```

### openapi-tags-uniqueness

OpenAPI object must not have duplicated tag names (identifiers).

**Recommended:** Yes

**Bad Example**

```yaml
tags:
- name: "Badger"
- name: "Badger"
```

**Good Example**

```yaml
tags:
- name: "Aardvark"
- name: "Badger"
```

### openapi-tags

OpenAPI object should have non-empty `tags` array.
Expand Down
@@ -0,0 +1,160 @@
import { DiagnosticSeverity } from '@stoplight/types';
import testRule from './__helpers__/tester';

testRule('asyncapi-tags-uniqueness', [
{
name: 'valid case',
document: {
asyncapi: '2.0.0',
tags: [{ name: 'one' }, { name: 'two' }],
},
errors: [],
},

{
name: 'tags has duplicated names (root)',
document: {
asyncapi: '2.0.0',
tags: [{ name: 'one' }, { name: 'one' }],
},
errors: [
{
message: '"tags" object contains duplicate tag name "one".',
path: ['tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'tags has duplicated names (operation)',
document: {
asyncapi: '2.0.0',
channels: {
someChannel: {
publish: {
tags: [{ name: 'one' }, { name: 'one' }],
},
subscribe: {
tags: [{ name: 'one' }, { name: 'one' }],
},
},
},
},
errors: [
{
message: '"tags" object contains duplicate tag name "one".',
path: ['channels', 'someChannel', 'publish', 'tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
{
message: '"tags" object contains duplicate tag name "one".',
path: ['channels', 'someChannel', 'subscribe', 'tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'tags has duplicated names (operation trait)',
document: {
asyncapi: '2.0.0',
channels: {
someChannel: {
publish: {
traits: [
{
tags: [{ name: 'one' }, { name: 'one' }],
},
],
},
subscribe: {
traits: [
{
tags: [{ name: 'one' }, { name: 'one' }],
},
],
},
},
},
},
errors: [
{
message: '"tags" object contains duplicate tag name "one".',
path: ['channels', 'someChannel', 'publish', 'traits', '0', 'tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
{
message: '"tags" object contains duplicate tag name "one".',
path: ['channels', 'someChannel', 'subscribe', 'traits', '0', 'tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'tags has duplicated names (message)',
document: {
asyncapi: '2.0.0',
components: {
messages: {
someMessage: {
tags: [{ name: 'one' }, { name: 'one' }],
},
},
},
},
errors: [
{
message: '"tags" object contains duplicate tag name "one".',
path: ['components', 'messages', 'someMessage', 'tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'tags has duplicated names (message trait)',
document: {
asyncapi: '2.0.0',
components: {
messages: {
someMessage: {
traits: [
{
tags: [{ name: 'one' }, { name: 'one' }],
},
],
},
},
},
},
errors: [
{
message: '"tags" object contains duplicate tag name "one".',
path: ['components', 'messages', 'someMessage', 'traits', '0', 'tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'tags has duplicated more that two times this same name',
document: {
asyncapi: '2.0.0',
tags: [{ name: 'one' }, { name: 'one' }, { name: 'two' }, { name: 'one' }],
},
errors: [
{
message: '"tags" object contains duplicate tag name "one".',
path: ['tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
{
message: '"tags" object contains duplicate tag name "one".',
path: ['tags', '3', 'name'],
severity: DiagnosticSeverity.Error,
},
],
},
]);
Expand Up @@ -94,7 +94,7 @@ export default createRulesetFunction<unknown, null>(
input: null,
options: null,
},
function oasDocumentSchema(targetVal, _, context) {
function asyncApi2DocumentSchema(targetVal, _, context) {
const formats = context.document.formats;
if (formats === null || formats === void 0) return;

Expand Down
35 changes: 35 additions & 0 deletions packages/rulesets/src/asyncapi/index.ts
Expand Up @@ -13,6 +13,7 @@ import asyncApi2DocumentSchema from './functions/asyncApi2DocumentSchema';
import asyncApi2SchemaValidation from './functions/asyncApi2SchemaValidation';
import asyncApi2PayloadValidation from './functions/asyncApi2PayloadValidation';
import asyncApi2ServerVariables from './functions/asyncApi2ServerVariables';
import { uniquenessTags } from '../shared/functions';

export default {
documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md',
Expand Down Expand Up @@ -382,6 +383,40 @@ export default {
},
},
},
'asyncapi-tags-uniqueness': {
description: 'Each tag must have a unique name.',
message: '{{error}}',
severity: 'error',
recommended: true,
type: 'validation',
given: [
// root
'$.tags',
// operations
'$.channels.*.[publish,subscribe].tags',
'$.components.channels.*.[publish,subscribe].tags',
// operation traits
'$.channels.*.[publish,subscribe].traits.*.tags',
'$.components.channels.*.[publish,subscribe].traits.*.tags',
'$.components.operationTraits.*.tags',
// messages
'$.channels.*.[publish,subscribe].message.tags',
'$.channels.*.[publish,subscribe].message.oneOf.*.tags',
'$.components.channels.*.[publish,subscribe].message.tags',
'$.components.channels.*.[publish,subscribe].message.oneOf.*.tags',
'$.components.messages.*.tags',
// message traits
'$.channels.*.[publish,subscribe].message.traits.*.tags',
'$.channels.*.[publish,subscribe].message.oneOf.*.traits.*.tags',
'$.components.channels.*.[publish,subscribe].message.traits.*.tags',
'$.components.channels.*.[publish,subscribe].message.oneOf.*.traits.*.tags',
'$.components.messages.*.traits.*.tags',
'$.components.messageTraits.*.tags',
],
then: {
function: uniquenessTags,
},
},
'asyncapi-tags': {
description: 'AsyncAPI object must have non-empty "tags" array.',
recommended: true,
Expand Down
@@ -0,0 +1,48 @@
import { DiagnosticSeverity } from '@stoplight/types';
import testRule from './__helpers__/tester';

testRule('openapi-tags-uniqueness', [
{
name: 'valid case',
document: {
swagger: '2.0',
tags: [{ name: 'one' }, { name: 'two' }],
},
errors: [],
},

{
name: 'tags has duplicated names',
document: {
swagger: '2.0',
tags: [{ name: 'one' }, { name: 'one' }],
},
errors: [
{
message: '"tags" object contains duplicate tag name "one".',
path: ['tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'tags has duplicated more that two times this same name',
document: {
swagger: '2.0',
tags: [{ name: 'one' }, { name: 'one' }, { name: 'two' }, { name: 'one' }],
},
errors: [
{
message: '"tags" object contains duplicate tag name "one".',
path: ['tags', '1', 'name'],
severity: DiagnosticSeverity.Error,
},
{
message: '"tags" object contains duplicate tag name "one".',
path: ['tags', '3', 'name'],
severity: DiagnosticSeverity.Error,
},
],
},
]);
12 changes: 12 additions & 0 deletions packages/rulesets/src/oas/index.ts
Expand Up @@ -25,6 +25,7 @@ import {
oasSchema,
oasDiscriminator,
} from './functions';
import { uniquenessTags } from '../shared/functions';

export { ruleset as default };

Expand Down Expand Up @@ -212,6 +213,17 @@ const ruleset = {
},
},
},
'openapi-tags-uniqueness': {
description: 'Each tag must have a unique name.',
message: '{{error}}',
severity: 'error',
recommended: true,
type: 'validation',
given: '$.tags',
then: {
function: uniquenessTags,
},
},
'openapi-tags': {
description: 'OpenAPI object must have non-empty "tags" array.',
recommended: false,
Expand Down

0 comments on commit 4447d81

Please sign in to comment.