Skip to content

Commit 30c0cb3

Browse files
committed
rework
1 parent 712db4e commit 30c0cb3

File tree

4 files changed

+91
-79
lines changed

4 files changed

+91
-79
lines changed

package-lock.json

+17-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"typescript": "^4.6.2"
5050
},
5151
"dependencies": {
52+
"@asyncapi/specs": "^3.1.0",
5253
"@stoplight/spectral-core": "^1.10.1",
5354
"@stoplight/spectral-functions": "^1.5.1",
5455
"@stoplight/spectral-parsers": "^1.0.1",

src/schema-parser/asyncapi-schema-parser.ts

+64-49
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { SchemaParser, ParseSchemaInput, ValidateSchemaInput } from "../schema-parser";
2-
import Ajv from "ajv";
3-
import { JSONSchema7 } from "json-schema"
2+
import Ajv, { ErrorObject, ValidateFunction } from "ajv";
43
import type { AsyncAPISchema, SchemaValidateResult } from '../types';
4+
// @ts-ignore
5+
import specs from '@asyncapi/specs';
56

67
const ajv = new Ajv({
78
allErrors: true,
8-
})
9+
strict: false,
10+
});
11+
12+
const specVersions = Object.keys(specs).filter((version: string) => !['1.0.0', '1.1.0', '1.2.0', '2.0.0-rc1', '2.0.0-rc2'].includes(version));
913

