From 3bf1b33c5c7ce71407b4fac3a3b70ae734fa8ea4 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Tue, 22 Mar 2022 10:32:16 +0100 Subject: [PATCH 1/5] feat(rulesets): add rules for validation of server variables and channel parameters --- docs/reference/asyncapi-rules.md | 12 ++ ...syncapi-channel-parameters-defined.test.ts | 142 ++++++++++++++++ .../asyncapi-server-variables-defined.test.ts | 154 ++++++++++++++++++ .../functions/asyncApi2ChannelParameters.ts | 38 +++++ .../functions/asyncApi2ServerVariables.ts | 37 +++++ .../rulesets/src/asyncapi/functions/utils.ts | 20 +++ packages/rulesets/src/asyncapi/index.ts | 30 ++++ 7 files changed, 433 insertions(+) create mode 100644 packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts create mode 100644 packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts create mode 100644 packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts create mode 100644 packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts create mode 100644 packages/rulesets/src/asyncapi/functions/utils.ts diff --git a/docs/reference/asyncapi-rules.md b/docs/reference/asyncapi-rules.md index 4e488bbe2..260d4c50c 100644 --- a/docs/reference/asyncapi-rules.md +++ b/docs/reference/asyncapi-rules.md @@ -24,6 +24,12 @@ Keep trailing slashes off of channel names, as it can cause some confusion. Most **Recommended:** Yes +### asyncapi-channel-parameters-defined + +All channel parameters should be defined in the `parameters` object of the channel. They should also not contain redundant parameters that do not exist in the channel address. + +**Recommended:** Yes + ### asyncapi-headers-schema-type-object The schema definition of the application headers must be of type “object”. @@ -288,6 +294,12 @@ Server URL should not point at example.com. **Recommended:** No +### asyncapi-server-variables-defined + +All server URL variables should be defined in the `variables` object of the server. They should also not contain redundant variables that do not exist in the server address. + +**Recommended:** Yes + ### asyncapi-servers A non empty `servers` object is expected to be located at the root of the document. diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts new file mode 100644 index 000000000..3a82a74c1 --- /dev/null +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts @@ -0,0 +1,142 @@ +import { DiagnosticSeverity } from '@stoplight/types'; +import testRule from './__helpers__/tester'; + +testRule('asyncapi-channel-parameters-defined', [ + { + name: 'valid case', + document: { + asyncapi: '2.0.0', + channels: { + 'users/{userId}/signedUp': { + parameters: { + userId: {} + } + }, + }, + }, + errors: [], + }, + + { + name: 'channel has not defined definition for one of the parameters', + document: { + asyncapi: '2.0.0', + channels: { + 'users/{userId}/{anotherParam}/signedUp': { + parameters: { + userId: {} + } + }, + }, + }, + errors: [ + { + message: 'Not all channel\'s parameters are described with \"parameters\" object. Missed: anotherParam.', + path: ['channels', 'users/{userId}/{anotherParam}/signedUp', 'parameters'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'channel has not defined definition for two+ of the parameters', + document: { + asyncapi: '2.0.0', + channels: { + 'users/{userId}/{anotherParam1}/{anotherParam2}/signedUp': { + parameters: { + userId: {} + } + }, + }, + }, + errors: [ + { + message: 'Not all channel\'s parameters are described with \"parameters\" object. Missed: anotherParam1, anotherParam2.', + path: ['channels', 'users/{userId}/{anotherParam1}/{anotherParam2}/signedUp', 'parameters'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'channel has not defined definition for one of the parameters (in the components.channels)', + document: { + asyncapi: '2.3.0', + components: { + channels: { + 'users/{userId}/{anotherParam}/signedUp': { + parameters: { + userId: {} + } + }, + }, + } + }, + errors: [ + { + message: 'Not all channel\'s parameters are described with \"parameters\" object. Missed: anotherParam.', + path: ['components', 'channels', 'users/{userId}/{anotherParam}/signedUp', 'parameters'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'channel has redundant paramaters', + document: { + asyncapi: '2.0.0', + channels: { + 'users/{userId}/signedUp': { + parameters: { + userId: {}, + anotherParam1: {}, + anotherParam2: {}, + } + }, + }, + }, + errors: [ + { + message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam1\" parameter.', + path: ['channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam1'], + severity: DiagnosticSeverity.Error, + }, + { + message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam2\" parameter.', + path: ['channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam2'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'channel has redundant paramaters (in the components.channels)', + document: { + asyncapi: '2.3.0', + components: { + channels: { + 'users/{userId}/signedUp': { + parameters: { + userId: {}, + anotherParam1: {}, + anotherParam2: {}, + } + }, + }, + }, + }, + errors: [ + { + message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam1\" parameter.', + path: ['components', 'channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam1'], + severity: DiagnosticSeverity.Error, + }, + { + message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam2\" parameter.', + path: ['components', 'channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam2'], + severity: DiagnosticSeverity.Error, + }, + ], + }, +]); diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts new file mode 100644 index 000000000..a250f406c --- /dev/null +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts @@ -0,0 +1,154 @@ +import { DiagnosticSeverity } from '@stoplight/types'; +import testRule from './__helpers__/tester'; + +testRule('asyncapi-server-variables-defined', [ + { + name: 'valid case', + document: { + asyncapi: '2.0.0', + servers: { + production: { + url: '{sub}.stoplight.io', + protocol: 'https', + variables: { + sub: {} + } + }, + }, + }, + errors: [], + }, + + { + name: 'server has not defined definition for one of the url variables', + document: { + asyncapi: '2.0.0', + servers: { + production: { + url: '{sub}.{anotherParam}.stoplight.io', + protocol: 'https', + variables: { + sub: {} + } + }, + }, + }, + errors: [ + { + message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam.', + path: ['servers', 'production', 'variables'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'server has not defined definition for two of the url variables', + document: { + asyncapi: '2.0.0', + servers: { + production: { + url: '{sub}.{anotherParam1}.{anotherParam2}.stoplight.io', + protocol: 'https', + variables: { + sub: {} + } + }, + }, + }, + errors: [ + { + message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam1, anotherParam2.', + path: ['servers', 'production', 'variables'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'server has not defined definition for one of the url variables (in the components.servers)', + document: { + asyncapi: '2.3.0', + components: { + servers: { + production: { + url: '{sub}.{anotherParam}.stoplight.io', + protocol: 'https', + variables: { + sub: {} + } + }, + }, + } + }, + errors: [ + { + message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam.', + path: ['components', 'servers', 'production', 'variables'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'server has redundant url variables', + document: { + asyncapi: '2.0.0', + servers: { + production: { + url: '{sub}.stoplight.io', + protocol: 'https', + variables: { + sub: {}, + anotherParam1: {}, + anotherParam2: {}, + } + }, + }, + }, + errors: [ + { + message: 'Server\'s \"variables\" object has redundant defined \"anotherParam1\" url variable.', + path: ['servers', 'production', 'variables', 'anotherParam1'], + severity: DiagnosticSeverity.Error, + }, + { + message: 'Server\'s \"variables\" object has redundant defined \"anotherParam2\" url variable.', + path: ['servers', 'production', 'variables', 'anotherParam2'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'server has redundant url variables (in the components.servers)', + document: { + asyncapi: '2.3.0', + components: { + servers: { + production: { + url: '{sub}.stoplight.io', + protocol: 'https', + variables: { + sub: {}, + anotherParam1: {}, + anotherParam2: {}, + } + }, + }, + } + }, + errors: [ + { + message: 'Server\'s \"variables\" object has redundant defined \"anotherParam1\" url variable.', + path: ['components', 'servers', 'production', 'variables', 'anotherParam1'], + severity: DiagnosticSeverity.Error, + }, + { + message: 'Server\'s \"variables\" object has redundant defined \"anotherParam2\" url variable.', + path: ['components', 'servers', 'production', 'variables', 'anotherParam2'], + severity: DiagnosticSeverity.Error, + }, + ], + }, +]); diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts new file mode 100644 index 000000000..d52ef1221 --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts @@ -0,0 +1,38 @@ +import { createRulesetFunction } from '@stoplight/spectral-core'; +import { parseUrlVariables, getMissingProps, getRedundantProps } from './utils'; + +import type { IFunctionResult } from '@stoplight/spectral-core'; + +export default createRulesetFunction<{ parameters: Record }, null>( + { + input: null, + options: null, + }, + function asyncApi2ChannelParameters(targetVal, _, ctx) { + const path = ctx.path[ctx.path.length - 1] as string; + const results: IFunctionResult[] = []; + + let parameters = parseUrlVariables(path); + if (!parameters || parameters.length === 0) return; + + const missingParameters = getMissingProps(parameters, targetVal.parameters); + if (missingParameters.length) { + results.push({ + message: `Not all channel's parameters are described with "parameters" object. Missed: ${missingParameters.join(', ')}.`, + path: [...ctx.path, 'parameters'], + }); + } + + const redundantParameters = getRedundantProps(parameters, targetVal.parameters); + if (redundantParameters.length) { + redundantParameters.forEach(param => { + results.push({ + message: `Channel's "parameters" object has redundant defined "${param}" parameter.`, + path: [...ctx.path, 'parameters', param], + }); + }); + } + + return results; + }, +); diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts new file mode 100644 index 000000000..be36a1ce4 --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts @@ -0,0 +1,37 @@ +import { createRulesetFunction } from '@stoplight/spectral-core'; +import { parseUrlVariables, getMissingProps, getRedundantProps } from './utils'; + +import type { IFunctionResult } from '@stoplight/spectral-core'; + +export default createRulesetFunction<{ url: string, variables: Record }, null>( + { + input: null, + options: null, + }, + function asyncApi2ServerVariables(targetVal, _, ctx) { + const results: IFunctionResult[] = []; + + let variables = parseUrlVariables(targetVal.url); + if (!variables || variables.length === 0) return results; + + const missingVariables = getMissingProps(variables, targetVal.variables); + if (missingVariables.length) { + results.push({ + message: `Not all server's variables are described with "variables" object. Missed: ${missingVariables.join(', ')}.`, + path: [...ctx.path, 'variables'], + }) + }; + + const redundantVariables = getRedundantProps(variables, targetVal.variables); + if (redundantVariables.length) { + redundantVariables.forEach(variable => { + results.push({ + message: `Server's "variables" object has redundant defined "${variable}" url variable.`, + path: [...ctx.path, 'variables', variable], + }); + }); + } + + return results; + }, +); diff --git a/packages/rulesets/src/asyncapi/functions/utils.ts b/packages/rulesets/src/asyncapi/functions/utils.ts new file mode 100644 index 000000000..7300bbc57 --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/utils.ts @@ -0,0 +1,20 @@ +export function parseUrlVariables(str: string): string[] | undefined { + if (typeof str !== 'string') return; + const variables = str.match(/{(.+?)}/g); + if (!variables || variables.length === 0) return; + return variables.map(v => v.slice(1,-1)); +}; + +export function getMissingProps(arr: string[] = [], obj: Record = {}) { + if (!Object.keys(obj).length) return arr; + return arr.filter(val => { + return !obj.hasOwnProperty(val); + }); +}; + +export function getRedundantProps(arr: string[] = [], obj: Record = {}) { + if (!arr.length) return Object.keys(obj); + return Object.keys(obj).filter(val => { + return !arr.includes(val); + }); +}; \ No newline at end of file diff --git a/packages/rulesets/src/asyncapi/index.ts b/packages/rulesets/src/asyncapi/index.ts index c36d3ed13..c2b0b8e4a 100644 --- a/packages/rulesets/src/asyncapi/index.ts +++ b/packages/rulesets/src/asyncapi/index.ts @@ -8,9 +8,11 @@ import { alphabetical, } from '@stoplight/spectral-functions'; +import asyncApi2ChannelParameters from './functions/asyncApi2ChannelParameters'; import asyncApi2DocumentSchema from './functions/asyncApi2DocumentSchema'; import asyncApi2SchemaValidation from './functions/asyncApi2SchemaValidation'; import asyncApi2PayloadValidation from './functions/asyncApi2PayloadValidation'; +import asyncApi2ServerVariables from './functions/asyncApi2ServerVariables'; export default { documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md', @@ -55,6 +57,20 @@ export default { }, }, }, + 'asyncapi-channel-parameters-defined': { + description: 'Channel parameters must be defined and there must be no redundant parameters.', + message: '{{error}}', + severity: 'error', + type: 'validation', + recommended: true, + given: [ + '$.channels.*', + '$.components.channels.*', + ], + then: { + function: asyncApi2ChannelParameters, + }, + }, 'asyncapi-headers-schema-type-object': { description: 'Headers schema type must be "object".', message: 'Headers schema type must be "object" ({{error}}).', @@ -282,6 +298,20 @@ export default { function: asyncApi2DocumentSchema, }, }, + 'asyncapi-server-variables-defined': { + description: 'Server variables must be defined and there must be no redundant variables.', + message: '{{error}}', + severity: 'error', + type: 'validation', + recommended: true, + given: [ + '$.servers.*', + '$.components.servers.*', + ], + then: { + function: asyncApi2ServerVariables, + }, + }, 'asyncapi-server-no-empty-variable': { description: 'Server URL must not have empty variable substitution pattern.', recommended: true, From 4f835884a61466dbfde0bc471a63a44f1a4681a0 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Tue, 22 Mar 2022 10:53:05 +0100 Subject: [PATCH 2/5] feat(rulesets): run linter --- ...syncapi-channel-parameters-defined.test.ts | 37 +++++++++--------- .../asyncapi-server-variables-defined.test.ts | 39 ++++++++++--------- .../functions/asyncApi2ChannelParameters.ts | 6 ++- .../functions/asyncApi2ServerVariables.ts | 12 +++--- .../rulesets/src/asyncapi/functions/utils.ts | 8 ++-- packages/rulesets/src/asyncapi/index.ts | 10 +---- 6 files changed, 56 insertions(+), 56 deletions(-) diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts index 3a82a74c1..1b697ec32 100644 --- a/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts @@ -9,8 +9,8 @@ testRule('asyncapi-channel-parameters-defined', [ channels: { 'users/{userId}/signedUp': { parameters: { - userId: {} - } + userId: {}, + }, }, }, }, @@ -24,14 +24,14 @@ testRule('asyncapi-channel-parameters-defined', [ channels: { 'users/{userId}/{anotherParam}/signedUp': { parameters: { - userId: {} - } + userId: {}, + }, }, }, }, errors: [ { - message: 'Not all channel\'s parameters are described with \"parameters\" object. Missed: anotherParam.', + message: 'Not all channel\'s parameters are described with "parameters" object. Missed: anotherParam.', path: ['channels', 'users/{userId}/{anotherParam}/signedUp', 'parameters'], severity: DiagnosticSeverity.Error, }, @@ -45,14 +45,15 @@ testRule('asyncapi-channel-parameters-defined', [ channels: { 'users/{userId}/{anotherParam1}/{anotherParam2}/signedUp': { parameters: { - userId: {} - } + userId: {}, + }, }, }, }, errors: [ { - message: 'Not all channel\'s parameters are described with \"parameters\" object. Missed: anotherParam1, anotherParam2.', + message: + 'Not all channel\'s parameters are described with "parameters" object. Missed: anotherParam1, anotherParam2.', path: ['channels', 'users/{userId}/{anotherParam1}/{anotherParam2}/signedUp', 'parameters'], severity: DiagnosticSeverity.Error, }, @@ -67,15 +68,15 @@ testRule('asyncapi-channel-parameters-defined', [ channels: { 'users/{userId}/{anotherParam}/signedUp': { parameters: { - userId: {} - } + userId: {}, + }, }, }, - } + }, }, errors: [ { - message: 'Not all channel\'s parameters are described with \"parameters\" object. Missed: anotherParam.', + message: 'Not all channel\'s parameters are described with "parameters" object. Missed: anotherParam.', path: ['components', 'channels', 'users/{userId}/{anotherParam}/signedUp', 'parameters'], severity: DiagnosticSeverity.Error, }, @@ -92,18 +93,18 @@ testRule('asyncapi-channel-parameters-defined', [ userId: {}, anotherParam1: {}, anotherParam2: {}, - } + }, }, }, }, errors: [ { - message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam1\" parameter.', + message: 'Channel\'s "parameters" object has redundant defined "anotherParam1" parameter.', path: ['channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam1'], severity: DiagnosticSeverity.Error, }, { - message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam2\" parameter.', + message: 'Channel\'s "parameters" object has redundant defined "anotherParam2" parameter.', path: ['channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam2'], severity: DiagnosticSeverity.Error, }, @@ -121,19 +122,19 @@ testRule('asyncapi-channel-parameters-defined', [ userId: {}, anotherParam1: {}, anotherParam2: {}, - } + }, }, }, }, }, errors: [ { - message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam1\" parameter.', + message: 'Channel\'s "parameters" object has redundant defined "anotherParam1" parameter.', path: ['components', 'channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam1'], severity: DiagnosticSeverity.Error, }, { - message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam2\" parameter.', + message: 'Channel\'s "parameters" object has redundant defined "anotherParam2" parameter.', path: ['components', 'channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam2'], severity: DiagnosticSeverity.Error, }, diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts index a250f406c..b4be3e8df 100644 --- a/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts @@ -11,8 +11,8 @@ testRule('asyncapi-server-variables-defined', [ url: '{sub}.stoplight.io', protocol: 'https', variables: { - sub: {} - } + sub: {}, + }, }, }, }, @@ -28,14 +28,14 @@ testRule('asyncapi-server-variables-defined', [ url: '{sub}.{anotherParam}.stoplight.io', protocol: 'https', variables: { - sub: {} - } + sub: {}, + }, }, }, }, errors: [ { - message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam.', + message: 'Not all server\'s variables are described with "variables" object. Missed: anotherParam.', path: ['servers', 'production', 'variables'], severity: DiagnosticSeverity.Error, }, @@ -51,14 +51,15 @@ testRule('asyncapi-server-variables-defined', [ url: '{sub}.{anotherParam1}.{anotherParam2}.stoplight.io', protocol: 'https', variables: { - sub: {} - } + sub: {}, + }, }, }, }, errors: [ { - message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam1, anotherParam2.', + message: + 'Not all server\'s variables are described with "variables" object. Missed: anotherParam1, anotherParam2.', path: ['servers', 'production', 'variables'], severity: DiagnosticSeverity.Error, }, @@ -75,15 +76,15 @@ testRule('asyncapi-server-variables-defined', [ url: '{sub}.{anotherParam}.stoplight.io', protocol: 'https', variables: { - sub: {} - } + sub: {}, + }, }, }, - } + }, }, errors: [ { - message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam.', + message: 'Not all server\'s variables are described with "variables" object. Missed: anotherParam.', path: ['components', 'servers', 'production', 'variables'], severity: DiagnosticSeverity.Error, }, @@ -102,18 +103,18 @@ testRule('asyncapi-server-variables-defined', [ sub: {}, anotherParam1: {}, anotherParam2: {}, - } + }, }, }, }, errors: [ { - message: 'Server\'s \"variables\" object has redundant defined \"anotherParam1\" url variable.', + message: 'Server\'s "variables" object has redundant defined "anotherParam1" url variable.', path: ['servers', 'production', 'variables', 'anotherParam1'], severity: DiagnosticSeverity.Error, }, { - message: 'Server\'s \"variables\" object has redundant defined \"anotherParam2\" url variable.', + message: 'Server\'s "variables" object has redundant defined "anotherParam2" url variable.', path: ['servers', 'production', 'variables', 'anotherParam2'], severity: DiagnosticSeverity.Error, }, @@ -133,19 +134,19 @@ testRule('asyncapi-server-variables-defined', [ sub: {}, anotherParam1: {}, anotherParam2: {}, - } + }, }, }, - } + }, }, errors: [ { - message: 'Server\'s \"variables\" object has redundant defined \"anotherParam1\" url variable.', + message: 'Server\'s "variables" object has redundant defined "anotherParam1" url variable.', path: ['components', 'servers', 'production', 'variables', 'anotherParam1'], severity: DiagnosticSeverity.Error, }, { - message: 'Server\'s \"variables\" object has redundant defined \"anotherParam2\" url variable.', + message: 'Server\'s "variables" object has redundant defined "anotherParam2" url variable.', path: ['components', 'servers', 'production', 'variables', 'anotherParam2'], severity: DiagnosticSeverity.Error, }, diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts index d52ef1221..92cf96dec 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts @@ -12,13 +12,15 @@ export default createRulesetFunction<{ parameters: Record }, nu const path = ctx.path[ctx.path.length - 1] as string; const results: IFunctionResult[] = []; - let parameters = parseUrlVariables(path); + const parameters = parseUrlVariables(path); if (!parameters || parameters.length === 0) return; const missingParameters = getMissingProps(parameters, targetVal.parameters); if (missingParameters.length) { results.push({ - message: `Not all channel's parameters are described with "parameters" object. Missed: ${missingParameters.join(', ')}.`, + message: `Not all channel's parameters are described with "parameters" object. Missed: ${missingParameters.join( + ', ', + )}.`, path: [...ctx.path, 'parameters'], }); } diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts index be36a1ce4..943ef2d3c 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts @@ -3,7 +3,7 @@ import { parseUrlVariables, getMissingProps, getRedundantProps } from './utils'; import type { IFunctionResult } from '@stoplight/spectral-core'; -export default createRulesetFunction<{ url: string, variables: Record }, null>( +export default createRulesetFunction<{ url: string; variables: Record }, null>( { input: null, options: null, @@ -11,16 +11,18 @@ export default createRulesetFunction<{ url: string, variables: Record v.slice(1,-1)); -}; + return variables.map(v => v.slice(1, -1)); +} export function getMissingProps(arr: string[] = [], obj: Record = {}) { if (!Object.keys(obj).length) return arr; return arr.filter(val => { return !obj.hasOwnProperty(val); }); -}; +} export function getRedundantProps(arr: string[] = [], obj: Record = {}) { if (!arr.length) return Object.keys(obj); return Object.keys(obj).filter(val => { return !arr.includes(val); }); -}; \ No newline at end of file +} diff --git a/packages/rulesets/src/asyncapi/index.ts b/packages/rulesets/src/asyncapi/index.ts index c2b0b8e4a..465923491 100644 --- a/packages/rulesets/src/asyncapi/index.ts +++ b/packages/rulesets/src/asyncapi/index.ts @@ -63,10 +63,7 @@ export default { severity: 'error', type: 'validation', recommended: true, - given: [ - '$.channels.*', - '$.components.channels.*', - ], + given: ['$.channels.*', '$.components.channels.*'], then: { function: asyncApi2ChannelParameters, }, @@ -304,10 +301,7 @@ export default { severity: 'error', type: 'validation', recommended: true, - given: [ - '$.servers.*', - '$.components.servers.*', - ], + given: ['$.servers.*', '$.components.servers.*'], then: { function: asyncApi2ServerVariables, }, From b33df5db7330f4dc66f9c4381e22ea7b3c96430e Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Thu, 7 Apr 2022 13:23:04 +0200 Subject: [PATCH 3/5] feat(rulesets): propagate review suggestions --- ...ts => asyncapi-channel-parameters.test.ts} | 2 +- ...t.ts => asyncapi-server-variables.test.ts} | 2 +- .../functions/asyncApi2ChannelParameters.ts | 7 +++++-- .../functions/asyncApi2ServerVariables.ts | 7 +++++-- .../rulesets/src/asyncapi/functions/utils.ts | 20 ------------------- .../utils/__tests__/getMissingProps.test.ts | 18 +++++++++++++++++ .../utils/__tests__/getRedundantProps.test.ts | 18 +++++++++++++++++ .../utils/__tests__/parseUrlVariables.test.ts | 13 ++++++++++++ .../functions/utils/getMissingProps.ts | 6 ++++++ .../functions/utils/getRedundantProps.ts | 6 ++++++ .../functions/utils/parseUrlVariables.ts | 6 ++++++ packages/rulesets/src/asyncapi/index.ts | 4 ++-- 12 files changed, 81 insertions(+), 28 deletions(-) rename packages/rulesets/src/asyncapi/__tests__/{asyncapi-channel-parameters-defined.test.ts => asyncapi-channel-parameters.test.ts} (98%) rename packages/rulesets/src/asyncapi/__tests__/{asyncapi-server-variables-defined.test.ts => asyncapi-server-variables.test.ts} (98%) delete mode 100644 packages/rulesets/src/asyncapi/functions/utils.ts create mode 100644 packages/rulesets/src/asyncapi/functions/utils/__tests__/getMissingProps.test.ts create mode 100644 packages/rulesets/src/asyncapi/functions/utils/__tests__/getRedundantProps.test.ts create mode 100644 packages/rulesets/src/asyncapi/functions/utils/__tests__/parseUrlVariables.test.ts create mode 100644 packages/rulesets/src/asyncapi/functions/utils/getMissingProps.ts create mode 100644 packages/rulesets/src/asyncapi/functions/utils/getRedundantProps.ts create mode 100644 packages/rulesets/src/asyncapi/functions/utils/parseUrlVariables.ts diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters.test.ts similarity index 98% rename from packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts rename to packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters.test.ts index 1b697ec32..e4715798a 100644 --- a/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters-defined.test.ts +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-channel-parameters.test.ts @@ -1,7 +1,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import testRule from './__helpers__/tester'; -testRule('asyncapi-channel-parameters-defined', [ +testRule('asyncapi-channel-parameters', [ { name: 'valid case', document: { diff --git a/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts b/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables.test.ts similarity index 98% rename from packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts rename to packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables.test.ts index b4be3e8df..b529611aa 100644 --- a/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables-defined.test.ts +++ b/packages/rulesets/src/asyncapi/__tests__/asyncapi-server-variables.test.ts @@ -1,7 +1,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import testRule from './__helpers__/tester'; -testRule('asyncapi-server-variables-defined', [ +testRule('asyncapi-server-variables', [ { name: 'valid case', document: { diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts index 92cf96dec..24897fc64 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts @@ -1,5 +1,8 @@ import { createRulesetFunction } from '@stoplight/spectral-core'; -import { parseUrlVariables, getMissingProps, getRedundantProps } from './utils'; + +import { parseUrlVariables } from './utils/parseUrlVariables'; +import { getMissingProps } from './utils/getMissingProps'; +import { getRedundantProps } from './utils/getRedundantProps'; import type { IFunctionResult } from '@stoplight/spectral-core'; @@ -13,7 +16,7 @@ export default createRulesetFunction<{ parameters: Record }, nu const results: IFunctionResult[] = []; const parameters = parseUrlVariables(path); - if (!parameters || parameters.length === 0) return; + if (parameters.length === 0) return; const missingParameters = getMissingProps(parameters, targetVal.parameters); if (missingParameters.length) { diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts index 943ef2d3c..2d7023a9d 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts @@ -1,5 +1,8 @@ import { createRulesetFunction } from '@stoplight/spectral-core'; -import { parseUrlVariables, getMissingProps, getRedundantProps } from './utils'; + +import { parseUrlVariables } from './utils/parseUrlVariables'; +import { getMissingProps } from './utils/getMissingProps'; +import { getRedundantProps } from './utils/getRedundantProps'; import type { IFunctionResult } from '@stoplight/spectral-core'; @@ -12,7 +15,7 @@ export default createRulesetFunction<{ url: string; variables: Record v.slice(1, -1)); -} - -export function getMissingProps(arr: string[] = [], obj: Record = {}) { - if (!Object.keys(obj).length) return arr; - return arr.filter(val => { - return !obj.hasOwnProperty(val); - }); -} - -export function getRedundantProps(arr: string[] = [], obj: Record = {}) { - if (!arr.length) return Object.keys(obj); - return Object.keys(obj).filter(val => { - return !arr.includes(val); - }); -} diff --git a/packages/rulesets/src/asyncapi/functions/utils/__tests__/getMissingProps.test.ts b/packages/rulesets/src/asyncapi/functions/utils/__tests__/getMissingProps.test.ts new file mode 100644 index 000000000..928d06ae0 --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/utils/__tests__/getMissingProps.test.ts @@ -0,0 +1,18 @@ +import { getMissingProps } from '../getMissingProps'; + +describe('getMissingProps', () => { + test('should return all props when object is empty', () => { + const result = getMissingProps(['one', 'two', 'three'], {}); + expect(result).toEqual(['one', 'two', 'three']); + }); + + test('should return only missed props', () => { + const result = getMissingProps(['one', 'two', 'three'], { one: {}, three: {} }); + expect(result).toEqual(['two']); + }); + + test('should return empty array when all props are defined', () => { + const result = getMissingProps(['one', 'two', 'three'], { one: {}, two: {}, three: {} }); + expect(result).toEqual([]); + }); +}); diff --git a/packages/rulesets/src/asyncapi/functions/utils/__tests__/getRedundantProps.test.ts b/packages/rulesets/src/asyncapi/functions/utils/__tests__/getRedundantProps.test.ts new file mode 100644 index 000000000..bad9660fa --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/utils/__tests__/getRedundantProps.test.ts @@ -0,0 +1,18 @@ +import { getRedundantProps } from '../getRedundantProps'; + +describe('getRedundantProps', () => { + test('should return all redundant props when array is empty', () => { + const result = getRedundantProps([], { one: {}, two: {}, three: {} }); + expect(result).toEqual(['one', 'two', 'three']); + }); + + test('should return only redundant props', () => { + const result = getRedundantProps(['one', 'three'], { one: {}, two: {}, three: {} }); + expect(result).toEqual(['two']); + }); + + test('should return empty array when all props are defined', () => { + const result = getRedundantProps(['one', 'two', 'three'], { one: {}, two: {}, three: {} }); + expect(result).toEqual([]); + }); +}); diff --git a/packages/rulesets/src/asyncapi/functions/utils/__tests__/parseUrlVariables.test.ts b/packages/rulesets/src/asyncapi/functions/utils/__tests__/parseUrlVariables.test.ts new file mode 100644 index 000000000..ada099353 --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/utils/__tests__/parseUrlVariables.test.ts @@ -0,0 +1,13 @@ +import { parseUrlVariables } from '../parseUrlVariables'; + +describe('parseUrlVariables', () => { + test('should return all variables from string', () => { + const result = parseUrlVariables('{stage}.some.{channel}'); + expect(result).toEqual(['stage', 'channel']); + }); + + test('should return empty array if no variable is defined', () => { + const result = parseUrlVariables('stage.some.channel'); + expect(result).toEqual([]); + }); +}); diff --git a/packages/rulesets/src/asyncapi/functions/utils/getMissingProps.ts b/packages/rulesets/src/asyncapi/functions/utils/getMissingProps.ts new file mode 100644 index 000000000..72d5784ed --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/utils/getMissingProps.ts @@ -0,0 +1,6 @@ +export function getMissingProps(arr: string[] = [], obj: Record = {}): string[] { + if (!Object.keys(obj).length) return arr; + return arr.filter(val => { + return !Object.prototype.hasOwnProperty.call(obj, val); + }); +} diff --git a/packages/rulesets/src/asyncapi/functions/utils/getRedundantProps.ts b/packages/rulesets/src/asyncapi/functions/utils/getRedundantProps.ts new file mode 100644 index 000000000..d7a9fdeae --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/utils/getRedundantProps.ts @@ -0,0 +1,6 @@ +export function getRedundantProps(arr: string[] = [], obj: Record = {}): string[] { + if (!arr.length) return Object.keys(obj); + return Object.keys(obj).filter(val => { + return !arr.includes(val); + }); +} diff --git a/packages/rulesets/src/asyncapi/functions/utils/parseUrlVariables.ts b/packages/rulesets/src/asyncapi/functions/utils/parseUrlVariables.ts new file mode 100644 index 000000000..071c3e388 --- /dev/null +++ b/packages/rulesets/src/asyncapi/functions/utils/parseUrlVariables.ts @@ -0,0 +1,6 @@ +export function parseUrlVariables(str: string): string[] { + if (typeof str !== 'string') return []; + const variables = str.match(/{(.+?)}/g); + if (!variables || variables.length === 0) return []; + return variables.map(v => v.slice(1, -1)); +} diff --git a/packages/rulesets/src/asyncapi/index.ts b/packages/rulesets/src/asyncapi/index.ts index 465923491..06114f22b 100644 --- a/packages/rulesets/src/asyncapi/index.ts +++ b/packages/rulesets/src/asyncapi/index.ts @@ -57,7 +57,7 @@ export default { }, }, }, - 'asyncapi-channel-parameters-defined': { + 'asyncapi-channel-parameters': { description: 'Channel parameters must be defined and there must be no redundant parameters.', message: '{{error}}', severity: 'error', @@ -295,7 +295,7 @@ export default { function: asyncApi2DocumentSchema, }, }, - 'asyncapi-server-variables-defined': { + 'asyncapi-server-variables': { description: 'Server variables must be defined and there must be no redundant variables.', message: '{{error}}', severity: 'error', From efd41161e2fa33d8a333bfc43df4372f3d656d17 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Fri, 8 Apr 2022 11:24:58 +0200 Subject: [PATCH 4/5] feat(rulesets): fix docs --- docs/reference/asyncapi-rules.md | 4 ++-- .../functions/asyncApi2ChannelParameters.ts | 10 +++++++++- .../asyncapi/functions/asyncApi2ServerVariables.ts | 13 ++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/reference/asyncapi-rules.md b/docs/reference/asyncapi-rules.md index 260d4c50c..768b9c4ae 100644 --- a/docs/reference/asyncapi-rules.md +++ b/docs/reference/asyncapi-rules.md @@ -24,7 +24,7 @@ Keep trailing slashes off of channel names, as it can cause some confusion. Most **Recommended:** Yes -### asyncapi-channel-parameters-defined +### asyncapi-channel-parameters All channel parameters should be defined in the `parameters` object of the channel. They should also not contain redundant parameters that do not exist in the channel address. @@ -294,7 +294,7 @@ Server URL should not point at example.com. **Recommended:** No -### asyncapi-server-variables-defined +### asyncapi-server-variables All server URL variables should be defined in the `variables` object of the server. They should also not contain redundant variables that do not exist in the server address. diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts index 24897fc64..4302b33a3 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts @@ -8,7 +8,15 @@ import type { IFunctionResult } from '@stoplight/spectral-core'; export default createRulesetFunction<{ parameters: Record }, null>( { - input: null, + input: { + type: 'object', + properties: { + parameters: { + type: 'object', + } + }, + required: ['parameters'], + }, options: null, }, function asyncApi2ChannelParameters(targetVal, _, ctx) { diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts index 2d7023a9d..e876cced5 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts @@ -8,7 +8,18 @@ import type { IFunctionResult } from '@stoplight/spectral-core'; export default createRulesetFunction<{ url: string; variables: Record }, null>( { - input: null, + input: { + type: 'object', + properties: { + url: { + type: 'string', + }, + variables: { + type: 'object', + } + }, + required: ['url', 'variables'], + }, options: null, }, function asyncApi2ServerVariables(targetVal, _, ctx) { From 0ba1f547100bcfa2130cd1136025cfaa02f38900 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Thu, 19 May 2022 12:55:18 +0200 Subject: [PATCH 5/5] feat(rulesets): fix linter errors --- .../src/asyncapi/functions/asyncApi2ChannelParameters.ts | 2 +- .../rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts index 4302b33a3..9c214e6fb 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ChannelParameters.ts @@ -13,7 +13,7 @@ export default createRulesetFunction<{ parameters: Record }, nu properties: { parameters: { type: 'object', - } + }, }, required: ['parameters'], }, diff --git a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts index e876cced5..3fda72d8e 100644 --- a/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts +++ b/packages/rulesets/src/asyncapi/functions/asyncApi2ServerVariables.ts @@ -16,7 +16,7 @@ export default createRulesetFunction<{ url: string; variables: Record