@mintlify/validation 0.1.97 → 0.1.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/openapi/OpenApiToEndpointConverter.d.ts +13 -0
- package/dist/openapi/OpenApiToEndpointConverter.js +96 -0
- package/dist/openapi/ParametersConverter.d.ts +13 -0
- package/dist/openapi/ParametersConverter.js +46 -0
- package/dist/openapi/SchemaConverter.d.ts +40 -0
- package/dist/openapi/SchemaConverter.js +467 -0
- package/dist/openapi/SecurityConverter.d.ts +10 -0
- package/dist/openapi/SecurityConverter.js +80 -0
- package/dist/openapi/ServersConverter.d.ts +9 -0
- package/dist/openapi/ServersConverter.js +47 -0
- package/dist/openapi/errors.d.ts +10 -0
- package/dist/openapi/errors.js +27 -0
- package/dist/openapi/utils.d.ts +2 -0
- package/dist/openapi/utils.js +12 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/dist/openapi/convertOpenApi.d.ts +0 -16
- package/dist/openapi/convertOpenApi.js +0 -118
- package/dist/openapi/convertParameters.d.ts +0 -9
- package/dist/openapi/convertParameters.js +0 -37
- package/dist/openapi/convertSchema.d.ts +0 -11
- package/dist/openapi/convertSchema.js +0 -462
- package/dist/openapi/convertSecurity.d.ts +0 -14
- package/dist/openapi/convertSecurity.js +0 -74
- package/dist/openapi/convertServers.d.ts +0 -8
- package/dist/openapi/convertServers.js +0 -39
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import lcm from 'lcm';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import { ConversionError, ImpossibleSchemaError, InvalidSchemaError } from './errors.js';
|
|
4
|
+
import { typeList, } from './types/endpoint.js';
|
|
5
|
+
import { addKeyIfDefined, copyKeyIfDefined } from './utils.js';
|
|
6
|
+
export class SchemaConverter {
|
|
7
|
+
constructor(schema, required, path = ['#'], location) {
|
|
8
|
+
this.schema = schema;
|
|
9
|
+
this.required = required;
|
|
10
|
+
this.path = path;
|
|
11
|
+
this.location = location;
|
|
12
|
+
}
|
|
13
|
+
convert() {
|
|
14
|
+
if (this.schema === undefined) {
|
|
15
|
+
throw new InvalidSchemaError(this.path, 'schema undefined');
|
|
16
|
+
}
|
|
17
|
+
// TODO(ronan): remove when fully migrated to endpoint type, or don't modify schema in evaluateCompositionsRecursive
|
|
18
|
+
let schema = _.cloneDeep(this.schema);
|
|
19
|
+
schema = this.evaluateCompositionsRecursive(this.path, schema);
|
|
20
|
+
return this.convertSchemaRecursive(this.path, schema, this.required);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* This function should be used to reduce strictly `oneOf` and `anyOf` compositions.
|
|
24
|
+
*
|
|
25
|
+
* @param schemaArray `schema.allOf` or `schema.oneOf`
|
|
26
|
+
* @returns a schema array equivalent to the `schemaArray` argument, but in reduced form
|
|
27
|
+
*/
|
|
28
|
+
evaluateOptionsCompositions(path, schemaArray) {
|
|
29
|
+
const evaluatedArray = schemaArray.flatMap((subschema, i) => {
|
|
30
|
+
var _a;
|
|
31
|
+
try {
|
|
32
|
+
return (_a = this.evaluateCompositionsRecursive([...path, i.toString()], subschema).oneOf) !== null && _a !== void 0 ? _a : [];
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof ImpossibleSchemaError) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
if (evaluatedArray.length === 0) {
|
|
44
|
+
throw new ImpossibleSchemaError(path, 'no valid options in schema:', JSON.stringify(schemaArray, undefined, 2));
|
|
45
|
+
}
|
|
46
|
+
return evaluatedArray;
|
|
47
|
+
}
|
|
48
|
+
evaluateCompositionsRecursive(path, schema) {
|
|
49
|
+
// evaluate compositions first; we are currently ignoring `not`
|
|
50
|
+
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
51
|
+
schema.oneOf = this.evaluateOptionsCompositions([...path, 'oneOf'], schema.oneOf);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
schema.oneOf = [];
|
|
55
|
+
}
|
|
56
|
+
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
57
|
+
schema.anyOf = this.evaluateOptionsCompositions([...path, 'anyOf'], schema.anyOf);
|
|
58
|
+
}
|
|
59
|
+
if (schema.allOf && schema.allOf.length > 0) {
|
|
60
|
+
const totalAllOfObj = schema.allOf
|
|
61
|
+
.map((subschema, i) => this.evaluateCompositionsRecursive([...path, 'allOf', i.toString()], subschema))
|
|
62
|
+
.reduce((schema1, schema2, i) => this.combineReducedSchemas([...path, 'allOf', i.toString()], schema1, schema2), {
|
|
63
|
+
oneOf: [],
|
|
64
|
+
});
|
|
65
|
+
schema.oneOf = this.multiplySchemaArrays(path, schema.oneOf, totalAllOfObj.oneOf);
|
|
66
|
+
}
|
|
67
|
+
// evaluate subschemas, if present
|
|
68
|
+
if (schema.properties) {
|
|
69
|
+
for (const key in schema.properties) {
|
|
70
|
+
schema.properties[key] = this.evaluateCompositionsRecursive([...path, 'properties', key], schema.properties[key]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if ('items' in schema && schema.items) {
|
|
74
|
+
schema.items = this.evaluateCompositionsRecursive([...path, 'items'], schema.items);
|
|
75
|
+
}
|
|
76
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
77
|
+
try {
|
|
78
|
+
schema.additionalProperties = this.evaluateCompositionsRecursive([...path, 'additionalProperties'], schema.additionalProperties);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof ImpossibleSchemaError) {
|
|
82
|
+
// if additionalProperties schema is impossible, rather than error, just disallow additionalProperties
|
|
83
|
+
schema.additionalProperties = false;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
91
|
+
schema.oneOf = this.multiplySchemaArrays(path, schema.oneOf, schema.anyOf);
|
|
92
|
+
}
|
|
93
|
+
const topLevelSchemaArray = this.generateTopLevelSchemaArray(schema);
|
|
94
|
+
return { oneOf: this.multiplySchemaArrays(path, schema.oneOf, topLevelSchemaArray) };
|
|
95
|
+
}
|
|
96
|
+
generateTopLevelSchemaArray(schema) {
|
|
97
|
+
if (schema.nullable) {
|
|
98
|
+
const typedSchema = Object.assign({}, schema);
|
|
99
|
+
delete typedSchema.oneOf;
|
|
100
|
+
delete typedSchema.nullable;
|
|
101
|
+
const nullSchema = Object.assign({}, schema);
|
|
102
|
+
delete nullSchema.oneOf;
|
|
103
|
+
delete nullSchema.nullable;
|
|
104
|
+
nullSchema.type = 'null';
|
|
105
|
+
return [typedSchema, nullSchema];
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(schema.type)) {
|
|
108
|
+
if (schema.type.length === 0) {
|
|
109
|
+
const topLevelSchema = Object.assign({}, schema);
|
|
110
|
+
delete topLevelSchema.oneOf;
|
|
111
|
+
delete topLevelSchema.type;
|
|
112
|
+
return [topLevelSchema];
|
|
113
|
+
}
|
|
114
|
+
return schema.type.map((typeString) => {
|
|
115
|
+
const topLevelSchema = Object.assign({}, schema);
|
|
116
|
+
delete topLevelSchema.oneOf;
|
|
117
|
+
topLevelSchema.type = typeString;
|
|
118
|
+
return topLevelSchema;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const topLevelSchema = Object.assign({}, schema);
|
|
122
|
+
delete topLevelSchema.oneOf;
|
|
123
|
+
return [topLevelSchema];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Given two arrays representing schema options, return an array representing schema options that satisfy one element in both arrays.
|
|
127
|
+
*
|
|
128
|
+
* It is helpful to think of each array as a union of all the schemas in the array. This function can then be thought of as taking
|
|
129
|
+
* the intersection of the two union types.
|
|
130
|
+
*
|
|
131
|
+
* @param a first array of schema options
|
|
132
|
+
* @param b second array of schema options
|
|
133
|
+
* @returns array of schemas that satisfy both arrays
|
|
134
|
+
*/
|
|
135
|
+
multiplySchemaArrays(path, a, b) {
|
|
136
|
+
if (a.length === 0 && b.length === 0) {
|
|
137
|
+
return [{}];
|
|
138
|
+
}
|
|
139
|
+
if (a.length === 0) {
|
|
140
|
+
return b;
|
|
141
|
+
}
|
|
142
|
+
if (b.length === 0) {
|
|
143
|
+
return a;
|
|
144
|
+
}
|
|
145
|
+
const product = a.flatMap((schema1) => {
|
|
146
|
+
return b.flatMap((schema2) => {
|
|
147
|
+
try {
|
|
148
|
+
const combinedSchema = this.combineTopLevelSchemas(path, schema1, schema2);
|
|
149
|
+
return [combinedSchema];
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof ImpossibleSchemaError) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
if (product.length === 0) {
|
|
162
|
+
throw new ImpossibleSchemaError(path, 'impossible schema combination:', 'schema array 1:', JSON.stringify(a, undefined, 2), 'schema array 2:', JSON.stringify(b, undefined, 2));
|
|
163
|
+
}
|
|
164
|
+
return product;
|
|
165
|
+
}
|
|
166
|
+
combineReducedSchemas(path, schema1, schema2) {
|
|
167
|
+
var _a, _b;
|
|
168
|
+
return {
|
|
169
|
+
oneOf: this.multiplySchemaArrays(path, ((_a = schema1.oneOf) !== null && _a !== void 0 ? _a : []), ((_b = schema2.oneOf) !== null && _b !== void 0 ? _b : [])),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
combineTopLevelSchemas(path, schema1, schema2) {
|
|
173
|
+
var _a, _b;
|
|
174
|
+
let type1 = schema1.type;
|
|
175
|
+
let type2 = schema2.type;
|
|
176
|
+
// don't throw an error if number type is being constricted
|
|
177
|
+
if (type1 === 'integer' && type2 === 'number') {
|
|
178
|
+
type2 = 'integer';
|
|
179
|
+
}
|
|
180
|
+
else if (type1 === 'number' && type2 === 'integer') {
|
|
181
|
+
type1 = 'integer';
|
|
182
|
+
}
|
|
183
|
+
if (type1 && type2 && type1 !== type2) {
|
|
184
|
+
throw new ImpossibleSchemaError(path, `mismatched type in composition: "${type1}" "${type2}"`);
|
|
185
|
+
}
|
|
186
|
+
for (const schema of [schema1, schema2]) {
|
|
187
|
+
if (typeof schema.exclusiveMaximum === 'number') {
|
|
188
|
+
if (schema.maximum === undefined || schema.maximum >= schema.exclusiveMaximum) {
|
|
189
|
+
schema.maximum = schema.exclusiveMaximum;
|
|
190
|
+
schema.exclusiveMaximum = true;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
schema.exclusiveMaximum = undefined;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (typeof schema.exclusiveMinimum === 'number') {
|
|
197
|
+
if (schema.minimum === undefined || schema.minimum <= schema.exclusiveMinimum) {
|
|
198
|
+
schema.minimum = schema.exclusiveMinimum;
|
|
199
|
+
schema.exclusiveMinimum = true;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
schema.exclusiveMinimum = undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const combinedSchema = {
|
|
207
|
+
title: takeLast(schema1, schema2, 'title'),
|
|
208
|
+
description: takeLast(schema1, schema2, 'description'),
|
|
209
|
+
format: takeLast(schema1, schema2, 'format'),
|
|
210
|
+
multipleOf: combine(schema1, schema2, 'multipleOf', lcm),
|
|
211
|
+
maximum: combine(schema1, schema2, 'maximum', Math.min),
|
|
212
|
+
minimum: combine(schema1, schema2, 'minimum', Math.max),
|
|
213
|
+
maxLength: combine(schema1, schema2, 'maxLength', Math.min),
|
|
214
|
+
minLength: combine(schema1, schema2, 'minLength', Math.max),
|
|
215
|
+
maxItems: combine(schema1, schema2, 'maxItems', Math.min),
|
|
216
|
+
minItems: combine(schema1, schema2, 'minItems', Math.max),
|
|
217
|
+
maxProperties: combine(schema1, schema2, 'maxProperties', Math.min),
|
|
218
|
+
minProperties: combine(schema1, schema2, 'minProperties', Math.max),
|
|
219
|
+
required: combine(schema1, schema2, 'required', (a, b) => b.concat(a.filter((value) => !b.includes(value)))),
|
|
220
|
+
enum: combine(schema1, schema2, 'enum', (a, b) => b.filter((value) => a.includes(value))),
|
|
221
|
+
readOnly: schema1.readOnly && schema2.readOnly,
|
|
222
|
+
writeOnly: schema1.writeOnly && schema2.writeOnly,
|
|
223
|
+
deprecated: schema1.deprecated && schema2.deprecated,
|
|
224
|
+
};
|
|
225
|
+
combinedSchema.exclusiveMaximum =
|
|
226
|
+
(schema1.maximum === combinedSchema.maximum ? schema1.exclusiveMaximum : undefined) ||
|
|
227
|
+
(schema2.maximum === combinedSchema.maximum ? schema2.exclusiveMaximum : undefined);
|
|
228
|
+
combinedSchema.exclusiveMinimum =
|
|
229
|
+
(schema1.minimum === combinedSchema.minimum ? schema1.exclusiveMinimum : undefined) ||
|
|
230
|
+
(schema2.minimum === combinedSchema.minimum ? schema2.exclusiveMinimum : undefined);
|
|
231
|
+
// don't use coalesce operator, since null is a valid example
|
|
232
|
+
const example1 = ((_a = schema1.examples) === null || _a === void 0 ? void 0 : _a[0]) !== undefined ? schema1.examples[0] : schema1.example;
|
|
233
|
+
const example2 = ((_b = schema2.examples) === null || _b === void 0 ? void 0 : _b[0]) !== undefined ? schema2.examples[0] : schema2.example;
|
|
234
|
+
if (example1 && example2 && typeof example1 === 'object' && typeof example2 === 'object') {
|
|
235
|
+
combinedSchema.example = Object.assign(Object.assign({}, example1), example2);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// don't use coalesce operator, since null is a valid example
|
|
239
|
+
combinedSchema.example = example2 !== undefined ? example2 : example1;
|
|
240
|
+
}
|
|
241
|
+
const type = type1 !== null && type1 !== void 0 ? type1 : type2;
|
|
242
|
+
if (type === 'array') {
|
|
243
|
+
return Object.assign({ type, items: this.combineReducedSchemas([...path, 'items'], 'items' in schema1 && schema1.items ? schema1.items : {}, 'items' in schema2 && schema2.items ? schema2.items : {}) }, combinedSchema);
|
|
244
|
+
}
|
|
245
|
+
if (schema1.properties && schema2.properties) {
|
|
246
|
+
const combinedProperties = Object.assign({}, schema1.properties);
|
|
247
|
+
Object.entries(schema2.properties).forEach(([property, schema]) => {
|
|
248
|
+
const schema1Property = combinedProperties[property];
|
|
249
|
+
if (schema1Property) {
|
|
250
|
+
combinedProperties[property] = this.combineReducedSchemas([...path, 'properties', property], schema1Property, schema);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
combinedProperties[property] = schema;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
combinedSchema.properties = combinedProperties;
|
|
257
|
+
}
|
|
258
|
+
else if (schema1.properties || schema2.properties) {
|
|
259
|
+
combinedSchema.properties = Object.assign(Object.assign({}, schema1.properties), schema2.properties);
|
|
260
|
+
}
|
|
261
|
+
if (schema1.additionalProperties === false || schema2.additionalProperties === false) {
|
|
262
|
+
combinedSchema.additionalProperties = false;
|
|
263
|
+
}
|
|
264
|
+
else if (schema1.additionalProperties &&
|
|
265
|
+
typeof schema1.additionalProperties === 'object' &&
|
|
266
|
+
schema2.additionalProperties &&
|
|
267
|
+
typeof schema2.additionalProperties === 'object') {
|
|
268
|
+
combinedSchema.additionalProperties = this.combineReducedSchemas([...path, 'additionalProperties'], schema1.additionalProperties, schema2.additionalProperties);
|
|
269
|
+
}
|
|
270
|
+
else if (schema1.additionalProperties && typeof schema1.additionalProperties === 'object') {
|
|
271
|
+
combinedSchema.additionalProperties = schema1.additionalProperties;
|
|
272
|
+
}
|
|
273
|
+
else if (schema2.additionalProperties && typeof schema2.additionalProperties === 'object') {
|
|
274
|
+
combinedSchema.additionalProperties = schema2.additionalProperties;
|
|
275
|
+
}
|
|
276
|
+
return Object.assign({ type }, combinedSchema);
|
|
277
|
+
}
|
|
278
|
+
convertSchemaRecursive(path, schema, required) {
|
|
279
|
+
if (schema.oneOf === undefined || schema.oneOf.length === 0) {
|
|
280
|
+
throw new ConversionError(path, 'missing schema definition');
|
|
281
|
+
}
|
|
282
|
+
const schemaArray = schema.oneOf.map((schema) => {
|
|
283
|
+
const sharedProps = {};
|
|
284
|
+
addKeyIfDefined('required', required, sharedProps);
|
|
285
|
+
copyKeyIfDefined('title', schema, sharedProps);
|
|
286
|
+
copyKeyIfDefined('description', schema, sharedProps);
|
|
287
|
+
copyKeyIfDefined('readOnly', schema, sharedProps);
|
|
288
|
+
copyKeyIfDefined('writeOnly', schema, sharedProps);
|
|
289
|
+
copyKeyIfDefined('deprecated', schema, sharedProps);
|
|
290
|
+
if (schema.type === undefined) {
|
|
291
|
+
const inferredType = inferType(schema);
|
|
292
|
+
if (inferredType === undefined) {
|
|
293
|
+
return Object.assign({ type: 'any' }, sharedProps);
|
|
294
|
+
}
|
|
295
|
+
schema.type = inferredType;
|
|
296
|
+
}
|
|
297
|
+
const type = schema.type;
|
|
298
|
+
if (!typeList.includes(type)) {
|
|
299
|
+
throw new InvalidSchemaError(path, `invalid schema type: ${schema.type}`);
|
|
300
|
+
}
|
|
301
|
+
switch (schema.type) {
|
|
302
|
+
case 'boolean':
|
|
303
|
+
const booleanProps = sharedProps;
|
|
304
|
+
copyKeyIfDefined('default', schema, booleanProps);
|
|
305
|
+
copyExampleIfDefined(schema, booleanProps);
|
|
306
|
+
return Object.assign({ type: schema.type }, booleanProps);
|
|
307
|
+
case 'number':
|
|
308
|
+
case 'integer':
|
|
309
|
+
if (schema.enum) {
|
|
310
|
+
const numberEnumProps = sharedProps;
|
|
311
|
+
copyKeyIfDefined('default', schema, numberEnumProps);
|
|
312
|
+
copyExampleIfDefined(schema, numberEnumProps);
|
|
313
|
+
return Object.assign({ type: schema.type === 'number' ? 'enum<number>' : 'enum<integer>', enum: schema.enum }, numberEnumProps);
|
|
314
|
+
}
|
|
315
|
+
const numberProps = sharedProps;
|
|
316
|
+
copyKeyIfDefined('multipleOf', schema, numberProps);
|
|
317
|
+
copyKeyIfDefined('maximum', schema, numberProps);
|
|
318
|
+
copyKeyIfDefined('exclusiveMaximum', schema, numberProps);
|
|
319
|
+
copyKeyIfDefined('minimum', schema, numberProps);
|
|
320
|
+
copyKeyIfDefined('exclusiveMinimum', schema, numberProps);
|
|
321
|
+
copyKeyIfDefined('default', schema, numberProps);
|
|
322
|
+
copyExampleIfDefined(schema, numberProps);
|
|
323
|
+
return Object.assign({ type: schema.type }, numberProps);
|
|
324
|
+
case 'string':
|
|
325
|
+
if (schema.enum) {
|
|
326
|
+
const stringEnumProps = sharedProps;
|
|
327
|
+
copyKeyIfDefined('default', schema, stringEnumProps);
|
|
328
|
+
copyExampleIfDefined(schema, stringEnumProps);
|
|
329
|
+
return Object.assign({ type: 'enum<string>', enum: schema.enum }, stringEnumProps);
|
|
330
|
+
}
|
|
331
|
+
const stringProps = sharedProps;
|
|
332
|
+
copyKeyIfDefined('format', schema, stringProps);
|
|
333
|
+
copyKeyIfDefined('pattern', schema, stringProps);
|
|
334
|
+
copyKeyIfDefined('maxLength', schema, stringProps);
|
|
335
|
+
copyKeyIfDefined('minLength', schema, stringProps);
|
|
336
|
+
copyKeyIfDefined('default', schema, stringProps);
|
|
337
|
+
copyExampleIfDefined(schema, stringProps);
|
|
338
|
+
return Object.assign({ type: schema.type }, stringProps);
|
|
339
|
+
case 'array':
|
|
340
|
+
const arrayProps = sharedProps;
|
|
341
|
+
const items =
|
|
342
|
+
// validator allows items to be null
|
|
343
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
344
|
+
typeof schema.items === 'object' && schema.items != null
|
|
345
|
+
? this.convertSchemaRecursive([...path, 'items'], schema.items)
|
|
346
|
+
: [{ type: 'any' }];
|
|
347
|
+
copyKeyIfDefined('maxItems', schema, arrayProps);
|
|
348
|
+
copyKeyIfDefined('minItems', schema, arrayProps);
|
|
349
|
+
copyKeyIfDefined('uniqueItems', schema, arrayProps);
|
|
350
|
+
copyKeyIfDefined('default', schema, arrayProps);
|
|
351
|
+
copyExampleIfDefined(schema, arrayProps);
|
|
352
|
+
return Object.assign({ type: schema.type, items }, arrayProps);
|
|
353
|
+
case 'object':
|
|
354
|
+
const properties = this.convertProperties([...path, 'properties'], schema.properties, schema.required);
|
|
355
|
+
const additionalProperties =
|
|
356
|
+
// validator allows additionalProperties to be null
|
|
357
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
358
|
+
typeof schema.additionalProperties === 'object' && schema.additionalProperties != null
|
|
359
|
+
? this.convertSchemaRecursive([...path, 'additionalProperties'], schema.additionalProperties)
|
|
360
|
+
: schema.additionalProperties;
|
|
361
|
+
const objectProperties = sharedProps;
|
|
362
|
+
addKeyIfDefined('additionalProperties', additionalProperties, objectProperties);
|
|
363
|
+
copyKeyIfDefined('maxProperties', schema, objectProperties);
|
|
364
|
+
copyKeyIfDefined('minProperties', schema, objectProperties);
|
|
365
|
+
copyKeyIfDefined('default', schema, objectProperties);
|
|
366
|
+
copyExampleIfDefined(schema, objectProperties);
|
|
367
|
+
return Object.assign({ type: schema.type, properties }, objectProperties);
|
|
368
|
+
case 'null':
|
|
369
|
+
const nullProps = sharedProps;
|
|
370
|
+
copyKeyIfDefined('default', schema, nullProps);
|
|
371
|
+
copyExampleIfDefined(schema, nullProps);
|
|
372
|
+
return Object.assign({ type: schema.type }, nullProps);
|
|
373
|
+
default:
|
|
374
|
+
throw new ImpossibleSchemaError(path, `impossible type reached: ${schema.type}`);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
if (!schemaArray[0]) {
|
|
378
|
+
throw new ConversionError(path, 'missing schema definition in position 0');
|
|
379
|
+
}
|
|
380
|
+
// must unpack first element to satisfy type
|
|
381
|
+
return [schemaArray[0], ...schemaArray.slice(1)];
|
|
382
|
+
}
|
|
383
|
+
convertProperties(path, properties, required) {
|
|
384
|
+
if (properties === undefined) {
|
|
385
|
+
return {};
|
|
386
|
+
}
|
|
387
|
+
const newEntries = Object.entries(properties).map(([name, schema]) => {
|
|
388
|
+
return [
|
|
389
|
+
name,
|
|
390
|
+
this.convertSchemaRecursive([...path, name], schema, (required === null || required === void 0 ? void 0 : required.includes(name)) ? true : undefined),
|
|
391
|
+
];
|
|
392
|
+
});
|
|
393
|
+
return Object.fromEntries(newEntries);
|
|
394
|
+
}
|
|
395
|
+
static convert({ schema, required, path, location, }) {
|
|
396
|
+
return new SchemaConverter(schema, required, path, location).convert();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const copyExampleIfDefined = (source, destination) => {
|
|
400
|
+
var _a;
|
|
401
|
+
const example = ((_a = source.examples) === null || _a === void 0 ? void 0 : _a[0]) !== undefined ? source.examples[0] : source.example;
|
|
402
|
+
if (example !== undefined) {
|
|
403
|
+
destination.example = example;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
const takeLast = (schema1, schema2, key) => {
|
|
407
|
+
var _a;
|
|
408
|
+
return (_a = schema2[key]) !== null && _a !== void 0 ? _a : schema1[key];
|
|
409
|
+
};
|
|
410
|
+
const combine = (schema1, schema2, key, transform) => {
|
|
411
|
+
var _a;
|
|
412
|
+
return schema1[key] !== undefined && schema2[key] !== undefined
|
|
413
|
+
? transform(schema1[key], schema2[key])
|
|
414
|
+
: (_a = schema1[key]) !== null && _a !== void 0 ? _a : schema2[key];
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* Given an OpenAPI 3.1 schema, this function will attempt to determine the schema type
|
|
418
|
+
* based on the properties present in the schema. This is useful for assigning types to
|
|
419
|
+
* schemas that are missing a type.
|
|
420
|
+
*
|
|
421
|
+
* For example, if a schema has no type but has `schema.properties`, we can infer the
|
|
422
|
+
* intended type is `object`.
|
|
423
|
+
*
|
|
424
|
+
* @param schema
|
|
425
|
+
* @returns if exactly one type can be inferred, the string corresponding to that type; otherwise `undefined`
|
|
426
|
+
*/
|
|
427
|
+
const inferType = (schema) => {
|
|
428
|
+
var _a, _b;
|
|
429
|
+
let type = undefined;
|
|
430
|
+
if (schema.format !== undefined ||
|
|
431
|
+
schema.pattern !== undefined ||
|
|
432
|
+
schema.minLength !== undefined ||
|
|
433
|
+
schema.maxLength !== undefined ||
|
|
434
|
+
((_a = schema.enum) === null || _a === void 0 ? void 0 : _a.every((option) => typeof option === 'string'))) {
|
|
435
|
+
type = 'string';
|
|
436
|
+
}
|
|
437
|
+
if (schema.multipleOf !== undefined ||
|
|
438
|
+
schema.minimum !== undefined ||
|
|
439
|
+
schema.maximum !== undefined ||
|
|
440
|
+
schema.exclusiveMinimum !== undefined ||
|
|
441
|
+
schema.exclusiveMaximum !== undefined ||
|
|
442
|
+
((_b = schema.enum) === null || _b === void 0 ? void 0 : _b.every((option) => typeof option === 'number'))) {
|
|
443
|
+
if (type !== undefined) {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
type = 'number'; // less specific than 'integer'
|
|
447
|
+
}
|
|
448
|
+
if (('items' in schema && schema.items !== undefined) ||
|
|
449
|
+
schema.minItems !== undefined ||
|
|
450
|
+
schema.maxItems !== undefined ||
|
|
451
|
+
schema.uniqueItems !== undefined) {
|
|
452
|
+
if (type !== undefined) {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
type = 'array';
|
|
456
|
+
}
|
|
457
|
+
if (schema.additionalProperties !== undefined ||
|
|
458
|
+
schema.properties !== undefined ||
|
|
459
|
+
schema.minProperties !== undefined ||
|
|
460
|
+
schema.maxProperties !== undefined) {
|
|
461
|
+
if (type !== undefined) {
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
type = 'object';
|
|
465
|
+
}
|
|
466
|
+
return type;
|
|
467
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { OpenAPIV3_1 } from 'openapi-types';
|
|
2
|
+
import { SecurityOption } from './types/endpoint.js';
|
|
3
|
+
export declare class SecurityConverter {
|
|
4
|
+
readonly securityRequirements: OpenAPIV3_1.SecurityRequirementObject[] | undefined;
|
|
5
|
+
readonly securitySchemes: OpenAPIV3_1.ComponentsObject['securitySchemes'];
|
|
6
|
+
private constructor();
|
|
7
|
+
private convert;
|
|
8
|
+
private addSecurityParameters;
|
|
9
|
+
static convert(securityRequirements: OpenAPIV3_1.SecurityRequirementObject[] | undefined, securitySchemes: OpenAPIV3_1.ComponentsObject['securitySchemes']): SecurityOption[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { InvalidSchemaError } from './errors.js';
|
|
2
|
+
import { copyKeyIfDefined } from './utils.js';
|
|
3
|
+
export class SecurityConverter {
|
|
4
|
+
constructor(securityRequirements, securitySchemes) {
|
|
5
|
+
this.securityRequirements = securityRequirements;
|
|
6
|
+
this.securitySchemes = securitySchemes;
|
|
7
|
+
}
|
|
8
|
+
convert() {
|
|
9
|
+
if (this.securityRequirements === undefined || this.securityRequirements.length === 0) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
if (this.securitySchemes === undefined) {
|
|
13
|
+
throw new InvalidSchemaError(['#', 'components'], 'securitySchemes not defined');
|
|
14
|
+
}
|
|
15
|
+
// TODO(ronan): make this work for camel-case as well
|
|
16
|
+
return this.securityRequirements.map((security) => {
|
|
17
|
+
const title = Object.keys(security)
|
|
18
|
+
.map((securityName) => securityName.replace(/[_-]/g, ' '))
|
|
19
|
+
.join(' & ');
|
|
20
|
+
const parameterSections = {
|
|
21
|
+
query: {},
|
|
22
|
+
header: {},
|
|
23
|
+
cookie: {},
|
|
24
|
+
};
|
|
25
|
+
Object.keys(security).forEach((securityName) => {
|
|
26
|
+
var _a;
|
|
27
|
+
const securityScheme = (_a = this.securitySchemes) === null || _a === void 0 ? void 0 : _a[securityName];
|
|
28
|
+
if (securityScheme === undefined) {
|
|
29
|
+
throw new InvalidSchemaError(['#', 'components', 'securitySchemes'], `security scheme not defined: '${securityName}'`);
|
|
30
|
+
}
|
|
31
|
+
this.addSecurityParameters(securityName, securityScheme, parameterSections);
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
title,
|
|
35
|
+
parameters: parameterSections,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
addSecurityParameters(securityName, securityScheme, parameterSections) {
|
|
40
|
+
switch (securityScheme.type) {
|
|
41
|
+
case 'apiKey': {
|
|
42
|
+
if (!['header', 'query', 'cookie'].includes(securityScheme.in)) {
|
|
43
|
+
throw new InvalidSchemaError(['#', 'components', 'securitySchemes', securityName], `invalid security scheme location provided: '${securityScheme.in}'`);
|
|
44
|
+
}
|
|
45
|
+
const paramGroup = securityScheme.in;
|
|
46
|
+
const schema = { type: 'apiKey' };
|
|
47
|
+
copyKeyIfDefined('description', securityScheme, schema);
|
|
48
|
+
parameterSections[paramGroup][securityScheme.name] = schema;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
case 'http': {
|
|
52
|
+
const scheme = securityScheme.scheme;
|
|
53
|
+
if (scheme === 'basic' || scheme === 'bearer') {
|
|
54
|
+
const schema = {
|
|
55
|
+
type: 'http',
|
|
56
|
+
scheme,
|
|
57
|
+
};
|
|
58
|
+
copyKeyIfDefined('description', securityScheme, schema);
|
|
59
|
+
parameterSections.header['Authorization'] = schema;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
throw new InvalidSchemaError(['#', 'components', 'securitySchemes', securityName], `encountered unknown HTTP security scheme: '${securityScheme.scheme}'`);
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
case 'oauth2': {
|
|
67
|
+
const schema = { type: 'oauth2' };
|
|
68
|
+
copyKeyIfDefined('description', securityScheme, schema);
|
|
69
|
+
parameterSections.header['Authorization'] = schema;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
case 'openIdConnect': {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
static convert(securityRequirements, securitySchemes) {
|
|
78
|
+
return new SecurityConverter(securityRequirements, securitySchemes).convert();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { OpenAPIV3_1 } from 'openapi-types';
|
|
2
|
+
import { Server } from './types/endpoint.js';
|
|
3
|
+
export declare class ServersConverter {
|
|
4
|
+
readonly servers: OpenAPIV3_1.ServerObject[] | undefined;
|
|
5
|
+
private constructor();
|
|
6
|
+
private convert;
|
|
7
|
+
private convertVariables;
|
|
8
|
+
static convert(servers: OpenAPIV3_1.ServerObject[] | undefined): Server[] | undefined;
|
|
9
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export class ServersConverter {
|
|
2
|
+
constructor(servers) {
|
|
3
|
+
this.servers = servers;
|
|
4
|
+
}
|
|
5
|
+
convert() {
|
|
6
|
+
if (this.servers === undefined || this.servers.length === 0) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return this.servers.map(({ url, description, variables }) => {
|
|
10
|
+
return {
|
|
11
|
+
url,
|
|
12
|
+
description,
|
|
13
|
+
variables: this.convertVariables(variables),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
convertVariables(variables) {
|
|
18
|
+
if (variables === undefined || Object.keys(variables).length === 0) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const newEntries = Object.entries(variables).map(([name, variable]) => {
|
|
22
|
+
if (variable.enum) {
|
|
23
|
+
return [
|
|
24
|
+
name,
|
|
25
|
+
{
|
|
26
|
+
type: 'enum<string>',
|
|
27
|
+
enum: variable.enum,
|
|
28
|
+
description: variable.description,
|
|
29
|
+
default: variable.default,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
return [
|
|
34
|
+
name,
|
|
35
|
+
{
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: variable.description,
|
|
38
|
+
default: variable.default,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
});
|
|
42
|
+
return Object.fromEntries(newEntries);
|
|
43
|
+
}
|
|
44
|
+
static convert(servers) {
|
|
45
|
+
return new ServersConverter(servers).convert();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const generateMessage: (path: string[], messages?: string[]) => string;
|
|
2
|
+
export declare class InvalidSchemaError extends Error {
|
|
3
|
+
constructor(path: string[], ...messages: string[]);
|
|
4
|
+
}
|
|
5
|
+
export declare class ImpossibleSchemaError extends Error {
|
|
6
|
+
constructor(path: string[], ...messages: string[]);
|
|
7
|
+
}
|
|
8
|
+
export declare class ConversionError extends Error {
|
|
9
|
+
constructor(path: string[], ...messages: string[]);
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const generateMessage = (path, messages = []) => {
|
|
2
|
+
const pathString = path
|
|
3
|
+
.map((component) => component.replace('\\', '\\\\').replace('/', '\\/'))
|
|
4
|
+
.join('/');
|
|
5
|
+
return [pathString, ...messages].join('\n');
|
|
6
|
+
};
|
|
7
|
+
export class InvalidSchemaError extends Error {
|
|
8
|
+
constructor(path, ...messages) {
|
|
9
|
+
super(generateMessage(path, messages));
|
|
10
|
+
this.name = 'InvalidSchemaError';
|
|
11
|
+
Object.setPrototypeOf(this, InvalidSchemaError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class ImpossibleSchemaError extends Error {
|
|
15
|
+
constructor(path, ...messages) {
|
|
16
|
+
super(generateMessage(path, messages));
|
|
17
|
+
this.name = 'ImpossibleSchemaError';
|
|
18
|
+
Object.setPrototypeOf(this, ImpossibleSchemaError.prototype);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class ConversionError extends Error {
|
|
22
|
+
constructor(path, ...messages) {
|
|
23
|
+
super(generateMessage(path, messages));
|
|
24
|
+
this.name = 'ConversionError';
|
|
25
|
+
Object.setPrototypeOf(this, ConversionError.prototype);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const addKeyIfDefined = (key, value, destination) => {
|
|
2
|
+
if (value !== undefined) {
|
|
3
|
+
destination[key] = value;
|
|
4
|
+
}
|
|
5
|
+
};
|
|
6
|
+
export const copyKeyIfDefined = (key, source, destination) => {
|
|
7
|
+
// eslint does not recognize that D[K] could be undefined
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
9
|
+
if (source[key] !== undefined) {
|
|
10
|
+
destination[key] = source[key];
|
|
11
|
+
}
|
|
12
|
+
};
|