1
1
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" ;
4
3
import type { AsyncAPISchema , SchemaValidateResult } from '../types' ;
4
+ // @ts -ignore
5
+ import specs from '@asyncapi/specs' ;
5
6
6
7
const ajv = new Ajv ( {
7
8
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 ) ) ;
9
13
10
14
export function AsyncAPISchemaParser ( ) : SchemaParser {
11
15
return {
@@ -16,62 +20,72 @@ export function AsyncAPISchemaParser(): SchemaParser {
16
20
}
17
21
18
22
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 ) ;
31
30
}
32
31
33
- return errors ;
32
+ return result ;
34
33
}
35
34
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 {
64
44
message : error . message ,
65
- } ;
45
+ path : parentPath . concat ( errorPath ) ,
46
+ } as SchemaValidateResult ;
47
+ } ) ;
48
+ }
66
49
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 ) ;
68
57
}
69
58
70
- return errors ;
59
+ return validator as ValidateFunction ;
71
60
}
72
61
73
62
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
+ } ;
75
89
}
76
90
77
91
function getMimeTypes ( ) {
@@ -80,7 +94,8 @@ function getMimeTypes() {
80
94
'application/schema+json;version=draft-07' ,
81
95
'application/schema+yaml;version=draft-07' ,
82
96
] ;
83
- [ '2.0.0' , '2.1.0' , '2.2.0' , '2.3.0' ] . forEach ( version => {
97
+
98
+ specVersions . forEach ( ( version : string ) => {
84
99
mimeTypes . push (
85
100
`application/vnd.aai.asyncapi;version=${ version } ` ,
86
101
`application/vnd.aai.asyncapi+json;version=${ version } ` ,
0 commit comments