11import { SchemaParser , ParseSchemaInput , ValidateSchemaInput } from "../schema-parser" ;
2- import Ajv from "ajv" ;
3- import { JSONSchema7 } from "json-schema"
2+ import Ajv , { ErrorObject , ValidateFunction } from "ajv" ;
43import type { AsyncAPISchema , SchemaValidateResult } from '../types' ;
4+ // @ts -ignore
5+ import specs from '@asyncapi/specs' ;
56
67const 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
1014export function AsyncAPISchemaParser ( ) : SchemaParser {
1115 return {
@@ -16,62 +20,72 @@ export function AsyncAPISchemaParser(): SchemaParser {
1620}
1721
1822async 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
7362async 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
7791function 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 } ` ,
0 commit comments