Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add isOldAsyncAPIDocument helper #687

Merged
merged 3 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ In AsyncAPI Initiative we support below custom schema parsers. To install them,
The parser uses custom extensions to define additional information about the spec. Each has a different purpose but all of them are there to make it much easier to work with the AsyncAPI document. These extensions are prefixed with `x-parser-`. The following extensions are used:

- `x-parser-spec-parsed` is used to specify if the AsyncAPI document is already parsed by the parser. Property `x-parser-spec-parsed` is added to the root of the document with the `true` value.
- `x-parser-api-version` is used to specify which version of the [Parser-API](https://github.com/asyncapi/parser-api) the parsed AsyncAPI document uses. Property `x-parser-api-version` is added to the root of the document with the `1` value if the parsed document uses [Parser-API](https://github.com/asyncapi/parser-api) in the `v1` version or `0` if document uses old `parser-js` API.
- `x-parser-message-name` is used to specify the name of the message if it is not provided. For messages without names, the parser generates anonymous names. Property `x-parser-message-name` is added to a message object with a value that follows this pattern: `<anonymous-message-${number}>`. This value is returned by `message.id()` (`message.uid()` in the [old API](#convert-to-the-old-api)) when regular `name` property is not present.
- `x-parser-original-payload` holds the original payload of the message. You can use different formats for payloads with the AsyncAPI documents and the parser converts them to. For example, it converts payload described with Avro schema to AsyncAPI schema. The original payload is preserved in the extension.
- [`x-parser-circular`](#circular-references).
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import specs from '@asyncapi/specs';

export const xParserSpecParsed = 'x-parser-spec-parsed';
export const xParserSpecStringified = 'x-parser-spec-stringified';
export const xParserApiVersion = 'x-parser-api-version';

export const xParserMessageName = 'x-parser-message-name';
export const xParserMessageParsed = 'x-parser-message-parsed';
Expand Down
22 changes: 21 additions & 1 deletion src/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { createDetailedAsyncAPI } from './utils';
import {
xParserSpecParsed,
xParserSpecStringified,
xParserApiVersion,
} from './constants';

import type { AsyncAPIDocumentInterface } from './models';
import type { OldAsyncAPIDocument } from './old-api';
import type { DetailedAsyncAPI, AsyncAPIObject } from './types';

export function createAsyncAPIDocument(asyncapi: DetailedAsyncAPI): AsyncAPIDocumentInterface {
Expand All @@ -32,7 +34,25 @@ export function toAsyncAPIDocument(maybeDoc: unknown): AsyncAPIDocumentInterface
}

export function isAsyncAPIDocument(maybeDoc: unknown): maybeDoc is AsyncAPIDocumentInterface {
return maybeDoc instanceof AsyncAPIDocumentV2 || maybeDoc instanceof AsyncAPIDocumentV3;
if (!maybeDoc) {
return false;
}
if (maybeDoc instanceof AsyncAPIDocumentV2 || maybeDoc instanceof AsyncAPIDocumentV3) {
return true;
}
if (maybeDoc && typeof (maybeDoc as AsyncAPIDocumentInterface).json === 'function') {
const versionOfParserAPI = (maybeDoc as AsyncAPIDocumentInterface).json()[xParserApiVersion];
return versionOfParserAPI === 1;
}
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

export function isOldAsyncAPIDocument(maybeDoc: unknown): maybeDoc is OldAsyncAPIDocument {
if (maybeDoc && typeof (maybeDoc as OldAsyncAPIDocument).json === 'function') {
const versionOfParserAPI = (maybeDoc as OldAsyncAPIDocument).json()[xParserApiVersion];
return versionOfParserAPI === undefined || versionOfParserAPI === 0;
}
return false;
}

export function isParsedDocument(maybeDoc: unknown): maybeDoc is AsyncAPIObject {
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export * from './models';
export { Parser };
export { stringify, unstringify } from './stringify';
export { fromURL, fromFile } from './from';
export { createAsyncAPIDocument, toAsyncAPIDocument, isAsyncAPIDocument } from './document';
export { createAsyncAPIDocument, toAsyncAPIDocument, isAsyncAPIDocument, isOldAsyncAPIDocument } from './document';
export { DiagnosticSeverity };

export * from './old-api';
Expand Down
11 changes: 10 additions & 1 deletion src/old-api/converter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { AsyncAPIDocument } from './asyncapi';
import { xParserOriginalPayload, xParserOriginalSchemaFormat, xParserOriginalTraits, xParserMessageParsed } from '../constants';
import { xParserApiVersion, xParserOriginalPayload, xParserOriginalSchemaFormat, xParserOriginalTraits, xParserMessageParsed } from '../constants';
import { createAsyncAPIDocument } from '../document';
import { copy } from '../stringify';
import { getDefaultSchemaFormat } from '../schema-parser';
import { createDetailedAsyncAPI, setExtension } from '../utils';

import type { AsyncAPIDocumentInterface } from '../models/asyncapi';

Expand All @@ -11,10 +13,17 @@ export function convertToOldAPI(newDocument: AsyncAPIDocumentInterface): AsyncAP

handleMessages(document);
handleOperations(document);
setExtension(xParserApiVersion, 0, document as any);

return document;
}

export function convertToNewAPI(oldDocument: AsyncAPIDocument): AsyncAPIDocumentInterface {
const data = copy(oldDocument.json());
const detailed = createDetailedAsyncAPI(data);
return createAsyncAPIDocument(detailed);
}

function handleMessages(document: AsyncAPIDocument) {
const defaultSchemaFormat = getDefaultSchemaFormat(document.version());
for (const message of document.allMessages().values()) {
Expand Down
2 changes: 1 addition & 1 deletion src/old-api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { convertToOldAPI } from './converter';
export { convertToOldAPI, convertToNewAPI } from './converter';

export { AsyncAPIDocument as OldAsyncAPIDocument } from './asyncapi';
export { Base as OldBase } from './base';
Expand Down
3 changes: 2 additions & 1 deletion src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { copy } from './stringify';
import { createAsyncAPIDocument } from './document';
import { createDetailedAsyncAPI, mergePatch, setExtension, createUncaghtDiagnostic } from './utils';

import { xParserSpecParsed } from './constants';
import { xParserSpecParsed, xParserApiVersion } from './constants';

import type { Spectral, Document, RulesetFunctionContext } from '@stoplight/spectral-core';
import type { Parser } from './parser';
Expand Down Expand Up @@ -61,6 +61,7 @@ export async function parse(parser: Parser, spectral: Spectral, asyncapi: Input,
const detailed = createDetailedAsyncAPI(validatedDoc, asyncapi as DetailedAsyncAPI['input'], options.source);
const document = createAsyncAPIDocument(detailed);
setExtension(xParserSpecParsed, true, document);
setExtension(xParserApiVersion, 1, document);
await customOperations(parser, document, detailed, inventory, options);

return {
Expand Down
53 changes: 52 additions & 1 deletion test/document.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { xParserSpecParsed, xParserSpecStringified } from '../src/constants';
import { xParserApiVersion, xParserSpecParsed, xParserSpecStringified } from '../src/constants';
import { BaseModel, AsyncAPIDocumentV2 } from '../src/models';
import { convertToOldAPI } from '../src/old-api';
import { Parser } from '../src/parser';
import {
createAsyncAPIDocument,
toAsyncAPIDocument,
isAsyncAPIDocument,
isParsedDocument,
isStringifiedDocument,
isOldAsyncAPIDocument,
} from '../src/document';
import { createDetailedAsyncAPI } from '../src/utils';

describe('utils', function() {
const parser = new Parser();
class Model extends BaseModel {}

describe('createAsyncAPIDocument()', function() {
Expand Down Expand Up @@ -98,6 +102,53 @@ describe('utils', function() {
const detailed = createDetailedAsyncAPI(doc as any, doc as any);
expect(isAsyncAPIDocument(createAsyncAPIDocument(detailed))).toEqual(true);
});

it('AsyncAPIDocument instance should be AsyncAPI document', function() {
const doc = { asyncapi: '2.0.0' };
const detailed = createDetailedAsyncAPI(doc as any, doc as any);
expect(isAsyncAPIDocument(createAsyncAPIDocument(detailed))).toEqual(true);
});

it('document with the x-parser-api-version extension set to 1 should be AsyncAPI document', async function() {
expect(isAsyncAPIDocument({ json() { return { [xParserApiVersion]: 1 }; } })).toEqual(true);
});

it('document with the x-parser-api-version extension set to 0 should not be AsyncAPI document', async function() {
expect(isAsyncAPIDocument({ json() { return { [xParserApiVersion]: 0 }; } })).toEqual(false);
});

it('document without the x-parser-api-version extension should not be AsyncAPI document', async function() {
expect(isAsyncAPIDocument({ json() { return {}; } })).toEqual(false);
});
});

describe('isOldAsyncAPIDocument()', function() {
it('OldAsyncAPIDocument instance should be old AsyncAPI document', async function() {
const documentRaw = {
asyncapi: '2.0.0',
info: {
title: 'Valid AsyncApi document',
version: '1.0',
},
channels: {}
};
const { document } = await parser.parse(documentRaw);
const oldDocument = convertToOldAPI(document!);

expect(isOldAsyncAPIDocument(oldDocument)).toEqual(true);
});

it('document with the x-parser-api-version extension set to 0 should be old AsyncAPI document', async function() {
expect(isOldAsyncAPIDocument({ json() { return { [xParserApiVersion]: 0 }; } })).toEqual(true);
});

it('document without the x-parser-api-version extension should be old AsyncAPI document', async function() {
expect(isOldAsyncAPIDocument({ json() { return {}; } })).toEqual(true);
});

it('document with the x-parser-api-version extension set to 1 should not be old AsyncAPI document', async function() {
expect(isOldAsyncAPIDocument({ json() { return { [xParserApiVersion]: 1 }; } })).toEqual(false);
});
});

describe('isParsedDocument()', function() {
Expand Down
Loading