Skip to content

Commit

Permalink
feat(restConfiguration): Add restConfiguration entity
Browse files Browse the repository at this point in the history
This commit adds support for RestConfiguration entity.

Note:
The node validation is still pending due to an issue with the
JSONSchema, please see #969 for more details.

fix: #562
relates: #969
  • Loading branch information
lordrip committed Mar 21, 2024
1 parent eff134e commit 519933e
Show file tree
Hide file tree
Showing 4 changed files with 600 additions and 1 deletion.
7 changes: 6 additions & 1 deletion packages/ui/src/models/camel/camel-route-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AddStepMode } from '../visualization/base-visual-entity';
import { CamelRouteVisualEntity, isCamelFrom, isCamelRoute } from '../visualization/flows';
import { CamelErrorHandlerVisualEntity } from '../visualization/flows/camel-error-handler-visual-entity';
import { CamelOnExceptionVisualEntity } from '../visualization/flows/camel-on-exception-visual-entity';
import { CamelRestConfigurationVisualEntity } from '../visualization/flows/camel-rest-configuration-visual-entity';
import { NonVisualEntity } from '../visualization/flows/non-visual-entity';
import { CamelComponentFilterService } from '../visualization/flows/support/camel-component-filter.service';
import { CamelRouteVisualEntityData } from '../visualization/flows/support/camel-component-types';
Expand All @@ -15,7 +16,11 @@ import { BaseCamelEntity } from './entities';
import { SourceSchemaType } from './source-schema-type';

export class CamelRouteResource implements CamelResource, BeansAwareResource {
static readonly SUPPORTED_ENTITIES = [CamelOnExceptionVisualEntity, CamelErrorHandlerVisualEntity] as const;
static readonly SUPPORTED_ENTITIES = [
CamelOnExceptionVisualEntity,
CamelErrorHandlerVisualEntity,
CamelRestConfigurationVisualEntity,
] as const;
static readonly PARAMETERS_ORDER = ['id', 'description', 'uri', 'parameters', 'steps'];
readonly sortFn = createCamelPropertiesSorter(CamelRouteResource.PARAMETERS_ORDER) as (
a: unknown,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import * as catalogIndex from '@kaoto-next/camel-catalog/index.json';
import { RestConfiguration } from '@kaoto-next/camel-catalog/types';
import { restConfigurationSchema, restConfigurationStub } from '../../../stubs/rest-configuration';
import { ICamelProcessorDefinition } from '../../camel-processors-catalog';
import { EntityType } from '../../camel/entities';
import { CatalogKind } from '../../catalog-kind';
import { CamelCatalogService } from './camel-catalog.service';
import { CamelRestConfigurationVisualEntity } from './camel-rest-configuration-visual-entity';

describe('CamelRestConfigurationVisualEntity', () => {
const REST_CONFIGURATION_ID_REGEXP = /^restConfiguration-[a-zA-Z0-9]{4}$/;
let restConfigurationDef: { restConfiguration: RestConfiguration };

beforeAll(async () => {
const entityCatalogMap = await import('@kaoto-next/camel-catalog/' + catalogIndex.catalogs.entities.file);
CamelCatalogService.setCatalogKey(
CatalogKind.Entity,
entityCatalogMap as unknown as Record<string, ICamelProcessorDefinition>,
);
});

afterAll(() => {
CamelCatalogService.clearCatalogs();
});

beforeEach(() => {
restConfigurationDef = {
restConfiguration: {
...restConfigurationStub.restConfiguration,
},
};
});

describe('isApplicable', () => {
it.each([
[true, { restConfiguration: {} }],
[true, { restConfiguration: { bindingMode: 'off' } }],
[true, restConfigurationStub],
[false, { from: { id: 'from-1234', steps: [] } }],
[false, { restConfiguration: { bindingMode: 'off' }, anotherProperty: true }],
])('should return %s for %s', (result, definition) => {
expect(CamelRestConfigurationVisualEntity.isApplicable(definition)).toEqual(result);
});
});

describe('constructor', () => {
it('should set id to generated id', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.id).toMatch(REST_CONFIGURATION_ID_REGEXP);
});
});

it('should return id', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getId()).toMatch(REST_CONFIGURATION_ID_REGEXP);
});

