Repository: asyncapi/parser-js
Pull request #687 (2022-12-08 19:46:14)

feat: add isOldAsyncAPIDocument helper
<!--   Thank you for your contribution. Before you submit the pull request:
1. Follow contributing guidelines and make sure PR title follows Conventional Commits specification -> https://github.com/asyncapi/generator/blob/master/CONTRIBUTING.md
2. Test your changes and attach their results to the pull request.
3. Update the relevant documentation.
-->

**Description**

- add `x-parser-api-version` to have info about parser-api used in AsyncAPI model
- `isOldAsyncAPIDocument` helper function to check if document use old api
- improve `isAsyncAPIDocument`
- add unit tests

cc @jonaslagoni as you want :)

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=asyncapi_parser-js&pullRequest=687)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=687) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=687&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=asyncapi_parser-js&pullRequest=687)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=687) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=687&metric=duplicated_lines_density&view=list) No Duplication information


derberg: one cent from me, please update readme where we document all custom parser extensions
magicmatatjahu: @jonaslagoni I can add but it won't work in 100% cases.
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=asyncapi_parser-js&pullRequest=687)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=asyncapi_parser-js&pullRequest=687&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=687) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=asyncapi_parser-js&pullRequest=687&metric=duplicated_lines_density&view=list) No Duplication information


magicmatatjahu: /rtm

Patch:
diff --git a/README.md b/README.md
index c9abb01..3f7e4ca 100644
--- a/README.md
+++ b/README.md
@@ -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).
diff --git a/src/constants.ts b/src/constants.ts
index d73391a..bf3fe45 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -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';
diff --git a/src/document.ts b/src/document.ts
index 2ff4629..cc3cbf3 100644
--- a/src/document.ts
+++ b/src/document.ts
@@ -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 {
@@ -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;
+  }
+  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 {
diff --git a/src/index.ts b/src/index.ts
index 8938c5e..76d234b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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';
diff --git a/src/old-api/converter.ts b/src/old-api/converter.ts
index 17b7510..2c5b2ac 100644
--- a/src/old-api/converter.ts
+++ b/src/old-api/converter.ts
@@ -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';
 
@@ -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()) {
diff --git a/src/old-api/index.ts b/src/old-api/index.ts
index 7d4a5c6..944df1a 100644
--- a/src/old-api/index.ts
+++ b/src/old-api/index.ts
@@ -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';
diff --git a/src/parse.ts b/src/parse.ts
index a0518a8..c743cf7 100644
--- a/src/parse.ts
+++ b/src/parse.ts
@@ -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';
@@ -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 { 


Test patch:
diff --git a/test/document.spec.ts b/test/document.spec.ts
index 2349448..3fd419b 100644
--- a/test/document.spec.ts
+++ b/test/document.spec.ts
@@ -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() {
@@ -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() {
diff --git a/test/old-api/asyncapi.spec.ts b/test/old-api/asyncapi.spec.ts
index a67d2c7..ff8c2ce 100644
--- a/test/old-api/asyncapi.spec.ts
+++ b/test/old-api/asyncapi.spec.ts
@@ -940,7 +940,7 @@ describe('AsyncAPIDocument', function() {
       const nestedSchemas = fs.readFileSync(source, 'utf8');
       const { document } = await parser.parse(nestedSchemas, { source });
       const doc = convertToOldAPI(document!);
-      const output = {asyncapi: '2.0.0',info: {title: 'Test API',version: '1.0.0'},channels: {mychannel: {publish: {message: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-2>'}},'x-parser-schema-id': '<anonymous-schema-1>'},'x-parser-message-name': '<anonymous-message-1>','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.channels.mychannel.publish.message.payload','x-parser-message-parsed': true}}}},components: {messages: {testMessage: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-3>'},test: {type: 'object',properties: {testing: {type: 'string','x-parser-schema-id': '<anonymous-schema-5>'}},'x-parser-schema-id': '<anonymous-schema-4>'}},'x-parser-schema-id': 'testSchema'},'x-parser-message-name': 'testMessage','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.components.messages.testMessage.payload','x-parser-message-parsed': true}},schemas: {testSchema: '$ref:$.components.messages.testMessage.payload'}},'x-parser-spec-parsed': true,'x-parser-spec-stringified': true};
+      const output = {asyncapi: '2.0.0',info: {title: 'Test API',version: '1.0.0'},channels: {mychannel: {publish: {message: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-2>'}},'x-parser-schema-id': '<anonymous-schema-1>'},'x-parser-message-name': '<anonymous-message-1>','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.channels.mychannel.publish.message.payload','x-parser-message-parsed': true}}}},components: {messages: {testMessage: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-3>'},test: {type: 'object',properties: {testing: {type: 'string','x-parser-schema-id': '<anonymous-schema-5>'}},'x-parser-schema-id': '<anonymous-schema-4>'}},'x-parser-schema-id': 'testSchema'},'x-parser-message-name': 'testMessage','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.components.messages.testMessage.payload','x-parser-message-parsed': true}},schemas: {testSchema: '$ref:$.components.messages.testMessage.payload'}},'x-parser-spec-parsed': true,'x-parser-api-version': 0,'x-parser-spec-stringified': true};
 
       const stringified = AsyncAPIDocument.stringify(doc);
       expect(stringified).toEqual(JSON.stringify(output));
