diff --git a/README.md b/README.md
index a6c8b0004..003cda979 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,11 @@ Use this package to validate and parse AsyncAPI documents —either YAML or JSON
![npm](https://img.shields.io/npm/v/@asyncapi/parser?style=for-the-badge) ![npm](https://img.shields.io/npm/dt/@asyncapi/parser?style=for-the-badge)
-> :warning: This package doesn't support AsyncAPI 1.x anymore. We recommend to upgrade to the latest AsyncAPI version using the [AsyncAPI converter](https://github.com/asyncapi/converter-js). If you need to convert documents on the fly, you may use the [Node.js](https://github.com/asyncapi/converter-js) or [Go](https://github.com/asyncapi/converter-go) converters.
+> **Warning**
+> This package doesn't support AsyncAPI 1.x anymore. We recommend to upgrade to the latest AsyncAPI version using the [AsyncAPI converter](https://github.com/asyncapi/converter-js). If you need to convert documents on the fly, you may use the [Node.js](https://github.com/asyncapi/converter-js) or [Go](https://github.com/asyncapi/converter-go) converters.
+
+> **Warning**
+> This package has rewrited Model API (old one) to [Intent API](https://github.com/asyncapi/parser-api). If you still need to use the old API, read the [Convert to the old API](#convert-to-the-old-api) section.
@@ -19,12 +23,15 @@ Use this package to validate and parse AsyncAPI documents —either YAML or JSON
* [Example using RAML data types](#example-using-raml-data-types)
* [Example with stringify and unstringify parsed document](#example-with-stringify-and-unstringify-parsed-document)
- [API documentation](#api-documentation)
-- [Using in the browser](#using-in-the-browser)
+- [Using in the browser/SPA applications](#using-in-the-browserspa-applications)
- [Custom schema parsers](#custom-schema-parsers)
* [Official supported custom schema parsers](#official-supported-custom-schema-parsers)
- [Custom extensions](#custom-extensions)
- [Circular references](#circular-references)
- [Stringify](#stringify)
+- [Convert to the old API](#convert-to-the-old-api)
+- [Bundler configuration](#bundler-configuration)
+ * [Webpack](#webpack)
- [Develop](#develop)
- [Contributing](#contributing)
- [Contributors](#contributors)
@@ -176,7 +183,7 @@ Direct access to the parsed JSON document is always available through the `doc.j
See [API documentation](./docs/api.md) for more examples and full API reference information.
-## Using in the browser/SPA apps
+## Using in the browser/SPA applications
The package contains a built-in version of the parser. To use it, you need to import the parser into the HTML file as below:
@@ -185,7 +192,7 @@ The package contains a built-in version of the parser. To use it, you need to im
```
@@ -195,11 +202,11 @@ Or, if you want to use the parser in a JS SPA-type application where you have a
import Parser from '@asyncapi/parser/browser';
const parser = new Parser();
-const { parsed, diagnostics } = parser.parse(...);
+const { document, diagnostics } = parser.parse(...);
```
> **Note**
-> Using the above code, we import the entire bundled parser into application. This may result in duplicate code in the final application bundle, only if the application uses the same dependencies what the parser. If, on the other hand, you want to have the smallest bundle possible, we recommend using the following import and properly configure bundler.
+> Using the above code, we import the entire bundled parser into application. This may result in a duplicate code in the final application bundle, only if the application uses the same dependencies what the parser. If, on the other hand, you want to have the smallest bundle as possible, we recommend using the following import and properly configure bundler.
Otherwise, if your application is bundled via bundlers like `webpack` and you can configure it, you can import the parser like a regular package:
@@ -207,7 +214,7 @@ Otherwise, if your application is bundled via bundlers like `webpack` and you ca
import { Parser } from '@asyncapi/parser';
const parser = new Parser();
-const { parsed, diagnostics } = parser.parse(...);
+const { document, diagnostics } = parser.parse(...);
```
> **Note**
@@ -252,38 +259,55 @@ AsyncAPI doesn't enforce one schema format. The payload of the messages can be d
In AsyncAPI Initiative we support below custom schema parsers. To install them, run below comamnds:
-- Avro schema:
+- [Avro schema](https://www.github.com/asyncapi/avro-schema-parser):
```bash
npm install @asyncapi/avro-schema-parser
yarn add @asyncapi/avro-schema-parser
```
-- OpenAPI (3.0.0) Schema Object:
+- [OpenAPI (3.0.0) Schema Object](https://www.github.com/asyncapi/openapi-schema-parser):
```bash
npm install @asyncapi/openapi-schema-parser
yarn add @asyncapi/openapi-schema-parser
```
-- RAML data type:
+- [RAML data type](https://www.github.com/asyncapi/raml-dt-schema-parser):
```bash
npm install @asyncapi/raml-dt-schema-parser
yarn add @asyncapi/raml-dt-schema-parser
```
- > **NOTE**: That custom parser works only in the NodeJS environment. Do not use it in browser applications!
+ > **Note**
+ > That custom parser works only in the NodeJS environment. Do not use it in browser applications!
## Custom extensions
-TBD
+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-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: ``. 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).
+
+In addition, the [`migrateToOldAPI()` function](#convert-to-the-old-api) which converts new API to an old one adds additional extensions:
+
+- `x-parser-message-parsed` is used to specify if the message is already parsed by the message parser. Property `x-parser-message-parsed` is added to the message object with the `true` value.
+- `x-parser-schema-id` is used to specify the ID of the schema if it is not provided. For schemas without IDs, the parser generates anonymous names. Property `x-parser-schema-id` is added to every object of a schema with a value that follows this pattern: ``. This value is returned by `schema.uid()` when regular `$id` property is not present.
+- `x-parser-original-traits` is where traits are stored after they are applied on the AsyncAPI document. The reason is because the original `traits` property is removed.
+- `x-parser-original-schema-format` holds information about the original schema format of the payload. You can use different schema formats with the AsyncAPI documents and the parser converts them to AsyncAPI schema. This is why different schema format is set, and the original one is preserved in the extension.
-> **NOTE**: All extensions added by the parser (including all properties) should be retrieved using special functions. Names of extensions and their location may change, and their eventual changes will not be announced.
+> **Warning**
+> All extensions added by the parser (including all properties) should be retrieved using special functions. Names of extensions and their location may change, and their eventual changes will not be announced.
## Circular references
-TBD
+Parser dereferences all circular references by default. In addition, to simplify interactions with the parser, the following is added:
+
+- `x-parser-circular` property is added to the root of the AsyncAPI document to indicate that the document contains circular references. In old API the Parser exposes `hasCircular()` function to check if given AsyncAPI document has circular references.
+- `isCircular()` function is added to the [Schema Model](./src/models/schema.ts) to determine if a given schema is circular with respect to previously occurring schemas in the tree.
## Stringify
@@ -300,11 +324,27 @@ For that, the Parser supports the ability to stringify a parsed AsyncAPI documen
To parse a stringified document into an AsyncAPIDocument instance, you must use the `unstringify` function (also exposed by package). It isn't compatible with the native `JSON.parse()` method. It replaces the given references pointed by the [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) path, with an `$ref:` prefix to the original objects.
A few advantages of this solution:
+
- The string remains as small as possible due to the use of [JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901).
- All references (also circular) are preserved.
Check [example](#example-with-stringify-and-unstringify-parsed-documentstringify).
+## Convert to the old API
+
+Version `2.0.0` of package introduced a lot of breaking changes, including changing the API of the returned parsed document (parser uses [New API](https://github.com/asyncapi/parser-api)). Due to the fact that a large part of the AsyncAPI tooling ecosystem uses a Parser with the old API and rewriting the tool for the new one can be time-consuming and difficult, the package exposes the `migrateToOldAPI()` function to convert new API to old one:
+
+```js
+import { Parser, migrateToOldAPI } from '@asyncapi/parser';
+
+const parser = new Parser();
+const { document } = parser.parse(...);
+const oldAsyncAPIDocument = migrateToOldAPI(document);
+```
+
+> **Note**
+> The old api will be supported only for a certain period of time. The target date for turning off support of the old API is around the end of January 2023.
+
## Bundler configuration
### Webpack
diff --git a/src/constants.ts b/src/constants.ts
index 54256868e..f2797dc32 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -5,6 +5,7 @@ export const xParserSpecParsed = 'x-parser-spec-parsed';
export const xParserSpecStringified = 'x-parser-spec-stringified';
export const xParserMessageName = 'x-parser-message-name';
+export const xParserMessageParsed = 'x-parser-message-parsed';
export const xParserSchemaId = 'x-parser-schema-id';
export const xParserOriginalSchemaFormat = 'x-parser-original-schema-format';
diff --git a/src/custom-operations/anonymous-naming.ts b/src/custom-operations/anonymous-naming.ts
new file mode 100644
index 000000000..53fd00053
--- /dev/null
+++ b/src/custom-operations/anonymous-naming.ts
@@ -0,0 +1,71 @@
+import { xParserMessageName, xParserSchemaId } from '../constants';
+import { traverseAsyncApiDocument } from '../iterator';
+import { setExtension } from '../utils';
+
+import type {
+ AsyncAPIDocumentInterface,
+ SchemaInterface
+} from '../models';
+
+export function anonymousNaming(document: AsyncAPIDocumentInterface) {
+ assignNameToComponentMessages(document);
+ assignNameToAnonymousMessages(document);
+
+ assignUidToComponentSchemas(document);
+ assignUidToComponentParameterSchemas(document);
+ assignUidToChannelParameterSchemas(document);
+ assignUidToAnonymousSchemas(document);
+}
+
+function assignNameToComponentMessages(document: AsyncAPIDocumentInterface) {
+ document.components().messages().forEach(message => {
+ if (message.name() === undefined) {
+ setExtension(xParserMessageName, message.id(), message);
+ }
+ });
+}
+
+function assignNameToAnonymousMessages(document: AsyncAPIDocumentInterface) {
+ let anonymousMessageCounter = 0;
+ document.messages().forEach(message => {
+ if (message.name() === undefined && message.extensions().get(xParserMessageName) === undefined) {
+ setExtension(xParserMessageName, ``, message);
+ }
+ });
+}
+
+function assignUidToComponentParameterSchemas(document: AsyncAPIDocumentInterface) {
+ document.components().channelParameters().forEach(parameter => {
+ const schema = parameter.schema();
+ if (schema && !schema.uid()) {
+ setExtension(xParserSchemaId, parameter.id(), schema);
+ }
+ });
+}
+
+function assignUidToChannelParameterSchemas(document: AsyncAPIDocumentInterface) {
+ document.channels().forEach(channel => {
+ channel.parameters().forEach(parameter => {
+ const schema = parameter.schema();
+ if (schema && !schema.uid()) {
+ setExtension(xParserSchemaId, parameter.id(), schema);
+ }
+ });
+ });
+}
+
+function assignUidToComponentSchemas(document: AsyncAPIDocumentInterface) {
+ document.components().schemas().forEach(schema => {
+ setExtension(xParserSchemaId, schema.uid(), schema);
+ });
+}
+
+function assignUidToAnonymousSchemas(doc: AsyncAPIDocumentInterface) {
+ let anonymousSchemaCounter = 0;
+ function callback(schema: SchemaInterface) {
+ if (!schema.uid()) {
+ setExtension(xParserSchemaId, ``, schema);
+ }
+ }
+ traverseAsyncApiDocument(doc, callback);
+}
diff --git a/src/custom-operations/apply-traits.ts b/src/custom-operations/apply-traits.ts
index 348052ebe..cc6191857 100644
--- a/src/custom-operations/apply-traits.ts
+++ b/src/custom-operations/apply-traits.ts
@@ -1,6 +1,5 @@
import { JSONPath } from 'jsonpath-plus';
-import { xParserOriginalTraits } from '../constants';
import { mergePatch } from '../utils';
import type { v2 } from '../spec-types';
@@ -55,8 +54,5 @@ function applyTraits(value: Record) {
value[String(key)] = mergePatch(value[String(key)], trait[String(key)]);
}
}
-
- value[xParserOriginalTraits] = value.traits;
- delete value.traits;
}
}
diff --git a/src/custom-operations/check-circular-refs.ts b/src/custom-operations/check-circular-refs.ts
new file mode 100644
index 000000000..f49e3a694
--- /dev/null
+++ b/src/custom-operations/check-circular-refs.ts
@@ -0,0 +1,24 @@
+import { setExtension } from '../utils';
+import { xParserCircular } from '../constants';
+
+import type { AsyncAPIDocumentInterface } from '../models';
+
+export function checkCircularRefs(document: AsyncAPIDocumentInterface) {
+ if (hasInlineRef(document.json())) {
+ setExtension(xParserCircular, true, document);
+ }
+}
+
+function hasInlineRef(data: Record): boolean {
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
+ if (Object.prototype.hasOwnProperty.call(data, '$ref')) {
+ return true;
+ }
+ for (const p in data) {
+ if (hasInlineRef(data[p])) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
diff --git a/src/custom-operations/index.ts b/src/custom-operations/index.ts
index 49f29aa6e..5bf91a714 100644
--- a/src/custom-operations/index.ts
+++ b/src/custom-operations/index.ts
@@ -1,18 +1,24 @@
-import { applyTraitsV2, applyTraitsV3 } from './apply-traits';
+import { applyTraitsV2 } from './apply-traits';
+import { checkCircularRefs } from './check-circular-refs';
import { parseSchemasV2 } from './parse-schema';
+import { anonymousNaming } from './anonymous-naming';
import type { Parser } from '../parser';
import type { ParseOptions } from '../parse';
+import type { AsyncAPIDocumentInterface } from '../models';
import type { DetailedAsyncAPI } from '../types';
-export async function customOperations(parser: Parser, detailed: DetailedAsyncAPI, options: ParseOptions): Promise {
+export async function customOperations(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, options: ParseOptions): Promise {
switch (detailed.semver.major) {
- case 2: return operationsV2(parser, detailed, options);
- case 3: return operationsV3(parser, detailed, options);
+ case 2: return operationsV2(parser, document, detailed, options);
+ // case 3: return operationsV3(parser, document, detailed, options);
}
}
-async function operationsV2(parser: Parser, detailed: DetailedAsyncAPI, options: ParseOptions): Promise {
+async function operationsV2(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, options: ParseOptions): Promise {
+ checkCircularRefs(document);
+ anonymousNaming(document);
+
if (options.applyTraits) {
applyTraitsV2(detailed.parsed);
}
@@ -21,8 +27,3 @@ async function operationsV2(parser: Parser, detailed: DetailedAsyncAPI, options:
}
}
-async function operationsV3(parser: Parser, detailed: DetailedAsyncAPI, options: ParseOptions): Promise {
- if (options.applyTraits) {
- applyTraitsV3(detailed.parsed);
- }
-}
diff --git a/src/custom-operations/parse-schema.ts b/src/custom-operations/parse-schema.ts
index 77c0c8ec1..6221af6cf 100644
--- a/src/custom-operations/parse-schema.ts
+++ b/src/custom-operations/parse-schema.ts
@@ -66,11 +66,15 @@ export async function parseSchemasV2(parser: Parser, detailed: DetailedAsyncAPI)
}
async function parseSchemaV2(parser: Parser, item: ToParseItem) {
- item.value[xParserOriginalPayload] = item.input.data;
- item.value.payload = await parseSchema(parser, item.input);
+ const originalData = item.input.data;
+ const parsedData = item.value.payload = await parseSchema(parser, item.input);
+ // save original payload only when data is different (returned by custom parsers)
+ if (originalData !== parsedData) {
+ item.value[xParserOriginalPayload] = originalData;
+ }
}
function splitPath(path: string): string[] {
// remove $[' from beginning and '] at the end and split by ']['
return path.slice(3).slice(0, -2).split('\'][\'');
-}
\ No newline at end of file
+}
diff --git a/src/index.ts b/src/index.ts
index 7cea42b90..88ee5d743 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,6 +6,9 @@ export { Parser };
export { stringify, unstringify } from './stringify';
export { AsyncAPISchemaParser } from './schema-parser/asyncapi-schema-parser';
+export { AsyncAPIDocument as OldAsyncAPIDocument } from './old-api/asyncapi';
+export { migrateToOldAPI } from './old-api/migrator';
+
export type { AsyncAPISemver, Diagnostic, SchemaValidateResult } from './types';
export type { LintOptions, ValidateOptions, ValidateOutput } from './lint';
export type { ParseInput, ParseOptions, ParseOutput } from './parse';
diff --git a/src/iterator.ts b/src/iterator.ts
index c066f4690..cf8e65e61 100644
--- a/src/iterator.ts
+++ b/src/iterator.ts
@@ -1,54 +1,54 @@
-import { AsyncAPIDocumentInterface } from 'models';
-import { ChannelInterface } from 'models/channel';
-import { ChannelParameterInterface } from 'models/channel-parameter';
-import { MessageInterface } from 'models/message';
-import { MessageTraitInterface } from 'models/message-trait';
-import { SchemaInterface } from 'models/schema';
-
+import type { AsyncAPIDocumentInterface } from './models/asyncapi';
+import type { ChannelInterface } from './models/channel';
+import type { ChannelParameterInterface } from './models/channel-parameter';
+import type { MessageInterface } from './models/message';
+import type { MessageTraitInterface } from './models/message-trait';
+import type { SchemaInterface } from './models/schema';
+
/**
* The different kind of stages when crawling a schema.
*/
export enum SchemaIteratorCallbackType {
- NEW_SCHEMA = 'NEW_SCHEMA', // The crawler just started crawling a schema.
- END_SCHEMA = 'END_SCHEMA', // The crawler just finished crawling a schema.
+ NEW_SCHEMA = 'NEW_SCHEMA', // The crawler just started crawling a schema.
+ END_SCHEMA = 'END_SCHEMA', // The crawler just finished crawling a schema.
}
/**
* The different types of schemas you can iterate
*/
export enum SchemaTypesToIterate {
- Parameters = 'parameters', // Crawl all schemas in payloads
- Payloads = 'payloads', // Crawl all schemas in payloads
- Headers = 'headers', // Crawl all schemas in headers
- Components = 'components', // Crawl all schemas in components
- Objects = 'objects', // Crawl all schemas of type object
- Arrays = 'arrays', // Crawl all schemas of type array
- OneOfs = 'oneOfs', // Crawl all schemas in oneOf's
- AllOfs = 'allOfs', // Crawl all schemas in allOf's
- AnyOfs = 'anyOfs', // Crawl all schemas in anyOf's
- Nots = 'nots', // Crawl all schemas in not field
- PropertyNames = 'propertyNames', // Crawl all schemas in propertyNames field
- PatternProperties = 'patternProperties', // Crawl all schemas in patternProperties field
- Contains = 'contains', // Crawl all schemas in contains field
- Ifs = 'ifs', // Crawl all schemas in if field
- Thenes = 'thenes', // Crawl all schemas in then field
- Elses = 'elses', // Crawl all schemas in else field
- Dependencies = 'dependencies', // Crawl all schemas in dependencies field
- Definitions = 'definitions', // Crawl all schemas in definitions field
+ Parameters = 'parameters', // Crawl all schemas in payloads
+ Payloads = 'payloads', // Crawl all schemas in payloads
+ Headers = 'headers', // Crawl all schemas in headers
+ Components = 'components', // Crawl all schemas in components
+ Objects = 'objects', // Crawl all schemas of type object
+ Arrays = 'arrays', // Crawl all schemas of type array
+ OneOfs = 'oneOfs', // Crawl all schemas in oneOf's
+ AllOfs = 'allOfs', // Crawl all schemas in allOf's
+ AnyOfs = 'anyOfs', // Crawl all schemas in anyOf's
+ Nots = 'nots', // Crawl all schemas in not field
+ PropertyNames = 'propertyNames', // Crawl all schemas in propertyNames field
+ PatternProperties = 'patternProperties', // Crawl all schemas in patternProperties field
+ Contains = 'contains', // Crawl all schemas in contains field
+ Ifs = 'ifs', // Crawl all schemas in if field
+ Thenes = 'thenes', // Crawl all schemas in then field
+ Elses = 'elses', // Crawl all schemas in else field
+ Dependencies = 'dependencies', // Crawl all schemas in dependencies field
+ Definitions = 'definitions', // Crawl all schemas in definitions field
}
-
+
export type TraverseOptions = {
- callback: Function
- schemaTypesToIterate: SchemaTypesToIterate[]
- seenSchemas: Set
+ callback: TraverseCallback
+ schemaTypesToIterate: SchemaTypesToIterate[]
+ seenSchemas: Set
}
-
-export type TraverseCallback = (schema: SchemaInterface, propOrIndex: string | number | null, callbackType: SchemaIteratorCallbackType) => void
+
+export type TraverseCallback = (schema: SchemaInterface, propOrIndex: string | number | null, callbackType: SchemaIteratorCallbackType) => boolean | void;
/**
* Go through each channel and for each parameter, and message payload and headers recursively call the callback for each schema.
*/
-export function traverseAsyncApiDocument(doc: AsyncAPIDocumentInterface, callback: TraverseCallback, schemaTypesToIterate: SchemaTypesToIterate[]) {
+export function traverseAsyncApiDocument(doc: AsyncAPIDocumentInterface, callback: TraverseCallback, schemaTypesToIterate: SchemaTypesToIterate[] = []) {
if (schemaTypesToIterate.length === 0) {
schemaTypesToIterate = Object.values(SchemaTypesToIterate);
}
@@ -156,7 +156,7 @@ function traverseSchema(schema: SchemaInterface, propOrIndex: string | number |
seenSchemas.delete(jsonSchema);
}
/* eslint-enable sonarjs/cognitive-complexity */
-
+
/**
* Recursively go through schema of object type and execute callback.
*/
@@ -180,7 +180,7 @@ function recursiveSchemaObject(schema: SchemaInterface, options: TraverseOptions
});
}
}
-
+
/**
* Recursively go through schema of array type and execute callback.
*/
@@ -205,7 +205,7 @@ function recursiveSchemaArray(schema: SchemaInterface, options: any) {
traverseSchema(schema.contains() as SchemaInterface, null, options);
}
}
-
+
/**
* Go through each schema in channel
*/
@@ -222,7 +222,7 @@ function traverseChannel(channel: ChannelInterface, options: TraverseOptions) {
traverseMessage(message, options);
});
}
-
+
/**
* Go through each schema in a message
*/
@@ -236,7 +236,7 @@ function traverseMessage(message: MessageInterface, options: TraverseOptions) {
traverseSchema(message.payload() as SchemaInterface, null, options);
}
}
-
+
/**
* Go through each schema in a messageTrait
*/
diff --git a/src/models/index.ts b/src/models/index.ts
index dc0a434a7..1161d0a38 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -1,7 +1,45 @@
export * from './v2';
export * from './v3';
+
export * from './asyncapi';
export * from './base';
-export * from './info';
+export * from './binding';
+export * from './bindings';
+export * from './channel-parameter';
+export * from './channel-parameters';
+export * from './channel';
+export * from './channels';
+export * from './collection';
+export * from './components';
export * from './contact';
+export * from './correlation-id';
+export * from './correlation-ids';
+export * from './extension';
+export * from './extensions';
+export * from './external-docs';
+export * from './info';
export * from './license';
+export * from './message-example';
+export * from './message-examples';
+export * from './message-trait';
+export * from './message-traits';
+export * from './message';
+export * from './messages';
+export * from './oauth-flow';
+export * from './oauth-flows';
+export * from './operation-trait';
+export * from './operation-traits';
+export * from './operation';
+export * from './operations';
+export * from './schema';
+export * from './schemas';
+export * from './security-requirement';
+export * from './security-requirements';
+export * from './security-scheme';
+export * from './security-schemes';
+export * from './server-variable';
+export * from './server-variables';
+export * from './server';
+export * from './servers';
+export * from './tag';
+export * from './tags';
\ No newline at end of file
diff --git a/src/models/v2/message-trait.ts b/src/models/v2/message-trait.ts
index 50634fe0f..914757e79 100644
--- a/src/models/v2/message-trait.ts
+++ b/src/models/v2/message-trait.ts
@@ -4,6 +4,7 @@ import { MessageExamples } from './message-examples';
import { MessageExample } from './message-example';
import { Schema } from './schema';
+import { xParserMessageName } from '../../constants';
import { getDefaultSchemaFormat } from '../../schema-parser';
import { bindings, hasDescription, description, extensions, hasExternalDocs, externalDocs, tags } from './mixins';
@@ -20,7 +21,7 @@ import type { v2 } from '../../spec-types';
export class MessageTrait extends BaseModel implements MessageTraitInterface {
id(): string {
- return this.messageId() || this._meta.id;
+ return this.messageId() || this._meta.id || this.extensions().get(xParserMessageName)?.value() as string;
}
schemaFormat(): string {
diff --git a/src/models/v2/schema.ts b/src/models/v2/schema.ts
index a7546cbc4..ee551ae63 100644
--- a/src/models/v2/schema.ts
+++ b/src/models/v2/schema.ts
@@ -1,5 +1,6 @@
import { BaseModel } from '../base';
+import { xParserSchemaId } from '../../constants';
import { extensions, hasExternalDocs, externalDocs } from './mixins';
import { retrievePossibleRef, hasRef } from '../../utils';
@@ -20,7 +21,7 @@ export class Schema extends BaseModel() as string;
}
$comment(): string | undefined {
diff --git a/src/old-api/asyncapi.ts b/src/old-api/asyncapi.ts
index c1bdf59b6..89c543302 100644
--- a/src/old-api/asyncapi.ts
+++ b/src/old-api/asyncapi.ts
@@ -6,11 +6,13 @@ import { Components } from './components';
import { Message } from './message';
import { Schema } from './schema';
+import { traverseAsyncApiDocument } from './iterator';
import { xParserCircular } from '../constants';
import { stringify, unstringify } from '../stringify';
import type { v2 } from '../spec-types';
import type { Operation } from './operation';
+import type { SchemaTypesToIterate, TraverseCallback } from './iterator';
export class AsyncAPIDocument extends SpecificationExtensionsModel {
version() {
@@ -137,19 +139,24 @@ export class AsyncAPIDocument extends SpecificationExtensionsModel {
- return new Map();
+ const schemas = new Map();
+ function allSchemasCallback(schema: Schema) {
+ if (schema.uid()) {
+ schemas.set(schema.uid(), schema);
+ }
+ }
+ traverseAsyncApiDocument(this, allSchemasCallback);
+ return schemas;
}
hasCircular() {
return !!this._json[xParserCircular];
}
- // TODO: Make traversing for old API and enable that function
- // traverseSchemas(callback, schemaTypesToIterate) {
- // traverseAsyncApiDocument(this, callback, schemaTypesToIterate);
- // }
+ traverseSchemas(callback: TraverseCallback, schemaTypesToIterate: SchemaTypesToIterate[]) {
+ traverseAsyncApiDocument(this, callback, schemaTypesToIterate);
+ }
static stringify(doc: AsyncAPIDocument, space: number): string | undefined {
return stringify(doc, { space });
diff --git a/src/old-api/components.ts b/src/old-api/components.ts
index e4db7cb41..4091b9fb0 100644
--- a/src/old-api/components.ts
+++ b/src/old-api/components.ts
@@ -113,12 +113,12 @@ export class Components extends SpecificationExtensionsModel, MessageTrait);
+ messageTraits(): Record> {
+ return createMapOfType(this._json.messageTraits as Record, MessageTrait) as Record>;
}
- messageTrait(name: string) {
- return getMapValue(this._json.messageTraits as Record, name, MessageTrait);
+ messageTrait(name: string): MessageTrait {
+ return getMapValue(this._json.messageTraits as Record, name, MessageTrait) as MessageTrait;
}
hasServerVariables() {
diff --git a/src/old-api/external-docs.ts b/src/old-api/external-docs.ts
index 1602bbcc7..d19bf2e3c 100644
--- a/src/old-api/external-docs.ts
+++ b/src/old-api/external-docs.ts
@@ -1,8 +1,9 @@
-import { SpecificationExtensionsModel, description, hasDescription } from './mixins';
+import { Base } from './base';
+import { description, hasDescription, extensionsMixins } from './mixins';
import type { v2 } from '../spec-types';
-export class ExternalDocs extends SpecificationExtensionsModel {
+export class ExternalDocs extends Base {
url() {
return this._json.url;
}
@@ -14,4 +15,36 @@ export class ExternalDocs extends SpecificationExtensionsModel
+}
+
+export type TraverseCallback = (schema: Schema, propOrIndex: string | number | null, callbackType: SchemaIteratorCallbackType) => boolean | void;
+
+/**
+ * Go through each channel and for each parameter, and message payload and headers recursively call the callback for each schema.
+ */
+export function traverseAsyncApiDocument(doc: AsyncAPIDocument, callback: TraverseCallback, schemaTypesToIterate: SchemaTypesToIterate[] = []) {
+ if (schemaTypesToIterate.length === 0) {
+ schemaTypesToIterate = Object.values(SchemaTypesToIterate);
+ }
+ const options: TraverseOptions = { callback, schemaTypesToIterate, seenSchemas: new Set() };
+
+ Object.values(doc.channels()).forEach(channel => {
+ traverseChannel(channel, options);
+ });
+
+ const components = doc.components();
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.components) && components) {
+ Object.values(components.messages()).forEach(message => {
+ traverseMessage(message, options);
+ });
+ Object.values(components.schemas()).forEach(schema => {
+ traverseSchema(schema, null, options);
+ });
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.parameters)) {
+ Object.values(components.parameters()).forEach(parameter => {
+ const schema = parameter.schema();
+ if (schema) {
+ traverseSchema(schema, null, options);
+ }
+ });
+ }
+ Object.values(components.messageTraits()).forEach(messageTrait => {
+ traverseMessageTrait(messageTrait, options);
+ });
+ }
+}
+
+/* eslint-disable sonarjs/cognitive-complexity */
+/**
+ * Traverse current schema and all nested schemas.
+ */
+function traverseSchema(schema: Schema, propOrIndex: string | number | null, options: TraverseOptions) { // NOSONAR
+ if (!schema) return;
+
+ const { schemaTypesToIterate, callback, seenSchemas } = options;
+
+ // handle circular references
+ const jsonSchema = schema.json();
+ if (seenSchemas.has(jsonSchema)) return;
+ seenSchemas.add(jsonSchema);
+
+ // `type` isn't required so save type as array in the fallback
+ let types = schema.type() || [];
+ // change primitive type to array of types for easier handling
+ if (!Array.isArray(types)) {
+ types = [types];
+ }
+
+ if (!schemaTypesToIterate.includes(SchemaTypesToIterate.objects) && types.includes('object')) return;
+ if (!schemaTypesToIterate.includes(SchemaTypesToIterate.arrays) && types.includes('array')) return;
+
+ // check callback `NEW_SCHEMA` case
+ if (callback(schema, propOrIndex, SchemaIteratorCallbackType.NEW_SCHEMA) === false) return;
+
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.objects) && types.includes('object')) {
+ recursiveSchemaObject(schema, options);
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.arrays) && types.includes('array')) {
+ recursiveSchemaArray(schema, options);
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.oneOfs)) {
+ (schema.oneOf() || []).forEach((combineSchema, idx) => {
+ traverseSchema(combineSchema, idx, options);
+ });
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.anyOfs)) {
+ (schema.anyOf() || []).forEach((combineSchema, idx) => {
+ traverseSchema(combineSchema, idx, options);
+ });
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.allOfs)) {
+ (schema.allOf() || []).forEach((combineSchema, idx) => {
+ traverseSchema(combineSchema, idx, options);
+ });
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.nots) && schema.not()) {
+ traverseSchema(schema.not() as Schema, null, options);
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.ifs) && schema.if()) {
+ traverseSchema(schema.if() as Schema, null, options);
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.thenes) && schema.then()) {
+ traverseSchema(schema.then() as Schema, null, options);
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.elses) && schema.else()) {
+ traverseSchema(schema.else() as Schema, null, options);
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.dependencies)) {
+ Object.entries(schema.dependencies() || {}).forEach(([depName, dep]) => {
+ // do not iterate dependent required
+ if (dep && !Array.isArray(dep)) {
+ traverseSchema(dep, depName, options);
+ }
+ });
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.definitions)) {
+ Object.entries(schema.definitions() || {}).forEach(([defName, def]) => {
+ traverseSchema(def, defName, options);
+ });
+ }
+
+ callback(schema, propOrIndex, SchemaIteratorCallbackType.END_SCHEMA);
+ seenSchemas.delete(jsonSchema);
+}
+/* eslint-enable sonarjs/cognitive-complexity */
+
+/**
+ * Recursively go through schema of object type and execute callback.
+ */
+function recursiveSchemaObject(schema: Schema, options: TraverseOptions) {
+ Object.entries(schema.properties()).forEach(([propertyName, property]) => {
+ traverseSchema(property, propertyName, options);
+ });
+
+ const additionalProperties = schema.additionalProperties();
+ if (typeof additionalProperties === 'object') {
+ traverseSchema(additionalProperties, null, options);
+ }
+
+ const schemaTypesToIterate = options.schemaTypesToIterate;
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.propertyNames) && schema.propertyNames()) {
+ traverseSchema(schema.propertyNames() as Schema, null, options);
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.patternProperties)) {
+ Object.entries(schema.patternProperties() || {}).forEach(([propertyName, property]) => {
+ traverseSchema(property, propertyName, options);
+ });
+ }
+}
+
+/**
+ * Recursively go through schema of array type and execute callback.
+ */
+function recursiveSchemaArray(schema: Schema, options: any) {
+ const items = schema.items();
+ if (items) {
+ if (Array.isArray(items)) {
+ items.forEach((item, idx) => {
+ traverseSchema(item, idx, options);
+ });
+ } else {
+ traverseSchema(items, null, options);
+ }
+ }
+
+ const additionalItems = schema.additionalItems();
+ if (typeof additionalItems === 'object') {
+ traverseSchema(additionalItems, null, options);
+ }
+
+ if (options.schemaTypesToIterate.includes('contains') && schema.contains()) {
+ traverseSchema(schema.contains() as Schema, null, options);
+ }
+}
+
+/**
+ * Go through each schema in channel
+ */
+function traverseChannel(channel: Channel, options: TraverseOptions) {
+ if (!channel) return;
+ const { schemaTypesToIterate } = options;
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.parameters)) {
+ Object.values(channel.parameters() || {}).forEach(parameter => {
+ const schema = parameter.schema();
+ if (schema) {
+ traverseSchema(schema, null, options);
+ }
+ });
+ }
+
+ const publish = channel.publish();
+ if (publish) {
+ publish.messages().forEach(message => {
+ traverseMessage(message, options);
+ });
+ }
+
+ const subscribe = channel.subscribe();
+ if (subscribe) {
+ subscribe.messages().forEach(message => {
+ traverseMessage(message, options);
+ });
+ }
+}
+
+/**
+ * Go through each schema in a message
+ */
+function traverseMessage(message: Message, options: TraverseOptions) {
+ if (!message) return;
+ const { schemaTypesToIterate } = options;
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.headers)) {
+ const headers = message.headers();
+ if (headers) {
+ traverseSchema(headers, null, options);
+ }
+ }
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.payloads)) {
+ const payload = message.payload();
+ if (payload) {
+ traverseSchema(payload, null, options);
+ }
+ }
+}
+
+/**
+ * Go through each schema in a messageTrait
+ */
+function traverseMessageTrait(messageTrait: MessageTrait, options: TraverseOptions) {
+ if (!messageTrait) return;
+ const { schemaTypesToIterate } = options;
+ if (schemaTypesToIterate.includes(SchemaTypesToIterate.headers)) {
+ const headers = messageTrait.headers();
+ if (headers) {
+ traverseSchema(headers, null, options);
+ }
+ }
+}
diff --git a/src/old-api/message-trait.ts b/src/old-api/message-trait.ts
index 5feec3545..8d762dc61 100644
--- a/src/old-api/message-trait.ts
+++ b/src/old-api/message-trait.ts
@@ -4,7 +4,7 @@ import { Schema } from './schema';
import type { v2 } from '../spec-types';
-export class MessageTrait extends SpecificationExtensionsModel {
+export class MessageTrait extends SpecificationExtensionsModel {
id() {
return this._json.messageId;
}
diff --git a/src/old-api/message.ts b/src/old-api/message.ts
index 9211d206c..fb1b380c4 100644
--- a/src/old-api/message.ts
+++ b/src/old-api/message.ts
@@ -1,11 +1,13 @@
import { MessageTrait } from './message-trait';
import { Schema } from './schema';
+import { xParserMessageName, xParserOriginalTraits, xParserOriginalPayload, xParserOriginalSchemaFormat } from '../constants';
+
import type { v2 } from '../spec-types';
export class Message extends MessageTrait {
uid() {
- return this.id() || this.name() || this.ext('x-parser-message-name') as string || Buffer.from(JSON.stringify(this._json)).toString('base64');
+ return this.id() || this.name() || this.ext(xParserMessageName) as string;
}
payload() {
@@ -14,20 +16,20 @@ export class Message extends MessageTrait {
}
traits() {
- const traits: v2.MessageTraitObject[] = this._json['x-parser-original-traits'] || this._json.traits;
+ const traits: v2.MessageTraitObject[] = this.ext(xParserOriginalTraits) || this._json.traits;
if (!traits) return [];
return traits.map(t => new MessageTrait(t));
}
hasTraits() {
- return !!this._json['x-parser-original-traits'] || !!this._json.traits;
+ return !!this.ext(xParserOriginalTraits) || !!this._json.traits;
}
originalPayload() {
- return this._json['x-parser-original-payload'] || this.payload();
+ return this.ext(xParserOriginalPayload) || this.payload();
}
originalSchemaFormat() {
- return this._json['x-parser-original-schema-format'] as string || this.schemaFormat();
+ return this.ext(xParserOriginalSchemaFormat) as string || this.schemaFormat();
}
}
diff --git a/src/old-api/migrator.ts b/src/old-api/migrator.ts
new file mode 100644
index 000000000..fdeea2728
--- /dev/null
+++ b/src/old-api/migrator.ts
@@ -0,0 +1,52 @@
+import { AsyncAPIDocument } from './asyncapi';
+import { xParserOriginalPayload, xParserOriginalSchemaFormat, xParserOriginalTraits, xParserMessageParsed } from '../constants';
+import { copy } from '../stringify';
+import { getDefaultSchemaFormat } from '../schema-parser';
+
+import type { AsyncAPIDocumentInterface } from '../models/asyncapi';
+
+export function migrateToOldAPI(newDocument: AsyncAPIDocumentInterface): AsyncAPIDocument {
+ const data = copy(newDocument.json());
+ const document = new AsyncAPIDocument(data);
+
+ handleMessages(document);
+ handleOperations(document);
+
+ return document;
+}
+
+function handleMessages(document: AsyncAPIDocument) {
+ const defaultSchemaFormat = getDefaultSchemaFormat(document.version());
+ for (const message of document.allMessages().values()) {
+ const json = message.json();
+ if (json.traits) {
+ json[xParserOriginalTraits] = json.traits;
+ delete json.traits;
+ }
+ json[xParserOriginalSchemaFormat] = json.schemaFormat || defaultSchemaFormat;
+ json.schemaFormat = defaultSchemaFormat;
+ json[xParserOriginalPayload] = json[xParserOriginalPayload] || json.payload;
+ json[xParserMessageParsed] = true;
+ }
+}
+
+function handleOperations(document: AsyncAPIDocument) {
+ Object.values(document.channels()).forEach(channel => {
+ const publish = channel.publish();
+ const subscribe = channel.subscribe();
+ if (publish) {
+ const json = publish.json();
+ if (json.traits) {
+ json[xParserOriginalTraits] = json.traits;
+ delete json.traits;
+ }
+ }
+ if (subscribe) {
+ const json = subscribe.json();
+ if (json.traits) {
+ json[xParserOriginalTraits] = json.traits;
+ delete json.traits;
+ }
+ }
+ });
+}
diff --git a/src/old-api/mixins.ts b/src/old-api/mixins.ts
index 69f41a621..7b0debbdd 100644
--- a/src/old-api/mixins.ts
+++ b/src/old-api/mixins.ts
@@ -8,47 +8,35 @@ import type { v2 } from '../spec-types';
export abstract class SpecificationExtensionsModel = Record> extends Base {
hasExtensions() {
- return !!this.extensionKeys().length;
+ return extensionsMixins.hasExtensions(this);
}
extensions(): v2.SpecificationExtensions {
- const result: v2.SpecificationExtensions = {};
- Object.entries(this._json).forEach(([key, value]) => {
- if (EXTENSION_REGEX.test(key)) {
- result[key as 'x-'] = value;
- }
- });
- return result;
+ return extensionsMixins.extensions(this);
}
extensionKeys() {
- return Object.keys(this.extensions());
+ return extensionsMixins.extensionKeys(this);
}
extKeys() {
- return this.extensionKeys();
+ return extensionsMixins.extKeys(this);
}
hasExtension(extension: string) {
- if (!extension.startsWith('x-')) {
- return false;
- }
- return !!(this._json as Record)[extension];
+ return extensionsMixins.hasExtension(this, extension);
}
extension(extension: string): v2.SpecificationExtension {
- if (!extension.startsWith('x-')) {
- return null;
- }
- return (this._json as Record)[extension];
+ return extensionsMixins.extension(this, extension);
}
hasExt(extension: string) {
- return this.hasExtension(extension);
+ return extensionsMixins.hasExt(this, extension);
}
ext(extension: string) {
- return this.extension(extension);
+ return extensionsMixins.ext(this, extension);
}
}
@@ -70,6 +58,52 @@ export function externalDocs(model: Base<{ externalDocs?: v2.ExternalDocumentati
}
}
+export const extensionsMixins = {
+ hasExtensions(model: Base<{ [extension: `x-${string}`]: v2.SpecificationExtension; }>) {
+ return !!extensionsMixins.extensionKeys(model).length;
+ },
+
+ extensions(model: Base<{ [extension: `x-${string}`]: v2.SpecificationExtension; }>): v2.SpecificationExtensions {
+ const result: v2.SpecificationExtensions = {};
+ Object.entries(model.json()).forEach(([key, value]) => {
+ if (EXTENSION_REGEX.test(key)) {
+ result[key as 'x-'] = value;
+ }
+ });
+ return result;
+ },
+
+ extensionKeys(model: Base<{ [extension: `x-${string}`]: v2.SpecificationExtension; }>) {
+ return Object.keys(extensionsMixins.extensions(model));
+ },
+
+ extKeys(model: Base<{ [extension: `x-${string}`]: v2.SpecificationExtension; }>) {
+ return extensionsMixins.extensionKeys(model);
+ },
+
+ hasExtension(model: Base<{ [extension: `x-${string}`]: v2.SpecificationExtension; }>, extension: string) {
+ if (!extension.startsWith('x-')) {
+ return false;
+ }
+ return !!(model.json() as Record)[extension];
+ },
+
+ extension(model: Base<{ [extension: `x-${string}`]: v2.SpecificationExtension; }>, extension: string): v2.SpecificationExtension | null {
+ if (!extension.startsWith('x-')) {
+ return null;
+ }
+ return (model.json() as Record)[extension];
+ },
+
+ hasExt(model: Base<{ [extension: `x-${string}`]: v2.SpecificationExtension; }>, extension: string) {
+ return extensionsMixins.hasExtension(model, extension);
+ },
+
+ ext(model: Base<{ [extension: `x-${string}`]: v2.SpecificationExtension; }>, extension: string) {
+ return extensionsMixins.extension(model, extension);
+ },
+};
+
export const bindingsMixins = {
hasBindings(model: Base<{ bindings?: Record }>) {
return !!Object.keys(bindingsMixins.bindings(model)).length;
diff --git a/src/old-api/tag.ts b/src/old-api/tag.ts
index 578143700..5b13faf9d 100644
--- a/src/old-api/tag.ts
+++ b/src/old-api/tag.ts
@@ -1,8 +1,9 @@
-import { SpecificationExtensionsModel, hasDescription, description, hasExternalDocs, externalDocs } from './mixins';
+import { Base } from './base';
+import { hasDescription, description, hasExternalDocs, externalDocs, extensionsMixins } from './mixins';
import type { v2 } from '../spec-types';
-export class Tag extends SpecificationExtensionsModel {
+export class Tag extends Base {
name() {
return this._json.name;
}
@@ -22,4 +23,36 @@ export class Tag extends SpecificationExtensionsModel {
hasExternalDocs() {
return hasExternalDocs(this);
}
+
+ hasExtensions() {
+ return extensionsMixins.hasExtensions(this);
+ }
+
+ extensions(): v2.SpecificationExtensions {
+ return extensionsMixins.extensions(this);
+ }
+
+ extensionKeys() {
+ return extensionsMixins.extensionKeys(this);
+ }
+
+ extKeys() {
+ return extensionsMixins.extKeys(this);
+ }
+
+ hasExtension(extension: string) {
+ return extensionsMixins.hasExtension(this, extension);
+ }
+
+ extension(extension: string): v2.SpecificationExtension {
+ return extensionsMixins.extension(this, extension);
+ }
+
+ hasExt(extension: string) {
+ return extensionsMixins.hasExt(this, extension);
+ }
+
+ ext(extension: string) {
+ return extensionsMixins.ext(this, extension);
+ }
}
diff --git a/src/parse.ts b/src/parse.ts
index b9edf9b45..00e84ce9f 100644
--- a/src/parse.ts
+++ b/src/parse.ts
@@ -2,7 +2,7 @@ import { AsyncAPIDocumentInterface, newAsyncAPIDocument } from './models';
import { customOperations } from './custom-operations';
import { validate } from './lint';
-import { unfreeze } from './stringify';
+import { copy } from './stringify';
import { toAsyncAPIDocument } from './document';
import { createDetailedAsyncAPI, normalizeInput } from './utils';
@@ -15,7 +15,7 @@ import type { MaybeAsyncAPI, Diagnostic } from './types';
export type ParseInput = string | MaybeAsyncAPI | AsyncAPIDocumentInterface;
export interface ParseOutput {
source: ParseInput;
- parsed: AsyncAPIDocumentInterface | undefined;
+ document: AsyncAPIDocumentInterface | undefined;
diagnostics: Diagnostic[];
}
@@ -30,7 +30,7 @@ export async function parse(parser: Parser, asyncapi: ParseInput, options?: Pars
if (maybeDocument) {
return {
source: asyncapi,
- parsed: maybeDocument,
+ document: maybeDocument,
diagnostics: [],
};
}
@@ -43,22 +43,22 @@ export async function parse(parser: Parser, asyncapi: ParseInput, options?: Pars
if (validated === undefined) {
return {
source: asyncapi,
- parsed: undefined,
+ document: undefined,
diagnostics,
};
}
// unfreeze the object - Spectral makes resolved document "freezed"
- const validatedDoc = unfreeze(validated as Record);
+ const validatedDoc = copy(validated as Record);
validatedDoc[String(xParserSpecParsed)] = true;
const detailed = createDetailedAsyncAPI(asyncapi as string | Record, validatedDoc);
- await customOperations(parser, detailed, options);
const parsedDoc = newAsyncAPIDocument(detailed);
+ await customOperations(parser, parsedDoc, detailed, options);
return {
source: asyncapi,
- parsed: parsedDoc,
+ document: parsedDoc,
diagnostics,
};
} catch (err: any) {
diff --git a/src/stringify.ts b/src/stringify.ts
index 4c786ef19..5f2b3e5f3 100644
--- a/src/stringify.ts
+++ b/src/stringify.ts
@@ -50,7 +50,7 @@ export function unstringify(document: unknown): AsyncAPIDocumentInterface | unde
return newAsyncAPIDocument(createDetailedAsyncAPI(document as string, parsed as DetailedAsyncAPI['parsed']));
}
-export function unfreeze(data: Record) {
+export function copy(data: Record) {
const stringifiedData = JSON.stringify(data, refReplacer());
const unstringifiedData = JSON.parse(stringifiedData);
traverseStringifiedData(unstringifiedData, undefined, unstringifiedData, new Map(), new Map());
diff --git a/src/utils.ts b/src/utils.ts
index cd5ae9173..a7e7bd7dc 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,6 +1,7 @@
import { DiagnosticSeverity } from '@stoplight/types';
import type { ISpectralDiagnostic } from '@stoplight/spectral-core';
+import type { BaseModel } from './models';
import type { AsyncAPISemver, AsyncAPIObject, DetailedAsyncAPI, MaybeAsyncAPI } from './types';
export function createDetailedAsyncAPI(source: string | Record, parsed: AsyncAPIObject): DetailedAsyncAPI {
@@ -42,6 +43,12 @@ export function hasWarningDiagnostic(diagnostics: ISpectralDiagnostic[]): boolea
return diagnostics.some(diagnostic => diagnostic.severity === DiagnosticSeverity.Warning);
}
+export function setExtension(id: string, value: any, model: BaseModel): void {
+ id = id.startsWith('x-') ? id : `x-${id}`;
+ const data = model.json();
+ data[id] = value;
+}
+
export function mergePatch(origin: unknown, patch: unknown) {
// If the patch is not an object, it replaces the origin.
if (!isObject(patch)) {
diff --git a/test/browser/sample-page.html b/test/browser/sample-page.html
index b1cb1a7f1..b30811629 100644
--- a/test/browser/sample-page.html
+++ b/test/browser/sample-page.html
@@ -15,9 +15,9 @@
try {
const parser = new window.AsyncAPIParser();
const spec = '{ "asyncapi": "2.0.0", "info": { "title": "My API", "version": "1.0.0" }, "channels": { "/test/tester": { "subscribe": { "operationId": "subscribeOperation", "message": { } } } } }';
- const { parsed, diagnostics } = await parser.parse(spec);
+ const { document: parsedDocument, diagnostics } = await parser.parse(spec);
- document.getElementById('content').innerHTML = parsed.version();
+ document.getElementById('content').innerHTML = parsedDocument.version();
document.getElementById('diagnostics').innerHTML = String(diagnostics.length);
} catch (error) {
console.error(error)
@@ -25,4 +25,4 @@
}
parse();
-