Skip to content

Commit

Permalink
Feat: XML schema validation (#61)
Browse files Browse the repository at this point in the history
* removing properties from schema

* merge master

* added error message if message payload is not in JSON format

* refactored the schema validation in post and patch

* fixed dupWindow condition checking

* added node-libxml

* fixed relative path issue
  • Loading branch information
yartman authored Sep 28, 2021
1 parent 8c088b1 commit f657820
Show file tree
Hide file tree
Showing 16 changed files with 578 additions and 77 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ dist/

.idea/

index.html
index.html

apps/dsb-message-broker/xsd_files/
7 changes: 5 additions & 2 deletions apps/dsb-message-broker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
"@nestjs/platform-socket.io": "8.0.6",
"socket.io": "4.1.3",
"@energyweb/dsb-address-book-nats-js": "~0.0.1",
"ajv-formats": "2.1.1"
"ajv-formats": "2.1.1",
"node-libxml": "4.1.2",
"uuid": "~8.3.2"
},
"devDependencies": {
"@nestjs/cli": "7.6.0",
Expand All @@ -78,6 +80,7 @@
"ts-node": "9.1.1",
"typescript": "4.4.3",
"json-to-pretty-yaml": "1.2.2",
"base64url": "3.0.1"
"base64url": "3.0.1",
"@types/uuid": "~8.3.1"
}
}
2 changes: 1 addition & 1 deletion apps/dsb-message-broker/src/channel/channel.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class ChannelController {
})
public async updateChannel(
@UserDecorator() user: any,
@Body(ChannelDataPipe) updateDto: UpdateChannelDto
@Body(FqcnValidationPipe, ChannelDataPipe) updateDto: UpdateChannelDto
): Promise<string> {
try {
const result = await this.channelService.updateChannel({
Expand Down
69 changes: 46 additions & 23 deletions apps/dsb-message-broker/src/channel/channel.data.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PipeTransform, Injectable, BadRequestException, Logger } from '@nestjs/common';

import { Channel } from '@energyweb/dsb-transport-core';

Expand All @@ -8,45 +7,69 @@ import { TopicSchemaService } from '../utils/topic.schema.service';

@Injectable()
export class ChannelDataPipe implements PipeTransform<any> {
constructor(
private readonly configService: ConfigService,
private readonly topicSchemaService: TopicSchemaService
) {}
private readonly logger = new Logger(ChannelDataPipe.name);

constructor(private readonly topicSchemaService: TopicSchemaService) {}

async transform(channelData: Channel) {
/* validation of first part of fqcn */
const { channel } = extractFqcn(channelData.fqcn);

const fqcnIsValid = new RegExp('^[a-zA-Z0-9]{1,16}$').test(channel);

if (!fqcnIsValid) {
throw new BadRequestException({
statusCode: 400,
message: 'fqcn does not match the defined pattern.',
message:
'First part of the fqcn does not match the defined pattern(^[a-zA-Z0-9]{1,16}$).',
error: 'Bad Request'
});
}

channelData.topics?.forEach((topic: any, index: number) => {
let _schema: any = topic.schema;
/* validation of each topic schema */
if (channelData.topics) {
this.topicSchemaService.removeValidators(channelData.fqcn);

if (typeof _schema === 'string') _schema = JSON.parse(_schema);
for await (const [index, topic] of channelData.topics.entries()) {
try {
let _schema = topic.schema as any;
if (topic.schemaType !== 'XSD') {
if (typeof _schema === 'string') {
_schema = JSON.parse(_schema);
}
if (_schema && _schema.hasOwnProperty('$schema')) {
delete _schema['$schema'];
}
if (_schema && _schema.hasOwnProperty('$id')) {
delete _schema['$id'];
}
if (_schema && _schema.hasOwnProperty('version')) {
delete _schema['version'];
}
}
topic.schema = _schema;

if (_schema && _schema.hasOwnProperty('$schema')) delete _schema['$schema'];
if (_schema && _schema.hasOwnProperty('$id')) delete _schema['$id'];
if (_schema && _schema.hasOwnProperty('version')) delete _schema['version'];
await this.topicSchemaService.validateSchema(
channelData.fqcn,
topic.namespace,
topic.schemaType,
topic.schema
);
} catch (error) {
this.logger.error(error.message);

const isValid = this.topicSchemaService.validateSchema(_schema);
let errMsg = error.message;
if (error instanceof SyntaxError) errMsg = [errMsg];
else errMsg = JSON.parse(errMsg);

if (!isValid) {
throw new BadRequestException({
statusCode: 400,
message: `topics.${index}.schema is not valid`,
error: 'Bad Request'
});
throw new BadRequestException({
statusCode: 400,
message: [`topics.${index}.schema is not valid`, ...errMsg],
error: 'Bad Request'
});
}
}

topic.schema = JSON.stringify(_schema);
});
}

return channelData;
}
Expand Down
2 changes: 0 additions & 2 deletions apps/dsb-message-broker/src/channel/channel.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ export class ChannelService implements OnModuleInit {

const result = await this.transport.updateChannel(updatedChannel);

this.topicSchemaService.removeValidators(updateDto.fqcn);

this.addressbook.registerChannel(updatedChannel);

return result;
Expand Down
20 changes: 18 additions & 2 deletions apps/dsb-message-broker/src/channel/dto/create-channel.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@ import {
IsOptional,
ArrayNotEmpty,
ValidateNested,
Max
Max,
IsEnum
} from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { JSONSchemaType } from 'ajv';

enum SchemaType {
'JSD-7',
'XSD'
}

class Topic {
@IsDefined()
@IsString()
namespace: string;

@IsOptional()
@IsEnum(SchemaType)
schemaType: SchemaType;

@IsDefined()
schema: string | Record<string, unknown>;
}
Expand All @@ -37,15 +47,20 @@ export class CreateChannelDto {
namespace: {
type: 'string'
},
shcemaType: {
description: 'Values are JSD-7(JSON Schema Draft-7) and XSD(XML Schema Definition)',
default: 'JSD-7',
type: 'enum'
},
schema: {
type: 'string | JSONSchemaType'
}
},
required: false,
description: 'Array of topic objects that determines topics for messages.',
example: [
{
namespace: 'testTopic',
schemaType: 'JSD-7',
schema: '{"type": "object","properties": {"data": {"type": "string"}},"required": ["data"],"additionalProperties": false}'
}
]
Expand All @@ -57,6 +72,7 @@ export class CreateChannelDto {
@Type(() => Topic)
topics?: {
namespace: string;

schema: JSONSchemaType<any> | string;
}[];

Expand Down
4 changes: 4 additions & 0 deletions apps/dsb-message-broker/src/message/error.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export const HttpMessageErrorHandler = (error: any) => {
});
}

if (error instanceof BadRequestException) {
throw error;
}

if (
error instanceof UnauthorizedToPublishError ||
error instanceof UnauthorizedToSubscribeError
Expand Down
4 changes: 2 additions & 2 deletions apps/dsb-message-broker/src/message/message.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ export class MessageService implements OnModuleInit {
): Promise<string> {
this.ensureCanPublish(fqcn, pubDID, pubVRs);

const { isValid, error } = this.topicSchemaService.validate(fqcn, topic, payload);
if (!isValid) throw new PayloadNotValidError(topic, error);
const { isValid, error } = await this.topicSchemaService.validate(fqcn, topic, payload);
if (!isValid) throw new PayloadNotValidError(topic, error ?? '[]');

return this.transport.publish(
fqcn,
Expand Down
Loading

0 comments on commit f657820

Please sign in to comment.