diff --git a/library/package.json b/library/package.json index 182b98471..639b8eaed 100644 --- a/library/package.json +++ b/library/package.json @@ -44,7 +44,7 @@ "./styles/default.min.css" ], "scripts": { - "start": "tsc -p tsconfig.esm.json --watch", + "start": "npm run build:styles:dev && tsc -p tsconfig.esm.json --watch", "build:dev": "npm run build:esm && npm run build:types && npm run build:styles", "build:prod": "npm run build:esm && npm run build:cjs && npm run build:umd && npm run build:standalone && npm run build:types && npm run build:styles", "build:esm": "tsc -p tsconfig.esm.json", @@ -53,7 +53,7 @@ "build:standalone": "cross-env BUILD_MODE=standalone webpack", "build:types": "tsc -p tsconfig.types.json", "build:styles": "npm run build:styles:dev && npm run build:styles:prod", - "build:styles:dev": "cross-env NODE_ENV=production postcss src/styles/default.css -o styles/default.css --verbose", + "build:styles:dev": "cross-env NODE_ENV=development postcss src/styles/default.css -o styles/default.css --verbose & cross-env NODE_ENV=development postcss src/styles/default.css -o styles/default.min.css --verbose", "build:styles:prod": "cross-env NODE_ENV=production MINIFY_STYLES=true postcss src/styles/default.css -o styles/default.min.css --verbose", "test": "npm run test:unit && npm run test:e2e", "test:unit": "jest --detectOpenHandles", @@ -117,4 +117,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/library/src/components/Bindings.tsx b/library/src/components/Bindings.tsx index a6c8fcc39..7965e7862 100644 --- a/library/src/components/Bindings.tsx +++ b/library/src/components/Bindings.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import React from 'react'; -import { Schema } from './Schema'; +import { Schema } from './Schema/Schema'; import { SchemaHelpers } from '../helpers'; import { BindingsInterface } from '@asyncapi/parser'; diff --git a/library/src/components/Extensions.tsx b/library/src/components/Extensions.tsx index f71ae4081..5894e613b 100644 --- a/library/src/components/Extensions.tsx +++ b/library/src/components/Extensions.tsx @@ -2,9 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import React, { useState } from 'react'; - -import { Schema } from './Schema'; - +import { Schema } from './Schema/Schema'; import { SchemaHelpers } from '../helpers'; import { useConfig, useSpec } from '../contexts'; import { CollapseButton } from './CollapseButton'; diff --git a/library/src/components/Schema.tsx b/library/src/components/Schema.tsx deleted file mode 100644 index 8ec964f8a..000000000 --- a/library/src/components/Schema.tsx +++ /dev/null @@ -1,534 +0,0 @@ -import React, { useState, useEffect, useContext } from 'react'; -import { SchemaInterface } from '@asyncapi/parser'; - -import { Href, CollapseButton, Markdown, Extensions } from './index'; -import { SchemaHelpers } from '../helpers'; - -interface Props { - schemaName?: React.ReactNode; - schema?: SchemaInterface; - required?: boolean; - isPatternProperty?: boolean; - isProperty?: boolean; - isCircular?: boolean; - dependentRequired?: string[]; - expanded?: boolean; - onlyTitle?: boolean; - isArray?: boolean; -} - -const SchemaContext = React.createContext({ - reverse: false, - deepExpanded: false, -}); - -export const Schema: React.FunctionComponent = ({ - schemaName, - schema, - required = false, - isPatternProperty = false, - isProperty = false, - isCircular = false, - dependentRequired, - expanded: propExpanded = false, - onlyTitle = false, - isArray = false, - // eslint-disable-next-line sonarjs/cognitive-complexity -}) => { - const { reverse, deepExpanded } = useContext(SchemaContext); - const [expanded, setExpanded] = useState(propExpanded || isArray); - const [deepExpand, setDeepExpand] = useState(false); - - useEffect(() => { - if (!isArray) { - setDeepExpand(deepExpanded); - } - }, [isArray, deepExpanded, setDeepExpand]); - - useEffect(() => { - if (!isArray) { - setExpanded(deepExpand); - } - }, [isArray, deepExpand, setExpanded]); - - if ( - !schema || - (typeof schemaName === 'string' && - (schemaName?.startsWith('x-parser-') || - schemaName?.startsWith('x-schema-private-'))) - ) { - return null; - } - - const dependentSchemas = SchemaHelpers.getDependentSchemas(schema); - - const constraints = SchemaHelpers.humanizeConstraints(schema); - const externalDocs = schema.externalDocs(); - - const rawValueExt = schema.extensions().get(SchemaHelpers.extRawValue); - const rawValue = rawValueExt?.value() === true; - - const parameterLocationExt = schema - .extensions() - .get(SchemaHelpers.extParameterLocation); - const parameterLocation = parameterLocationExt?.value() === true; - - const schemaType = SchemaHelpers.toSchemaType(schema); - const isExpandable = SchemaHelpers.isExpandable(schema) || dependentSchemas; - - isCircular = isCircular || schema.isCircular() || false; - const uid = schema.$id(); - const styledSchemaName = isProperty ? 'italic' : ''; - const renderedSchemaName = - typeof schemaName === 'string' ? ( - - {schemaName} - - ) : ( - schemaName - ); - - return ( - -
-
-
- {isExpandable && !isCircular && !isArray ? ( - <> - setExpanded((prev) => !prev)} - expanded={expanded} - > - {renderedSchemaName} - - - - ) : ( - - {schemaName} - - )} - {isPatternProperty && ( -
- (pattern property) -
- )} - {required &&
required
} - {dependentRequired && ( - <> -
- required when defined: -
-
- {dependentRequired.join(', ')} -
- - )} - {schema.deprecated() && ( -
deprecated
- )} - {schema.writeOnly() && ( -
write-only
- )} - {schema.readOnly() && ( -
read-only
- )} -
- {rawValue ? ( -
-
- {SchemaHelpers.prettifyValue(schema.const(), false)} -
-
- ) : ( -
-
-
- {isCircular ? `${schemaType} [CIRCULAR]` : schemaType} -
-
- {schema.format() && ( - - format: {schema.format()} - - )} - - {/* related to string */} - {schema.pattern() !== undefined && ( - - must match: {schema.pattern()} - - )} - {schema.contentMediaType() !== undefined && ( - - media type: {schema.contentMediaType()} - - )} - {schema.contentEncoding() !== undefined && ( - - encoding: {schema.contentEncoding()} - - )} - - {/* constraints */} - {!!constraints.length && - constraints.map((c) => ( - - {c} - - ))} - - {uid && !uid.startsWith(' - uid: {uid} - - )} -
- - {schema.description() !== undefined && ( -
- {schema.description()} -
- )} - - {schema.default() !== undefined && ( -
- Default value: - - {SchemaHelpers.prettifyValue(schema.default())} - -
- )} - {schema.const() !== undefined && ( -
- Const: - - {SchemaHelpers.prettifyValue(schema.const())} - -
- )} - {schema.enum() && ( -
    - Allowed values:{' '} - {schema.enum()?.map((e, idx) => ( -
  • - {SchemaHelpers.prettifyValue(e)} -
  • - ))} -
- )} - {parameterLocation && ( -
- Parameter location:{' '} - - {parameterLocation} - -
- )} - {externalDocs && ( - - - Documentation - - - )} - {schema.examples() && ( -
    - Examples values:{' '} - {schema.examples()?.map((e, idx) => ( -
  • - {SchemaHelpers.prettifyValue(e)} -
  • - ))} -
- )} -
-
- )} -
- - {isCircular || !isExpandable ? null : ( -
- - - - {schema - .oneOf() - ?.map((s, idx) => ( - - ))} - {schema - .anyOf() - ?.map((s, idx) => ( - - ))} - {schema - .allOf() - ?.map((s, idx) => ( - - ))} - {schema.not() && ( - - )} - - {schema.propertyNames() && ( - - )} - {schema.contains() && ( - - )} - - {schema.if() && ( - - )} - {schema.then() && ( - - )} - {schema.else() && ( - - )} - - {dependentSchemas && ( - - )} - - - - - -
- )} -
-
- ); -}; - -interface SchemaPropertiesProps { - schema: SchemaInterface; -} - -const SchemaProperties: React.FunctionComponent = ({ - schema, -}) => { - const properties = schema.properties(); - if (properties === undefined || !Object.keys(properties)) { - return null; - } - - const required = schema.required() ?? []; - const patternProperties = schema.patternProperties(); - - return ( - <> - {Object.entries(properties).map(([propertyName, property]) => ( - - ))} - {Object.entries(patternProperties ?? {}).map( - ([propertyName, property]) => ( - - ), - )} - - ); -}; - -interface AdditionalPropertiesProps { - schema: SchemaInterface; -} - -const AdditionalProperties: React.FunctionComponent< - AdditionalPropertiesProps -> = ({ schema }) => { - if ( - schema.extensions().get(SchemaHelpers.extRenderAdditionalInfo)?.value() === - false - ) { - return null; - } - - const type = schema.type(); - if (!type?.includes('object')) { - return null; - } - - const additionalProperties = schema.additionalProperties(); - if (additionalProperties === true || additionalProperties === undefined) { - return ( -

- Additional properties are allowed. -

- ); - } - if (additionalProperties === false) { - return ( -

- Additional properties are NOT allowed. -

- ); - } - return ( - - ); -}; - -interface SchemaItemsProps { - schema: SchemaInterface; -} - -const SchemaItems: React.FunctionComponent = ({ schema }) => { - const type = schema.type(); - if (!type?.includes('array')) { - return null; - } - const items = schema.items(); - - // object in items - if ( - items && - !Array.isArray(items) && - Object.keys(items.properties() ?? {}).length - ) { - return ; - } else if (Array.isArray(items)) { - return ( - <> - {items.map((item, idx) => ( - - ))} - - ); - } - return ; -}; - -interface AdditionalItemsProps { - schema: SchemaInterface; -} - -const AdditionalItems: React.FunctionComponent = ({ - schema, -}) => { - if ( - schema.extensions().get(SchemaHelpers.extRenderAdditionalInfo)?.value() === - false - ) { - return null; - } - - const type = schema.type(); - if (!type?.includes('array')) { - return null; - } - if (!Array.isArray(schema.items())) { - return null; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any - const additionalItems = schema.additionalItems() as any; - if (additionalItems === true || additionalItems === undefined) { - return ( -

- Additional items are allowed. -

- ); - } - if (additionalItems === false) { - return ( -

- Additional items are NOT allowed. -

- ); - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - return ; -}; diff --git a/library/src/components/Schema/AdditionalItems.tsx b/library/src/components/Schema/AdditionalItems.tsx new file mode 100644 index 000000000..7bda52121 --- /dev/null +++ b/library/src/components/Schema/AdditionalItems.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { SchemaInterface } from '@asyncapi/parser'; +import { SchemaHelpers } from '../../helpers'; +import { Schema } from './Schema'; + +interface AdditionalItemsProps { + schema: SchemaInterface; +} + +export const AdditionalItems: React.FunctionComponent = ({ + schema, +}) => { + if ( + schema.extensions().get(SchemaHelpers.extRenderAdditionalInfo)?.value() === + false + ) { + return null; + } + + const type = schema.type(); + if (!type?.includes('array')) { + return null; + } + if (!Array.isArray(schema.items())) { + return null; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + const additionalItems = schema.additionalItems() as any; + if (additionalItems === true || additionalItems === undefined) { + return ( +

+ Additional items are allowed. +

+ ); + } + if (additionalItems === false) { + return ( +

+ Additional items are NOT allowed. +

+ ); + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + return ; +}; diff --git a/library/src/components/Schema/AdditionalProperties.tsx b/library/src/components/Schema/AdditionalProperties.tsx new file mode 100644 index 000000000..c768b1686 --- /dev/null +++ b/library/src/components/Schema/AdditionalProperties.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { SchemaInterface } from '@asyncapi/parser'; +import { SchemaHelpers } from '../../helpers'; +import { Schema } from './Schema'; + +interface AdditionalPropertiesProps { + schema: SchemaInterface; +} + +export const AdditionalProperties: React.FunctionComponent< + AdditionalPropertiesProps +> = ({ schema }) => { + if ( + schema.extensions().get(SchemaHelpers.extRenderAdditionalInfo)?.value() === + false + ) { + return null; + } + + const type = schema.type(); + if (!type?.includes('object')) { + return null; + } + + const additionalProperties = schema.additionalProperties(); + if (additionalProperties === true || additionalProperties === undefined) { + return ( +

+ Additional properties are allowed. +

+ ); + } + if (additionalProperties === false) { + return ( +

+ Additional properties are NOT allowed. +

+ ); + } + return ( + + ); +}; diff --git a/library/src/components/Schema/Conditions/Conditions.tsx b/library/src/components/Schema/Conditions/Conditions.tsx new file mode 100644 index 000000000..78fdd2d8d --- /dev/null +++ b/library/src/components/Schema/Conditions/Conditions.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { SchemaInterface } from '@asyncapi/parser'; +import { Schema } from '../Schema'; +import { SchemaHelpers } from '../../../helpers'; + +interface ConditionsProps { + schema: SchemaInterface; + dependentSchemas?: SchemaInterface; +} + +export const Conditions = ({ schema, dependentSchemas }: ConditionsProps) => { + return ( +
+ {schema.oneOf()?.length && ( +
+
+ Can be One Of the following: +
+ {schema + .oneOf() + ?.map((s, idx) => ( + + ))} +
+ )} + + {schema.anyOf()?.length && ( +
+
+ Can be Any Of the following: +
+ {schema + .anyOf() + ?.map((s, idx) => ( + + ))} +
+ )} + + {schema.allOf()?.length && ( +
+
+ Must consist All Of the following: +
+ {schema + .allOf() + ?.map((s, idx) => ( + + ))} +
+ )} + + {schema.not() && ( + + )} + + {schema.propertyNames() && ( + + )} + + {schema.contains() && ( + + )} + + {schema.if() && ( +
+ {schema.if() && ( + + )} + {schema.then() && ( + + )} + {schema.else() && ( + + )} +
+ )} + + {dependentSchemas && ( + + )} +
+ ); +}; diff --git a/library/src/components/Schema/FeildStatusIndicators.tsx b/library/src/components/Schema/FeildStatusIndicators.tsx new file mode 100644 index 000000000..4a6a16177 --- /dev/null +++ b/library/src/components/Schema/FeildStatusIndicators.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { SchemaInterface } from '@asyncapi/parser'; + +interface FeildStatusIndicatorProps { + schema: SchemaInterface; + required?: boolean; + isPatternProperty?: boolean; +} + +export const FeildStatusIndicator = ({ + schema, + required = false, + isPatternProperty, +}: FeildStatusIndicatorProps) => { + const isRequired = required ?? false; + const isDeprecated = schema.deprecated() ?? false; + const isWriteOnly = schema.writeOnly() ?? false; + const isReadOnly = schema.readOnly() ?? false; + const isPattern = isPatternProperty ?? false; + + return ( + <> + {(isRequired || + isDeprecated || + isWriteOnly || + isReadOnly || + isPattern) && ( +
+ {required && ( + required + )} + {schema.deprecated() && ( + + deprecated + + )} + {isPatternProperty && ( +
+ (pattern property) +
+ )} + {schema.writeOnly() && ( + write-only + )} + {schema.readOnly() && ( + read-only + )} +
+ )} + + ); +}; diff --git a/library/src/components/Schema/Rules/Rules.tsx b/library/src/components/Schema/Rules/Rules.tsx new file mode 100644 index 000000000..827e23a47 --- /dev/null +++ b/library/src/components/Schema/Rules/Rules.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { SchemaInterface } from '@asyncapi/parser'; +import { SchemaHelpers } from '../../../helpers'; + +interface RulesProps { + schema: SchemaInterface; + constraints: string[]; +} + +export const Rules = ({ schema, constraints }: RulesProps) => { + return ( +
+ {schema.format() && ( + + format:{' '} + + {schema.format()} + + + )} + {schema.pattern() && ( + + must match:{' '} + + {schema.pattern()} + + + )} + {schema.contentEncoding() !== undefined && ( + + encoding:{' '} + + {schema.contentEncoding()} + + + )} + {constraints.map((constraint) => ( + + {constraint} + + ))} + {schema.default() !== undefined && ( +
+ Default value: + + {SchemaHelpers.prettifyValue(schema.default())} + +
+ )} + {schema.const() !== undefined && ( +
+ Constant value: + + {SchemaHelpers.prettifyValue(schema.const())} + +
+ )} + {schema.enum() && ( +
+ Allowed values: + {schema.enum()?.map((e, idx) => ( + + {SchemaHelpers.prettifyValue(e)} + + ))} +
+ )} +
+ ); +}; diff --git a/library/src/components/Schema/Schema.tsx b/library/src/components/Schema/Schema.tsx new file mode 100644 index 000000000..e5b347142 --- /dev/null +++ b/library/src/components/Schema/Schema.tsx @@ -0,0 +1,292 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { SchemaInterface } from '@asyncapi/parser'; + +import { Href, CollapseButton, Markdown, Extensions } from '../index'; +import { SchemaHelpers } from '../../helpers'; +import { SchemaItems } from './SchemaItems'; +import { AdditionalItems } from './AdditionalItems'; +import { SchemaProperties } from './SchemaProperties'; +import { AdditionalProperties } from './AdditionalProperties'; +import { Conditions } from './Conditions/Conditions'; +import { Rules } from './Rules/Rules'; +import { FeildStatusIndicator } from './FeildStatusIndicators'; + +export interface Props { + schemaName?: React.ReactNode; + schema?: SchemaInterface; + showSchemaType?: boolean; + required?: boolean; + isPatternProperty?: boolean; + isProperty?: boolean; + isCircular?: boolean; + dependentRequired?: string[]; + expanded?: boolean; + onlyTitle?: boolean; + isArray?: boolean; + showConditionSidebar?: boolean; +} + +const SchemaContext = React.createContext({ + reverse: false, + deepExpanded: false, +}); + +export const Schema: React.FunctionComponent = ({ + schemaName, + schema, + // showSchemaType = true, + required = false, + isPatternProperty = false, + isProperty = false, + isCircular = false, + // dependentRequired, + expanded: propExpanded = false, + // onlyTitle = false, + isArray = false, + // eslint-disable-next-line sonarjs/cognitive-complexity +}) => { + const { reverse, deepExpanded } = useContext(SchemaContext); + const [expanded, setExpanded] = useState(propExpanded || isArray); + const [deepExpand, setDeepExpand] = useState(false); + const [tabOpen, setTabOpen] = useState<'RULES' | 'CONDITIONS'>('RULES'); + + useEffect(() => { + if (!isArray) { + setDeepExpand(deepExpanded); + } + }, [isArray, deepExpanded, setDeepExpand]); + + useEffect(() => { + if (!isArray) { + setExpanded(deepExpand); + } + }, [isArray, deepExpand, setExpanded]); + + useEffect(() => { + if (!rulesExist) setTabOpen('CONDITIONS'); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + if ( + !schema || + (typeof schemaName === 'string' && + (schemaName?.startsWith('x-parser-') || + schemaName?.startsWith('x-schema-private-'))) + ) { + return null; + } + + const dependentSchemas = SchemaHelpers.getDependentSchemas(schema); + + const constraints = SchemaHelpers.humanizeConstraints(schema); + const externalDocs = schema.externalDocs(); + + const parameterLocationExt = schema + .extensions() + .get(SchemaHelpers.extParameterLocation); + const parameterLocation = parameterLocationExt?.value() === true; + + const schemaType = SchemaHelpers.toSchemaType(schema); + + isCircular = isCircular || schema.isCircular() || false; + + const uid = schema.$id(); + + const styledSchemaName = isProperty ? 'italic' : ''; + const renderedSchemaName = + typeof schemaName === 'string' ? ( + + {schemaName} + + ) : ( + schemaName + ); + + // comes in Rules section + const rulesExist = SchemaHelpers.hasRules(schema, constraints); + // comes in Conditions section + const conditionsExist = SchemaHelpers.hasConditions(schema); + + // we want the expanding dropdown to be present if schema has got other stuff, rules or conditions + const isExpandable = + SchemaHelpers.isExpandable(schema) || rulesExist || conditionsExist; + + return ( + +
+
+ {/* Header Section */} +
+
+
+ {isExpandable && !isCircular && !isArray ? ( + // TODO: make this sticky +
+ setExpanded((prev) => !prev)} + expanded={expanded} + > + {renderedSchemaName} + +
+ ) : ( + + {schemaName} + + )} + + {isCircular ? `${schemaType} [CIRCULAR]` : schemaType} + + {schema.contentMediaType() !== undefined && ( + + media type: {schema.contentMediaType()} + + )} + {uid && !uid.startsWith(' + uid: {uid} + + )} + {/* TODO: find out if below is really needed ?? + cuz schema.const() is already shown in a strict manner in Rules */} + {/* + {SchemaHelpers.prettifyValue(schema.const(), false) && ( + + {SchemaHelpers.prettifyValue(schema.const(), false)} + + )} + */} + + {/* Field Status Indicators */} + + +
+ {isExpandable && !isCircular && !isArray && ( + + )} +
+
+
+ + {/* Description */} + {schema.description() && ( +
+ {schema.description()} +
+ )} + {schema.examples() && ( +
    + Examples values:{' '} + {schema.examples()?.map((e, idx) => ( +
  • + {SchemaHelpers.prettifyValue(e)} +
  • + ))} +
+ )} + {parameterLocation && ( +
+ Parameter location:{' '} + + {parameterLocation} + +
+ )} + {externalDocs && ( + + + Documentation + + + )} +
+ +
+ {/* Expandable Content */} + {!isCircular && isExpandable && expanded && ( +
+ {/* Properties Section */} + + + {/* Array Items Section */} + + +
+
+ {rulesExist && ( + + )} + {conditionsExist && ( + + )} +
+ {/* Conditions Section: has deep recursions */} + {conditionsExist && tabOpen == 'CONDITIONS' && ( +
+ +
+ )} + + {/* Rules Section: typically does not involve recursion */} + {rulesExist && tabOpen == 'RULES' && ( +
+ +
+ )} +
+ + {/* Additional Properties/Items Section */} +
+ + +
+ + {/* Extensions Section */} + +
+ )} +
+
+
+
+ ); +}; diff --git a/library/src/components/Schema/SchemaItems.tsx b/library/src/components/Schema/SchemaItems.tsx new file mode 100644 index 000000000..22e91d1bf --- /dev/null +++ b/library/src/components/Schema/SchemaItems.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { SchemaInterface } from '@asyncapi/parser'; +import { Schema } from './Schema'; + +interface SchemaItemsProps { + schema: SchemaInterface; +} + +export const SchemaItems: React.FunctionComponent = ({ + schema, +}) => { + const type = schema.type(); + if (!type?.includes('array')) { + return null; + } + const items = schema.items(); + + // object in items + if ( + items && + !Array.isArray(items) && + Object.keys(items.properties() ?? {}).length + ) { + return ; + } else if (Array.isArray(items)) { + return ( + <> + {items.map((item, idx) => ( + + ))} + + ); + } + return ; +}; diff --git a/library/src/components/Schema/SchemaProperties.tsx b/library/src/components/Schema/SchemaProperties.tsx new file mode 100644 index 000000000..6f54dcff0 --- /dev/null +++ b/library/src/components/Schema/SchemaProperties.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { SchemaInterface } from '@asyncapi/parser'; +import { SchemaHelpers } from '../../helpers'; +import { Schema } from './Schema'; + +interface SchemaPropertiesProps { + schema: SchemaInterface; +} + +export const SchemaProperties: React.FunctionComponent< + SchemaPropertiesProps +> = ({ schema }) => { + const properties = schema.properties(); + if (properties === undefined || !Object.keys(properties)) { + return null; + } + + const required = schema.required() ?? []; + const patternProperties = schema.patternProperties(); + + return ( + <> + {Object.entries(properties).map(([propertyName, property]) => ( + + ))} + {Object.entries(patternProperties ?? {}).map( + ([propertyName, property]) => ( + + ), + )} + + ); +}; diff --git a/library/src/components/__tests__/Bindings.test.tsx b/library/src/components/__tests__/Bindings.test.tsx index b7321dee3..2214dcd27 100644 --- a/library/src/components/__tests__/Bindings.test.tsx +++ b/library/src/components/__tests__/Bindings.test.tsx @@ -5,7 +5,7 @@ /* eslint-disable sonarjs/no-duplicate-string */ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { BindingV2 as BindingSchema, BindingsV2 as BindingsSchema, @@ -41,14 +41,19 @@ describe('Bindings component', () => { }; render(); + const expandAllButtons = screen.getAllByRole('button', { + name: 'Expand all', + }); + expandAllButtons.forEach((button) => fireEvent.click(button)); + expect(screen.getAllByText('Binding specific information')).toBeDefined(); expect(screen.getAllByText('Binding specific information')).toHaveLength(2); expect(screen.getByText('mqtt')).toBeDefined(); expect(screen.getByText('fooMqtt')).toBeDefined(); - expect(screen.getByText('barMqtt')).toBeDefined(); + expect(screen.getByText('"barMqtt"')).toBeDefined(); expect(screen.getByText('kafka')).toBeDefined(); expect(screen.getByText('fooKafka')).toBeDefined(); - expect(screen.getByText('barKafka')).toBeDefined(); + expect(screen.getByText('"barKafka"')).toBeDefined(); }); test('should render empty binding as string', () => { @@ -61,11 +66,16 @@ describe('Bindings component', () => { }; render(); + const expandAllButtons = screen.getAllByRole('button', { + name: 'Expand all', + }); + expandAllButtons.forEach((button) => fireEvent.click(button)); + expect(screen.getAllByText('Binding specific information')).toBeDefined(); expect(screen.getAllByText('Binding specific information')).toHaveLength(3); expect(screen.getByText('mqtt')).toBeDefined(); expect(screen.getByText('foo')).toBeDefined(); - expect(screen.getByText('bar')).toBeDefined(); + expect(screen.getByText('"bar"')).toBeDefined(); expect(screen.getByText('kafka')).toBeDefined(); expect(screen.getByText('http')).toBeDefined(); expect(screen.queryByText('undefined')).toEqual(null); diff --git a/library/src/components/__tests__/Extensions.test.tsx b/library/src/components/__tests__/Extensions.test.tsx index d7e5e5e41..032cc57cd 100644 --- a/library/src/components/__tests__/Extensions.test.tsx +++ b/library/src/components/__tests__/Extensions.test.tsx @@ -3,12 +3,16 @@ */ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { SchemaV2 as SchemaModel } from '@asyncapi/parser'; import { Extensions } from '../Extensions'; +const expandAll = () => { + fireEvent.click(screen.getByRole('button', { name: 'Expand all' })); +}; + describe('Extensions component', () => { test('should work with simple data', () => { const schema = { @@ -18,11 +22,13 @@ describe('Extensions component', () => { const schemaModel = new SchemaModel(schema); render(); + expandAll(); + expect(screen.getByText('Extensions')).toBeDefined(); expect(screen.getByText('x-foo')).toBeDefined(); - expect(screen.getByText('xBar')).toBeDefined(); + expect(screen.getByText('"xBar"')).toBeDefined(); expect(screen.getByText('x-bar')).toBeDefined(); - expect(screen.getByText('xFoo')).toBeDefined(); + expect(screen.getByText('"xFoo"')).toBeDefined(); }); test('should filter non extensions', () => { @@ -39,11 +45,13 @@ describe('Extensions component', () => { const schemaModel = new SchemaModel(schema); render(); + expandAll(); + expect(screen.getByText('Extensions')).toBeDefined(); expect(screen.getByText('x-foo')).toBeDefined(); - expect(screen.getByText('xBar')).toBeDefined(); + expect(screen.getByText('"xBar"')).toBeDefined(); expect(screen.getByText('x-bar')).toBeDefined(); - expect(screen.getByText('xFoo')).toBeDefined(); + expect(screen.getByText('"xFoo"')).toBeDefined(); expect(screen.queryByText('foo')).toEqual(null); expect(screen.queryByText('bar')).toEqual(null); }); @@ -57,9 +65,11 @@ describe('Extensions component', () => { const schemaModel = new SchemaModel(schema); render(); + expandAll(); + expect(screen.getByText('Extensions')).toBeDefined(); expect(screen.getByText('x-foo')).toBeDefined(); - expect(screen.getByText('xBar')).toBeDefined(); + expect(screen.getByText('"xBar"')).toBeDefined(); expect(screen.getByText('x-bar')).toBeDefined(); expect(screen.getByText('x-foobar')).toBeDefined(); expect(screen.queryByText('undefined')).toEqual(null); diff --git a/library/src/components/__tests__/Schema.test.tsx b/library/src/components/__tests__/Schema.test.tsx index 9222fbffc..6ba8b6eb3 100644 --- a/library/src/components/__tests__/Schema.test.tsx +++ b/library/src/components/__tests__/Schema.test.tsx @@ -3,10 +3,13 @@ */ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { SchemaV2 as SchemaModel } from '@asyncapi/parser'; +import { Schema } from '../Schema/Schema'; -import { Schema } from '../Schema'; +const expandAll = () => { + fireEvent.click(screen.getByRole('button', { name: 'Expand all' })); +}; describe('Schema component', () => { // eslint-disable-next-line jest/expect-expect @@ -41,6 +44,8 @@ describe('Schema component', () => { render(); + expandAll(); + // properties expect(screen.getByText('nonCircular')).toBeDefined(); expect(screen.getByText('circular')).toBeDefined(); @@ -69,6 +74,8 @@ describe('Schema component', () => { render(); + expandAll(); + expect(screen.getByText('true')).toBeDefined(); expect(screen.getByText('false')).toBeDefined(); }); @@ -91,6 +98,12 @@ describe('Schema component', () => { render(); + expandAll(); + + const constantValueElements = screen.getAllByText('Constant value:'); + expect(constantValueElements).toBeDefined(); + expect(constantValueElements).toHaveLength(2); + expect(screen.getByText('true')).toBeDefined(); expect(screen.getByText('false')).toBeDefined(); }); @@ -147,8 +160,18 @@ describe('Schema component', () => { render(); - expect(screen.getByText('Adheres to Cat:')).toBeDefined(); - expect(screen.getByText('Or to Dog:')).toBeDefined(); + expandAll(); + + expandAll(); + + expect( + screen.getAllByRole('heading', { + name: /can be one of the following/i, + }), + ).toBeDefined(); + + expect(screen.getByText('Cat:')).toBeDefined(); + expect(screen.getByText('Dog:')).toBeDefined(); }); }); }); diff --git a/library/src/components/index.ts b/library/src/components/index.ts index 4fddf52ef..76eeb98d0 100644 --- a/library/src/components/index.ts +++ b/library/src/components/index.ts @@ -4,6 +4,6 @@ export * from './JSONSnippet'; export * from './Extensions'; export * from './Href'; export * from './Markdown'; -export * from './Schema'; +export * from './Schema/Schema'; export * from './Tag'; export * from './Tags'; diff --git a/library/src/containers/Operations/Operation.tsx b/library/src/containers/Operations/Operation.tsx index cb1b76bf6..91cf09819 100644 --- a/library/src/containers/Operations/Operation.tsx +++ b/library/src/containers/Operations/Operation.tsx @@ -53,12 +53,13 @@ export const Operation: React.FunctionComponent = (props) => { {servers.map((server) => (
  • + MEOW {server.id()}
  • diff --git a/library/src/helpers/__tests__/schema.test.ts b/library/src/helpers/__tests__/schema.test.ts index 637674997..e60997db0 100644 --- a/library/src/helpers/__tests__/schema.test.ts +++ b/library/src/helpers/__tests__/schema.test.ts @@ -219,37 +219,37 @@ describe('SchemaHelpers', () => { test('should handle number/integer inclusive range', () => { const schema = new Schema({ minimum: 2, maximum: 5 }); const result = SchemaHelpers.humanizeConstraints(schema); - expect(result).toEqual(['[ 2 .. 5 ]']); + expect(result).toEqual(['2 <= value <= 5']); }); test('should handle number/integer exclusive range', () => { const schema = new Schema({ minimum: 2, exclusiveMaximum: 5 }); const result = SchemaHelpers.humanizeConstraints(schema); - expect(result).toEqual(['[ 2 .. 5 )']); + expect(result).toEqual(['2 <= value < 5']); }); test('should handle inclusive minimum', () => { const schema = new Schema({ minimum: 2 }); const result = SchemaHelpers.humanizeConstraints(schema); - expect(result).toEqual(['>= 2']); + expect(result).toEqual(['2 <= value']); }); test('should handle inclusive maximum', () => { const schema = new Schema({ maximum: 2 }); const result = SchemaHelpers.humanizeConstraints(schema); - expect(result).toEqual(['<= 2']); + expect(result).toEqual(['value <= 2']); }); test('should handle exclusive minimum', () => { const schema = new Schema({ exclusiveMinimum: 5 }); const result = SchemaHelpers.humanizeConstraints(schema); - expect(result).toEqual(['> 5']); + expect(result).toEqual(['5 < value']); }); test('should handle exclusive maximum', () => { const schema = new Schema({ exclusiveMaximum: 5 }); const result = SchemaHelpers.humanizeConstraints(schema); - expect(result).toEqual(['< 5']); + expect(result).toEqual(['value < 5']); }); test('should handle integer multipleOf', () => { diff --git a/library/src/helpers/schema.ts b/library/src/helpers/schema.ts index e057803a7..783335239 100644 --- a/library/src/helpers/schema.ts +++ b/library/src/helpers/schema.ts @@ -112,7 +112,8 @@ export class SchemaHelpers { otherCases: string, title?: string, ) { - const suffix = title ? ` ${title}:` : ':'; + let suffix = title ? ` ${title}:` : ':'; + if (suffix.startsWith(' ' : '>= '; + numberRange = ''; numberRange += hasExclusiveMin ? exclusiveMin : min; + numberRange += hasExclusiveMin ? ' < ' : ' <= '; + numberRange += 'value'; } else if (hasMax) { - numberRange = hasExclusiveMax ? '< ' : '<= '; + numberRange = ''; + numberRange += 'value'; + numberRange += hasExclusiveMax ? ' < ' : ' <= '; numberRange += hasExclusiveMax ? exclusiveMax : max; } return numberRange; @@ -590,4 +597,36 @@ export class SchemaHelpers { } return false; } + + public static hasRules(schema: SchemaInterface, constraints: string[]) { + return ( + /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ + schema.format() || + schema.pattern() || + constraints.length > 0 || + schema.contentEncoding() || + schema.enum() || + schema.default() !== undefined || + schema.const() !== undefined + /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ + ); + } + + public static hasConditions(schema: SchemaInterface) { + const dependentSchemas = this.getDependentSchemas(schema); + return ( + /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ + schema.oneOf()?.length || + schema.anyOf()?.length || + schema.allOf()?.length || + schema.not() || + schema.propertyNames() || + schema.contains() || + schema.if() || + schema.then() || + schema.else() || + dependentSchemas + /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ + ); + } } diff --git a/library/src/styles/default.css b/library/src/styles/default.css index 75e8f4ef7..903968243 100644 --- a/library/src/styles/default.css +++ b/library/src/styles/default.css @@ -13,7 +13,7 @@ @apply lg:relative lg:block lg:w-64 lg:h-auto; } - .container\:xl .sidebar--wrapper{ + .container\:xl .sidebar--wrapper { @apply xl:w-full; @apply sm:w-full; } diff --git a/playground/specs/complex-schema.ts b/playground/specs/complex-schema.ts new file mode 100644 index 000000000..f7d06e0ed --- /dev/null +++ b/playground/specs/complex-schema.ts @@ -0,0 +1,253 @@ +export const complexSchema = ` +asyncapi: 3.0.0 +info: + title: Complex Schema Demo API + version: 1.0.0 + description: Demonstrates various schema combinations and corner cases + contact: + name: API Support + email: support@example.com + url: 'https://example.com/support' + license: + name: Apache 2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0' +servers: + production: + host: broker.example.com + protocol: mqtt + description: Production MQTT Broker + security: + - $ref: '#/components/securitySchemes/userPassword' + bindings: + mqtt: + clientId: demo-client + cleanSession: true + keepAlive: 60 + bindingVersion: 0.2.0 +channels: + user/events: + address: user/events + messages: + publishUserEvent.message: + name: UserEvent + title: User Event Message + contentType: application/json + correlationId: + location: $message.header#/correlationId + headers: + type: object + properties: + correlationId: + type: string + format: uuid + payload: + $ref: '#/components/schemas/ComplexUserPayload' + traits: + - $ref: '#/components/messageTraits/CommonHeaders' + receiveUserEvent.message: + $ref: '#/components/messages/UserEventMessage' +operations: + publishUserEvent: + action: receive + channel: + $ref: '#/channels/user~1events' + summary: User-related events + messages: + - $ref: '#/channels/user~1events/messages/publishUserEvent.message' + receiveUserEvent: + action: send + channel: + $ref: '#/channels/user~1events' + summary: Receive user events + messages: + - $ref: '#/channels/user~1events/messages/receiveUserEvent.message' +components: + schemas: + ComplexUserPayload: + type: object + properties: + id: + type: string + format: uuid + basicTypes: + type: object + properties: + stringField: + type: string + minLength: 1 + maxLength: 100 + pattern: '^[A-Za-z0-9]+$' + integerField: + type: integer + minimum: 0 + maximum: 1000 + multipleOf: 5 + numberField: + type: number + format: float + exclusiveMinimum: 0 + exclusiveMaximum: 100 + booleanField: + type: boolean + nullableField: + type: + - string + - 'null' + enumField: + type: string + enum: + - PENDING + - ACTIVE + - SUSPENDED + arrays: + type: object + properties: + simpleArray: + type: array + items: + type: string + minItems: 1 + maxItems: 10 + uniqueItems: true + tupleArray: + type: array + items: + - type: string + - type: integer + - type: boolean + additionalItems: false + complexArray: + type: array + items: + type: object + properties: + name: + type: string + value: + type: integer + conditionalLogic: + type: object + if: + properties: + userType: + const: premium + then: + properties: + features: + type: array + items: + type: string + minItems: 3 + else: + properties: + features: + type: array + items: + type: string + maxItems: 2 + oneOfExample: + oneOf: + - type: object + properties: + type: + const: personal + personalEmail: + type: string + format: email + - type: object + properties: + type: + const: business + companyEmail: + type: string + format: email + department: + type: string + anyOfExample: + anyOf: + - type: object + properties: + phone: + type: string + pattern: '^+[1-9]d{1,14}$' + - type: object + properties: + email: + type: string + format: email + allOfExample: + allOf: + - type: object + properties: + baseField: + type: string + - type: object + properties: + extendedField: + type: integer + notExample: + not: + type: object + properties: + restricted: + const: true + dateTimeFields: + type: object + properties: + created: + type: string + format: date-time + date: + type: string + format: date + time: + type: string + format: time + additionalProperties: + type: object + additionalProperties: + type: string + patternProperties: + type: object + patternProperties: + '^[A-Z][a-z]+$': + type: string + dependencies: + type: object + dependencies: + creditCard: + required: + - billingAddress + required: + - id + - basicTypes + - arrays + messageTraits: + CommonHeaders: + headers: + type: object + properties: + messageId: + type: string + format: uuid + timestamp: + type: string + format: date-time + version: + type: string + required: + - messageId + - timestamp + messages: + UserEventMessage: + name: UserEvent + title: User Event Message + summary: A message containing user event information + contentType: application/json + payload: + $ref: '#/components/schemas/ComplexUserPayload' + securitySchemes: + userPassword: + type: userPassword + description: Basic user/password authentication +`; diff --git a/playground/specs/index.ts b/playground/specs/index.ts index a2d734318..b17ed9ecc 100644 --- a/playground/specs/index.ts +++ b/playground/specs/index.ts @@ -11,3 +11,5 @@ export * from './rpc-client'; export * from './rpc-server'; export * from './slack-rtm'; export * from './streetlights'; +export * from './overcomplicated-streetlight'; +export * from './complex-schema'; diff --git a/playground/specs/overcomplicated-streetlight.ts b/playground/specs/overcomplicated-streetlight.ts new file mode 100644 index 000000000..8e78a5323 --- /dev/null +++ b/playground/specs/overcomplicated-streetlight.ts @@ -0,0 +1,630 @@ +export const overcomplicatedStreetlights = `asyncapi: '2.6.0' +id: 'urn:com:smartylighting:streetlights:server' +info: + title: Streetlights API + version: '1.0.0' + description: | + The Smartylighting Streetlights API allows you to remotely manage the city lights. + + ### Check out its awesome features: + + * Turn a specific streetlight on/off 🌃 + * Dim a specific streetlight 😎 + * Receive real-time information about environmental lighting conditions 📈 + + termsOfService: http://asyncapi.org/terms/ + contact: + name: API Support + url: http://www.asyncapi.org/support + email: support@asyncapi.org + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +tags: + - name: root-tag1 + externalDocs: + description: External docs description 1 + url: https://www.asyncapi.com/ + - name: root-tag2 + description: Description 2 + externalDocs: + url: "https://www.asyncapi.com/" + - name: root-tag3 + - name: root-tag4 + description: Description 4 + - name: root-tag5 + externalDocs: + url: "https://www.asyncapi.com/" +externalDocs: + description: Find more info here + url: https://example.com +defaultContentType: application/json + +servers: + production: + url: api.streetlights.smartylighting.com:{port} + protocol: mqtt + description: | + Private server that requires authorization. + Once the socket is open you can subscribe to private-data channels by sending an authenticated subscribe request message. + + The API client must request an authentication "token" via the following REST API endpoint "GetWebSocketsToken" to connect to WebSockets Private endpoints. For more details read https://support.kraken.com/hc/en-us/articles/360034437672-How-to-retrieve-a-WebSocket-authentication-token-Example-code-in-Python-3 + + The resulting token must be provided in the "token" field of any new private WebSocket feed subscription: + \`\`\`json + { + "event": "subscribe", + "subscription": + { + "name": "ownTrades", + "token": "WW91ciBhdXRoZW50aWNhdGlvbiB0b2tlbiBnb2VzIGhlcmUu" + } + } + \`\`\` + + \`\`\`elixir + defmodule Hello do + def world do + IO.puts("hello") + end + end + \`\`\` + variables: + port: + description: Secure connection (TLS) is available through port 8883. + default: '1883' + enum: + - '1883' + - '8883' + tags: + - name: 'env:production' + security: + - apiKey: [] + - supportedOauthFlows: + - streetlights:on + - streetlights:off + - streetlights:dim + - openIdConnectWellKnown: [] + dummy-mqtt: + url: mqtt://localhost + protocol: mqtt + description: | + Private server + + \`\`\`csharp + using System; + + namespace HelloWorld + { + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } + } + \`\`\` + bindings: + mqtt: + clientId: guest + cleanSession: false + keepAlive: 60 + bindingVersion: 0.1.0 + lastWill: + topic: smartylighting/streetlights/1/0/lastwill + qos: 1 + message: so long and thanks for all the fish + retain: false + dummy-amqp: + url: amqp://localhost:{port} + protocol: amqp + description: dummy AMQP broker + protocolVersion: "0.9.1" + variables: + port: + enum: + - '15672' + - '5672' + dommy-kafka: + url: http://localhost:{port} + protocol: kafka + description: dummy Kafka broker + variables: + port: + default: '9092' + +channels: + smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured: + x-security: + $ref: '#/components/securitySchemes/supportedOauthFlows/flows/clientCredentials' + description: The topic on which measured values may be produced and consumed. + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + servers: + - production + - dommy-kafka + subscribe: + summary: Receive information about environmental lighting conditions of a particular streetlight. + operationId: receiveLightMeasurement + externalDocs: + description: Find more info here + url: https://example.com + traits: + - $ref: '#/components/operationTraits/kafka' + message: + $ref: '#/components/messages/lightMeasured' + bindings: + mqtt: + qos: 1 + bindingVersion: 0.1.0 + http: + type: request + method: GET + query: + type: object + required: + - companyId + properties: + companyId: + type: number + minimum: 1 + description: The Id of the company. + additionalProperties: false + + smartylighting/streetlights/1/0/action/{streetlightId}/turn/on: + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + servers: + - production + - dummy-amqp + publish: + operationId: turnOn + security: + - supportedOauthFlows: + - streetlights:on + externalDocs: + description: Find more info here + url: https://example.com + traits: + - $ref: '#/components/operationTraits/kafka' + message: + $ref: '#/components/messages/turnOnOff' + + smartylighting/streetlights/1/0/action/{streetlightId}/turn/off: + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + publish: + traits: + - $ref: '#/components/operationTraits/kafka' + message: + $ref: '#/components/messages/turnOnOff' + + smartylighting/streetlights/1/0/action/{streetlightId}/dim: + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + servers: + - production + - dummy-amqp + publish: + operationId: dimLight + traits: + - $ref: '#/components/operationTraits/kafka' + message: + $ref: '#/components/messages/dimLight' + +components: + messages: + lightMeasured: + messageId: lightMeasured Message ID + name: lightMeasured + title: Light measured + summary: Inform about environmental lighting conditions for a particular streetlight. + contentType: application/json + correlationId: + $ref: "#/components/correlationIds/sentAtCorrelator" + externalDocs: + url: "https://www.asyncapi.com/" + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: "#/components/schemas/lightMeasuredPayload" + bindings: + mqtt: + bindingVersion: 0.1.0 + examples: + - headers: + my-app-header: 12 + payload: + lumens: 1 + sentAt: "2020-01-31T13:24:53Z" + - headers: + my-app-header: 13 + - payload: + lumens: 3 + sentAt: "2020-10-31T13:24:53Z" + x-schema-extensions-as-object: + type: object + properties: + prop1: + type: string + prop2: + type: integer + minimum: 0 + x-schema-extensions-as-primitive: dummy + x-schema-extensions-as-array: + - "item1" + - "item2" + LwM2mOjbects: + payload: + type: object + properties: + objectLinks: + type: string + example: + objectLinks: "lwm2m=1.1, , ;ssid=1, , " + turnOnOff: + name: turnOnOff + title: Turn on/off + summary: Command a particular streetlight to turn the lights on or off. + payload: + $ref: "#/components/schemas/turnOnOffPayload" + headers: + type: object + properties: + $ref: '#/components/schemas/streamHeaders' + dimLight: + name: dimLight + title: Dim light + summary: Command a particular streetlight to dim the lights. + correlationId: + $ref: "#/components/correlationIds/sentAtCorrelator" + externalDocs: + url: "https://www.asyncapi.com/" + tags: + - name: operation-tag1 + externalDocs: + description: External docs description 1 + url: https://www.asyncapi.com/ + - name: operation-tag2 + description: Description 2 + externalDocs: + url: "https://www.asyncapi.com/" + - name: operation-tag3 + - name: operation-tag4 + description: Description 4 + - name: operation-tag5 + externalDocs: + url: "https://www.asyncapi.com/" + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: "#/components/schemas/dimLightPayload" + + schemas: + lightMeasuredPayload: + $id: "#lightMeasuredID" + type: object + properties: + lumens: + type: integer + description: Light intensity measured in lumens. + writeOnly: true + oneOf: + - type: integer + minimum: 0 + maximum: 5 + - minimum: 10 + maximum: 20 + externalDocs: + url: "https://www.asyncapi.com/" + sentAt: + $ref: "#/components/schemas/sentAt" + location: + type: string + pattern: "^[A-Z]{2}-[0-9]{5}$" # This will match patterns like "NY-12345" + description: Location code for the streetlight following format XX-12345 + ifElseThen: + type: integer + minimum: 1 + maximum: 1000 + if: + minimum: 100 + then: + multipleOf: 100 + else: + if: + minimum: 10 + then: + multipleOf: 10 + dependencies: + $ref: "#/components/schemas/dependenciesObject" + anySchema: true + cannotBeDefined: false + restrictedAny: + minimum: 1 + maximum: 1000 + required: + - lumens + x-schema-extensions-as-object: + type: object + properties: + prop1: + type: string + prop2: + type: integer + minimum: 0 + x-schema-extensions-as-primitive: dummy + x-schema-extensions-as-array: + - "item1" + - "item2" + turnOnOffPayload: + type: object + properties: + command: + type: string + enum: + - on + - off + description: Whether to turn on or off the light. + sentAt: + $ref: "#/components/schemas/sentAt" + arrayRank: + $ref: '#/components/schemas/arrayRank' + additionalProperties: + type: string + + dimLightPayload: + type: object + properties: + percentage: + type: integer + description: Percentage to which the light should be dimmed to. + minimum: 0 + maximum: 100 + readOnly: true + sentAt: + $ref: "#/components/schemas/sentAt" + key: + type: integer + not: + minimum: 3 + patternProperties: + ^S_: + type: string + ^I_: + type: integer + additionalProperties: false + sentAt: + type: string + format: date-time + description: Date and time when the message was sent. + union: + type: [string, number] + objectWithKey: + title: objectWithKey + type: object + propertyNames: + format: email + properties: + key: + type: string + objectWithKey2: + type: object + properties: + key2: + type: string + format: time + oneOfSchema: + oneOf: + - $ref: "#/components/schemas/objectWithKey" + - $ref: "#/components/schemas/objectWithKey2" + anyOfSchema: + anyOf: + - $ref: "#/components/schemas/objectWithKey" + - $ref: "#/components/schemas/objectWithKey2" + allOfSchema: + allOf: + - $ref: "#/components/schemas/objectWithKey" + - $ref: "#/components/schemas/objectWithKey2" + arrayContains: + type: array + contains: + type: integer + dependenciesObject: + type: object + properties: + name: + type: string + credit_card: + type: integer + billing_address: + type: string + schema_dependency: + type: string + required: + - name + dependencies: + credit_card: + properties: + billing_address: + type: string + billing_address2: + type: string + required: + - billing_address + dependencies: + billing_address2: + properties: + billing_address3: + type: string + required: + - billing_address3 + + subscriptionStatus: + type: object + oneOf: + - properties: + channelID: + type: integer + description: ChannelID on successful subscription, applicable to public messages only. + channelName: + type: string + description: Channel Name on successful subscription. For payloads 'ohlc' and 'book', respective interval or depth will be added as suffix. + - properties: + errorMessage: + type: string + properties: + event: + type: string + const: subscriptionStatus + subscription: + type: object + properties: + depth: + type: string + interval: + type: string + required: + - name + required: + - event + + arrayRank: + type: object + properties: + valueRank: + $ref: '#/components/schemas/arrayValueRank' + arrayDimensions: + $ref: '#/components/schemas/arrayArrayDimensions' + + arrayValueRank: + description: > + This Attribute indicates whether the val Attribute of the datapoint is an + array and how many dimensions the array has. + type: integer + default: -1 + examples: + - 2 + oneOf: + - const: -1 + description: 'Scalar: The value is not an array.' + - const: 0 + description: 'OneOrMoreDimensions: The value is an array with one or more dimensions.' + - const: 1 + description: 'OneDimension: The value is an array with one dimension.' + - const: 2 + description: 'The value is an array with two dimensions.' + + arrayArrayDimensions: + type: array + items: + type: integer + minimum: 0 + examples: + - [3, 5] + + streamHeaders: + Etag: + type: string + description: | + The RFC7232 ETag header field in a response provides the current entity- + tag for the selected resource. An entity-tag is an opaque identifier for + different versions of a resource over time, regardless whether multiple + versions are valid at the same time. An entity-tag consists of an opaque + quoted string, possibly prefixed by a weakness indicator. + example: 411a + Cache-Control: + description: The Cache-Control HTTP header holds directives (instructions) for caching in request. + type: string + example: no-cache, no-store, must-revalidate + + securitySchemes: + apiKey: + type: apiKey + in: user + description: Provide your API key as the user and leave the password empty. + supportedOauthFlows: + type: oauth2 + description: Flows to support OAuth 2.0 + flows: + implicit: + authorizationUrl: 'https://authserver.example/auth' + scopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + password: + tokenUrl: 'https://authserver.example/token' + scopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + clientCredentials: + tokenUrl: 'https://authserver.example/token' + scopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + authorizationCode: + authorizationUrl: 'https://authserver.example/auth' + tokenUrl: 'https://authserver.example/token' + refreshUrl: 'https://authserver.example/refresh' + scopes: + 'streetlights:on': Ability to switch lights on + 'streetlights:off': Ability to switch lights off + 'streetlights:dim': Ability to dim the lights + openIdConnectWellKnown: + type: openIdConnect + openIdConnectUrl: 'https://authserver.example/.well-known' + + parameters: + streetlightId: + description: The ID of the streetlight. + schema: + type: string + location: "$message.payload#/user/id" + Location: + description: The physical location coordinates of the streetlight + schema: + type: object + properties: + latitude: + type: number + minimum: -90 + maximum: 90 + longitude: + type: number + minimum: -180 + maximum: 180 + required: + - latitude + - longitude + + correlationIds: + sentAtCorrelator: + description: Data from message payload used as correlation ID + location: $message.payload#/sentAt + + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + required: + - my-app-header + + operationTraits: + kafka: + bindings: + kafka: + clientId: my-app-id +`;