-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathvalidation.ts
executable file
·153 lines (138 loc) · 4.25 KB
/
validation.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
const debug = require('debug')('openapi-cop:proxy');
const swaggerClient = require('swagger-client');
const swaggerParser = require('swagger-parser');
import { IncomingHttpHeaders } from 'http';
import * as refParser from 'json-schema-ref-parser';
import {
OpenAPIValidator,
Request as OasRequest,
ValidationResult,
Operation,
SetMatchType,
} from 'openapi-backend';
import { SchemaValidationException } from '../types/errors';
import { ValidationResults } from '../types/validation';
/**
* Checks if the OpenAPI document is a valid definition. Wrapper around
* SwaggerParser.validate.
* @param spec The object representation of the OpenAPI document
*/
export async function validateDocument(spec: any): Promise<void> {
try {
const api = await swaggerParser.validate(spec);
debug(
` Validated API definition for "${api.info.title}" [version ${api.info.version}]`,
);
} catch (error) {
throw new SchemaValidationException(error as string);
}
}
/**
* Resolves all references, including allOf relationships. Wrapper around
* SwaggerClient.resolve.
* @param spec The object representation of the OpenAPI document
* @param baseDoc The path to the API base document (i.e. the 'swagger.json'
* file)
*/
export async function resolve(spec: any, baseDoc: string): Promise<any> {
const resolutionResult = await swaggerClient.resolve({
pathDiscriminator: [],
spec,
baseDoc,
allowMetaPatches: true,
skipNormalization: true,
});
if (resolutionResult.errors.length > 0) {
throw new Error(
`Could not resolve references in OpenAPI document due to the following errors:
${JSON.stringify(resolutionResult.errors, null, 2)}`,
);
}
const apiDoc = resolutionResult.spec;
delete apiDoc['$$normalized']; // delete additional property that is added by
// the resolver
return apiDoc;
}
/**
* Collects all file references and yields a single OpenAPI object with only
* local references.
* @param spec The object representation of the OpenAPI document
* @param basePath The path to the API base document (i.e. the 'swagger.json'
* file)
*/
export function dereference(
spec: any,
basePath: string,
): Promise<refParser.JSONSchema> {
return refParser.dereference(basePath, spec, {});
}
/**
* Validator to match requests to operations and validate
* requests and responses using a OpenAPI document. Wrapper around
* OpenAPIValidator.
*/
export class Validator {
apiDoc: any;
oasValidator: OpenAPIValidator;
constructor(apiDoc: any) {
this.apiDoc = apiDoc;
this.oasValidator = new OpenAPIValidator({
definition: apiDoc,
ajvOpts: { unknownFormats: ['int32', 'int64', 'float', 'double'] },
});
}
matchOperation(oasRequest: OasRequest): Operation | undefined {
return this.oasValidator.router.matchOperation(oasRequest);
}
validateRequest(
oasRequest: OasRequest,
operation: Operation | undefined,
): ValidationResult {
if (!operation || !operation.operationId) {
return {
valid: false,
errors: [
{
keyword: 'operation',
dataPath: '',
schemaPath: '',
params: [],
message: `Unknown operation '${oasRequest.path}'`,
},
],
};
}
return this.oasValidator.validateRequest(oasRequest, operation);
}
validateResponse(
responseBody: string,
operation: Operation,
statusCode: number,
): ValidationResult {
return this.oasValidator.validateResponse(
responseBody,
operation,
statusCode,
);
}
validateResponseHeaders(
headers: IncomingHttpHeaders,
operation: Operation,
statusCode: number,
): ValidationResult {
return this.oasValidator.validateResponseHeaders(headers, operation, {
statusCode,
setMatchType: SetMatchType.Superset,
});
}
}
export function hasErrors(validationResults: ValidationResults): boolean {
const isRequestValid =
!validationResults.request || validationResults.request.valid;
const isResponseValid =
!validationResults.response || validationResults.response.valid;
const areResponseHeadersValid =
!validationResults.responseHeaders ||
validationResults.responseHeaders.valid;
return !isRequestValid || !isResponseValid || !areResponseHeadersValid;
}