Skip to content

Commit

Permalink
feat: new options to generate nice $id (asyncapi#516)
Browse files Browse the repository at this point in the history
This should be used when using it with a code generator like modelina
  • Loading branch information
GreenRover committed May 4, 2022
1 parent b382043 commit c376570
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 15 deletions.
39 changes: 35 additions & 4 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
</dd>
</dl>

## Functions

<dl>
<dt><a href="#ucFirst">ucFirst(word)</a></dt>
<dd><p>returns a string with the first character of string capitalized, if that character is alphabetic.</p>
</dd>
</dl>

## Typedefs

<dl>
Expand Down Expand Up @@ -494,6 +502,7 @@
* [.ext(key)](#module_@asyncapi/parser+Schema+ext) ⇒ <code>any</code>
* [~parse(asyncapiYAMLorJSON, [options])](#module_@asyncapi/parser..parse) ⇒ <code>Promise.&lt;AsyncAPIDocument&gt;</code>
* [~parseFromUrl(url, [fetchOptions], [options])](#module_@asyncapi/parser..parseFromUrl) ⇒ <code>Promise.&lt;AsyncAPIDocument&gt;</code>
* [~generateIdsInSpec(key, spec)](#module_@asyncapi/parser..generateIdsInSpec)
* [~registerSchemaParser(parserModule)](#module_@asyncapi/parser..registerSchemaParser)
* [~ParserOptions](#module_@asyncapi/parser..ParserOptions) : <code>Object</code>

Expand Down Expand Up @@ -3000,8 +3009,7 @@ Implements functions to deal with the AsyncAPI document.
<a name="module_@asyncapi/parser+AsyncAPIDocument+traverseSchemas"></a>

#### asyncAPIDocument.traverseSchemas(callback, schemaTypesToIterate)
Traverse schemas in the document and select which types of schemas to include.
By default all schemas are iterated
Traverse schemas in the document and select which types of schemas to include.By default all schemas are iterated

**Kind**: instance method of [<code>AsyncAPIDocument</code>](#module_@asyncapi/parser+AsyncAPIDocument)

Expand Down Expand Up @@ -3223,8 +3231,7 @@ By default all schemas are iterated
<a name="module_@asyncapi/parser+AsyncAPIDocument.stringify"></a>

#### AsyncAPIDocument.stringify(doc, [space]) ⇒ <code>string</code>
Converts a valid AsyncAPI document to a JavaScript Object Notation (JSON) string.
A stringified AsyncAPI document using this function should be parsed via the AsyncAPIDocument.parse() function - the JSON.parse() function is not compatible.
Converts a valid AsyncAPI document to a JavaScript Object Notation (JSON) string.A stringified AsyncAPI document using this function should be parsed via the AsyncAPIDocument.parse() function - the JSON.parse() function is not compatible.

**Kind**: static method of [<code>AsyncAPIDocument</code>](#module_@asyncapi/parser+AsyncAPIDocument)

Expand Down Expand Up @@ -3758,6 +3765,18 @@ Fetches an AsyncAPI document from the given URL and passes its content to the `p
| [fetchOptions] | <code>Object</code> | Configuration to pass to the [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Request) call. |
| [options] | <code>ParserOptions</code> | Configuration to pass to the [ParserOptions](#asyncapiparserparseroptions--object) method. |

<a name="module_@asyncapi/parser..generateIdsInSpec"></a>

### @asyncapi/parser~generateIdsInSpec(key, spec)
Recursive iterate thorug the json schema spec and add $id where ever it is not set.

**Kind**: inner method of [<code>@asyncapi/parser</code>](#module_@asyncapi/parser)

| Param | Type | Description |
| --- | --- | --- |
| key | <code>String</code> | The name of the parent element |
| spec | <code>Object</code> | The JS of a json schema spec. |

<a name="module_@asyncapi/parser..registerSchemaParser"></a>

### @asyncapi/parser~registerSchemaParser(parserModule)
Expand All @@ -3783,6 +3802,7 @@ The complete list of parse configuration options used to parse the given data.
| [parse] | <code>Object</code> | Options object to pass to [json-schema-ref-parser](https://apitools.dev/json-schema-ref-parser/docs/options.html). |
| [resolve] | <code>Object</code> | Options object to pass to [json-schema-ref-parser](https://apitools.dev/json-schema-ref-parser/docs/options.html). |
| [applyTraits] | <code>Boolean</code> | Whether to resolve and apply traits or not. Defaults to true. |
| [genererateIdInSchema] | <code>Boolean</code> | Genereate speaking $id where everver not given by schema. |

<a name="MixinBindings"></a>

Expand Down Expand Up @@ -3979,6 +3999,17 @@ Implements functions to deal with the Tags object.
| --- | --- | --- |
| name | <code>string</code> | Name of the tag. |

<a name="ucFirst"></a>

## ucFirst(word)
returns a string with the first character of string capitalized, if that character is alphabetic.

**Kind**: global function

| Param | Type |
| --- | --- |
| word | <code>String</code> |

<a name="SchemaIteratorCallbackType"></a>

## SchemaIteratorCallbackType
Expand Down
2 changes: 1 addition & 1 deletion dist/bundle.js

Large diffs are not rendered by default.

62 changes: 54 additions & 8 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const $RefParser = require('@apidevtools/json-schema-ref-parser');
const mergePatch = require('tiny-merge-patch').apply;
const ParserError = require('./errors/parser-error');
const { validateChannels, validateTags, validateServerVariables, validateOperationId, validateServerSecurity, validateMessageId } = require('./customValidators.js');
const { toJS, findRefs, getLocationOf, improveAjvErrors, getDefaultSchemaFormat } = require('./utils');
const { toJS, findRefs, getLocationOf, improveAjvErrors, getDefaultSchemaFormat, ucFirst } = require('./utils');
const AsyncAPIDocument = require('./models/asyncapi');

const OPERATIONS = ['publish', 'subscribe'];
Expand Down Expand Up @@ -42,6 +42,7 @@ module.exports = {
* @property {Object=} parse - Options object to pass to {@link https://apitools.dev/json-schema-ref-parser/docs/options.html|json-schema-ref-parser}.
* @property {Object=} resolve - Options object to pass to {@link https://apitools.dev/json-schema-ref-parser/docs/options.html|json-schema-ref-parser}.
* @property {Boolean=} applyTraits - Whether to resolve and apply traits or not. Defaults to true.
* @property {Boolean=} genererateIdInSchema - Genereate speaking $id where everver not given by schema.
*/

/**
Expand All @@ -67,15 +68,15 @@ async function parse(asyncapiYAMLorJSON, options = {}) {
detail: 'Most probably the AsyncAPI document contains invalid YAML or YAML features not supported in JSON.'
});
}

if (!parsedJSON.asyncapi) {
throw new ParserError({
type: 'missing-asyncapi-field',
title: 'The `asyncapi` field is missing.',
parsedJSON,
});
}

if (parsedJSON.asyncapi.startsWith('1.') || !asyncapi[parsedJSON.asyncapi]) {
throw new ParserError({
type: 'unsupported-version',
Expand All @@ -87,6 +88,13 @@ async function parse(asyncapiYAMLorJSON, options = {}) {
}

if (options.applyTraits === undefined) options.applyTraits = true;
if (options.genererateIdInSchema === undefined) options.genererateIdInSchema = false;

if (parsedJSON.components && parsedJSON.components.schemas && options.genererateIdInSchema) {
for (const [key, value] of Object.entries(parsedJSON.components.schemas)) {
generateIdsInSpec(key, value);
}
}

const refParser = new $RefParser;
//because of Ajv lacks support for circular refs, parser should not resolve them before Ajv validation and first needs to ignore them and leave circular $refs to successfully validate the document
Expand Down Expand Up @@ -192,6 +200,44 @@ function getValidator(version) {
return validate;
}

/**
* Recursive iterate thorug the json schema spec and add $id where ever it is not set.
*
* @param {String} key The name of the parent element
* @param {Object} spec The JS of a json schema spec.
*/
function generateIdsInSpec(key, spec) {
if (!spec.type ||
spec.type === 'object' ||
spec.type === 'array' ||
(spec.type === 'string' && spec.enum)
) {
if (!spec.$id && !spec.$ref) {
spec.$id = key;
}

if (spec.properties) {
for (const [k, value] of Object.entries(spec.properties)) {
generateIdsInSpec(key + ucFirst(k), value);
}
}

if (spec.type === 'array' && spec.items) {
if (spec.$id === key) {
//You want the shorter on the child, this is used by the code generator to define the class name.
spec.$id += 'Array';
}
generateIdsInSpec(key, spec.items);
}

if (spec.allOf) {
for (const child of spec.allOf) {
generateIdsInSpec(key, child);
}
}
}
}

async function customDocumentOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options) {
validateServerVariables(parsedJSON, asyncapiYAMLorJSON, initialFormat);
validateServerSecurity(parsedJSON, asyncapiYAMLorJSON, initialFormat, SPECIAL_SECURITY_TYPES);
Expand Down Expand Up @@ -219,7 +265,7 @@ async function validateAndConvertMessage(msg, originalAsyncAPIDocument, fileForm
defaultSchemaFormat,
originalAsyncAPIDocument,
parsedAsyncAPIDocument,
fileFormat,
fileFormat,
pathToPayload
});

Expand All @@ -233,9 +279,9 @@ async function validateAndConvertMessage(msg, originalAsyncAPIDocument, fileForm
* @param {Object} parserModule The schema parser module containing parse() and getMimeTypes() functions.
*/
function registerSchemaParser(parserModule) {
if (typeof parserModule !== 'object'
|| typeof parserModule.parse !== 'function'
|| typeof parserModule.getMimeTypes !== 'function')
if (typeof parserModule !== 'object'
|| typeof parserModule.parse !== 'function'
|| typeof parserModule.getMimeTypes !== 'function')
throw new ParserError({
type: 'impossible-to-register-parser',
title: 'parserModule must have parse() and getMimeTypes() functions.'
Expand Down Expand Up @@ -277,7 +323,7 @@ async function customChannelsOperations(parsedJSON, asyncapiYAMLorJSON, initialF
if (!op) return;

const messages = op.message ? (op.message.oneOf || [op.message]) : [];
if (options.applyTraits) {
if (options.applyTraits) {
applyTraits(op);
messages.forEach(m => applyTraits(m));
}
Expand Down
10 changes: 10 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,13 @@ utils.getUnknownServers = (parsedJSON, channel) => {
utils.getDefaultSchemaFormat = (asyncapiVersion) => {
return `application/vnd.aai.asyncapi;version=${asyncapiVersion}`;
};

/**
* returns a string with the first character of string capitalized, if that character is alphabetic.
*
* @function ucFirst
* @param {String} word
*/
utils.ucFirst = (word) => {
return word.charAt(0).toUpperCase() + word.slice(1);
};
17 changes: 15 additions & 2 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ declare module "@asyncapi/parser" {
hasCircular(): boolean;
/**
* Traverse schemas in the document and select which types of schemas to include.
* By default all schemas are iterated
By default all schemas are iterated
*/
traverseSchemas(callback: TraverseSchemas, schemaTypesToIterate: SchemaTypesToIterate[]): void;
/**
* Converts a valid AsyncAPI document to a JavaScript Object Notation (JSON) string.
* A stringified AsyncAPI document using this function should be parsed via the AsyncAPIDocument.parse() function - the JSON.parse() function is not compatible.
A stringified AsyncAPI document using this function should be parsed via the AsyncAPIDocument.parse() function - the JSON.parse() function is not compatible.
* @param doc - A valid AsyncAPIDocument instance.
* @param [space] - Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
*/
Expand Down Expand Up @@ -1176,12 +1176,14 @@ declare module "@asyncapi/parser" {
* @property [parse] - Options object to pass to {@link https://apitools.dev/json-schema-ref-parser/docs/options.html|json-schema-ref-parser}.
* @property [resolve] - Options object to pass to {@link https://apitools.dev/json-schema-ref-parser/docs/options.html|json-schema-ref-parser}.
* @property [applyTraits] - Whether to resolve and apply traits or not. Defaults to true.
* @property [genererateIdInSchema] - Genereate speaking $id where everver not given by schema.
*/
type ParserOptions = {
path?: string;
parse?: any;
resolve?: any;
applyTraits?: boolean;
genererateIdInSchema?: boolean;
};
/**
* Parses and validate an AsyncAPI document from YAML or JSON.
Expand All @@ -1198,10 +1200,21 @@ declare module "@asyncapi/parser" {
* @returns The parsed AsyncAPI document.
*/
function parseFromUrl(url: string, fetchOptions?: any, options?: ParserOptions): Promise<AsyncAPIDocument>;
/**
* Recursive iterate thorug the json schema spec and add $id where ever it is not set.
* @param key - The name of the parent element
* @param spec - The JS of a json schema spec.
*/
function generateIdsInSpec(key: string, spec: any): void;
/**
* Registers a new schema parser. Schema parsers are in charge of parsing and transforming payloads to AsyncAPI Schema format.
* @param parserModule - The schema parser module containing parse() and getMimeTypes() functions.
*/
function registerSchemaParser(parserModule: any): void;
}

/**
* returns a string with the first character of string capitalized, if that character is alphabetic.
*/
declare function ucFirst(word: string): void;

0 comments on commit c376570

Please sign in to comment.