@@ -951,7 +951,7 @@ describe('AsyncAPIDocument', function() {
       const nestedSchemas = fs.readFileSync(source, 'utf8');
       const { document } = await parser.parse(nestedSchemas, { source });
       const doc = convertToOldAPI(document!);
-      const output = {asyncapi: '2.0.0',info: {title: 'Test API',version: '1.0.0'},channels: {mychannel: {publish: {message: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-2>'}},'x-parser-schema-id': '<anonymous-schema-1>'},'x-parser-message-name': '<anonymous-message-1>','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.channels.mychannel.publish.message.payload','x-parser-message-parsed': true}}}},components: {messages: {testMessage: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-3>'},deep: {type: 'object',properties: {circular: '$ref:$.components.messages.testMessage.payload'},'x-parser-schema-id': '<anonymous-schema-4>'}},'x-parser-schema-id': 'testSchema'},'x-parser-message-name': 'testMessage','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.components.messages.testMessage.payload','x-parser-message-parsed': true}},schemas: {testSchema: '$ref:$.components.messages.testMessage.payload'}},'x-parser-spec-parsed': true,'x-parser-circular': true,'x-parser-spec-stringified': true};
+      const output = {asyncapi: '2.0.0',info: {title: 'Test API',version: '1.0.0'},channels: {mychannel: {publish: {message: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-2>'}},'x-parser-schema-id': '<anonymous-schema-1>'},'x-parser-message-name': '<anonymous-message-1>','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.channels.mychannel.publish.message.payload','x-parser-message-parsed': true}}}},components: {messages: {testMessage: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-3>'},deep: {type: 'object',properties: {circular: '$ref:$.components.messages.testMessage.payload'},'x-parser-schema-id': '<anonymous-schema-4>'}},'x-parser-schema-id': 'testSchema'},'x-parser-message-name': 'testMessage','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.components.messages.testMessage.payload','x-parser-message-parsed': true}},schemas: {testSchema: '$ref:$.components.messages.testMessage.payload'}},'x-parser-spec-parsed': true,'x-parser-api-version': 0,'x-parser-circular': true,'x-parser-spec-stringified': true};
 
       const stringified = AsyncAPIDocument.stringify(doc);
       expect(stringified).toEqual(JSON.stringify(output));
