Skip to content

Commit

Permalink
fix(rulesets): handle latest version of AsyncAPI
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu committed Oct 5, 2022
1 parent 57cd059 commit d2bfabe
Show file tree
Hide file tree
Showing 7 changed files with 26 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { DiagnosticSeverity } from '@stoplight/types';
import { latestAsyncApiVersion } from '../functions/asyncApi2DocumentSchema';
import { latestVersion } from '../functions/utils/specs';
import testRule from './__helpers__/tester';

testRule('asyncapi-latest-version', [
{
name: 'valid case',
document: {
asyncapi: latestAsyncApiVersion,
asyncapi: latestVersion,
},
errors: [],
},
Expand All @@ -18,7 +18,7 @@ testRule('asyncapi-latest-version', [
},
errors: [
{
message: `The latest version is not used. You should update to the "${latestAsyncApiVersion}" version.`,
message: `The latest version is not used. You should update to the "${latestVersion}" version.`,
path: ['asyncapi'],
severity: DiagnosticSeverity.Information,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import type { ErrorObject } from 'ajv';
import type { IFunctionResult, Format } from '@stoplight/spectral-core';
import type { AsyncAPISpecVersion } from './utils/specs';

export const asyncApiSpecVersions = ['2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0'];
export const latestAsyncApiVersion = asyncApiSpecVersions[asyncApiSpecVersions.length - 1];

function shouldIgnoreError(error: ErrorObject): boolean {
return (
// oneOf is a fairly error as we have 2 options to choose from for most of the time.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createRulesetFunction } from '@stoplight/spectral-core';
import { schema as schemaFn } from '@stoplight/spectral-functions';

import { mergeTraits } from './utils/mergeTraits';

import type { JsonPath } from '@stoplight/types';
import type { IFunctionResult, RulesetFunctionContext } from '@stoplight/spectral-core';
import type { JSONSchema7 } from 'json-schema';
import { mergeTraits } from './utils/mergeTraits';

interface MessageExample {
name?: string;
Expand All @@ -20,15 +21,15 @@ export interface MessageFragment {
examples?: MessageExample[];
}

function getMessageExamples(message: MessageFragment): Array<{ path: JsonPath; value: MessageExample }> {
function getMessageExamples(message: MessageFragment): Array<{ path: JsonPath; example: MessageExample }> {
if (!Array.isArray(message.examples)) {
return [];
}
return (
message.examples.map((example, index) => {
return {
path: ['examples', index],
value: example,
example,
};
}) ?? []
);
Expand Down Expand Up @@ -78,18 +79,18 @@ export default createRulesetFunction<MessageFragment, null>(

for (const example of examples) {
// validate payload
if (example.value.payload !== undefined) {
if (example.example.payload !== undefined) {
const payload = targetVal.payload ?? {}; // if payload is undefined we treat it as any schema
const errors = validate(example.value.payload, example.path, 'payload', payload, ctx);
const errors = validate(example.example.payload, example.path, 'payload', payload, ctx);
if (Array.isArray(errors)) {
results.push(...errors);
}
}

// validate headers
if (example.value.headers !== undefined) {
if (example.example.headers !== undefined) {
const headers = targetVal.headers ?? {}; // if headers are undefined we treat them as any schema
const errors = validate(example.value.headers, example.path, 'headers', headers, ctx);
const errors = validate(example.example.headers, example.path, 'headers', headers, ctx);
if (Array.isArray(errors)) {
results.push(...errors);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/rulesets/src/asyncapi/functions/utils/mergeTraits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { isPlainObject } from '@stoplight/json';

type HaveTraits = { traits?: any[] } & Record<string, any>;

/**
* A function used to merge traits defined for the given object from the AsyncAPI document.
* It uses the [JSON Merge Patch](https://www.rfc-editor.org/rfc/rfc7386).
*
* @param data An object with the traits
* @returns Merged object
*/
export function mergeTraits<T extends HaveTraits>(data: T): T {
if (Array.isArray(data.traits)) {
data = { ...data }; // shallow copy
Expand Down
3 changes: 3 additions & 0 deletions packages/rulesets/src/asyncapi/functions/utils/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const specs = {
'2.5.0': asyncAPI2_5_0Schema,
};

const versions = Object.keys(specs);
export const latestVersion = versions[versions.length - 1];

export function getCopyOfSchema(version: AsyncAPISpecVersion): Record<string, unknown> {
return JSON.parse(JSON.stringify(specs[version])) as Record<string, unknown>;
}
7 changes: 4 additions & 3 deletions packages/rulesets/src/asyncapi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {

import asyncApi2ChannelParameters from './functions/asyncApi2ChannelParameters';
import asyncApi2ChannelServers from './functions/asyncApi2ChannelServers';
import asyncApi2DocumentSchema, { latestAsyncApiVersion } from './functions/asyncApi2DocumentSchema';
import asyncApi2DocumentSchema from './functions/asyncApi2DocumentSchema';
import asyncApi2MessageExamplesValidation from './functions/asyncApi2MessageExamplesValidation';
import asyncApi2MessageIdUniqueness from './functions/asyncApi2MessageIdUniqueness';
import asyncApi2OperationIdUniqueness from './functions/asyncApi2OperationIdUniqueness';
Expand All @@ -19,6 +19,7 @@ import asyncApi2PayloadValidation from './functions/asyncApi2PayloadValidation';
import asyncApi2ServerVariables from './functions/asyncApi2ServerVariables';
import { uniquenessTags } from '../shared/functions';
import asyncApi2Security from './functions/asyncApi2Security';
import { latestVersion } from './functions/utils/specs';

export default {
documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md',
Expand Down Expand Up @@ -174,7 +175,7 @@ export default {
},
'asyncapi-latest-version': {
description: 'Checking if the AsyncAPI document is using the latest version.',
message: `The latest version is not used. You should update to the "${latestAsyncApiVersion}" version.`,
message: `The latest version is not used. You should update to the "${latestVersion}" version.`,
recommended: true,
type: 'style',
severity: 'info',
Expand All @@ -183,7 +184,7 @@ export default {
function: schema,
functionOptions: {
schema: {
const: latestAsyncApiVersion,
const: latestVersion,
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion test-harness/scenarios/asyncapi2-streetlights.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ module.exports = asyncapi;
====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.4.0" version. asyncapi
1:11 information asyncapi-latest-version The latest version is not used. You should update to the "2.5.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
Expand Down

0 comments on commit d2bfabe

Please sign in to comment.