it('should set id', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);
const newId = 'newId';
entity.setId(newId);

expect(entity.getId()).toEqual(newId);
});

it('should return node label', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getNodeLabel()).toEqual('restConfiguration');
});

it('should return tooltip content', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getTooltipContent()).toEqual('restConfiguration');
});

describe('getComponentSchema', () => {
it('should return entity current definition', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getComponentSchema().definition).toEqual(restConfigurationDef.restConfiguration);
});

it.skip('should return schema from store', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getComponentSchema().schema).toEqual(restConfigurationSchema);
});

it('should return hardcoded schema title', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getComponentSchema().title).toEqual('Rest Configuration');
});
});

describe('updateModel', () => {
it('should update model', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);
const path = 'restConfiguration.bindingMode';
const value = 'json';

entity.updateModel(path, value);

expect(restConfigurationDef.restConfiguration.bindingMode).toEqual(value);
});

it('should not update model if path is not defined', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);
const value = 'json_xml';

entity.updateModel(undefined, value);

expect(restConfigurationDef.restConfiguration.bindingMode).toEqual('off');
});

it('should reset the restConfiguration object if it is not defined', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

entity.updateModel('restConfiguration', {});

expect(restConfigurationDef.restConfiguration).toEqual({});
});
});

it('return no interactions', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.getNodeInteraction()).toEqual({
canHavePreviousStep: false,
canHaveNextStep: false,
canHaveChildren: false,
canHaveSpecialChildren: false,
canRemoveStep: false,
canReplaceStep: false,
});
});

/** Skip until https://github.com/KaotoIO/kaoto-next/issues/969 gets resolved */
describe.skip('getNodeValidationText', () => {
it('should return undefined for valid definitions', () => {
const entity = new CamelRestConfigurationVisualEntity({
...restConfigurationDef.restConfiguration,
useXForwardHeaders: true,
apiVendorExtension: true,
skipBindingOnErrorCode: true,
clientRequestValidation: true,
enableCORS: true,
enableNoContentResponse: true,
inlineRoutes: true,
});

expect(entity.getNodeValidationText()).toBeUndefined();
});

it('should not modify the original definition when validating', () => {
const originalRestConfigurationDef: RestConfiguration = { ...restConfigurationDef.restConfiguration };
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

entity.getNodeValidationText();

expect(restConfigurationDef.restConfiguration).toEqual(originalRestConfigurationDef);
});

it('should return errors when there is an invalid property', () => {
const invalidRestConfigurationDef: RestConfiguration = {
...restConfigurationDef.restConfiguration,
bindingMode: 'WildModeOn',
};
const entity = new CamelRestConfigurationVisualEntity(invalidRestConfigurationDef);

expect(entity.getNodeValidationText()).toEqual(`'/useXForwardHeaders' must be boolean,
'/apiVendorExtension' must be boolean,
'/skipBindingOnErrorCode' must be boolean,
'/clientRequestValidation' must be boolean,
'/enableCORS' must be boolean,
'/enableNoContentResponse' must be boolean,
'/inlineRoutes' must be boolean`);
});
});

describe('toVizNode', () => {
it('should return visualization node', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

const vizNode = entity.toVizNode();

expect(vizNode.data).toEqual({
componentName: undefined,
entity: {
id: entity.getId(),
restConfigurationDef,
type: EntityType.RestConfiguration,
},
icon: '',
isGroup: true,
path: 'restConfiguration',
processorName: 'restConfiguration',
});
});
});