@@ -975,7 +975,7 @@ describe('AsyncAPIDocument', function() {
       const nestedSchemas = fs.readFileSync(source, 'utf8');
       const { document } = await parser.parse(nestedSchemas, { source });
       const oldDoc = convertToOldAPI(document!);
-      const output = {asyncapi: '2.0.0',info: {title: 'Test API',version: '1.0.0'},channels: {mychannel: {publish: {message: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-2>'}},'x-parser-schema-id': '<anonymous-schema-1>'},'x-parser-message-name': '<anonymous-message-1>','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.channels.mychannel.publish.message.payload','x-parser-message-parsed': true}}}},components: {messages: {testMessage: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-3>'},test: {type: 'object',properties: {testing: {type: 'string','x-parser-schema-id': '<anonymous-schema-5>'}},'x-parser-schema-id': '<anonymous-schema-4>'}},'x-parser-schema-id': 'testSchema'},'x-parser-message-name': 'testMessage','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.components.messages.testMessage.payload','x-parser-message-parsed': true}},schemas: {testSchema: '$ref:$.components.messages.testMessage.payload'}},'x-parser-spec-parsed': true,'x-parser-spec-stringified': true};
+      const output = {asyncapi: '2.0.0',info: {title: 'Test API',version: '1.0.0'},channels: {mychannel: {publish: {message: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-2>'}},'x-parser-schema-id': '<anonymous-schema-1>'},'x-parser-message-name': '<anonymous-message-1>','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.channels.mychannel.publish.message.payload','x-parser-message-parsed': true}}}},components: {messages: {testMessage: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-3>'},test: {type: 'object',properties: {testing: {type: 'string','x-parser-schema-id': '<anonymous-schema-5>'}},'x-parser-schema-id': '<anonymous-schema-4>'}},'x-parser-schema-id': 'testSchema'},'x-parser-message-name': 'testMessage','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.components.messages.testMessage.payload','x-parser-message-parsed': true}},schemas: {testSchema: '$ref:$.components.messages.testMessage.payload'}},'x-parser-spec-parsed': true,'x-parser-api-version': 0,'x-parser-spec-stringified': true};
       const doc = AsyncAPIDocument.parse(output) as AsyncAPIDocument;
       expect(JSON.stringify(doc.json())).toEqual(JSON.stringify(oldDoc.json()));
     }); 