1014
export function AsyncAPISchemaParser(): SchemaParser {
1115
return {
@@ -16,62 +20,72 @@ export function AsyncAPISchemaParser(): SchemaParser {
1620
}
1721

1822
async function validate(input: ValidateSchemaInput<unknown, unknown>): Promise<SchemaValidateResult[]> {
19-
const schema = input.data as JSONSchema7;
20-
let errors: SchemaValidateResult[] = [];
21-
22-
try {
23-
ajv.compile(schema);
24-
} catch (error: any) {
25-
if (error! instanceof Error) {
26-
errors = ajvToSpectralErrors(error);
27-
} else {
28-
// Unknown and unexpected error
29-
throw error;
30-
}
23+
const version = input.asyncapi.semver.version
24+
const validator = findSchemaValidator(version);
25+
26+
let result: SchemaValidateResult[] = []
27+
const valid = validator(input.data);
28+
if (!valid && validator.errors) {
29+
result = ajvToSpectralResult(validator.errors, input.path);
3130
}
3231

33-
return errors;
32+
return result;
3433
}
3534

36-
function ajvToSpectralErrors(error: Error): SchemaValidateResult[] {
37-
let errors: SchemaValidateResult[] = [];
38-
let errorMessage = error.message;
39-
40-
// Validation errors.
41-
// See related AJV function where the error message is generated:
42-
// https://github.com/ajv-validator/ajv/blob/99e884dc4bbb828cf47771b7bbdb14f23193b0b1/lib/core.ts#L501-L522
43-
const validationErrorPrefix = "schema is invalid: ";
44-
if (error.message.startsWith(validationErrorPrefix)) {
45-
// remove prefix
46-
errorMessage = errorMessage.substring(validationErrorPrefix.length);
47-
48-
// message can contain multiple validation errors separated by ',' (comma)
49-
errorMessage.split(", ").forEach((message: string) => {
50-
const splitIndex = message.indexOf(" ");
51-
const path = message.slice(0, splitIndex);
52-
const error = message.slice(splitIndex + 1);
53-
54-
const resultErr: SchemaValidateResult = {
55-
message: error,
56-
path: path.split("/")
57-
};
58-
59-
errors.push(resultErr);
60-
});
61-
} else {
62-
// Not a validation error
63-
const resultErr: SchemaValidateResult = {
35+
function ajvToSpectralResult(errors: ErrorObject[], parentPath: Array<string | number>): SchemaValidateResult[] {
36+
if (parentPath === undefined) {
37+
parentPath = [];
38+
}
39+
40+
return errors.map(error => {
41+
const errorPath = error.instancePath.replace(/^\//, '').split('/'); // TODO: Instance Path or Schema Path?
42+
43+
return {
6444
message: error.message,
65-
};
45+
path: parentPath.concat(errorPath),
46+
} as SchemaValidateResult;
47+
});
48+
}
6649

67-
errors.push(resultErr);
50+
function findSchemaValidator(version: string): ValidateFunction {
51+
let validator = ajv.getSchema(version);
52+
if (!validator) {
53+
const schema = preparePayloadSchema2(specs[version], version);
54+
55+
ajv.addSchema(schema, version);
56+
validator = ajv.getSchema(version);
6857
}
6958

70-
return errors;
59+
return validator as ValidateFunction;
7160
}
7261

7362
async function parse(input: ParseSchemaInput<unknown, unknown>): Promise<AsyncAPISchema> {
74-
return input.data as JSONSchema7;
63+
return input.data as AsyncAPISchema;
64+
}
65+
66+
/**
67+
* To validate schema of the payload we just need a small portion of official AsyncAPI spec JSON Schema, the definition of the schema must be
68+
* a main part of the JSON Schema
69+
*
70+
* @private
71+
* @param {Object} asyncapiSchema AsyncAPI specification JSON Schema
72+
* @param {Object} version AsyncAPI version.
73+
* @returns {Object} valid JSON Schema document describing format of AsyncAPI-valid schema for message payload
74+
*/
75+
function preparePayloadSchema2(asyncapiSchema: AsyncAPISchema, version: string) {
76+
const payloadSchema = `http://asyncapi.com/definitions/${version}/schema.json`;
77+
const definitions = asyncapiSchema.definitions;
78+
if (definitions === undefined) {
79+
throw new Error("AsyncAPI schema must contain definitions");
80+
}
81+
82+
// Remove the meta schemas because it is already present within Ajv, and it's not possible to add duplicate schemas.
83+
delete definitions['http://json-schema.org/draft-07/schema'];
84+
delete definitions['http://json-schema.org/draft-04/schema'];
85+
return {
86+
$ref: payloadSchema,
87+
definitions
88+
};
7589
}
7690

7791
function getMimeTypes() {
@@ -80,7 +94,8 @@ function getMimeTypes() {
8094
'application/schema+json;version=draft-07',
8195
'application/schema+yaml;version=draft-07',
8296
];
83-
['2.0.0', '2.1.0', '2.2.0', '2.3.0'].forEach(version => {
97+
98+
specVersions.forEach((version: string) => {
8499
mimeTypes.push(
85100
`application/vnd.aai.asyncapi;version=${version}`,
86101
`application/vnd.aai.asyncapi+json;version=${version}`,

test/schema-parser/asyncapi-schema-parser.spec.ts

+9-24
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ describe('AsyncAPISchemaParser', function () {
77
const validSchema = {
88
asyncapi: {
99
semver: {
10-
major: 2
10+
version: "2.4.0",
1111
}
1212
},
1313
data: {
@@ -28,6 +28,10 @@ describe('AsyncAPISchemaParser', function () {
2828

2929
const parser = AsyncAPISchemaParser();
3030

31+
it('should return Mime Types', async function () {
32+
expect(parser.getMimeTypes()).not.toEqual([]);
33+
});
34+
3135
it('should parse valid AsyncAPI Schema', async function () {
3236
const schema = <ParseSchemaInput<object>>validSchema;
3337
const parsed = await parser.parse(schema);
@@ -45,9 +49,10 @@ describe('AsyncAPISchemaParser', function () {
4549
const schema = <ValidateSchemaInput<object>>{
4650
asyncapi: {
4751
semver: {
48-
major: 2
52+
version: "2.4.0",
4953
}
5054
},
55+
path: ["components", "schemas", "schema1", "payload"],
5156
data: {
5257
oneOf: "this should be an array",
5358
properties: {
@@ -60,28 +65,8 @@ describe('AsyncAPISchemaParser', function () {
6065

6166
const result = await parser.validate(schema);
6267
const expectedResult: SchemaValidateResult[] = [
63-
{ "message": "must be object,boolean", "path": ["data", "properties", "name", "if"] },
64-
{ "message": "must be array", "path": ["data", "oneOf"] }
65-
];
66-
67-
expect(result).toEqual(expectedResult);
68-
});
69-
70-
it('should validate invalid AsyncAPI Schema with invalid meta schema', async function () {
71-
const schema = <ValidateSchemaInput<object>>{
72-
asyncapi: {
73-
semver: {
74-
major: 2
75-
}
76-
},
77-
data: {
78-
$schema: "non-existent-meta-schema",
79-
}
80-
};
81-
82-
const result = await parser.validate(schema);
83-
const expectedResult: SchemaValidateResult[] = [
84-
{ "message": "no schema with key or ref \"non-existent-meta-schema\"" },
68+
{ "message": "must be object,boolean", "path": ["components", "schemas", "schema1", "payload", "properties", "name", "if"] },
69+
{ "message": "must be array", "path": ["components", "schemas", "schema1", "payload", "oneOf"] }
8570
];
8671

8772
expect(result).toEqual(expectedResult);

0 commit comments

Comments
 (0)