- - - -
+The project was originally developed under the [Kyma project](https://kyma-project.io/), in 2019 it was moved under AsyncAPI Initiative. ## Contributors diff --git a/docs/README.md b/docs/README.md index 822242156..4cd2e0952 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,14 +1,17 @@ # Documentation -## Overview - This directory contains the following documents that relate to the project: - configuration: - - [Style Modification](./configuration/style-modification.md) describes how to edit the styles of the AsyncAPI component. - - [Configuration Modification](./configuration/config-modification.md) describes the available component configuration options. + - [Configuration Modification](./configuration/config-modification.md) describes the available component configuration options. - development: - - [Development Guide](./development/guide.md) describes how to set up a development environment, write, and run tests. It also contains the naming and architecture convention. - - [Releasing](./development/releasing.md) describes how to release a new version of the library or playground. It also explains how to create release notes. + - [Development Guide](./development/guide.md) describes how to set up a development environment, write, and run tests. It also contains the naming and architecture convention. - features: - - [Anchors](./features/anchors.md) describes internal anchors in the AsyncAPI component. + - [Anchors](./features/anchors.md) describes internal anchors in the AsyncAPI component. + - [Highlight markdown's code blocks](./features/highlight.md) describes how to add custom language configuration for `highlight.js` used in the component. +- usage: + - [Using in Angular](./usage/angular.md) describes how to use the AsyncAPI component in Angular project. + - [Using in Vue](./usage/vue.md) describes how to use the AsyncAPI component in Vue project. + - [Using in NextJS](./usage/nextjs.md) describes how to use the AsyncAPI component in NextJS project. + - [Standalone bundle usage](./usage/standalone-bundle.md) describes how to use the Standalone Bundle of AsyncAPI component with [ReactDOM](https://reactjs.org/docs/react-dom.html) package onboard. + - [Web Component usage](./usage/web-component.md) describes how to use the `web-component`. diff --git a/docs/configuration/config-modification.md b/docs/configuration/config-modification.md index 29de0e625..d056d2ba8 100644 --- a/docs/configuration/config-modification.md +++ b/docs/configuration/config-modification.md @@ -12,33 +12,24 @@ See the definition of the object that you must pass to props to modify the compo interface ConfigInterface { schemaID?: string; show?: { + sidebar?: boolean; info?: boolean; - channels?: boolean; servers?: boolean; + operations?: boolean; messages?: boolean; schemas?: boolean; + errors?: boolean; }; expand?: { - channels?: { - root?: boolean; - elements?: boolean; - }; - servers?: { - root?: boolean; - elements?: boolean; - }; - messages?: { - root?: boolean; - elements?: boolean; - }; - schemas?: { - root?: boolean; - elements?: boolean; - }; + messageExamples?: boolean; + }, + sidebar?: { + showServers?: 'byDefault' | 'bySpecTags' | 'byServersTags'; + showOperations?: 'byDefault' | 'bySpecTags' | 'byOperationsTags'; }, - showErrors?: boolean; parserOptions?: ParserOptions; - pushStateBehavior?: (hash: string) => void; + publishLabel?: string; + subscribeLabel?: string; } ``` @@ -50,28 +41,33 @@ interface ConfigInterface { - **show?: Partial
- {code}
-
- + Additional properties are allowed. +
+ ); + } + if (additionalProperties === false) { + return ( ++ Additional properties are NOT allowed. +
+ ); + } + return ( ++ Additional items are allowed. +
+ ); + } + if (additionalItems === false) { + return ( ++ Additional items are NOT allowed. +
+ ); + } + return
- {formattedError}
+
+ {`${singleError.location.startLine}.`}
+
+ {singleError.title}
);
@@ -31,37 +25,31 @@ const renderErrors = (errors: ValidationError[]): React.ReactNode => {
.filter(Boolean);
};
-export const formatError = (singleError: ValidationError): string =>
- singleError.title;
-
interface Props {
error: ErrorObject;
}
-export const ErrorComponent: React.FunctionComponent = ({ error }) => {
+export const Error: React.FunctionComponent = ({ error }) => {
if (!error) {
return null;
}
- const className = `error`;
const { title, validationErrors } = error;
- const header = (
-
- {ERROR_TEXT}: {title}
-
- );
-
return (
-
-
- {validationErrors && validationErrors.length && (
-
-
- {renderErrors(validationErrors)}
-
-
- )}
-
-
+
+
+
+
+ {title ? `${ERROR_TEXT}: ${title}` : ERROR_TEXT}
+
+ {validationErrors && validationErrors.length ? (
+
+ {renderErrors(validationErrors)}
+
+ ) : null}
+
+
+
+
);
};
diff --git a/library/src/containers/Info/Contact.tsx b/library/src/containers/Info/Contact.tsx
deleted file mode 100644
index df26eee7f..000000000
--- a/library/src/containers/Info/Contact.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-
-import { Href } from '../../components';
-
-import { Contact } from '../../types';
-import { bemClasses } from '../../helpers';
-import { URL_SUPPORT_TEXT, EMAIL_SUPPORT_TEXT } from '../../constants';
-
-export const ContactComponent: React.FunctionComponent = ({
- url,
- email,
-}) => (
- <>
- {url && (
-
-
- {URL_SUPPORT_TEXT}
-
-
- )}
- {email && (
-
-
- {EMAIL_SUPPORT_TEXT}
-
-
- )}
- >
-);
diff --git a/library/src/containers/Info/DefaultContentType.tsx b/library/src/containers/Info/DefaultContentType.tsx
deleted file mode 100644
index cfdaed417..000000000
--- a/library/src/containers/Info/DefaultContentType.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-
-import { Href } from '../../components';
-
-import { CONTENT_TYPES_SITE } from '../../constants';
-
-interface DefaultContentTypeProps {
- type: string;
-}
-
-export const DefaultContentTypeComponent: React.FunctionComponent = ({
- type,
-}) => (
-
-
- {type}
-
-
-);
diff --git a/library/src/containers/Info/Info.tsx b/library/src/containers/Info/Info.tsx
index ed42675de..8e43b25fd 100644
--- a/library/src/containers/Info/Info.tsx
+++ b/library/src/containers/Info/Info.tsx
@@ -1,82 +1,135 @@
import React from 'react';
-import { TermsOfServiceComponent } from './TermsOfService';
-import { LicenseComponent } from './License';
-import { ContactComponent } from './Contact';
-import { DefaultContentTypeComponent } from './DefaultContentType';
+import { Href, Markdown, Tags } from '../../components';
+import { useSpec } from '../../contexts';
+import {
+ TERMS_OF_SERVICE_TEXT,
+ CONTENT_TYPES_SITE,
+ URL_SUPPORT_TEXT,
+ EXTERAL_DOCUMENTATION_TEXT,
+} from '../../constants';
-import { Markdown, CollapseButton } from '../../components';
-import { bemClasses } from '../../helpers';
-import { Info, DefaultContentType } from '../../types';
+export const Info: React.FunctionComponent = () => {
+ const asyncapi = useSpec();
-interface Props {
- info: Info;
- defaultContentType?: DefaultContentType;
-}
+ const info = asyncapi.info();
+ if (!info) {
+ return null;
+ }
+
+ const specId = asyncapi.info().id();
+ const externalDocs = asyncapi.info().externalDocs();
+ const license = info.license();
+ const termsOfService = info.termsOfService();
+ const defaultContentType = asyncapi.defaultContentType();
+ const contact = info.contact();
-export const InfoComponent: React.FunctionComponent = ({
- info: { title, version, description, termsOfService, contact, license },
- defaultContentType,
-}) => {
- const className = `info`;
const showInfoList =
- defaultContentType || termsOfService || license || contact;
+ license || termsOfService || defaultContentType || contact || externalDocs;
return (
-
-
-
-
-
- {title}
-
- {version && (
-
- {version}
-
- )}
-
-
+
+
+
+ {info.title()} {info.version()}
- {!showInfoList ? null : (
-
- {defaultContentType && (
- -
+ {license && (
+
-
+ {license.url() ? (
+
+ {license.name()}
+
+ ) : (
+
+ {license.name()}
+
)}
- >
-
)}
{termsOfService && (
- -
-
+ -
+
+ {TERMS_OF_SERVICE_TEXT}
+
)}
- {license && (
- -
-
+ {defaultContentType && (
+ -
+
+ {defaultContentType}
+
+
+ )}
+ {externalDocs && (
+ -
+
+ {EXTERAL_DOCUMENTATION_TEXT}
+
+
+ )}
+ {contact && (
+ <>
+ {contact.url() && (
+ -
+
+ {contact.name() || URL_SUPPORT_TEXT}
+
+
+ )}
+ {contact.email() && (
+ -
+
+ {contact.email()}
+
+
+ )}
+ >
+ )}
+ {specId && (
+ -
+
+ ID: {specId}
+
)}
- {contact && (contact.url || contact.email) ? (
-
- ) : null}
)}
-
- {description && (
-
- {description}
-
- )}
-
+
+ {info.hasDescription() && (
+
+ {info.description()}
+
+ )}
+
+ {asyncapi.info().tags().length > 0 && (
+
+
+
+ )}
+
+
+
+
);
};
diff --git a/library/src/containers/Info/License.tsx b/library/src/containers/Info/License.tsx
deleted file mode 100644
index 96a1d2056..000000000
--- a/library/src/containers/Info/License.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-
-import { Href } from '../../components';
-
-import { License } from '../../types';
-
-export const LicenseComponent: React.FunctionComponent = ({
- name,
- url,
-}) => {
- const nameWrapper = {name};
-
- return {url ? {nameWrapper} : nameWrapper};
-};
diff --git a/library/src/containers/Info/TermsOfService.tsx b/library/src/containers/Info/TermsOfService.tsx
deleted file mode 100644
index 55384080d..000000000
--- a/library/src/containers/Info/TermsOfService.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-
-import { Href } from '../../components';
-
-import { TERMS_OF_SERVICE_TEXT } from '../../constants';
-
-interface TermsOfServiceProps {
- url: string;
-}
-
-export const TermsOfServiceComponent: React.FunctionComponent = ({
- url,
-}) => (
-
-
- {TERMS_OF_SERVICE_TEXT}
-
-
-);
diff --git a/library/src/containers/Messages/Message.tsx b/library/src/containers/Messages/Message.tsx
index 5d465345d..9e2a834f8 100644
--- a/library/src/containers/Messages/Message.tsx
+++ b/library/src/containers/Messages/Message.tsx
@@ -1,229 +1,184 @@
import React from 'react';
+import { MessageInterface } from '@asyncapi/parser';
-import { SchemaComponent } from '../Schemas/Schema';
-import { PayloadComponent } from './Payload';
-import { BindingsComponent } from '../Bindings/Bindings';
-
+import { MessageExample } from './MessageExample';
import {
- bemClasses,
- removeSpecialChars,
- getExamplesFromSpec,
-} from '../../helpers';
-import { Message, isRawMessage } from '../../types';
-
-import { Markdown, Badge, BadgeType, Toggle } from '../../components';
-
+ Href,
+ Markdown,
+ Schema,
+ Bindings,
+ Tags,
+ Extensions,
+} from '../../components';
+
+import { useConfig } from '../../contexts';
+import { CommonHelpers } from '../../helpers';
import {
- DEPRECATED_TEXT,
- HEADERS_TEXT,
- MESSAGE_HEADERS_TEXT,
- HEADERS_EXAMPLE_TEXT,
- CONTAINER_LABELS,
- ITEM_LABELS,
- MESSAGE_BINDINGS_TEXT,
+ CONTENT_TYPES_SITE,
+ EXTERAL_DOCUMENTATION_TEXT,
} from '../../constants';
interface Props {
- title?: string;
- message: Message;
- hideTags?: boolean;
- inChannel?: boolean;
- toggleExpand?: boolean;
- oneOf?: boolean;
+ message: MessageInterface;
+ messageName?: string;
+ index?: number | string;
+ showExamples?: boolean;
}
-export const MessageComponent: React.FunctionComponent = ({
- title,
+export const Message: React.FunctionComponent = ({
message,
- hideTags,
- inChannel = false,
- toggleExpand = false,
- oneOf = false,
+ messageName,
+ index,
+ showExamples = false,
}) => {
+ const config = useConfig();
+
if (!message) {
return null;
}
- const className = ITEM_LABELS.MESSAGE;
- const messageID =
- title && title.length
- ? bemClasses.identifier([CONTAINER_LABELS.MESSAGES, title])
- : bemClasses.identifier([CONTAINER_LABELS.MESSAGES]);
- const messageDataID =
- title && title.length
- ? bemClasses.identifier([
- CONTAINER_LABELS.MESSAGES,
- removeSpecialChars(title),
- ])
- : bemClasses.identifier([CONTAINER_LABELS.MESSAGES]);
-
- if (!isRawMessage(message)) {
- return (
-
- {message.oneOf.map((elem, index) => (
- -
-
-
- ))}
-
- );
- }
- title = title || message.title || message.name;
- const examples = message.examples;
+ // check typeof as fallback for older version than `2.4.0`
+ const messageId = typeof message.id === 'function' && message.id();
+ const title = message.title();
+ const summary = message.summary();
+ const payload = message.payload();
+ const headers = message.headers();
+ const correlationId = message.correlationId();
- const summary = message.summary && (
-
- {message.summary}
-
- );
+ const contentType = message.contentType();
+ const externalDocs = message.externalDocs();
+ const showInfoList = contentType || externalDocs;
- const description = message.description && (
-
- {message.description}
-
- );
+ return (
+
+
+
+
+ {index !== undefined && (
+ #{index}
+ )}
+ {title && {title}}
+
+ {message.id()}
+
+
+
+ {summary && {summary}
}
+
+ {showInfoList && (
+
+ {contentType && (
+ -
+
+ {contentType}
+
+
+ )}
+ {externalDocs && (
+ -
+
+ {EXTERAL_DOCUMENTATION_TEXT}
+
+
+ )}
+
+ )}
- const header = !(title || summary) ? null : (
-
- {message.deprecated && (
-
- {DEPRECATED_TEXT}
-
- )}
- {title ? (
-
- {title}
-
- ) : null}
-
- );
+ {messageId && (
+
+
+ Message ID
+
+ {messageId}
+
+
+
+ )}
- const headersID = !inChannel
- ? bemClasses.identifier([{ id: messageID, toKebabCase: false }, 'headers'])
- : undefined;
-
- const headers = message.headers && (
-
-
- {HEADERS_TEXT}
-
-
-
-
-
- );
+ {correlationId && (
+
+
+ Correlation ID
+
+ {correlationId.location()}
+
+
+
+ {correlationId.hasDescription() && (
+
+ {correlationId.description()}
+
+ )}
+
+ )}
- const payloadID = !inChannel
- ? bemClasses.identifier([{ id: messageID, toKebabCase: false }, 'payload'])
- : undefined;
- const payloadDataID = !inChannel
- ? bemClasses.identifier([
- { id: messageDataID, toKebabCase: false },
- 'payload',
- ])
- : undefined;
- const payload = message.payload && (
-
- );
+ {message.hasDescription() && (
+
+ {message.description()}
+
+ )}
- // TAGS IS NOT SUPPORTED YET - please don't remove code!
- // const tags = !hideTags && message.tags && (
- //
- //
- // {TAGS_TEXT}
- //
- //
- // {message.tags.map(tag => (
- // -
- //
{tag.name}
- //
- // ))}
- //
- //
- // );
-
- const content = (
- <>
- {headers}
- {payload}
- {message.bindings && (
-
- )}
- >
- );
+ {payload && (
+
+
+
+ )}
+ {headers && (
+
+
+
+ )}
- const isBody = !!(
- message.description ||
- message.headers ||
- message.payload ||
- (!hideTags && message.tags)
- );
+ {message.bindings().length > 0 && (
+
+
+
+ )}
- const identifier = !inChannel ? messageID : undefined;
- const dataIdentifier = !inChannel ? messageDataID : undefined;
- return (
-
- {!inChannel ? (
-
- {!isBody ? null : (
- <>
- {summary}
- {description}
- {content}
- >
+
+
+ {message.tags().length > 0 && (
+
+
+
)}
-
- ) : (
- <>
- {header}
- {summary}
- {description}
- {content}
- >
+
+
+
+ {showExamples && (
+
+
+
)}
-
+
);
};
diff --git a/library/src/containers/Messages/MessageExample.tsx b/library/src/containers/Messages/MessageExample.tsx
new file mode 100644
index 000000000..7b32ae4ad
--- /dev/null
+++ b/library/src/containers/Messages/MessageExample.tsx
@@ -0,0 +1,115 @@
+import React, { useState, useEffect } from 'react';
+import { MessageInterface, SchemaInterface } from '@asyncapi/parser';
+
+import { CollapseButton, JSONSnippet } from '../../components';
+import { MessageHelpers } from '../../helpers/message';
+import { MessageExample as MessageExampleType } from '../../types';
+import { useConfig } from '../../contexts';
+
+interface Props {
+ message: MessageInterface;
+}
+
+export const MessageExample: React.FunctionComponent = ({ message }) => {
+ if (!message) {
+ return null;
+ }
+
+ const payload = message.payload();
+ const headers = message.headers();
+
+ return (
+
+ Examples
+ {payload && (
+
+ )}
+ {headers && (
+
+ )}
+
+ );
+};
+
+interface ExampleProps {
+ type: 'Payload' | 'Headers';
+ schema: SchemaInterface;
+ examples?: MessageExampleType[];
+}
+
+export const Example: React.FunctionComponent = ({
+ type = 'Payload',
+ schema,
+ examples = [],
+}) => {
+ const config = useConfig();
+ const [expanded, setExpanded] = useState(
+ (config && config.expand && config.expand.messageExamples) || false,
+ );
+
+ useEffect(() => {
+ setExpanded(
+ (config && config.expand && config.expand.messageExamples) || false,
+ );
+ }, [config.expand]);
+
+ return (
+
+
+ setExpanded(prev => !prev)}
+ expanded={expanded}
+ chevronProps={{
+ className: 'fill-current text-gray-200',
+ }}
+ >
+
+ {type}
+
+
+
+
+ {examples && examples.length > 0 ? (
+
+ {examples.map((example, idx) => (
+ -
+
+ {example.name
+ ? `#${idx + 1} Example - ${example.name}`
+ : `#${idx + 1} Example`}
+
+ {example.summary && (
+
+ {example.summary}
+
+ )}
+
+
+
+
+ ))}
+
+ ) : (
+
+
+
+ This example has been generated automatically.
+
+
+ )}
+
+
+ );
+};
diff --git a/library/src/containers/Messages/Messages.tsx b/library/src/containers/Messages/Messages.tsx
index f71900abd..ed8fe8469 100644
--- a/library/src/containers/Messages/Messages.tsx
+++ b/library/src/containers/Messages/Messages.tsx
@@ -1,87 +1,49 @@
import React from 'react';
-import { MessageComponent } from './Message';
-
-import { ExpandNestedConfig } from '../../config';
-import { bemClasses } from '../../helpers';
-import { Toggle } from '../../components';
-import { Message, RawMessage } from '../../types';
-import { MESSAGES_TEXT, CONTAINER_LABELS } from '../../constants';
-
-interface Props {
- messages?: Record;
- expand?: ExpandNestedConfig;
- inChannel?: boolean;
-}
-
-export const MessagesComponent: React.FunctionComponent = ({
- messages,
- expand,
- inChannel = false,
-}) => {
- if (!messages) {
+import { Message } from './Message';
+
+import { useConfig, useSpec } from '../../contexts';
+import { CommonHelpers } from '../../helpers';
+import { MESSAGES_TEXT } from '../../constants';
+
+export const Messages: React.FunctionComponent = () => {
+ const asyncapi = useSpec();
+ const config = useConfig();
+ const messages =
+ !asyncapi.components().isEmpty() &&
+ asyncapi
+ .components()
+ .messages()
+ .all();
+
+ if (!messages || messages.length === 0) {
return null;
}
- const className = CONTAINER_LABELS.MESSAGES;
- const messagesLength = Object.keys(messages).length;
- const wrapper = (children: React.ReactNode) => (
+ return (
- {children}
-
- );
- const header = {MESSAGES_TEXT}
;
- const content = (
-
- {Object.entries(messages).map(([key, message]) => {
- const msg = message as RawMessage;
- let inferredName = (msg['x-parser-message-name'] as string) || '';
- inferredName = inferredName.includes('anonymous-message')
- ? ''
- : inferredName;
-
- const title =
- messagesLength < 2 && inChannel
- ? ''
- : (message as RawMessage).title ||
- (message as RawMessage).name ||
- inferredName ||
- key;
-
- return (
+
+ {MESSAGES_TEXT}
+
+
+ {messages.map((message, idx) => (
-
-
- );
- })}
-
- );
-
- if (inChannel) {
- return wrapper(content);
- }
-
- return wrapper(
-
- {content}
- ,
+ ))}
+
+
);
};
diff --git a/library/src/containers/Messages/Payload.tsx b/library/src/containers/Messages/Payload.tsx
deleted file mode 100644
index 445c8707e..000000000
--- a/library/src/containers/Messages/Payload.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import React from 'react';
-
-import { SchemaComponent } from '../Schemas/Schema';
-
-import { bemClasses } from '../../helpers';
-import { RawMessage, isOneOfPayload, isAnyOfPayload } from '../../types';
-import { Toggle } from '../../components';
-import {
- ONE_OF_PAYLOADS_TEXT,
- ANY_OF_PAYLOADS_TEXT,
- PAYLOAD_TEXT,
- MESSAGE_PAYLOAD_TEXT,
- PAYLOAD_EXAMPLE_TEXT,
-} from '../../constants';
-
-interface Props extends Required> {
- oneOf?: boolean;
- anyOf?: boolean;
- identifier?: string;
- dataIdentifier?: string;
- id?: number;
- examples?: object[];
-}
-
-export const PayloadComponent: React.FunctionComponent = ({
- payload,
- oneOf = false,
- anyOf = false,
- identifier,
- dataIdentifier,
- id,
- examples,
-}) => {
- const className = `message-payload`;
- const payloadsID = identifier ? `${identifier}s` : undefined;
-
- if (isOneOfPayload(payload)) {
- return (
-
-
- {ONE_OF_PAYLOADS_TEXT}
-
-
- {payload.oneOf.map((elem, index: number) => (
- -
-
-
- ))}
-
-
- );
- }
-
- if (isAnyOfPayload(payload)) {
- return (
-
-
- {ANY_OF_PAYLOADS_TEXT}
-
-
- {payload.anyOf.map((elem, index: number) => (
- -
-
-
- ))}
-
-
- );
- }
-
- let inferredId = (payload['x-parser-schema-id'] as string) || '';
- inferredId = inferredId.includes('anonymous-schema') ? '' : inferredId;
- const title =
- id !== undefined ? (inferredId ? `${id} ${inferredId}` : id) : PAYLOAD_TEXT;
- const header = (
-
- {title}
-
- );
-
- const content = (
-
-
-
- );
-
- let payloadID;
- if (identifier) {
- payloadID =
- payload.title && payload.title.length
- ? `${identifier}-${payload.title}`
- : `${identifier}${id !== undefined ? `-${id}` : ''}`;
- }
- let payloadDataID;
- if (dataIdentifier) {
- payloadDataID =
- payload.title && payload.title.length
- ? `${dataIdentifier}-${payload.title}`
- : `${dataIdentifier}${id !== undefined ? `-${id}` : ''}`;
- }
-
- if (oneOf || anyOf) {
- return (
-
-
- {content}
-
-
- );
- }
-
- return (
-
- {header}
- {content}
-
- );
-};
diff --git a/library/src/containers/Operations/Operation.tsx b/library/src/containers/Operations/Operation.tsx
new file mode 100644
index 000000000..97e2682f9
--- /dev/null
+++ b/library/src/containers/Operations/Operation.tsx
@@ -0,0 +1,258 @@
+import React from 'react';
+import { ChannelInterface, OperationInterface } from '@asyncapi/parser';
+
+import { Message } from '../Messages/Message';
+import { Security } from '../Servers/Security';
+import {
+ Href,
+ Markdown,
+ Schema,
+ Bindings,
+ Tags,
+ Extensions,
+} from '../../components';
+
+import { useConfig } from '../../contexts';
+import { CommonHelpers, SchemaHelpers } from '../../helpers';
+import {
+ EXTERAL_DOCUMENTATION_TEXT,
+ PUBLISH_LABEL_DEFAULT_TEXT,
+ SUBSCRIBE_LABEL_DEFAULT_TEXT,
+} from '../../constants';
+import { PayloadType } from '../../types';
+import { ConfigInterface } from '../../config/config';
+interface Props {
+ type: PayloadType;
+ operation: OperationInterface;
+ channelName: string;
+ channel: ChannelInterface;
+}
+
+export const Operation: React.FunctionComponent = props => {
+ const config = useConfig();
+ const { type = PayloadType.PUBLISH, operation, channelName, channel } = props;
+
+ if (!operation || !channel) {
+ return null;
+ }
+
+ // check typeof as fallback for older version than `2.2.0`
+ const servers =
+ typeof channel.servers === 'function' && channel.servers().all();
+ // check typeof as fallback for older version than `2.4.0`
+ const security =
+ typeof operation.security === 'function' && operation.security();
+ const parameters =
+ channel.parameters() !== undefined
+ ? SchemaHelpers.parametersToSchema(channel.parameters())
+ : undefined;
+
+ return (
+
+
+
+
+ {servers && servers.length > 0 ? (
+
+ Available only on servers:
+
+ {servers.map(server => (
+ -
+
+ {server.id()}
+
+
+ ))}
+
+
+ ) : null}
+
+ {parameters && (
+
+
+
+ )}
+
+ {security && (
+
+
+
+ )}
+
+ {channel.bindings() && (
+
+
+
+ )}
+
+
+
+ {operation.bindings() && (
+
+
+
+ )}
+
+
+
+ {operation.tags() && (
+
+
+
+ )}
+
+
+
+ {operation.messages().length > 1 ? (
+
+
+ Accepts one of the following messages:
+
+
+ {operation
+ .messages()
+ .all()
+ .map((msg, idx) => (
+ -
+
+
+ ))}
+
+
+ ) : (
+
+ Accepts the following message:
+
+
+
+
+ )}
+
+
+ );
+};
+
+function getTypeInformation({
+ type = PayloadType.PUBLISH,
+ config,
+}: {
+ type: PayloadType;
+ config: ConfigInterface;
+}): { borderColor: string; typeLabel: string } {
+ if (type === PayloadType.SUBSCRIBE) {
+ return {
+ borderColor: 'border-green-600 text-green-600',
+ typeLabel: config.subscribeLabel ?? SUBSCRIBE_LABEL_DEFAULT_TEXT,
+ };
+ }
+ // type === PayloadType.PUBLISH
+ return {
+ borderColor: 'border-blue-600 text-blue-500',
+ typeLabel: config.publishLabel ?? PUBLISH_LABEL_DEFAULT_TEXT,
+ };
+}
+
+export const OperationInfo: React.FunctionComponent = props => {
+ const { type = PayloadType.PUBLISH, operation, channelName, channel } = props;
+ const config = useConfig();
+ const operationSummary = operation.summary();
+ const externalDocs = operation.externalDocs();
+ const operationId = operation.id();
+ const { borderColor, typeLabel } = getTypeInformation({ type, config });
+
+ return (
+ <>
+
+
+
+ {typeLabel}
+ {' '}
+ {channelName}
+
+
+
+ {channel.hasDescription() && (
+
+ {channel.description()}
+
+ )}
+ {operationSummary && (
+ {operationSummary}
+ )}
+ {operation.hasDescription() && (
+
+ {operation.description()}
+
+ )}
+
+ {externalDocs && (
+
+ {externalDocs && (
+ -
+
+ {EXTERAL_DOCUMENTATION_TEXT}
+
+
+ )}
+
+ )}
+
+ {operationId && (
+
+
+ Operation ID
+
+ {operationId}
+
+
+
+ )}
+ >
+ );
+};
diff --git a/library/src/containers/Operations/Operations.tsx b/library/src/containers/Operations/Operations.tsx
new file mode 100644
index 000000000..d9f43ad27
--- /dev/null
+++ b/library/src/containers/Operations/Operations.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+
+import { Operation } from './Operation';
+import { useConfig, useSpec } from '../../contexts';
+import { CommonHelpers } from '../../helpers';
+import { OPERATIONS_TEXT } from '../../constants';
+import { PayloadType } from '../../types';
+
+export const Operations: React.FunctionComponent = () => {
+ const operations = useSpec()
+ .operations()
+ .all();
+ const config = useConfig();
+
+ if (!Object.keys(operations).length) {
+ return null;
+ }
+
+ const operationsList: React.ReactNodeArray = [];
+ operations.forEach(operation => {
+ const channel = operation.channels().all()[0];
+ const channelAddress = channel?.address() ?? '';
+ if (operation.isSend()) {
+ operationsList.push(
+
+
+ ,
+ );
+ }
+ if (operation.isReceive()) {
+ operationsList.push(
+
+
+ ,
+ );
+ }
+ });
+
+ return (
+
+
+ {OPERATIONS_TEXT}
+
+ {operationsList}
+
+ );
+};
diff --git a/library/src/containers/Schemas/Schema.tsx b/library/src/containers/Schemas/Schema.tsx
index 25e4a8530..cde92ec77 100644
--- a/library/src/containers/Schemas/Schema.tsx
+++ b/library/src/containers/Schemas/Schema.tsx
@@ -1,142 +1,30 @@
import React from 'react';
+import { SchemaInterface } from '@asyncapi/parser';
-import { SchemaPropertiesComponent as SchemaProperties } from './SchemaProperties';
-import { SchemaExampleComponent } from './SchemaExample';
-
-import { Toggle } from '../../components';
-import { Schema } from '../../types';
-import {
- bemClasses,
- searchForNestedObject,
- removeSpecialChars,
-} from '../../helpers';
-import { ITEM_LABELS, CONTAINER_LABELS } from '../../constants';
+import { Schema as SchemaComponent } from '../../components';
interface Props {
- name: string;
- schema?: Schema;
- description?: React.ReactNode;
- exampleTitle?: string;
- hideTitle?: boolean;
- toggle?: boolean;
- toggleExpand?: boolean;
- examples?: object[];
- required?: boolean;
+ schemaName: string;
+ schema: SchemaInterface;
}
-const renderSchemaProps = (
- schemaName: string,
- schema: Schema,
- required: boolean,
-): React.ReactNode => {
- const properties = schema.properties;
-
- if (properties) {
- return Object.entries(properties).map(([key, prop]) => (
-
- ));
- }
-
- return (
-
- );
-};
-
-export const SchemaComponent: React.FunctionComponent = ({
- name,
+export const Schema: React.FunctionComponent = ({
+ schemaName,
schema,
- description,
- exampleTitle,
- hideTitle = false,
- toggle = false,
- toggleExpand = false,
- examples = [],
- required = false,
}) => {
if (!schema) {
return null;
}
- schema.description = schema.description || description || '';
-
- const className = ITEM_LABELS.SCHEMA;
- const hasNotField = searchForNestedObject(schema, 'not');
- const header = (
-
-
- {name}
-
-
- );
- const hasExamples = examples.length;
- const content = (
- <>
-
- {renderSchemaProps(name, schema, required)}
+ return (
+
+
+
+
+
- {hasExamples ? (
- examples.map((el, i) => (
- 1 ? `${exampleTitle} ${i}` : exampleTitle}
- example={el}
- key={i}
- />
- )) // we need to disable this component if schema has "not" field anywhere in it
- ) : hasNotField ? null : (
-
- )}
- >
- );
-
- const schemaID = toggle
- ? bemClasses.identifier([CONTAINER_LABELS.SCHEMAS, name])
- : undefined;
- const schemaDataID = toggle
- ? bemClasses.identifier([
- CONTAINER_LABELS.SCHEMAS,
- removeSpecialChars(name),
- ])
- : undefined;
- return (
-
- {toggle ? (
-
- {content}
-
- ) : (
- <>
- {hideTitle ? null : (
-
- {header}
-
- )}
- {content}
- >
- )}
-
+
+
);
};
diff --git a/library/src/containers/Schemas/SchemaExample.tsx b/library/src/containers/Schemas/SchemaExample.tsx
deleted file mode 100644
index e33f34d17..000000000
--- a/library/src/containers/Schemas/SchemaExample.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-
-import { CodeComponent, Badge, BadgeType } from '../../components';
-
-import { bemClasses, generateExampleSchema } from '../../helpers';
-import { Schema } from '../../types';
-import { SCHEMA_EXAMPLE_TEXT } from '../../constants';
-
-interface Props {
- title?: string;
- schema?: Schema;
- example?: object;
-}
-
-export const SchemaExampleComponent: React.FunctionComponent = ({
- title,
- schema,
- example,
-}) => {
- const schemaExample =
- schema && schema.example
- ? schema.example
- : schema && generateExampleSchema(schema);
- const exampleString = JSON.stringify(example || schemaExample || '', null, 2);
-
- if (!exampleString) {
- return null;
- }
-
- return (
-
-
-
- {title ? title : SCHEMA_EXAMPLE_TEXT}
-
- {example || (schema && schema.example) ? null : (
-
-
-
- )}
-
- }
- />
-
- );
-};
diff --git a/library/src/containers/Schemas/SchemaProperties.tsx b/library/src/containers/Schemas/SchemaProperties.tsx
deleted file mode 100644
index 6304124a1..000000000
--- a/library/src/containers/Schemas/SchemaProperties.tsx
+++ /dev/null
@@ -1,361 +0,0 @@
-import React from 'react';
-import merge from 'merge';
-
-import { bemClasses } from '../../helpers';
-import { TypeWithKey, Schema } from '../../types';
-import { Markdown, TreeSpace, TreeLeaf } from '../../components';
-
-type SchemaWithKey = TypeWithKey;
-interface SchemaElement {
- schema: SchemaWithKey;
- treeSpace: number;
- required: boolean;
-}
-
-const getEnumHTMLElements = (schema: SchemaWithKey): HTMLElement[] => {
- let enumElements: any[] = [];
- if (schema.content.enum && schema.content.enum.length) {
- enumElements = schema.content.enum.map((value: any, i: number) => (
- <>
- {' '}
-
- "{value}"
-
- >
- ));
- }
-
- return enumElements;
-};
-
-const handleNotProperty = (prop: Schema): Schema => {
- if (prop.not) {
- const arrayOfChangedObjects = Object.entries(prop).map(([key, val]) => {
- if (key === 'not') {
- return { properties: { [key]: val } };
- }
- return prop[key];
- });
-
- return merge.recursive(...arrayOfChangedObjects);
- }
- return prop;
-};
-
-const renderItems = (schema: Schema, treeSpace: number): React.ReactNode => {
- const properties =
- schema.items && schema.items.properties ? schema.items.properties : null;
-
- if (!properties) {
- return null;
- }
-
- return renderProperties(schema.items as Schema, treeSpace);
-};
-
-const renderProperties = (
- schema: Schema,
- treeSpace: number,
-): React.ReactNode => {
- const properties = schema.properties;
-
- if (!properties) {
- return null;
- }
-
- return Object.entries(properties).map(([key, prop]) => (
-
- ));
-};
-
-const renderAdditionalProperties = (
- schema: Schema,
- treeSpace: number,
-): React.ReactNode => {
- const additionalProperties = schema.additionalProperties;
-
- if (!additionalProperties || typeof additionalProperties === 'boolean') {
- return null;
- }
-
- return (
-
-
-
- );
-};
-
-const renderOf = (treeSpace: number, schemas?: Schema[]): React.ReactNode => {
- if (!schemas) {
- return null;
- }
-
- return schemas.map((schema, index) => {
- const id = index.toString();
-
- return (
-
- );
- });
-};
-
-interface Props {
- name: string;
- hasDynamicName?: boolean;
- properties: Schema;
- treeSpace: number;
- description?: React.ReactNode;
- required?: boolean;
-}
-
-const renderPropertyName = (el: SchemaElement): React.ReactNode => (
- <>
- {(() => {
- const treeSpaces = [];
- if (el.treeSpace) {
- for (let i = 0; i < el.treeSpace; i++) {
- treeSpaces.push( );
- }
- treeSpaces.push( );
- }
- return treeSpaces;
- })()}
- {el.schema.key}
-
- {el.required && (
-
- {(() => {
- const treeSpaces = [];
- if (el.treeSpace) {
- // el.treeSpace + 1 to account for the missing TreeLeaf
- for (let i = 0; i < el.treeSpace + 1; i++) {
- treeSpaces.push( );
- }
- }
- return treeSpaces;
- })()}
- required
-
- )}
- >
-);
-
-const renderPropertyDescription = (el: SchemaElement): React.ReactNode => {
- const enumElements = getEnumHTMLElements(el.schema);
- return (
-
- {el.schema.content.description && (
- {el.schema.content.description}
- )}
- {enumElements.length > 0 && Enum: {enumElements}}
- {el.schema.content.default && (
-
- Default: {el.schema.content.default}
-
- )}
- {el.schema.content.hasOwnProperty('const') && (
-
- Const:{' '}
-
- {typeof el.schema.content.const !== 'object'
- ? String(el.schema.content.const)
- : JSON.stringify(el.schema.content.const)}
-
-
- )}
-
- );
-};
-
-export const SchemaPropertiesComponent: React.FunctionComponent = ({
- name,
- hasDynamicName = false,
- properties,
- treeSpace,
- required = false,
-}) => {
- const alteredProperties = handleNotProperty(properties);
- const space = treeSpace + 1;
- const element: SchemaElement = {
- schema: {
- key: name,
- content: alteredProperties,
- },
- treeSpace,
- required,
- };
-
- return (
-
-
-
- {renderPropertyName(element)}
-
-
-
- {element.schema.content.type}
- {element.schema.content.anyOf
- ? ` ${element.schema.content.anyOf}`
- : ''}
- {element.schema.content.oneOf
- ? ` ${element.schema.content.oneOf}`
- : ''}
- {element.schema.content.items && element.schema.content.items.type
- ? ` (${element.schema.content.items.type})`
- : ''}
-
- {element.schema.content.format && (
-
- {element.schema.content.format}
-
- )}
- {element.schema.content.pattern && (
-
- must match {element.schema.content.pattern}
-
- )}
- {element.schema.content.uniqueItems && (
-
- Unique
-
- )}
- {typeof element.schema.content.minItems === 'number' && (
-
- >= {element.schema.content.minItems} items
-
- )}
- {typeof element.schema.content.maxItems === 'number' && (
-
- <= {element.schema.content.maxItems} items
-
- )}
- {typeof element.schema.content.minLength === 'number' && (
-
- length >= {element.schema.content.minLength}
-
- )}
- {typeof element.schema.content.maxLength === 'number' && (
-
- length <= {element.schema.content.maxLength}
-
- )}
- {typeof element.schema.content.minimum === 'number' && (
-
- >= {element.schema.content.minimum}
-
- )}
- {typeof element.schema.content.maximum === 'number' && (
-
- <= {element.schema.content.maximum}
-
- )}
- {element.schema.content.exclusiveMinimum && (
-
- > {element.schema.content.exclusiveMinimum}
-
- )}
- {element.schema.content.exclusiveMaximum && (
-
- < {element.schema.content.exclusiveMaximum}
-
- )}
- {renderPropertyDescription(element)}
- {element.schema.content.type === 'object' && (
-
- {(!element.schema.content.additionalProperties ||
- typeof element.schema.content.additionalProperties ===
- 'boolean') && (
-
- Additional properties are{' '}
- {element.schema.content.additionalProperties === false &&
- 'NOT'}{' '}
- allowed.
-
- )}
- {element.schema.content.additionalProperties &&
- typeof element.schema.content.additionalProperties ===
- 'object' && (
-
- Additional properties must adhere to the following schema.
-
- )}
-
- )}
- {element.schema.content.items && (
-
- {element.schema.content.items &&
- typeof element.schema.content.items === 'object' && (
-
- Array items must adhere to the following schema.
-
- )}
-
- )}
-
-
- {renderOf(space, alteredProperties.anyOf)}
- {renderOf(space, alteredProperties.oneOf)}
- {renderProperties(alteredProperties, space)}
- {renderAdditionalProperties(alteredProperties, space)}
- {renderItems(alteredProperties, space)}
-
- );
-};
diff --git a/library/src/containers/Schemas/Schemas.tsx b/library/src/containers/Schemas/Schemas.tsx
index ff9efdc43..844f3c592 100644
--- a/library/src/containers/Schemas/Schemas.tsx
+++ b/library/src/containers/Schemas/Schemas.tsx
@@ -1,58 +1,44 @@
import React from 'react';
-import { SchemaComponent } from './Schema';
+import { Schema } from './Schema';
-import { ExpandNestedConfig } from '../../config';
-import { bemClasses } from '../../helpers';
-import { Toggle } from '../../components';
-import { SCHEMAS_TEXT, CONTAINER_LABELS } from '../../constants';
-import { Schema } from '../../types';
+import { useConfig, useSpec } from '../../contexts';
+import { CommonHelpers } from '../../helpers';
+import { SCHEMAS_TEXT } from '../../constants';
-interface Props {
- schemas?: Record;
- expand?: ExpandNestedConfig;
-}
+export const Schemas: React.FunctionComponent = () => {
+ const asyncapi = useSpec();
+ const config = useConfig();
+ const schemas =
+ !asyncapi.components().isEmpty() &&
+ asyncapi
+ .components()
+ .schemas()
+ .all();
-export const SchemasComponent: React.FunctionComponent = ({
- schemas,
- expand,
-}) => {
- if (!schemas) {
+ if (!schemas || schemas.length === 0) {
return null;
}
- const className = CONTAINER_LABELS.SCHEMAS;
-
- const header = {SCHEMAS_TEXT}
;
-
- const content = (
-
- {Object.entries(schemas).map(([key, schema]) => (
- -
-
-
- ))}
-
- );
return (
-
- {content}
-
+
+ {SCHEMAS_TEXT}
+
+
+ {schemas.map(schema => (
+ -
+
+
+ ))}
+
);
};
diff --git a/library/src/containers/Servers/Flow.tsx b/library/src/containers/Servers/Flow.tsx
deleted file mode 100644
index a362cc8bc..000000000
--- a/library/src/containers/Servers/Flow.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-
-import { ServerSecurityFlowScopes } from './Scopes';
-
-import { bemClasses } from '../../helpers';
-import { Href } from '../../components';
-import { OAuthFlow, OAuthFlowsType } from '../../types';
-import { FLOWS_TEXTS } from '../../constants';
-
-interface Props {
- name: string;
- flow: OAuthFlow;
-}
-
-export const ServerSecurityFlow: React.FunctionComponent = ({
- name,
- flow,
-}) => (
-
-
- -
- {FLOWS_TEXTS.FLOW}:{OAuthFlowsType[name]}
-
- {flow.authorizationUrl && (
- -
- {FLOWS_TEXTS.AUTHORIZATION_URL}:
-
{flow.authorizationUrl}
-
- )}
- {flow.tokenUrl && (
- -
- {FLOWS_TEXTS.TOKEN_URL}:
-
{flow.tokenUrl}
-
- )}
- {flow.refreshUrl && (
- -
- {FLOWS_TEXTS.REFRESH_URL}:
-
{flow.refreshUrl}
-
- )}
- {flow.scopes && (
- -
- {FLOWS_TEXTS.SCOPES}:
-
-
- )}
-
-
-);
diff --git a/library/src/containers/Servers/Flows.tsx b/library/src/containers/Servers/Flows.tsx
deleted file mode 100644
index 6523784a2..000000000
--- a/library/src/containers/Servers/Flows.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React from 'react';
-
-import { ServerSecurityFlow } from './Flow';
-
-import { TableRow } from '../../components';
-import { bemClasses } from '../../helpers';
-import { OAuthFlows, OAuthFlow } from '../../types';
-
-interface Props {
- flows: OAuthFlows;
-}
-
-export const ServerSecurityFlows: React.FunctionComponent = ({
- flows,
-}) => {
- if (!Object.keys(flows).length) {
- return null;
- }
-
- const sortedFlows: OAuthFlows = Object.keys(flows)
- .sort()
- .reduce((accumulator, currentValue) => {
- accumulator[currentValue] = flows[currentValue];
- return accumulator;
- }, {});
-
- const nodes = Object.entries(sortedFlows).map(
- ([flowName, flow]: [string, OAuthFlow]) => (
-
-
-
- ),
- );
-
- const nestedTableCellClassName = bemClasses.modifier(`nested`, `table-cell`);
- const flowsTableCellClassName = bemClasses.element(
- `server-security-flows-table-cell`,
- );
- const className = bemClasses.concatenate([
- nestedTableCellClassName,
- flowsTableCellClassName,
- ]);
-
- const element = (
-
-
- {nodes}
-
-
- );
-
- return ;
-};
diff --git a/library/src/containers/Servers/Scopes.tsx b/library/src/containers/Servers/Scopes.tsx
deleted file mode 100644
index 99eaec8c2..000000000
--- a/library/src/containers/Servers/Scopes.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-
-import { bemClasses } from '../../helpers';
-
-interface Props {
- scopes: Record;
-}
-
-export const ServerSecurityFlowScopes: React.FunctionComponent = ({
- scopes,
-}) => (
-
- {Object.entries(scopes).map(([name, description]) => (
- -
-
- {name}
-
-
- ))}
-
-);
diff --git a/library/src/containers/Servers/Security.tsx b/library/src/containers/Servers/Security.tsx
index cd07e9e3d..2575c286b 100644
--- a/library/src/containers/Servers/Security.tsx
+++ b/library/src/containers/Servers/Security.tsx
@@ -1,77 +1,294 @@
import React from 'react';
-
-import { ServerSecurityItemComponent } from './SecurityItem';
-
-import { Table } from '../../components';
-import { bemClasses } from '../../helpers';
import {
- Components,
- SecurityRequirement,
- ExcludeNullable,
- SecurityScheme,
-} from '../../types';
-import { SECURITY_TEXT, SERVER_SECURITY_COLUMN_NAMES } from '../../constants';
+ OAuthFlowInterface,
+ SecurityRequirementsInterface,
+ SecuritySchemeInterface,
+} from '@asyncapi/parser';
+
+import { Href, Markdown } from '../../components';
+import { useSpec } from '../../contexts';
+import { ServerHelpers } from '../../helpers';
interface Props {
- requirements: SecurityRequirement[];
- schemes: ExcludeNullable;
- openAccordion?: boolean;
- identifier: string;
- dataIdentifier: string;
+ security: SecurityRequirementsInterface[];
+ protocol?: string;
+ header?: string;
}
-export const ServerSecurityComponent: React.FunctionComponent = ({
- requirements,
- schemes,
- identifier: id,
- dataIdentifier: dataId,
+export const Security: React.FunctionComponent = ({
+ security = [],
+ protocol = '',
+ header = 'Security',
}) => {
- const identifier = bemClasses.identifier([
- { id, toKebabCase: false },
- 'security',
- ]);
- const dataIdentifier = bemClasses.identifier([
- { id: dataId, toKebabCase: false },
- 'security',
- ]);
- const className = `server-security`;
-
- const rows: React.ReactNodeArray = requirements
- .map(requirement => {
- const def: SecurityScheme | undefined =
- schemes[Object.keys(requirement)[0]];
-
- if (!def) {
- return null;
- }
- return (
-
+ const asyncapi = useSpec();
+ const securitySchemes =
+ !asyncapi.components().isEmpty() && asyncapi.components().securitySchemes();
+
+ let renderedSecurities;
+ if (
+ !security ||
+ !security.length ||
+ !securitySchemes ||
+ !Object.keys(securitySchemes).length
+ ) {
+ if (protocol === 'kafka' || protocol === 'kafka-secure') {
+ renderedSecurities = (
+
);
- })
- .filter(Boolean);
+ }
+ } else {
+ const securities: React.ReactNodeArray = Object.values(security)
+ .map(requirement => {
+ const requirements = requirement.all();
+ const key = Object.keys(requirements)[0];
+ const def = securitySchemes[String(key)];
+ const requiredScopes: string[] = requirements[String(key)];
- if (!rows || !rows.length) {
+ if (!def) {
+ return null;
+ }
+ return (
+
+ );
+ })
+ .filter(Boolean);
+
+ renderedSecurities = (
+
+ {securities.map((s, idx) => (
+ -
+ {s}
+
+ ))}
+
+ );
+ }
+
+ if (!renderedSecurities) {
return null;
}
return (
-
-
- {SECURITY_TEXT}
-
-
-
+ {header}:
+ {renderedSecurities}
+
+ );
+};
+
+function collectSecuritySchemas(
+ securitySchema: SecuritySchemeInterface | null,
+ requiredScopes: string[] = [],
+): React.ReactNodeArray {
+ const schemas: React.ReactNodeArray = [];
+ if (securitySchema) {
+ if (securitySchema.name()) {
+ schemas.push(Name: {securitySchema.name()});
+ }
+ if (securitySchema.in()) {
+ schemas.push(In: {securitySchema.in()});
+ }
+ if (securitySchema.scheme()) {
+ schemas.push(Scheme: {securitySchema.scheme()});
+ }
+ if (securitySchema.bearerFormat()) {
+ schemas.push(Bearer format: {securitySchema.bearerFormat()});
+ }
+ if (securitySchema.openIdConnectUrl()) {
+ schemas.push(
+
- {rows}
-
+ Connect URL
+ ,
+ );
+ }
+ if (requiredScopes.length) {
+ schemas.push(Required scopes: {requiredScopes.join(', ')});
+ }
+ }
+ return schemas;
+}
+
+interface SecurityItemProps {
+ securitySchema: SecuritySchemeInterface | null;
+ protocol: string;
+ requiredScopes?: string[];
+}
+
+const SecurityItem: React.FunctionComponent = ({
+ securitySchema,
+ protocol,
+ requiredScopes,
+}) => {
+ const schemas: React.ReactNodeArray = collectSecuritySchemas(
+ securitySchema,
+ requiredScopes,
+ );
+
+ let renderedKafkaSecurity;
+ if (['kafka', 'kafka-secure'].includes(protocol)) {
+ const { securityProtocol, saslMechanism } = ServerHelpers.getKafkaSecurity(
+ protocol,
+ securitySchema,
+ );
+
+ renderedKafkaSecurity = (
+
+ {securityProtocol && (
+
+
+ security.protocol:
+
+
+ {securityProtocol}
+
+
+ )}
+ {saslMechanism && (
+
+
+ sasl.mechanism:
+
+
+ {saslMechanism}
+
+
+ )}
-
+ );
+ }
+
+ const flows = securitySchema?.flows();
+ const unwrappedFlows: Record = {};
+ if (flows?.hasImplicit()) {
+ unwrappedFlows.implicit = flows.implicit() as OAuthFlowInterface;
+ }
+ if (flows?.hasAuthorizationCode()) {
+ unwrappedFlows.authorizationCode = flows.authorizationCode() as OAuthFlowInterface;
+ }
+ if (flows?.hasClientCredentials()) {
+ unwrappedFlows.clientCredentials = flows.clientCredentials() as OAuthFlowInterface;
+ }
+ if (flows?.hasPassword()) {
+ unwrappedFlows.password = flows.implicit() as OAuthFlowInterface;
+ }
+ const renderedFlows = Object.entries(unwrappedFlows).map(
+ ([flowName, flow]) => {
+ const authorizationUrl = flow.authorizationUrl();
+ const tokenUrl = flow.tokenUrl();
+ const refreshUrl = flow.refreshUrl();
+ const scopes = flow.scopes();
+
+ return (
+
+
+
+ Flow:
+
+
+ {ServerHelpers.flowName(flowName)}
+
+
+
+ {authorizationUrl && (
+
+
+ Auth URL:
+
+
+ {authorizationUrl}
+
+
+ )}
+ {tokenUrl && (
+
+
+ Token URL:
+
+
+ {tokenUrl}
+
+
+ )}
+ {refreshUrl && (
+
+
+ Refresh URL:
+
+
+ {refreshUrl}
+
+
+ )}
+ {scopes && (
+
+
+ Scopes:
+
+
+ {scopes &&
+ Object.entries(scopes).map(([scopeName, scopeDesc]) => (
+ -
+ {scopeName}
+
+ ))}
+
+
+ )}
+
+ );
+ },
+ );
+
+ return (
+
+ {securitySchema && schemas && (
+
+
+ {ServerHelpers.securityType(securitySchema.type())}
+ {schemas.length > 0 && (
+
+ {schemas.map((schema, idx) => (
+ -
+ {schema}
+
+ ))}
+
+ )}
+
+
+ )}
+
+ {securitySchema && securitySchema.hasDescription() && (
+
+ {securitySchema.description()}
+
+ )}
+
+ {renderedFlows && renderedFlows.length > 0 && (
+
+ - {renderedFlows}
+
+ )}
+
+ {renderedKafkaSecurity && {renderedKafkaSecurity}}
+
);
};
diff --git a/library/src/containers/Servers/SecurityItem.tsx b/library/src/containers/Servers/SecurityItem.tsx
deleted file mode 100644
index de43b3b8c..000000000
--- a/library/src/containers/Servers/SecurityItem.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-
-import { ServerSecurityFlows } from './Flows';
-
-import { SecurityScheme } from '../../types';
-import { Markdown, TableAccessor, TableRow } from '../../components';
-import { bemClasses } from '../../helpers';
-
-const securitySchemeAccessors: Array> = [
- el => {el.type},
- el => {el.bearerFormat},
- el => {el.in},
- el => {el.scheme},
- el => {el.name},
- el => el.description && {el.description} ,
-];
-
-interface Props {
- securityScheme: SecurityScheme;
-}
-
-export const ServerSecurityItemComponent: React.FunctionComponent = ({
- securityScheme,
-}) => (
- <>
-
- {securityScheme.flows && (
-
- )}
- >
-);
diff --git a/library/src/containers/Servers/Server.tsx b/library/src/containers/Servers/Server.tsx
index b0cee1410..97c162357 100644
--- a/library/src/containers/Servers/Server.tsx
+++ b/library/src/containers/Servers/Server.tsx
@@ -1,120 +1,102 @@
import React from 'react';
+import { ServerInterface } from '@asyncapi/parser';
-import { ServerVariablesComponent } from './Variables';
-import { ServerSecurityComponent } from './Security';
-import { BindingsComponent } from '../Bindings/Bindings';
+import { Security } from './Security';
+import { Markdown, Schema, Bindings, Tags, Extensions } from '../../components';
-import { bemClasses, removeSpecialChars } from '../../helpers';
-import { Toggle, Markdown } from '../../components';
-import { Server, SecurityScheme } from '../../types';
-import {
- ITEM_LABELS,
- CONTAINER_LABELS,
- SERVER_BINDINGS_TEXT,
-} from '../../constants';
+import { useConfig } from '../../contexts';
+import { CommonHelpers, SchemaHelpers } from '../../helpers';
interface Props {
- server: Server;
- stage: string;
- securitySchemes?: Record;
- toggleExpand?: boolean;
+ serverName: string;
+ server: ServerInterface;
}
-export const ServerComponent: React.FunctionComponent = ({
+export const Server: React.FunctionComponent = ({
+ serverName,
server,
- stage,
- securitySchemes,
- toggleExpand = false,
}) => {
- const className = ITEM_LABELS.SERVER;
+ const config = useConfig();
- const variables = server.variables
- ? Object.entries(server.variables).map(([key, variable]) => ({
- key,
- content: variable,
- }))
- : [];
+ if (!server) {
+ return null;
+ }
- const header = (
- <>
-
- {`${server.protocol}${
- server.protocolVersion ? ` ${server.protocolVersion}` : ``
- }`}
-
- {stage}
-
- {server.url}
-
- >
+ const urlVariables = SchemaHelpers.serverVariablesToSchema(
+ server.variables(),
);
+ const protocolVersion = server.protocolVersion();
+ const security = server.security();
- const identifier = bemClasses.identifier([
- CONTAINER_LABELS.SERVERS,
- server.url,
- ]);
- const dataIdentifier = bemClasses.identifier([
- CONTAINER_LABELS.SERVERS,
- removeSpecialChars(server.url),
- ]);
- const content = (
- <>
- {server.description && (
-
- {server.description}
-
- )}
-
- {server.security && securitySchemes && (
-
- )}
- {server.bindings && (
-
- )}
- >
- );
+ return (
+
+
+
+
+ {server.url()}
+
+ {protocolVersion
+ ? `${server.protocol()} ${protocolVersion}`
+ : server.protocol()}
+
+
+ {serverName}
+
+
- const body =
- (server.description || server.security || server.variables) && content;
+ {server.hasDescription() && (
+
+ {server.description()}
+
+ )}
- return (
-
-
- {body}
-
-
+ {urlVariables && (
+
+
+
+ )}
+
+ {
+
+
+
+ }
+
+ {server.bindings() && (
+
+
+
+ )}
+
+
+
+ {server.tags().length > 0 && (
+
+
+
+ )}
+
+
+
+
+
);
};
diff --git a/library/src/containers/Servers/Servers.tsx b/library/src/containers/Servers/Servers.tsx
index 7323c4921..b0a6f5923 100644
--- a/library/src/containers/Servers/Servers.tsx
+++ b/library/src/containers/Servers/Servers.tsx
@@ -1,64 +1,50 @@
import React from 'react';
-import { ServerComponent } from './Server';
+import { Server } from './Server';
-import { ExpandNestedConfig } from '../../config';
-import { bemClasses } from '../../helpers';
-import { Servers, SecurityScheme } from '../../types';
-import { Toggle } from '../../components';
-import { SERVERS, CONTAINER_LABELS } from '../../constants';
+import { useConfig, useSpec } from '../../contexts';
+import { CommonHelpers } from '../../helpers';
+import { SERVERS_TEXT } from '../../constants';
-interface Props {
- servers?: Servers;
- securitySchemes?: Record;
- expand?: ExpandNestedConfig;
-}
+export const Servers: React.FunctionComponent = () => {
+ const servers = useSpec()
+ .servers()
+ .all();
+ const config = useConfig();
-export const ServersComponent: React.FunctionComponent = ({
- servers,
- securitySchemes,
- expand,
-}) => {
- if (!servers) {
+ if (!servers.length) {
return null;
}
- const className = CONTAINER_LABELS.SERVERS;
-
- const header = {SERVERS}
;
-
- const content = (
-
- {Object.entries(servers).map(([stage, server]) => (
- -
-
-
- ))}
-
- );
return (
-
- {content}
-
+
+ {SERVERS_TEXT}
+
+
+ {servers.map(server => {
+ const serverName = server.id();
+ return (
+ -
+
+
+ );
+ })}
+
);
};
diff --git a/library/src/containers/Servers/Variables.tsx b/library/src/containers/Servers/Variables.tsx
deleted file mode 100644
index 26b37c818..000000000
--- a/library/src/containers/Servers/Variables.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React from 'react';
-
-import { bemClasses } from '../../helpers';
-import { ServerVariable, TypeWithKey } from '../../types';
-import {
- Markdown,
- Table,
- TableRowProps,
- TableAccessor,
-} from '../../components';
-import {
- URL_VARIABLES_TEXT,
- NONE_TEXT,
- ANY_TEXT,
- SERVER_COLUMN_NAMES,
-} from '../../constants';
-
-type ServerVariableWithKey = TypeWithKey;
-
-const serverVariablesAccessors: Array> = [
- el => {el.key},
- el =>
- el.content.default ? (
- {el.content.default}
- ) : (
- {NONE_TEXT}
- ),
- el =>
- el.content.enum ? (
-
- {el.content.enum.map(value => (
- -
- {value}
-
- ))}
-
- ) : (
- {ANY_TEXT}
- ),
- el => el.content.description && {el.content.description} ,
-];
-
-interface Props {
- variables: ServerVariableWithKey[];
- identifier: string;
- dataIdentifier: string;
-}
-
-export const ServerVariablesComponent: React.FunctionComponent = ({
- variables,
- identifier: id,
- dataIdentifier: dataId,
-}) => {
- if (!variables.length) {
- return null;
- }
- const className = `server-variables`;
- const rows: TableRowProps[] = variables.map(variable => ({
- key: variable.key,
- accessors: serverVariablesAccessors,
- element: variable,
- }));
- const identifier = bemClasses.identifier([
- { id, toKebabCase: false },
- 'url-variables',
- ]);
- const dataIdentifier = bemClasses.identifier([
- { id: dataId, toKebabCase: false },
- 'url-variables',
- ]);
-
- return (
-
-
- {URL_VARIABLES_TEXT}
-
-
-
-
-
- );
-};
diff --git a/library/src/containers/Sidebar/Sidebar.tsx b/library/src/containers/Sidebar/Sidebar.tsx
new file mode 100644
index 000000000..92b3d2df8
--- /dev/null
+++ b/library/src/containers/Sidebar/Sidebar.tsx
@@ -0,0 +1,452 @@
+import React, { useState, useContext } from 'react';
+
+import { CollapseButton } from '../../components';
+import { useConfig, useSpec } from '../../contexts';
+import {
+ PUBLISH_LABEL_DEFAULT_TEXT,
+ SUBSCRIBE_LABEL_DEFAULT_TEXT,
+} from '../../constants';
+
+const SidebarContext = React.createContext<{
+ setShowSidebar: React.Dispatch>;
+}>({
+ setShowSidebar: (value: boolean | ((prevValue: boolean) => boolean)) => value,
+});
+
+export const Sidebar: React.FunctionComponent = () => {
+ const [showSidebar, setShowSidebar] = useState(false);
+ const asyncapi = useSpec();
+
+ const info = asyncapi.info();
+ const logo = info
+ .extensions()
+ .get('x-logo')
+ ?.value();
+ const components = asyncapi.components();
+ const messages = components?.messages().all();
+ const schemas = components?.schemas().all();
+ const hasOperations = asyncapi.operations().length > 0;
+
+ const messagesList = messages?.length > 0 && (
+
+ setShowSidebar(false)}
+ >
+ Messages
+
+
+ {messages.map(message => (
+ -
+ setShowSidebar(false)}
+ >
+ {message.id()}
+
+
+ ))}
+
+
+ );
+
+ const schemasList = schemas?.length > 0 && (
+
+ setShowSidebar(false)}
+ >
+ Schemas
+
+
+ {schemas.map(schema => (
+ -
+ setShowSidebar(false)}
+ >
+ {schema.id()}
+
+
+ ))}
+
+
+ );
+
+ return (
+
+ setShowSidebar(prev => !prev)}
+ data-lol={showSidebar}
+ >
+
+
+
+
+
+
+ {logo ? (
+
+ ) : (
+
+ {info.title()} {info.version()}
+
+ )}
+
+
+
+ -
+ setShowSidebar(false)}
+ >
+ Introduction
+
+
+ {asyncapi.servers().length > 0 && (
+ -
+ setShowSidebar(false)}
+ >
+ Servers
+
+
+
+ )}
+ {hasOperations && (
+ <>
+ -
+ setShowSidebar(false)}
+ >
+ Operations
+
+
+
+ {messagesList}
+ {schemasList}
+ >
+ )}
+
+
+
+
+
+ );
+};
+
+interface TagObject {
+ name: string;
+ object: { tags?: () => Array<{ name: () => string }> };
+ data: T;
+}
+
+function filterObjectsByTags(
+ tags: string[],
+ objects: Array>,
+): { tagged: Map; untagged: TagObject[] } {
+ const taggedObjects = new Set();
+ const tagged = new Map();
+
+ tags.forEach(tag => {
+ const taggedForTag: TagObject[] = [];
+ objects.forEach(obj => {
+ const object = obj.object;
+ if (typeof object.tags !== 'function') {
+ return;
+ }
+
+ const objectTags = (object.tags() || []).map(t => t.name());
+ const hasTag = objectTags.includes(tag);
+ if (hasTag) {
+ taggedForTag.push(obj);
+ taggedObjects.add(obj);
+ }
+ });
+ tagged.set(tag, taggedForTag);
+ });
+
+ const untagged: TagObject[] = [];
+ objects.forEach(obj => {
+ if (!taggedObjects.has(obj)) {
+ untagged.push(obj);
+ }
+ });
+
+ return { tagged, untagged };
+}
+
+const ServersList: React.FunctionComponent = () => {
+ const sidebarConfig = useConfig().sidebar;
+ const asyncapi = useSpec();
+ const servers = asyncapi.servers().all();
+ const showServers = sidebarConfig?.showServers || 'byDefault';
+
+ if (showServers === 'byDefault') {
+ return (
+
+ {servers.map(server => (
+
+ ))}
+
+ );
+ }
+
+ let specTagNames: string[];
+ if (showServers === 'bySpecTags') {
+ specTagNames = (asyncapi.info().tags() || []).map(tag => tag.name());
+ } else {
+ const serverTagNamesSet = new Set();
+ servers.forEach(server => {
+ server.tags().forEach(t => serverTagNamesSet.add(t.name()));
+ });
+ specTagNames = Array.from(serverTagNamesSet);
+ }
+
+ const serializedServers: TagObject[] = servers.map(server => ({
+ name: server.id(),
+ object: server,
+ data: {},
+ }));
+ const { tagged, untagged } = filterObjectsByTags(
+ specTagNames,
+ serializedServers,
+ );
+ return (
+
+ {Array.from(tagged.entries()).map(([tag, taggedServers]) => (
+ -
+
+ {taggedServers.map(({ name: serverName }) => (
+
+ ))}
+
+
+ ))}
+ {untagged.length > 0 ? (
+ -
+
+ {untagged.map(({ name: serverName }) => (
+
+ ))}
+
+
+ ) : null}
+
+ );
+};
+
+const OperationsList: React.FunctionComponent = () => {
+ const sidebarConfig = useConfig().sidebar;
+ const asyncapi = useSpec();
+ const operations = asyncapi.operations().all();
+ const showOperations = sidebarConfig?.showOperations || 'byDefault';
+
+ const processedOperations: Array> = [];
+ operations.forEach(operation => {
+ const operationChannel = operation.channels();
+ const operationChannels = operationChannel.all();
+ const channelAddress = operationChannels[0]?.address();
+ if (operation.isSend()) {
+ processedOperations.push({
+ name: `publish-${operation.id()}`,
+ object: operation,
+ data: {
+ channelName: channelAddress || '',
+ kind: 'publish',
+ summary: operation.summary() || '',
+ },
+ });
+ }
+ if (operation.isReceive()) {
+ processedOperations.push({
+ name: `subscribe-${operation.id()}`,
+ object: operation,
+ data: {
+ channelName: channelAddress || '',
+ kind: 'subscribe',
+ summary: operation.summary() || '',
+ },
+ });
+ }
+ });
+
+ if (showOperations === 'byDefault') {
+ return (
+
+ {processedOperations.map(({ name, data }) => (
+
+ ))}
+
+ );
+ }
+
+ let operationTagNames: string[];
+ if (showOperations === 'bySpecTags') {
+ operationTagNames = (asyncapi.info().tags() || []).map(tag => tag.name());
+ } else {
+ const operationTagNamesSet = new Set();
+ operations.forEach(operation => {
+ operation.tags().forEach(t => operationTagNamesSet.add(t.name()));
+ });
+ operationTagNames = Array.from(operationTagNamesSet);
+ }
+
+ const { tagged, untagged } = filterObjectsByTags(
+ operationTagNames,
+ processedOperations,
+ );
+ return (
+
+ {Array.from(tagged.entries()).map(([tag, taggedOperations]) => (
+ -
+
+ {taggedOperations.map(({ name, data }) => (
+
+ ))}
+
+
+ ))}
+ {untagged.length > 0 ? (
+ -
+
+ {untagged.map(({ name, data }) => (
+
+ ))}
+
+
+ ) : null}
+
+ );
+};
+
+interface OperationItemProps {
+ channelName: string;
+ summary: string;
+ kind: 'publish' | 'subscribe';
+}
+
+const OperationItem: React.FunctionComponent = ({
+ channelName,
+ summary,
+ kind,
+}) => {
+ const config = useConfig();
+ const { setShowSidebar } = useContext(SidebarContext);
+
+ const isPublish = kind === 'publish';
+ let label: string = '';
+ if (isPublish) {
+ label = config.publishLabel || PUBLISH_LABEL_DEFAULT_TEXT;
+ } else {
+ label = config.subscribeLabel || SUBSCRIBE_LABEL_DEFAULT_TEXT;
+ }
+
+ return (
+
+ setShowSidebar(false)}
+ >
+
+ {label}
+
+ {summary || channelName}
+
+
+ );
+};
+
+interface ServerItemProps {
+ serverName: string;
+}
+
+const ServerItem: React.FunctionComponent = ({
+ serverName,
+}) => {
+ const { setShowSidebar } = useContext(SidebarContext);
+
+ return (
+
+ setShowSidebar(false)}
+ >
+ {serverName}
+
+
+ );
+};
+
+interface ItemsByTagItemProps {
+ tagName: string;
+}
+
+const ItemsByTagItem: React.FunctionComponent = ({
+ tagName,
+ children,
+}) => {
+ const [expand, setExpand] = useState(false);
+
+ return (
+
+ setExpand(prev => !prev)}
+ chevronProps={{
+ className: expand ? '-rotate-180' : '-rotate-90',
+ }}
+ >
+
+ {tagName}
+
+
+
+ {children}
+
+
+ );
+};
diff --git a/library/src/contexts/index.ts b/library/src/contexts/index.ts
new file mode 100644
index 000000000..327a5632b
--- /dev/null
+++ b/library/src/contexts/index.ts
@@ -0,0 +1,2 @@
+export * from './useConfig';
+export * from './useSpec';
diff --git a/library/src/contexts/useConfig.ts b/library/src/contexts/useConfig.ts
new file mode 100644
index 000000000..d9f1a2bc2
--- /dev/null
+++ b/library/src/contexts/useConfig.ts
@@ -0,0 +1,8 @@
+import { createContext, useContext } from 'react';
+import { ConfigInterface } from '../config';
+
+export const ConfigContext = createContext({});
+
+export function useConfig() {
+ return useContext(ConfigContext);
+}
diff --git a/library/src/contexts/useSpec.ts b/library/src/contexts/useSpec.ts
new file mode 100644
index 000000000..6af268243
--- /dev/null
+++ b/library/src/contexts/useSpec.ts
@@ -0,0 +1,10 @@
+import React, { useContext } from 'react';
+import { AsyncAPIDocumentInterface } from '@asyncapi/parser';
+
+export const SpecificationContext = React.createContext<
+ AsyncAPIDocumentInterface
+>(null as any);
+
+export function useSpec() {
+ return useContext(SpecificationContext);
+}
diff --git a/library/src/helpers/__tests__/bemClasses.test.ts b/library/src/helpers/__tests__/bemClasses.test.ts
deleted file mode 100644
index a3a0f250f..000000000
--- a/library/src/helpers/__tests__/bemClasses.test.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { CSS_PREFIX } from '../../constants';
-import { Identifier } from '../../types';
-import { bemClasses } from '../bemClasses';
-
-describe('bemClasses', () => {
- describe('element', () => {
- test('should be equal', () => {
- const element = `foo-bar`;
- const expectedClassName = `${CSS_PREFIX}__${element}`;
-
- expect(bemClasses.element(element)).toBe(expectedClassName);
- });
-
- test('should be empty', () => {
- const element = ``;
- const expectedClassName = ``;
-
- expect(bemClasses.element(element)).toBe(expectedClassName);
- });
- });
-
- describe('modifier', () => {
- test('should be equal with element', () => {
- const modifier = `modifier`;
- const element = `element`;
- const expectedClassName = `${CSS_PREFIX}__${element}--${modifier}`;
-
- expect(bemClasses.modifier(modifier, element)).toBe(expectedClassName);
- });
-
- test('should be equal without element', () => {
- const modifier = `modifier`;
- const expectedClassName = `${CSS_PREFIX}--${modifier}`;
-
- expect(bemClasses.modifier(modifier)).toBe(expectedClassName);
- });
-
- test('should be empty', () => {
- const modifier = ``;
- const expectedClassName = ``;
-
- expect(bemClasses.modifier(modifier)).toBe(expectedClassName);
- });
- });
-
- describe('concatenate', () => {
- test('should return empty string if input is empty array', () => {
- const arr: string[] = [];
- const expectedClassName = ``;
-
- expect(bemClasses.concatenate(arr)).toBe(expectedClassName);
- });
-
- test('should return empty string if input is empty array', () => {
- const arr: string[] = [`foo`, `bar`];
- const expectedClassName = `foo bar`;
-
- expect(bemClasses.concatenate(arr)).toBe(expectedClassName);
- });
-
- test('should filter false values from input array', () => {
- const arr: string[] = [`foo`, `bar`, ``];
- const expectedClassName = `foo bar`;
-
- expect(bemClasses.concatenate(arr)).toBe(expectedClassName);
- });
- });
-
- describe('identifier', () => {
- test('should be equal', () => {
- const elements: Array = [
- { id: `${CSS_PREFIX}--foo`, toKebabCase: false },
- { id: 'fooBar', toKebabCase: true },
- '',
- 'bar',
- ];
- const expectedID = `${CSS_PREFIX}--foo--foo-bar--bar`;
-
- expect(bemClasses.identifier(elements)).toBe(expectedID);
- });
- });
-});
diff --git a/library/src/helpers/__tests__/common.test.ts b/library/src/helpers/__tests__/common.test.ts
new file mode 100644
index 000000000..3049195c7
--- /dev/null
+++ b/library/src/helpers/__tests__/common.test.ts
@@ -0,0 +1,17 @@
+import { CommonHelpers } from '../common';
+
+describe('CommonHelpers', () => {
+ describe('.getIdentifier', () => {
+ test('should return identifier without config argument', () => {
+ const result = CommonHelpers.getIdentifier('test');
+ expect(result).toEqual(`test`);
+ });
+
+ test('should return identifier without config argument', () => {
+ const result = CommonHelpers.getIdentifier('test', {
+ schemaID: 'prefix-id',
+ });
+ expect(result).toEqual(`prefix-id-test`);
+ });
+ });
+});
diff --git a/library/src/helpers/__tests__/createNestedClassName.test.ts b/library/src/helpers/__tests__/createNestedClassName.test.ts
deleted file mode 100644
index bdf8faff4..000000000
--- a/library/src/helpers/__tests__/createNestedClassName.test.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { CSS_PREFIX } from '../../constants';
-import { createNestedClassName } from '../createNestedClassName';
-
-describe('createNestedClassName', () => {
- test('should create className with nested modifier', () => {
- const element = `foo-bar`;
- const expectedClassName = `${CSS_PREFIX}__${element}--nested`;
-
- expect(createNestedClassName(element, true)).toBe(expectedClassName);
- });
-
- test('should create className without nested suffix', () => {
- const element = `foo-bar`;
- const expectedClassName = `${CSS_PREFIX}__${element}`;
-
- expect(createNestedClassName(element, false)).toBe(expectedClassName);
- });
-});
diff --git a/library/src/helpers/__tests__/extractHashData.test.ts b/library/src/helpers/__tests__/extractHashData.test.ts
deleted file mode 100644
index dafa4357f..000000000
--- a/library/src/helpers/__tests__/extractHashData.test.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { CSS_PREFIX, CONTAINER_LABELS } from '../../constants';
-import { extractHashData, HashData } from '../extractHashData';
-
-describe('extractHashData', () => {
- const schema = CSS_PREFIX;
- const container = CONTAINER_LABELS.CHANNELS;
- const itemName = `foo-bar`;
- const property = `parameters`;
-
- test('extract only container and schema', () => {
- const hash = `${schema}--${container}`;
- const expectedResult: HashData = {
- schema,
- container,
- };
-
- expect(extractHashData(hash)).toStrictEqual(expectedResult);
- });
-
- test('extract whole parameters', () => {
- const hash = `${schema}--${container}--${itemName}`;
- const expectedResult: HashData = {
- schema,
- container,
- item: itemName,
- };
-
- expect(extractHashData(hash)).toStrictEqual(expectedResult);
- });
-
- test('extract with additional property of item', () => {
- const hash = `${schema}--${container}--${itemName}--${property}`;
- const expectedResult: HashData = {
- schema,
- container,
- item: itemName,
- };
-
- expect(extractHashData(hash)).toStrictEqual(expectedResult);
- });
-
- test('extract non default schema', () => {
- const nonDefaultSchema = `non-default-schema`;
- const hash = `${nonDefaultSchema}--${container}--${itemName}--${property}`;
- const expectedResult: HashData = {
- schema: nonDefaultSchema,
- container,
- item: itemName,
- };
-
- expect(extractHashData(hash)).toStrictEqual(expectedResult);
- });
-
- test('remove hash char in schema', () => {
- const hash = `#${schema}--${container}--${itemName}--${property}`;
- const expectedResult: HashData = {
- schema,
- container,
- item: itemName,
- };
-
- expect(extractHashData(hash)).toStrictEqual(expectedResult);
- });
-});
diff --git a/library/src/helpers/__tests__/generateExample.test.ts b/library/src/helpers/__tests__/generateExample.test.ts
deleted file mode 100644
index b49597acf..000000000
--- a/library/src/helpers/__tests__/generateExample.test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { generateExampleSchema } from '../generateExampleSchema';
-
-describe('generateExampleSchema', () => {
- test('should return null by default', () => {
- const result = generateExampleSchema({});
- expect(result).toEqual(null);
- });
-
- test('should instantiate all properties', () => {
- const result = generateExampleSchema({
- properties: {
- a: { type: 'string' },
- b: { type: 'integer' },
- },
- });
- expect(result).toEqual({
- a: 'string',
- b: 0,
- });
- });
-});
diff --git a/library/src/helpers/__tests__/getExamplesFromSpec.test.ts b/library/src/helpers/__tests__/getExamplesFromSpec.test.ts
deleted file mode 100644
index d7c88ef5c..000000000
--- a/library/src/helpers/__tests__/getExamplesFromSpec.test.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { getExamplesFromSpec } from '../getExamplesFromSpec';
-
-describe('getExamplesFromSpec', () => {
- test('should return headers', () => {
- const result = getExamplesFromSpec(
- [
- {
- headers: {
- header1: 1,
- },
- payload: {
- prop1: 1,
- },
- },
- {
- headers: {
- header2: 2,
- },
- payload: {
- prop2: 2,
- },
- },
- ],
- 'headers',
- );
- expect(result).toEqual([
- {
- header1: 1,
- },
- {
- header2: 2,
- },
- ]);
- });
-
- test('should return payload', () => {
- const result = getExamplesFromSpec(
- [
- {
- headers: {
- header1: 1,
- },
- payload: {
- prop1: 1,
- },
- },
- {
- headers: {
- header2: 2,
- },
- payload: {
- prop2: 2,
- },
- },
- ],
- 'payload',
- );
- expect(result).toEqual([
- {
- prop1: 1,
- },
- {
- prop2: 2,
- },
- ]);
- });
-
- test('should return payload and no undefined', () => {
- const result = getExamplesFromSpec(
- [
- {
- headers: {
- header1: 1,
- },
- payload: {
- prop1: 1,
- },
- },
- {
- headers: {
- header2: 2,
- },
- },
- ],
- 'payload',
- );
- expect(result).toEqual([
- {
- prop1: 1,
- },
- ]);
- });
-});
diff --git a/library/src/helpers/__tests__/inContainer.test.ts b/library/src/helpers/__tests__/inContainer.test.ts
deleted file mode 100644
index 8053f4d43..000000000
--- a/library/src/helpers/__tests__/inContainer.test.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { CONTAINER_LABELS, ITEM_LABELS } from '../../constants';
-import { inContainer } from '../inContainer';
-
-describe('inContainer', () => {
- test('should be equal', () => {
- expect(inContainer(ITEM_LABELS.CHANNEL)).toBe(CONTAINER_LABELS.CHANNELS);
- });
-
- test('should be empty', () => {
- expect(inContainer('' as ITEM_LABELS)).toBe('');
- });
-});
diff --git a/library/src/helpers/__tests__/marked.test.ts b/library/src/helpers/__tests__/marked.test.ts
new file mode 100644
index 000000000..2c9a9b4cf
--- /dev/null
+++ b/library/src/helpers/__tests__/marked.test.ts
@@ -0,0 +1,32 @@
+import { renderMarkdown } from '../marked';
+
+describe('marked', () => {
+ test('should render simple markdown', () => {
+ const result = renderMarkdown('text');
+ expect(result).toEqual(`text
\n`);
+ });
+
+ test('should render complex markdown', () => {
+ const result = renderMarkdown(`
+# heading
+
+paragraph
+
+**bold**
+`);
+ expect(result).toEqual(
+ `heading
\nparagraph
\nbold
\n`,
+ );
+ });
+
+ test('should render code blocks with highlights', () => {
+ const result = renderMarkdown(`
+\`\`\`json
+{"foo": "bar"}
+\`\`\`
+`);
+ expect(result).toEqual(
+ `{"foo": "bar"}\n
\n`,
+ );
+ });
+});
diff --git a/library/src/helpers/__tests__/message.test.ts b/library/src/helpers/__tests__/message.test.ts
new file mode 100644
index 000000000..c0b5ece8a
--- /dev/null
+++ b/library/src/helpers/__tests__/message.test.ts
@@ -0,0 +1,302 @@
+import { MessageHelpers } from '../message';
+import { MessageV2 as Message } from '@asyncapi/parser';
+
+describe('MessageHelpers', () => {
+ describe('.generateExample', () => {
+ test('should return empty string by default', () => {
+ const result = MessageHelpers.generateExample({});
+ expect(result).toEqual('');
+ });
+
+ test('should instantiate all properties', () => {
+ const result = MessageHelpers.generateExample({
+ properties: {
+ a: { type: 'string' },
+ b: { type: 'integer' },
+ },
+ });
+ expect(result).toEqual({
+ a: 'string',
+ b: 0,
+ });
+ });
+ });
+
+ describe('.sanitizeExample', () => {
+ test('should sanitize example', () => {
+ const result = MessageHelpers.sanitizeExample({
+ properties: {
+ a: { type: 'string' },
+ b: { type: 'integer', 'x-schema-private-foo': true },
+ },
+ 'x-parser-foo': true,
+ });
+ expect(result).toEqual({
+ properties: {
+ a: { type: 'string' },
+ b: { type: 'integer' },
+ },
+ });
+ });
+ });
+
+ describe('.getPayloadExamples', () => {
+ test('should return epmty examples', () => {
+ const result = MessageHelpers.getPayloadExamples(
+ new Message({
+ examples: [
+ {
+ name: 'example name',
+ summary: 'example summary',
+ headers: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ headers: { bar: 'foo' },
+ },
+ ],
+ }),
+ );
+ expect(result).toEqual(undefined);
+ });
+
+ test('should return payload examples', () => {
+ const result = MessageHelpers.getPayloadExamples(
+ new Message({
+ examples: [
+ {
+ name: 'example name',
+ summary: 'example summary',
+ payload: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ payload: { bar: 'foo' },
+ },
+ ],
+ }),
+ );
+ expect(result).toEqual([
+ {
+ name: 'example name',
+ summary: 'example summary',
+ example: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ example: { bar: 'foo' },
+ },
+ ]);
+ });
+
+ test('should return examples from payload schema', () => {
+ const result = MessageHelpers.getPayloadExamples(
+ new Message({
+ payload: {
+ examples: [{ foo: 'bar' }, { bar: 'foo' }],
+ },
+ }),
+ );
+ expect(result).toEqual([
+ {
+ example: { foo: 'bar' },
+ },
+ {
+ example: { bar: 'foo' },
+ },
+ ]);
+ });
+
+ test('should return examples from payload schema - case when only headers examples are defined in `examples` field', () => {
+ const result = MessageHelpers.getPayloadExamples(
+ new Message({
+ payload: {
+ examples: [{ foo: 'bar' }, { bar: 'foo' }],
+ },
+ examples: [
+ {
+ name: 'example name',
+ summary: 'example summary',
+ headers: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ headers: { bar: 'foo' },
+ },
+ ],
+ }),
+ );
+ expect(result).toEqual([
+ {
+ example: { foo: 'bar' },
+ },
+ {
+ example: { bar: 'foo' },
+ },
+ ]);
+ });
+
+ test('should return examples for payload - case when at least one item in `examples` array has `payload` field with existing `payload.examples`', () => {
+ const result = MessageHelpers.getPayloadExamples(
+ new Message({
+ payload: {
+ examples: [{ foo: 'bar' }, { bar: 'foo' }],
+ },
+ examples: [
+ {
+ name: 'example name',
+ summary: 'example summary',
+ headers: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ payload: { bar: 'foo' },
+ },
+ ],
+ }),
+ );
+ expect(result).toEqual([
+ {
+ name: 'example name',
+ summary: 'example summary',
+ example: { bar: 'foo' },
+ },
+ ]);
+ });
+ });
+
+ describe('.getHeadersExamples', () => {
+ test('should return epmty examples', () => {
+ const result = MessageHelpers.getHeadersExamples(
+ new Message({
+ examples: [
+ {
+ name: 'example name',
+ summary: 'example summary',
+ payload: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ payload: { bar: 'foo' },
+ },
+ ],
+ }),
+ );
+ expect(result).toEqual(undefined);
+ });
+
+ test('should return headers examples', () => {
+ const result = MessageHelpers.getHeadersExamples(
+ new Message({
+ examples: [
+ {
+ name: 'example name',
+ summary: 'example summary',
+ headers: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ headers: { bar: 'foo' },
+ },
+ ],
+ }),
+ );
+ expect(result).toEqual([
+ {
+ name: 'example name',
+ summary: 'example summary',
+ example: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ example: { bar: 'foo' },
+ },
+ ]);
+ });
+
+ test('should return examples from headers schema', () => {
+ const result = MessageHelpers.getHeadersExamples(
+ new Message({
+ headers: {
+ examples: [{ foo: 'bar' }, { bar: 'foo' }],
+ },
+ }),
+ );
+ expect(result).toEqual([
+ {
+ example: { foo: 'bar' },
+ },
+ {
+ example: { bar: 'foo' },
+ },
+ ]);
+ });
+
+ test('should return examples from headers schema - case when only payload examples are defined in `examples` field', () => {
+ const result = MessageHelpers.getHeadersExamples(
+ new Message({
+ headers: {
+ examples: [{ foo: 'bar' }, { bar: 'foo' }],
+ },
+ examples: [
+ {
+ name: 'example name',
+ summary: 'example summary',
+ payload: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ payload: { bar: 'foo' },
+ },
+ ],
+ }),
+ );
+ expect(result).toEqual([
+ {
+ example: { foo: 'bar' },
+ },
+ {
+ example: { bar: 'foo' },
+ },
+ ]);
+ });
+
+ test('should return examples for headers - case when at least one item in `examples` array has `headers` field with existing `headers.examples`', () => {
+ const result = MessageHelpers.getHeadersExamples(
+ new Message({
+ headers: {
+ examples: [{ foo: 'bar' }, { bar: 'foo' }],
+ },
+ examples: [
+ {
+ name: 'example name',
+ summary: 'example summary',
+ payload: { foo: 'bar' },
+ },
+ {
+ name: 'example name',
+ summary: 'example summary',
+ headers: { bar: 'foo' },
+ },
+ ],
+ }),
+ );
+ expect(result).toEqual([
+ {
+ name: 'example name',
+ summary: 'example summary',
+ example: { bar: 'foo' },
+ },
+ ]);
+ });
+ });
+});
diff --git a/library/src/helpers/__tests__/renderMarkdown.test.tsx b/library/src/helpers/__tests__/renderMarkdown.test.tsx
deleted file mode 100644
index 8f6b1fd76..000000000
--- a/library/src/helpers/__tests__/renderMarkdown.test.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { renderMd } from '../renderMarkdown';
-
-describe('renderMd', () => {
- test('should return input value (input is ReactNode)', () => {
- const input = foobar;
- const result = renderMd(input);
-
- expect(result).toEqual(input);
- });
-
- test('should return input value (input is number)', () => {
- const input = 0;
- const result = renderMd(input);
-
- expect(result).toEqual(input);
- });
-});
diff --git a/library/src/helpers/__tests__/schema.test.ts b/library/src/helpers/__tests__/schema.test.ts
new file mode 100644
index 000000000..5515aa5e7
--- /dev/null
+++ b/library/src/helpers/__tests__/schema.test.ts
@@ -0,0 +1,872 @@
+import { ParameterObject } from '@asyncapi/parser/esm/spec-types/v2';
+import { SchemaHelpers, SchemaCustomTypes } from '../schema';
+import {
+ SchemaV2 as Schema,
+ ServerVariableV2 as ServerVariable,
+ ChannelParameterV2 as ChannelParameter,
+ ChannelParametersV2 as ChannelParameters,
+ ServerVariablesV2,
+} from '@asyncapi/parser';
+
+describe('SchemaHelpers', () => {
+ describe('.toSchemaType', () => {
+ test('should handle falsy value', () => {
+ const result = SchemaHelpers.toSchemaType(null as any);
+ expect(result).toEqual(SchemaCustomTypes.UNKNOWN);
+ });
+
+ test('should handle object without .json() function', () => {
+ const result = SchemaHelpers.toSchemaType({} as any);
+ expect(result).toEqual(SchemaCustomTypes.UNKNOWN);
+ });
+
+ test('should handle true schemas', () => {
+ const schema = new Schema(true);
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(SchemaCustomTypes.ANY);
+ });
+
+ test('should handle empty schema', () => {
+ const schema = new Schema({});
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(SchemaCustomTypes.ANY);
+ });
+
+ test('should handle schema with non JSON Schema keywords ', () => {
+ const schema = new Schema({ foo: 'bar', 'x-ext': 'someExt' });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(SchemaCustomTypes.ANY);
+ });
+
+ test('should handle false schemas', () => {
+ const schema = new Schema(false);
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(SchemaCustomTypes.NEVER);
+ });
+
+ test('should handle empty not schemas', () => {
+ const schema = new Schema({ not: {}, type: 'string' });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(SchemaCustomTypes.NEVER);
+ });
+
+ test('should handle not schemas with non JSON Schema keywords', () => {
+ const schema = new Schema({ not: { foo: 'bar', 'x-ext': 'someExt' } });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(SchemaCustomTypes.NEVER);
+ });
+
+ test('should handle flat types', () => {
+ const schema = new Schema({ type: 'string' });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(`string`);
+ });
+
+ test('should handle union types', () => {
+ const schema = new Schema({ type: ['string', 'boolean', 'number'] });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(`string | boolean | number`);
+ });
+
+ test('should infer types', () => {
+ const schema = new Schema({
+ pattern: '^foo',
+ properties: {},
+ items: { multipleOf: 2 },
+ });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(SchemaCustomTypes.RESTRICTED_ANY);
+ });
+
+ test('should handle union types in array', () => {
+ const schema = new Schema({
+ type: 'array',
+ items: { type: ['string', 'number'] },
+ });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(`array`);
+ });
+
+ test('should handle empty array type', () => {
+ const schema = new Schema({
+ type: 'array',
+ });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(`array<${SchemaCustomTypes.ANY}>`);
+ });
+
+ test('should handle tuple types', () => {
+ const schema = new Schema({
+ type: 'array',
+ items: [{ type: 'object' }, { type: 'string' }, {}],
+ });
+ const result = SchemaHelpers.toSchemaType(schema);
+ expect(result).toEqual(
+ `tuple