@@ -997,7 +997,7 @@ describe('AsyncAPIDocument', function() {
     });
 
     it('should parse stringified document with circular references', async function() {
-      const circularOutput = {asyncapi: '2.0.0',info: {title: 'Test API',version: '1.0.0'},channels: {mychannel: {publish: {message: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-2>'}},'x-parser-schema-id': '<anonymous-schema-1>'},'x-parser-message-name': '<anonymous-message-1>','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.channels.mychannel.publish.message.payload','x-parser-message-parsed': true}}}},components: {messages: {testMessage: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-3>'},deep: {type: 'object',properties: {circular: '$ref:$.components.messages.testMessage.payload'},'x-parser-schema-id': '<anonymous-schema-4>'}},'x-parser-schema-id': 'testSchema'},'x-parser-message-name': 'testMessage','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.components.messages.testMessage.payload','x-parser-message-parsed': true}},schemas: {testSchema: '$ref:$.components.messages.testMessage.payload'}},'x-parser-spec-parsed': true,'x-parser-circular': true,'x-parser-spec-stringified': true};
+      const circularOutput = {asyncapi: '2.0.0',info: {title: 'Test API',version: '1.0.0'},channels: {mychannel: {publish: {message: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-2>'}},'x-parser-schema-id': '<anonymous-schema-1>'},'x-parser-message-name': '<anonymous-message-1>','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.channels.mychannel.publish.message.payload','x-parser-message-parsed': true}}}},components: {messages: {testMessage: {payload: {type: 'object',properties: {name: {type: 'string','x-parser-schema-id': '<anonymous-schema-3>'},deep: {type: 'object',properties: {circular: '$ref:$.components.messages.testMessage.payload'},'x-parser-schema-id': '<anonymous-schema-4>'}},'x-parser-schema-id': 'testSchema'},'x-parser-message-name': 'testMessage','x-parser-original-schema-format': 'application/vnd.aai.asyncapi;version=2.0.0',schemaFormat: 'application/vnd.aai.asyncapi;version=2.0.0','x-parser-original-payload': '$ref:$.components.messages.testMessage.payload','x-parser-message-parsed': true}},schemas: {testSchema: '$ref:$.components.messages.testMessage.payload'}},'x-parser-spec-parsed': true,'x-parser-circular': true,'x-parser-spec-stringified': true,'x-parser-api-version': 0};
       const result = AsyncAPIDocument.parse(circularOutput) as AsyncAPIDocument;
 
       expect(result.hasCircular()).toEqual(true);
diff --git a/test/old-api/converter.spec.ts b/test/old-api/converter.spec.ts
index 5d05b74..9066479 100644
--- a/test/old-api/converter.spec.ts
+++ b/test/old-api/converter.spec.ts
@@ -1,10 +1,10 @@
-import { convertToOldAPI } from '../../src/old-api/converter';
+import { convertToOldAPI, convertToNewAPI } from '../../src/old-api/converter';
 import { AsyncAPIDocument as OldAsyncAPIDocument } from '../../src/old-api/asyncapi';
 import { AsyncAPIDocumentV2 } from '../../src';
 import { anonymousNaming } from '../../src/custom-operations/anonymous-naming';
 import { checkCircularRefs } from '../../src/custom-operations/check-circular-refs';
 import { getDefaultSchemaFormat } from '../../src/schema-parser';
-import { xParserCircular, xParserOriginalTraits, xParserOriginalSchemaFormat, xParserMessageParsed, xParserOriginalPayload } from '../../src/constants';
+import { xParserApiVersion, xParserCircular, xParserOriginalTraits, xParserOriginalSchemaFormat, xParserMessageParsed, xParserOriginalPayload } from '../../src/constants';
 
 describe('convertToOldAPI()', function() {
   it('should return AsyncAPIDocument instance', function() {
@@ -12,6 +12,22 @@ describe('convertToOldAPI()', function() {
     expect(convertToOldAPI(newApi)).toBeInstanceOf(OldAsyncAPIDocument);
   });
 
+  it('should assign x-parser-api-version extension to the 0 value', function() {
+    const newApi = new AsyncAPIDocumentV2({
+      channels: {
+        channel: {
+          publish: {
+            message: {
+              payload: {},
+            }
+          }
+        }
+      }
+    } as any);
+
+    expect(convertToOldAPI(newApi).ext(xParserApiVersion)).toEqual(0);
+  });
+
   it('should not assign x-parser-circular extension when document has not circular schemas', function() {
     const newApi = new AsyncAPIDocumentV2({
       channels: {
@@ -260,3 +276,10 @@ describe('convertToOldAPI()', function() {
     expect(oldApi.json().channels.channel?.subscribe?.traits).toBeUndefined();
   });
 });
+
+describe('convertToNewAPI()', function() {
+  it('should return AsyncAPIDocumentV2 instance', function() {
+    const oldDocument = new OldAsyncAPIDocument({ asyncapi: '2.0.0' } as any);
+    expect(convertToNewAPI(oldDocument)).toBeInstanceOf(AsyncAPIDocumentV2);
+  });
+});
diff --git a/test/parse.spec.ts b/test/parse.spec.ts
index 8894c18..cdca94a 100644
--- a/test/parse.spec.ts
+++ b/test/parse.spec.ts
@@ -2,6 +2,7 @@ import { Document } from '@stoplight/spectral-core';
 
 import { AsyncAPIDocumentV2 } from '../src/models';
 import { Parser } from '../src/parser';
+import { xParserApiVersion } from '../src/constants';
 
 describe('parse()', function() {
   const parser = new Parser();
@@ -51,6 +52,21 @@ describe('parse()', function() {
     expect(diagnostics.length > 0).toEqual(true);
   });
 
+  it('should assign x-parser-api-version extension to the 1 value', async function() {
+    const documentRaw = {
+      asyncapi: '2.0.0',
+      info: {
+        title: 'Valid AsyncApi document',
+        version: '1.0',
+      },
+      channels: {}
+    };
+    const { document } = await parser.parse(documentRaw);
+    
+    expect(document).toBeInstanceOf(AsyncAPIDocumentV2);
+    expect(document?.extensions().get(xParserApiVersion)?.value()).toEqual(1);
+  });
+
   it('should preserve references', async function() {
     const documentRaw = {
       asyncapi: '2.0.0',