Skip to content

Commit

Permalink
feat(sourcecode): Serialize URI string
Browse files Browse the repository at this point in the history
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: KaotoIO#884
  • Loading branch information
lordrip committed Mar 6, 2024
1 parent 5ffd944 commit 9fefcee
Show file tree
Hide file tree
Showing 7 changed files with 424 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ export class KameletVisualEntity extends AbstractCamelVisualEntity {
}

updateModel(path: string | undefined, value: Record<string, unknown>): void {
if (!path) return;

if (path === ROOT_PATH) {
updateKameletFromCustomSchema(this.kamelet, value);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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('');
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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
Expand Down
206 changes: 205 additions & 1 deletion packages/ui/src/utils/camel-uri-helper.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CamelUriHelper } from './camel-uri-helper';
import { CamelUriHelper, ParsedParameters } from './camel-uri-helper';

describe('CamelUriHelper', () => {
describe('getUriString', () => {
Expand Down Expand Up @@ -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',
Expand All @@ -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 }) => {
Expand All @@ -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);
},
);
});
});
Loading

0 comments on commit 9fefcee

Please sign in to comment.