From 9fefcee4f655830e7f09ed58e4aaec22889cbe16 Mon Sep 17 00:00:00 2001 From: Ricardo M Date: Fri, 1 Mar 2024 19:00:19 +0100 Subject: [PATCH] feat(sourcecode): Serialize URI string Currently, only the component name gets serialized into the URI string, the remaining parameters ar serialized in the parameters dictionary as follows: ```yaml - from: uri: timer parameters: period: "1000" timerName: template ``` While this works for the Camel CLI, CamelK doesn't support this schema at the moment, causing the deployment to fail. This commit write the path parameters into the URI string, in order to support CamelK deployments, f.i.: ```yaml - from: uri: timer:template parameters: period: "1000" ``` fixes: https://github.com/KaotoIO/kaoto-next/issues/884 --- .../abstract-camel-visual-entity.test.ts | 18 ++ .../flows/abstract-camel-visual-entity.ts | 3 +- .../flows/kamelet-visual-entity.ts | 2 - .../camel-component-schema.service.test.ts | 48 +++- .../support/camel-component-schema.service.ts | 39 +++- .../ui/src/utils/camel-uri-helper.test.ts | 206 +++++++++++++++++- packages/ui/src/utils/camel-uri-helper.ts | 123 ++++++++++- 7 files changed, 424 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.test.ts index b27047678..3c186dd5f 100644 --- a/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.test.ts @@ -6,6 +6,7 @@ import { CamelCatalogService } from './camel-catalog.service'; import { CatalogKind } from '../../catalog-kind'; import { ICamelComponentDefinition } from '../../camel-components-catalog'; import { ICamelProcessorDefinition } from '../../camel-processors-catalog'; +import { CamelComponentSchemaService } from './support/camel-component-schema.service'; describe('AbstractCamelVisualEntity', () => { let abstractVisualEntity: CamelRouteVisualEntity; @@ -52,4 +53,21 @@ describe('AbstractCamelVisualEntity', () => { expect(result).toEqual('1 required parameter is not yet configured: [ uri ]'); }); }); + + describe('updateModel', () => { + it('should update the model with the new value', () => { + const newUri = 'timer:MyTimer'; + abstractVisualEntity.updateModel('from', { uri: newUri }); + + expect(abstractVisualEntity.route.from.uri).toEqual(newUri); + }); + + it('should delegate the serialization to the `CamelComponentSchemaService`', () => { + const newUri = 'timer:MyTimer'; + const spy = jest.spyOn(CamelComponentSchemaService, 'getUriSerializedDefinition'); + abstractVisualEntity.updateModel('from', { uri: newUri }); + + expect(spy).toHaveBeenCalledWith('from', { uri: newUri }); + }); + }); }); diff --git a/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts b/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts index ff1c991ab..d1a7a47a5 100644 --- a/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts @@ -69,8 +69,9 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity updateModel(path: string | undefined, value: unknown): void { if (!path) return; + const updatedValue = CamelComponentSchemaService.getUriSerializedDefinition(path, value); - setValue(this.route, path, value); + setValue(this.route, path, updatedValue); } /** diff --git a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts index 1a199973b..26367d776 100644 --- a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts @@ -45,8 +45,6 @@ export class KameletVisualEntity extends AbstractCamelVisualEntity { } updateModel(path: string | undefined, value: Record): void { - if (!path) return; - if (path === ROOT_PATH) { updateKameletFromCustomSchema(this.kamelet, value); return; diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts index 5e7e2b944..ee0778691 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts @@ -6,7 +6,7 @@ import { CamelComponentSchemaService } from './camel-component-schema.service'; import { ICamelComponentDefinition } from '../../../camel-components-catalog'; import { ICamelProcessorDefinition } from '../../../camel-processors-catalog'; import { IKameletDefinition } from '../../../kamelets-catalog'; -import { ROOT_PATH } from '../../../../utils'; +import { CamelUriHelper, ROOT_PATH } from '../../../../utils'; describe('CamelComponentSchemaService', () => { let path: string; @@ -463,6 +463,52 @@ describe('CamelComponentSchemaService', () => { }); }); + describe('getUriSerializedDefinition', () => { + it('should return the same parameters if the definition is not a component', () => { + const definition = { log: { message: 'Hello World' } }; + const result = CamelComponentSchemaService.getUriSerializedDefinition('from', definition); + + expect(result).toEqual(definition); + }); + + it('should return the same parameters if the component is not found', () => { + const definition = { uri: 'unknown-component' }; + const result = CamelComponentSchemaService.getUriSerializedDefinition('from', definition); + + expect(result).toEqual(definition); + }); + + it('should query the catalog service and generate the required parameters array', () => { + const definition = { uri: 'log', parameters: { message: 'Hello World' } }; + const catalogServiceSpy = jest.spyOn(CamelCatalogService, 'getCatalogLookup'); + const camelUriHelperSpy = jest.spyOn(CamelUriHelper, 'getUriStringFromParameters'); + + CamelComponentSchemaService.getUriSerializedDefinition('from', definition); + + expect(catalogServiceSpy).toHaveBeenCalledWith('log'); + expect(camelUriHelperSpy).toHaveBeenCalledWith(definition.uri, 'log:loggerName', definition.parameters, { + requiredParameters: ['loggerName'], + defaultValues: { + groupActiveOnly: 'true', + level: 'INFO', + maxChars: 10000, + showBody: true, + showBodyType: true, + showCachedStreams: true, + skipBodyLineSeparator: true, + style: 'Default', + }, + }); + }); + + it('should return the serialized definition', () => { + const definition = { uri: 'timer', parameters: { timerName: 'MyTimer', delay: '1000', repeatCount: 10 } }; + const result = CamelComponentSchemaService.getUriSerializedDefinition('from', definition); + + expect(result).toEqual({ uri: 'timer:MyTimer', parameters: { delay: '1000', repeatCount: 10 } }); + }); + }); + describe('getComponentNameFromUri', () => { it('should return undefined if the uri is empty', () => { const componentName = CamelComponentSchemaService.getComponentNameFromUri(''); diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts index 23592f26b..0bdc778b0 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts @@ -1,6 +1,6 @@ import { ProcessorDefinition } from '@kaoto-next/camel-catalog/types'; import cloneDeep from 'lodash/cloneDeep'; -import { CamelUriHelper, ROOT_PATH, isDefined } from '../../../../utils'; +import { CamelUriHelper, ParsedParameters, ROOT_PATH, isDefined } from '../../../../utils'; import { ICamelComponentDefinition } from '../../../camel-components-catalog'; import { CatalogKind } from '../../../catalog-kind'; import { IKameletDefinition } from '../../../kamelets-catalog'; @@ -171,6 +171,43 @@ export class CamelComponentSchemaService { return ''; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static getUriSerializedDefinition(path: string, definition: any): ParsedParameters | undefined { + const camelElementLookup = this.getCamelComponentLookup(path, definition); + if (camelElementLookup.componentName === undefined) { + return definition; + } + + const catalogLookup = CamelCatalogService.getCatalogLookup(camelElementLookup.componentName); + if ( + catalogLookup.catalogKind === CatalogKind.Component && + catalogLookup.definition?.component.syntax !== undefined + ) { + const requiredParameters: string[] = []; + const defaultValues: ParsedParameters = {}; + if (catalogLookup.definition?.properties !== undefined) { + Object.entries(catalogLookup.definition.properties).forEach(([key, value]) => { + if (value.required) requiredParameters.push(key); + if (value.defaultValue) defaultValues[key] = value.defaultValue; + }); + } + + const result = CamelUriHelper.getUriStringFromParameters( + definition.uri, + catalogLookup.definition.component.syntax, + definition.parameters, + { + requiredParameters, + defaultValues, + }, + ); + + return Object.assign({}, definition, { uri: result.uri, parameters: result.parameters }); + } + + return definition; + } + /** * If the processor is a `from` or `to` processor, we need to extract the component name from the uri property * and return both the processor name and the underlying component name to build the combined schema diff --git a/packages/ui/src/utils/camel-uri-helper.test.ts b/packages/ui/src/utils/camel-uri-helper.test.ts index 7ed1e74c2..0cb21aece 100644 --- a/packages/ui/src/utils/camel-uri-helper.test.ts +++ b/packages/ui/src/utils/camel-uri-helper.test.ts @@ -1,4 +1,4 @@ -import { CamelUriHelper } from './camel-uri-helper'; +import { CamelUriHelper, ParsedParameters } from './camel-uri-helper'; describe('CamelUriHelper', () => { describe('getUriString', () => { @@ -62,6 +62,7 @@ describe('CamelUriHelper', () => { syntax: 'aws2-eventbridge://eventbusNameOrArn', uri: 'aws2-eventbridge://arn:aws:iam::123456789012:user/johndoe', result: { eventbusNameOrArn: 'arn:aws:iam::123456789012:user/johndoe' }, + requiredParameters: ['eventbusNameOrArn'], }, { syntax: 'jms:destinationType:destinationName', @@ -81,6 +82,42 @@ describe('CamelUriHelper', () => { result: { destinationName: 'myQueue' }, requiredParameters: ['destinationName'], }, + { + syntax: 'http://httpUri', + uri: 'http://helloworld.io/api/greetings/{header.name}', + requiredParameters: ['httpUri'], + result: { httpUri: 'helloworld.io/api/greetings/{header.name}' }, + }, + { + syntax: 'https://httpUri', + uri: 'https://helloworld.io/api/greetings/{header.name}', + requiredParameters: ['httpUri'], + result: { httpUri: 'helloworld.io/api/greetings/{header.name}' }, + }, + { + syntax: 'ftp:host:port/directoryName', + uri: 'ftp:localhost:21/a/nested/directory', + requiredParameters: ['host'], + result: { host: 'localhost', port: 21, directoryName: 'a/nested/directory' }, + }, + { + syntax: 'rest-openapi:specificationUri#operationId', + uri: 'rest-openapi:afile-openapi.json#myOperationId', + requiredParameters: ['operationId'], + result: { specificationUri: 'afile-openapi.json', operationId: 'myOperationId' }, + }, + { + syntax: 'rest:method:path:uriTemplate', + uri: 'rest:::{header.name}', + requiredParameters: ['method', 'path'], + result: { uriTemplate: '{header.name}' }, + }, + { + syntax: 'rest:method:path:uriTemplate', + uri: 'rest:options:myPath:', + requiredParameters: ['method', 'path'], + result: { method: 'options', path: 'myPath' }, + }, ])( 'for an URI: `$uri`, using the syntax: `$syntax`, should return `$result`', ({ syntax, uri, result, requiredParameters }) => { @@ -102,4 +139,171 @@ describe('CamelUriHelper', () => { expect(CamelUriHelper.getParametersFromQueryString(queryString)).toEqual(result); }); }); + + describe('getUriStringFromParameters', () => { + it.each([ + { uri: 'log', syntax: 'log', parameters: {}, result: { uri: 'log', parameters: {} } }, + { + uri: 'timer', + syntax: 'timer:timerName', + parameters: undefined, + result: { uri: 'timer', parameters: undefined }, + }, + { + uri: 'timer', + syntax: 'timer:timerName', + parameters: null, + result: { uri: 'timer', parameters: null }, + }, + { + uri: 'timer', + syntax: 'timer:timerName', + parameters: {}, + result: { uri: 'timer', parameters: {} }, + }, + { + uri: 'timer:myTimer', + syntax: 'timer:timerName', + parameters: { timerName: 'myTimer' }, + result: { uri: 'timer:myTimer', parameters: {} }, + }, + { + uri: 'timer:myTimer', + syntax: 'timer:timerName', + parameters: { timerName: 'myTimer', groupDelay: 1000, groupSize: 5 }, + result: { uri: 'timer:myTimer', parameters: { groupDelay: 1000, groupSize: 5 } }, + }, + { uri: 'as2', syntax: 'as2:apiName/methodName', parameters: {}, result: { uri: 'as2:/', parameters: {} } }, + { + uri: 'as2', + syntax: 'activemq:destinationType:destinationName', + parameters: { destinationType: 'queue', destinationName: 'myQueue' }, + result: { uri: 'activemq:queue:myQueue', parameters: {} }, + }, + { + uri: 'as2:CLIENT/GET', + syntax: 'as2:apiName/methodName', + parameters: { + apiName: 'CLIENT', + methodName: 'GET', + }, + result: { uri: 'as2:CLIENT/GET', parameters: {} }, + }, + { + uri: 'atmosphere-websocket', + syntax: 'atmosphere-websocket:servicePath', + parameters: { servicePath: '//localhost:8080/echo' }, + result: { uri: 'atmosphere-websocket://localhost:8080/echo', parameters: {} }, + }, + { + uri: 'avro', + syntax: 'avro:transport:host:port/messageName', + parameters: { transport: 'netty', host: 'localhost', port: 41414, messageName: 'foo' }, + result: { uri: 'avro:netty:localhost:41414/foo', parameters: {} }, + }, + { + uri: 'avro', + syntax: 'avro:transport:host:port/messageName', + parameters: {}, + result: { uri: 'avro:::/', parameters: {} }, + }, + { + uri: 'aws2-eventbridge', + syntax: 'aws2-eventbridge://eventbusNameOrArn', + parameters: { eventbusNameOrArn: 'arn:aws:iam::123456789012:user/johndoe' }, + requiredParameters: ['eventbusNameOrArn'], + result: { uri: 'aws2-eventbridge://arn:aws:iam::123456789012:user/johndoe', parameters: {} }, + }, + { + uri: 'aws2-eventbridge://arn:aws:iam::123456789012:user/johndoe', + syntax: 'aws2-eventbridge://eventbusNameOrArn', + parameters: { eventbusNameOrArn: 'arn:aws:iam::123456789012:user/johndoe' }, + requiredParameters: ['eventbusNameOrArn'], + result: { uri: 'aws2-eventbridge://arn:aws:iam::123456789012:user/johndoe', parameters: {} }, + }, + { + uri: 'jms', + syntax: 'jms:destinationType:destinationName', + parameters: { destinationType: 'queue', destinationName: 'myQueue' }, + requiredParameters: ['destinationName'], + defaultValues: { destinationType: 'queue' }, + result: { uri: 'jms:queue:myQueue', parameters: {} }, + }, + { + uri: 'jms', + syntax: 'jms:destinationType:destinationName', + parameters: { destinationName: 'myQueue' }, + requiredParameters: ['destinationName'], + defaultValues: { destinationType: 'queue' }, + result: { uri: 'jms:queue:myQueue', parameters: {} }, + }, + { + uri: 'jms:myQueue', + syntax: 'jms:destinationType:destinationName', + parameters: { destinationName: 'myQueue' }, + requiredParameters: ['destinationName'], + defaultValues: { destinationType: 'queue' }, + result: { uri: 'jms:queue:myQueue', parameters: {} }, + }, + { + uri: 'http', + syntax: 'http://httpUri', + parameters: { httpUri: 'helloworld.io/api/greetings/{header.name}' }, + requiredParameters: ['httpUri'], + result: { uri: 'http://helloworld.io/api/greetings/{header.name}', parameters: {} }, + }, + { + uri: 'https', + syntax: 'https://httpUri', + parameters: { httpUri: 'helloworld.io/api/greetings/{header.name}' }, + requiredParameters: ['httpUri'], + result: { uri: 'https://helloworld.io/api/greetings/{header.name}', parameters: {} }, + }, + { + uri: 'https', + syntax: 'https://httpUri', + parameters: { httpUri: 'https://helloworld.io/api/greetings/{header.name}' }, + requiredParameters: ['httpUri'], + result: { uri: 'https://helloworld.io/api/greetings/{header.name}', parameters: {} }, + }, + { + uri: 'ftp', + syntax: 'ftp:host:port/directoryName', + parameters: { host: 'localhost', port: 21, directoryName: 'a/nested/directory' }, + requiredParameters: ['host'], + result: { uri: 'ftp:localhost:21/a/nested/directory', parameters: {} }, + }, + { + uri: 'rest-openapi', + syntax: 'rest-openapi:specificationUri#operationId', + parameters: { specificationUri: 'afile-openapi.json', operationId: 'myOperationId' }, + requiredParameters: ['operationId'], + result: { uri: 'rest-openapi:afile-openapi.json#myOperationId', parameters: {} }, + }, + { + uri: 'rest', + syntax: 'rest:method:path:uriTemplate', + parameters: { uriTemplate: '{header.name}' }, + requiredParameters: ['method', 'path'], + result: { uri: 'rest:::{header.name}', parameters: {} }, + }, + { + uri: 'rest', + syntax: 'rest:method:path:uriTemplate', + parameters: { method: 'options', path: 'myPath' }, + requiredParameters: ['method', 'path'], + result: { uri: 'rest:options:myPath:', parameters: {} }, + }, + ])( + 'should return `$result` for `$parameters`', + ({ uri, syntax, parameters, requiredParameters, defaultValues, result }) => { + expect( + CamelUriHelper.getUriStringFromParameters(uri, syntax, parameters as unknown as ParsedParameters, { + requiredParameters, + defaultValues, + }), + ).toEqual(result); + }, + ); + }); }); diff --git a/packages/ui/src/utils/camel-uri-helper.ts b/packages/ui/src/utils/camel-uri-helper.ts index 96c17fe9d..25e50d37a 100644 --- a/packages/ui/src/utils/camel-uri-helper.ts +++ b/packages/ui/src/utils/camel-uri-helper.ts @@ -1,13 +1,18 @@ import get from 'lodash/get'; import { getParsedValue } from './get-parsed-value'; +import { isDefined } from './is-defined'; -type ParsedParameters = Record; +export type ParsedParameters = Record; /** * Helper class for working with Camel URIs */ export class CamelUriHelper { - private static readonly URI_SEPARATORS_REGEX = /:|(\/\/)|\//g; + private static readonly URI_SEPARATORS_REGEX = /:|(\/\/)|#|\//g; + private static readonly KNOWN_URI_MAP: Record = { + 'http://httpUri': 'http://', + 'https://httpUri': 'https://', + }; static getUriString(value: T | undefined | null): string | undefined { /** For string-based processor definitions, we can return the definition itself */ @@ -34,20 +39,17 @@ export class CamelUriHelper { /** If `:` is not present in the syntax, we can return an empty object since there's nothing to parse */ if (!uriSyntax || !uriString || !uriSyntax.includes(':')) return {}; - /** Remove the scheme from the URI syntax: 'avro:transport:host:port/messageName' => 'transport:host:port/messageName' */ - const syntaxWithoutScheme = uriSyntax.substring(uriSyntax.indexOf(':') + 1); - /** Validate that the actual URI contains the correct schema, otherwise return empty object since we could be validating the wrong URI */ if (!uriString.startsWith(uriSyntax.substring(0, uriSyntax.indexOf(':')))) return {}; - /** Remove the scheme from the URI string: 'avro:netty:localhost:41414/foo' => 'netty:localhost:41414/foo' */ - const uriWithoutScheme = uriString.substring(uriSyntax.indexOf(':') + 1); - /** Prepare options */ const requiredParameters = options?.requiredParameters ?? []; /** Holder for parsed parameters */ const parameters: ParsedParameters = {}; + const syntaxWithoutScheme = this.getSyntaxWithoutSchema(uriSyntax).syntax; + const uriWithoutScheme = this.getUriWithoutScheme(uriString, uriSyntax); + /** * Retrieve the delimiters from the syntax by matching the delimiters * Example: 'transport:host:port/messageName' => [':', ':', '/'] @@ -85,7 +87,13 @@ export class CamelUriHelper { } keys.forEach((key, index) => { - const parsedValue = getParsedValue(values[index]); + let parsedValue = getParsedValue(values[index]); + const isLastItem = index === keys.length - 1; + /** If the values length is greater than the keys length, we need to join the remaining values, f.i. ftp component */ + if (isLastItem && values.length > keys.length) { + parsedValue = getParsedValue(values.slice(index).join(delimiters[index - 1])); + } + if (key !== '' && parsedValue !== '') { parameters[key] = parsedValue; } @@ -108,4 +116,101 @@ export class CamelUriHelper { }, {} as ParsedParameters) ?? {} ); } + + /** + * Write the appropriate parameters in the URI string, and + * and keep the parameters that are not present in the URI syntax + */ + static getUriStringFromParameters( + originalUri: string, + uriSyntax: string, + parameters?: ParsedParameters, + options?: { requiredParameters?: string[]; defaultValues?: ParsedParameters }, + ): { uri: string; parameters: ParsedParameters | undefined } { + const { schema, syntax: syntaxWithoutScheme } = this.getSyntaxWithoutSchema(uriSyntax); + /** Prepare options */ + const requiredParameters = options?.requiredParameters ?? []; + const defaultValues = options?.defaultValues ?? {}; + + /** + * Retrieve the delimiters from the syntax by matching the delimiters + * Example: 'transport:host:port/messageName' => [':', ':', '/'] + */ + const delimiters = syntaxWithoutScheme.match(this.URI_SEPARATORS_REGEX); + this.URI_SEPARATORS_REGEX.lastIndex = 0; + + /** If the syntax does not contain any delimiters, we can return the URI string as is */ + if ( + syntaxWithoutScheme === '' || + (delimiters === null && parameters?.[syntaxWithoutScheme] === undefined) || + !isDefined(parameters) + ) { + return { uri: originalUri, parameters }; + } else if (delimiters === null) { + const value = parameters?.[syntaxWithoutScheme] ?? defaultValues[syntaxWithoutScheme] ?? ''; + const uri = `${schema}:${this.cleanUriParts(value.toString(), uriSyntax)}`; + const filteredParameters = this.filterParameters(parameters, [syntaxWithoutScheme]); + return { uri, parameters: filteredParameters }; + } + + /** Otherwise, we create a RegExp using the delimiters found [':', ':', '/'] */ + const delimitersRegex = new RegExp(delimiters.join('|'), 'g'); + + /** + * Splitting the syntax string using the delimiters + * keys: [ 'transport', 'host', 'port', 'messageName' ] + */ + const keys = syntaxWithoutScheme.split(delimitersRegex); + const uri = keys.reduce((uri, key, index) => { + let previousDelimiter = ':'; + + if (index > 0) { + const isRequiredParameter = requiredParameters.includes(key); + const isUriDelimiterTerminated = uri.endsWith(delimiters[index - 1]); + const isLastKey = index === keys.length - 1; + const shouldAddSeparator = + isRequiredParameter && !isUriDelimiterTerminated && !isLastKey && !isDefined(parameters[keys[index - 1]]); + previousDelimiter = shouldAddSeparator ? ':' : ''; + } + + const value = parameters[key] ?? defaultValues[key] ?? ''; + const cleanValue = this.cleanUriParts(value.toString(), uriSyntax); + const nextDelimiter = delimiters[index] ?? ''; + + return `${uri}${previousDelimiter}${cleanValue}${nextDelimiter}`; + }, schema); + + const filteredParameters = this.filterParameters(parameters, keys); + return { uri, parameters: filteredParameters }; + } + + /** + * Remove the scheme from the URI syntax: + * 'avro:transport:host:port/messageName' => { schema: 'avro', syntax: 'transport:host:port/messageName' } */ + private static getSyntaxWithoutSchema(uriSyntax: string): { schema: string; syntax: string } { + const splitIndex = uriSyntax.indexOf(':'); + const schema = uriSyntax.substring(0, splitIndex === -1 ? uriSyntax.length : splitIndex); + const syntax = splitIndex === -1 ? '' : uriSyntax.substring(splitIndex + 1); + return { schema, syntax }; + } + + /** Remove the scheme from the URI string: 'avro:netty:localhost:41414/foo' => 'netty:localhost:41414/foo' */ + private static getUriWithoutScheme(uriString: string, uriSyntax: string): string { + return uriString.substring(uriSyntax.indexOf(':') + 1); + } + + /** Remove parts from URI for known components, particularly for URL-relate components */ + private static cleanUriParts(uri: string, syntax: string): string { + return uri.replace(this.KNOWN_URI_MAP[syntax], ''); + } + + /** Return a new object ignoring the parameters from the `keys` array*/ + private static filterParameters(parameters: ParsedParameters, keys: string[]): ParsedParameters { + return Object.keys(parameters).reduce((acc, parameterKey) => { + if (!keys.includes(parameterKey)) { + acc[parameterKey] = parameters[parameterKey]; + } + return acc; + }, {} as ParsedParameters); + } }