it('should serialize the restConfiguration definition', () => {
const entity = new CamelRestConfigurationVisualEntity(restConfigurationDef);

expect(entity.toJSON()).toEqual(restConfigurationDef);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { ProcessorDefinition, RestConfiguration } from '@kaoto-next/camel-catalog/types';
import Ajv, { ValidateFunction } from 'ajv-draft-04';
import addFormats from 'ajv-formats';
import { getCamelRandomId } from '../../../camel-utils/camel-random-id';
import { isDefined, setValue } from '../../../utils';
import { EntityType } from '../../camel/entities/base-entity';
import { CatalogKind } from '../../catalog-kind';
import {
BaseVisualCamelEntity,
IVisualizationNode,
IVisualizationNodeData,
NodeInteraction,
VisualComponentSchema,
} from '../base-visual-entity';
import { CamelCatalogService } from './camel-catalog.service';
import { CamelStepsService } from './support/camel-steps.service';

export class CamelRestConfigurationVisualEntity implements BaseVisualCamelEntity {
id: string;
readonly type = EntityType.RestConfiguration;
private schemaValidator: ValidateFunction<RestConfiguration> | undefined;

constructor(public restConfigurationDef: { restConfiguration: RestConfiguration }) {
const id = getCamelRandomId('restConfiguration');
this.id = id;
}

static isApplicable(restConfigurationDef: unknown): restConfigurationDef is { restConfiguration: RestConfiguration } {
if (
!isDefined(restConfigurationDef) ||
Array.isArray(restConfigurationDef) ||
typeof restConfigurationDef !== 'object'
) {
return false;
}

const objectKeys = Object.keys(restConfigurationDef!);

return (
objectKeys.length === 1 &&
'restConfiguration' in restConfigurationDef! &&
typeof restConfigurationDef.restConfiguration === 'object'
);
}

getId(): string {
return this.id;
}

setId(id: string): void {
this.id = id;
}

getNodeLabel(): string {
return 'restConfiguration';
}

getTooltipContent(): string {
return 'restConfiguration';
}

addStep(): void {
return;
}

removeStep(): void {
return;
}

getComponentSchema(): VisualComponentSchema {
const schema = CamelCatalogService.getComponent(CatalogKind.Entity, 'restConfiguration');

return {
definition: Object.assign({}, this.restConfigurationDef.restConfiguration),
schema: schema?.propertiesSchema || {},
title: 'Rest Configuration',
};
}

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

setValue(this.restConfigurationDef, path, value);

if (!isDefined(this.restConfigurationDef.restConfiguration)) {
this.restConfigurationDef.restConfiguration = {};
}
}

getNodeInteraction(): NodeInteraction {
return {
canHavePreviousStep: false,
canHaveNextStep: false,
canHaveChildren: false,
canHaveSpecialChildren: false,
canRemoveStep: false,
canReplaceStep: false,
};
}

getNodeValidationText(): string | undefined {
const componentVisualSchema = this.getComponentSchema();
if (!componentVisualSchema) return undefined;

if (!this.schemaValidator) {
this.schemaValidator = this.getValidatorFunction(componentVisualSchema);
}

this.schemaValidator?.({ ...this.restConfigurationDef.restConfiguration });

return this.schemaValidator?.errors?.map((error) => `'${error.instancePath}' ${error.message}`).join(',\n');
}

toVizNode(): IVisualizationNode<IVisualizationNodeData> {
const restConfigurationGroupNode = CamelStepsService.getVizNodeFromProcessor(
'restConfiguration',
{ processorName: 'restConfiguration' as keyof ProcessorDefinition },
this.restConfigurationDef,
);
restConfigurationGroupNode.data.entity = this;
restConfigurationGroupNode.data.isGroup = true;

return restConfigurationGroupNode;
}

toJSON(): unknown {
return this.restConfigurationDef;
}

private getValidatorFunction(
componentVisualSchema: VisualComponentSchema,
): ValidateFunction<RestConfiguration> | undefined {
const ajv = new Ajv({
strict: false,
allErrors: true,
useDefaults: 'empty',
});
addFormats(ajv);

let schemaValidator: ValidateFunction<RestConfiguration> | undefined;
try {
schemaValidator = ajv.compile<RestConfiguration>(componentVisualSchema.schema);
} catch (error) {
console.error('Could not compile schema', error);
}

return schemaValidator;
}
}
Loading

0 comments on commit 519933e

Please sign in to comment.