@scalar/openapi-parser 0.8.6 → 0.8.8
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/configuration/index.js +27 -1
- package/dist/index.js +26 -1
- package/dist/lib/Validator/Validator.js +127 -1
- package/dist/{utils/load/plugins → plugins/fetch-urls}/fetchUrls.d.ts +1 -1
- package/dist/plugins/fetch-urls/fetchUrls.d.ts.map +1 -0
- package/dist/plugins/fetch-urls/fetchUrls.js +45 -0
- package/dist/plugins/fetch-urls/index.d.ts +2 -0
- package/dist/plugins/fetch-urls/index.d.ts.map +1 -0
- package/dist/plugins/fetch-urls/index.js +1 -0
- package/dist/plugins/read-files/index.d.ts +2 -0
- package/dist/plugins/read-files/index.d.ts.map +1 -0
- package/dist/plugins/read-files/index.js +1 -0
- package/dist/plugins/read-files/readFiles.d.ts +3 -0
- package/dist/plugins/read-files/readFiles.d.ts.map +1 -0
- package/dist/plugins/read-files/readFiles.js +56 -0
- package/dist/polyfills/path.js +120 -1
- package/dist/schemas/v2.0/schema.json.js +1623 -1
- package/dist/schemas/v3.0/schema.json.js +1673 -1
- package/dist/schemas/v3.1/schema.json.js +1451 -1
- package/dist/utils/betterAjvErrors/helpers.js +108 -1
- package/dist/utils/betterAjvErrors/index.js +15 -1
- package/dist/utils/betterAjvErrors/utils.js +31 -1
- package/dist/utils/betterAjvErrors/validation-errors/additional-prop.js +20 -1
- package/dist/utils/betterAjvErrors/validation-errors/base.js +34 -1
- package/dist/utils/betterAjvErrors/validation-errors/default.js +20 -1
- package/dist/utils/betterAjvErrors/validation-errors/enum.js +58 -1
- package/dist/utils/betterAjvErrors/validation-errors/pattern.js +20 -1
- package/dist/utils/betterAjvErrors/validation-errors/required.js +19 -1
- package/dist/utils/betterAjvErrors/validation-errors/unevaluated-prop.js +20 -1
- package/dist/utils/dereference.js +21 -1
- package/dist/utils/details.js +25 -1
- package/dist/utils/escapeJsonPointer.js +10 -1
- package/dist/utils/filter.js +17 -1
- package/dist/utils/getEntrypoint.js +8 -1
- package/dist/utils/getListOfReferences.js +27 -1
- package/dist/utils/getSegmentsFromPath.js +18 -1
- package/dist/utils/isFilesystem.js +11 -1
- package/dist/utils/isJson.js +11 -1
- package/dist/utils/isObject.js +6 -1
- package/dist/utils/isYaml.js +19 -1
- package/dist/utils/load/load.js +121 -1
- package/dist/utils/makeFilesystem.js +25 -1
- package/dist/utils/normalize.js +25 -1
- package/dist/utils/openapi/actions/details.js +13 -1
- package/dist/utils/openapi/actions/files.js +11 -1
- package/dist/utils/openapi/actions/get.js +13 -1
- package/dist/utils/openapi/actions/toJson.js +13 -1
- package/dist/utils/openapi/actions/toYaml.js +13 -1
- package/dist/utils/openapi/commands/dereferenceCommand.js +29 -1
- package/dist/utils/openapi/commands/filterCommand.js +28 -1
- package/dist/utils/openapi/commands/loadCommand.js +44 -1
- package/dist/utils/openapi/commands/upgradeCommand.js +31 -1
- package/dist/utils/openapi/commands/validateCommand.js +35 -1
- package/dist/utils/openapi/openapi.js +18 -1
- package/dist/utils/openapi/utils/queueTask.js +11 -1
- package/dist/utils/openapi/utils/workThroughQueue.js +67 -1
- package/dist/utils/resolveReferences.js +153 -1
- package/dist/utils/toJson.js +3 -1
- package/dist/utils/toYaml.js +5 -1
- package/dist/utils/transformErrors.js +23 -1
- package/dist/utils/traverse.js +26 -1
- package/dist/utils/unescapeJsonPointer.js +11 -1
- package/dist/utils/upgrade.js +20 -1
- package/dist/utils/upgradeFromThreeToThreeOne.js +122 -1
- package/dist/utils/upgradeFromTwoToThree.js +174 -1
- package/dist/utils/validate.js +18 -1
- package/package.json +15 -13
- package/dist/utils/load/plugins/fetchUrls.d.ts.map +0 -1
- package/dist/utils/load/plugins/fetchUrls.js +0 -1
- package/dist/utils/load/plugins/readFiles.d.ts +0 -3
- package/dist/utils/load/plugins/readFiles.d.ts.map +0 -1
- package/dist/utils/load/plugins/readFiles.js +0 -1
|
@@ -1 +1,153 @@
|
|
|
1
|
-
import{ERRORS
|
|
1
|
+
import { ERRORS } from '../configuration/index.js';
|
|
2
|
+
import { getEntrypoint } from './getEntrypoint.js';
|
|
3
|
+
import { getSegmentsFromPath } from './getSegmentsFromPath.js';
|
|
4
|
+
import { makeFilesystem } from './makeFilesystem.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Takes a specification and resolves all references.
|
|
8
|
+
*/
|
|
9
|
+
function resolveReferences(
|
|
10
|
+
// Just a specification, or a set of files.
|
|
11
|
+
input,
|
|
12
|
+
// Additional options to control the behaviour
|
|
13
|
+
options,
|
|
14
|
+
// Fallback to the entrypoint
|
|
15
|
+
file,
|
|
16
|
+
// Errors that occurred during the process
|
|
17
|
+
errors) {
|
|
18
|
+
// Initialize errors
|
|
19
|
+
if (errors === undefined) {
|
|
20
|
+
errors = [];
|
|
21
|
+
}
|
|
22
|
+
// Detach from input
|
|
23
|
+
const clonedInput = structuredClone(input);
|
|
24
|
+
// Make it a filesystem, even if it’s just one file
|
|
25
|
+
const filesystem = makeFilesystem(clonedInput);
|
|
26
|
+
// Get the main file
|
|
27
|
+
const entrypoint = getEntrypoint(filesystem);
|
|
28
|
+
// Recursively resolve all references
|
|
29
|
+
resolve(file?.specification ?? entrypoint.specification, filesystem, file ?? entrypoint);
|
|
30
|
+
// If we replace references with content, that includes a reference, we can’t deal with that right-away.
|
|
31
|
+
// That’s why we need a second run.
|
|
32
|
+
resolve(file?.specification ?? entrypoint.specification, filesystem, file ?? entrypoint);
|
|
33
|
+
// Remove duplicats (according to message) from errors
|
|
34
|
+
errors = errors.filter((error, index, self) => index ===
|
|
35
|
+
self.findIndex((t) => t.message === error.message && t.code === error.code));
|
|
36
|
+
// Return the resolved specification
|
|
37
|
+
return {
|
|
38
|
+
valid: errors.length === 0,
|
|
39
|
+
errors: errors,
|
|
40
|
+
schema: (file ?? getEntrypoint(filesystem))
|
|
41
|
+
.specification,
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Resolves the circular reference to an object and deletes the $ref properties.
|
|
45
|
+
*/
|
|
46
|
+
function resolve(schema, resolveFilesystem, resolveFile) {
|
|
47
|
+
let result;
|
|
48
|
+
// Iterate over the whole objecct
|
|
49
|
+
Object.entries(schema ?? {}).forEach(([_, value]) => {
|
|
50
|
+
// Ignore parts without a reference
|
|
51
|
+
if (schema.$ref !== undefined) {
|
|
52
|
+
// Find the referenced content
|
|
53
|
+
const target = resolveUri(schema.$ref, options, resolveFile, resolveFilesystem, errors);
|
|
54
|
+
if (target === undefined) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
// Get rid of the reference
|
|
58
|
+
delete schema.$ref;
|
|
59
|
+
if (typeof target === 'object') {
|
|
60
|
+
Object.keys(target).forEach((key) => {
|
|
61
|
+
if (schema[key] === undefined) {
|
|
62
|
+
schema[key] = target[key];
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === 'object' && !isCircular(value)) {
|
|
68
|
+
result = resolve(value, resolveFilesystem, resolveFile);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
errors: result?.errors ?? [],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// TODO: Is there a better way? :D
|
|
77
|
+
function isCircular(schema) {
|
|
78
|
+
try {
|
|
79
|
+
JSON.stringify(schema);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolves a URI to a part of the specification
|
|
88
|
+
*/
|
|
89
|
+
function resolveUri(
|
|
90
|
+
// 'foobar.json#/foo/bar'
|
|
91
|
+
uri, options,
|
|
92
|
+
// { filename: './foobar.json '}
|
|
93
|
+
file,
|
|
94
|
+
// [ { filename: './foobar.json '} ]
|
|
95
|
+
filesystem, errors) {
|
|
96
|
+
// Ignore invalid URIs
|
|
97
|
+
if (typeof uri !== 'string') {
|
|
98
|
+
if (options?.throwOnError) {
|
|
99
|
+
throw new Error(ERRORS.INVALID_REFERENCE.replace('%s', uri));
|
|
100
|
+
}
|
|
101
|
+
errors.push({
|
|
102
|
+
code: 'INVALID_REFERENCE',
|
|
103
|
+
message: ERRORS.INVALID_REFERENCE.replace('%s', uri),
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// Understand the URI
|
|
108
|
+
const [prefix, path] = uri.split('#', 2);
|
|
109
|
+
/** Check whether the file is pointing to itself */
|
|
110
|
+
const isDifferentFile = prefix !== file.filename;
|
|
111
|
+
// External references
|
|
112
|
+
if (prefix && isDifferentFile) {
|
|
113
|
+
const externalReference = filesystem.find((entry) => {
|
|
114
|
+
return entry.filename === prefix;
|
|
115
|
+
});
|
|
116
|
+
if (!externalReference) {
|
|
117
|
+
if (options?.throwOnError) {
|
|
118
|
+
throw new Error(ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', prefix));
|
|
119
|
+
}
|
|
120
|
+
errors.push({
|
|
121
|
+
code: 'EXTERNAL_REFERENCE_NOT_FOUND',
|
|
122
|
+
message: ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', prefix),
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const result = resolveReferences(filesystem, options, externalReference, errors);
|
|
127
|
+
// $ref: 'other-file.yaml'
|
|
128
|
+
if (path === undefined) {
|
|
129
|
+
return result.schema;
|
|
130
|
+
}
|
|
131
|
+
// $ref: 'other-file.yaml#/foo/bar'
|
|
132
|
+
return resolveUri(`#${path}`, options, externalReference, filesystem, errors);
|
|
133
|
+
}
|
|
134
|
+
// Pointers
|
|
135
|
+
const segments = getSegmentsFromPath(path);
|
|
136
|
+
// Try to find the URI
|
|
137
|
+
try {
|
|
138
|
+
return segments.reduce((acc, key) => {
|
|
139
|
+
return acc[key];
|
|
140
|
+
}, file.specification);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (options?.throwOnError) {
|
|
144
|
+
throw new Error(ERRORS.INVALID_REFERENCE.replace('%s', uri));
|
|
145
|
+
}
|
|
146
|
+
errors.push({
|
|
147
|
+
code: 'INVALID_REFERENCE',
|
|
148
|
+
message: ERRORS.INVALID_REFERENCE.replace('%s', uri),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { resolveReferences };
|
package/dist/utils/toJson.js
CHANGED
package/dist/utils/toYaml.js
CHANGED
|
@@ -1 +1,23 @@
|
|
|
1
|
-
import{betterAjvErrors
|
|
1
|
+
import { betterAjvErrors } from './betterAjvErrors/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transforms ajv errors, finds the positions in the schema and returns an enriched format.
|
|
5
|
+
*/
|
|
6
|
+
function transformErrors(specification, errors) {
|
|
7
|
+
// TODO: This should work with multiple files
|
|
8
|
+
if (typeof errors === 'string') {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
message: errors,
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
return betterAjvErrors(specification, null, errors, {
|
|
16
|
+
indent: 2,
|
|
17
|
+
}).map((error) => {
|
|
18
|
+
error.message = error.message.trim();
|
|
19
|
+
return error;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { transformErrors };
|
package/dist/utils/traverse.js
CHANGED
|
@@ -1 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Recursively traverses the specification and applies the transform function to each node.
|
|
3
|
+
*/
|
|
4
|
+
function traverse(specification, transform, path = []) {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (const [key, value] of Object.entries(specification)) {
|
|
7
|
+
const currentPath = [...path, key];
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
result[key] = value.map((item, index) => {
|
|
10
|
+
if (typeof item === 'object' && item !== null) {
|
|
11
|
+
return traverse(item, transform, [...currentPath, index.toString()]);
|
|
12
|
+
}
|
|
13
|
+
return item;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
else if (typeof value === 'object' && value !== null) {
|
|
17
|
+
result[key] = traverse(value, transform, currentPath);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
result[key] = value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return transform(result, path);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { traverse };
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Unescape JSON pointer
|
|
3
|
+
*
|
|
4
|
+
* Examples:
|
|
5
|
+
* /foo~1bar~0baz -> /foo/bar~baz
|
|
6
|
+
*/
|
|
7
|
+
function unescapeJsonPointer(uri) {
|
|
8
|
+
return decodeURI(uri.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { unescapeJsonPointer };
|
package/dist/utils/upgrade.js
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import { getEntrypoint } from './getEntrypoint.js';
|
|
2
|
+
import { makeFilesystem } from './makeFilesystem.js';
|
|
3
|
+
import { upgradeFromThreeToThreeOne } from './upgradeFromThreeToThreeOne.js';
|
|
4
|
+
import { upgradeFromTwoToThree } from './upgradeFromTwoToThree.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Upgrade specification to OpenAPI 3.1.0
|
|
8
|
+
*/
|
|
9
|
+
function upgrade(value) {
|
|
10
|
+
const upgraders = [upgradeFromTwoToThree, upgradeFromThreeToThreeOne];
|
|
11
|
+
// TODO: Run upgrade over the whole filesystem
|
|
12
|
+
const result = upgraders.reduce((currentSpecification, upgrader) => upgrader(currentSpecification), getEntrypoint(makeFilesystem(value)).specification);
|
|
13
|
+
return {
|
|
14
|
+
specification: result,
|
|
15
|
+
// TODO: Make dynamic
|
|
16
|
+
version: '3.1',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { upgrade };
|
|
@@ -1 +1,122 @@
|
|
|
1
|
-
import{traverse
|
|
1
|
+
import { traverse } from './traverse.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Upgrade from OpenAPI 3.0.x to 3.1.0
|
|
5
|
+
*
|
|
6
|
+
* https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0
|
|
7
|
+
*/
|
|
8
|
+
function upgradeFromThreeToThreeOne(originalSpecification) {
|
|
9
|
+
let specification = originalSpecification;
|
|
10
|
+
// Version
|
|
11
|
+
if (specification.openapi?.startsWith('3.0')) {
|
|
12
|
+
specification.openapi = '3.1.0';
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// Skip if it’s something else than 3.0.x
|
|
16
|
+
return specification;
|
|
17
|
+
}
|
|
18
|
+
// Nullable types
|
|
19
|
+
specification = traverse(specification, (schema) => {
|
|
20
|
+
if (schema.type !== 'undefined' && schema.nullable === true) {
|
|
21
|
+
schema.type = ['null', schema.type];
|
|
22
|
+
delete schema.nullable;
|
|
23
|
+
}
|
|
24
|
+
return schema;
|
|
25
|
+
});
|
|
26
|
+
// exclusiveMinimum and exclusiveMaximum
|
|
27
|
+
specification = traverse(specification, (schema) => {
|
|
28
|
+
if (schema.exclusiveMinimum === true) {
|
|
29
|
+
schema.exclusiveMinimum = schema.minimum;
|
|
30
|
+
delete schema.minimum;
|
|
31
|
+
}
|
|
32
|
+
else if (schema.exclusiveMinimum === false) {
|
|
33
|
+
delete schema.exclusiveMinimum;
|
|
34
|
+
}
|
|
35
|
+
if (schema.exclusiveMaximum === true) {
|
|
36
|
+
schema.exclusiveMaximum = schema.maximum;
|
|
37
|
+
delete schema.maximum;
|
|
38
|
+
}
|
|
39
|
+
else if (schema.exclusiveMaximum === false) {
|
|
40
|
+
delete schema.exclusiveMaximum;
|
|
41
|
+
}
|
|
42
|
+
return schema;
|
|
43
|
+
});
|
|
44
|
+
// Use examples not example
|
|
45
|
+
specification = traverse(specification, (schema, path) => {
|
|
46
|
+
if (schema.example !== undefined) {
|
|
47
|
+
// Arrays in schemas
|
|
48
|
+
if (isSchemaPath(path)) {
|
|
49
|
+
schema.examples = [schema.example];
|
|
50
|
+
}
|
|
51
|
+
// Objects everywhere else
|
|
52
|
+
else {
|
|
53
|
+
schema.examples = {
|
|
54
|
+
default: schema.example,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
delete schema.example;
|
|
58
|
+
}
|
|
59
|
+
return schema;
|
|
60
|
+
});
|
|
61
|
+
// Multipart file uploads with a binary file
|
|
62
|
+
specification = traverse(specification, (schema) => {
|
|
63
|
+
if (schema.type === 'object' && schema.properties !== undefined) {
|
|
64
|
+
// Types
|
|
65
|
+
const entries = Object.entries(schema.properties);
|
|
66
|
+
for (const [_, value] of entries) {
|
|
67
|
+
if (typeof value === 'object' &&
|
|
68
|
+
value.type === 'string' &&
|
|
69
|
+
value.format === 'binary') {
|
|
70
|
+
value.contentEncoding = 'application/octet-stream';
|
|
71
|
+
delete value.format;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return schema;
|
|
76
|
+
});
|
|
77
|
+
// Uploading a binary file in a POST request
|
|
78
|
+
specification = traverse(specification, (schema) => {
|
|
79
|
+
if (schema.type === 'string' && schema.format === 'binary') {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
return schema;
|
|
83
|
+
});
|
|
84
|
+
// Uploading an image with base64 encoding
|
|
85
|
+
specification = traverse(specification, (schema) => {
|
|
86
|
+
if (schema.type === 'string' && schema.format === 'base64') {
|
|
87
|
+
return {
|
|
88
|
+
type: 'string',
|
|
89
|
+
contentEncoding: 'base64',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return schema;
|
|
93
|
+
});
|
|
94
|
+
// Declaring $schema Dialects to protect against change
|
|
95
|
+
// if (typeof specification.$schema === 'undefined') {
|
|
96
|
+
// specification.$schema = 'http://json-schema.org/draft-07/schema#'
|
|
97
|
+
// }
|
|
98
|
+
return specification;
|
|
99
|
+
}
|
|
100
|
+
/** Determine if the current path is within a schema */
|
|
101
|
+
function isSchemaPath(path) {
|
|
102
|
+
const schemaLocations = [
|
|
103
|
+
['components', 'schemas'],
|
|
104
|
+
'properties',
|
|
105
|
+
'items',
|
|
106
|
+
'allOf',
|
|
107
|
+
'anyOf',
|
|
108
|
+
'oneOf',
|
|
109
|
+
'not',
|
|
110
|
+
'additionalProperties',
|
|
111
|
+
];
|
|
112
|
+
return (schemaLocations.some((location) => {
|
|
113
|
+
if (Array.isArray(location)) {
|
|
114
|
+
return location.every((segment, index) => path[index] === segment);
|
|
115
|
+
}
|
|
116
|
+
return path.includes(location);
|
|
117
|
+
}) ||
|
|
118
|
+
path.includes('schema') ||
|
|
119
|
+
path.some((segment) => segment.endsWith('Schema')));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { isSchemaPath, upgradeFromThreeToThreeOne };
|
|
@@ -1 +1,174 @@
|
|
|
1
|
-
import{traverse
|
|
1
|
+
import { traverse } from './traverse.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Upgrade Swagger 2.0 to OpenAPI 3.0
|
|
5
|
+
*
|
|
6
|
+
* https://swagger.io/blog/news/whats-new-in-openapi-3-0/
|
|
7
|
+
*/
|
|
8
|
+
function upgradeFromTwoToThree(specification) {
|
|
9
|
+
// Version
|
|
10
|
+
if (specification.swagger?.startsWith('2.0')) {
|
|
11
|
+
specification.openapi = '3.0.3';
|
|
12
|
+
delete specification.swagger;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// Skip if it’s something else than 3.0.x
|
|
16
|
+
return specification;
|
|
17
|
+
}
|
|
18
|
+
console.warn(`[upgradeFromTwoToThree] The upgrade from Swagger 2.0 to OpenAPI 3.0 documents is experimental and lacks features.`);
|
|
19
|
+
// Servers
|
|
20
|
+
if (specification.host) {
|
|
21
|
+
const schemes = specification.schemes?.length
|
|
22
|
+
? specification.schemes
|
|
23
|
+
: ['http'];
|
|
24
|
+
specification.servers = schemes.map((scheme) => ({
|
|
25
|
+
url: `${scheme}://${specification.host}${specification.basePath ?? ''}`,
|
|
26
|
+
}));
|
|
27
|
+
delete specification.basePath;
|
|
28
|
+
delete specification.schemes;
|
|
29
|
+
delete specification.host;
|
|
30
|
+
}
|
|
31
|
+
// Schemas
|
|
32
|
+
if (specification.definitions) {
|
|
33
|
+
if (typeof specification.components !== 'object') {
|
|
34
|
+
specification.components = {};
|
|
35
|
+
}
|
|
36
|
+
specification.components.schemas = specification.definitions;
|
|
37
|
+
delete specification.definitions;
|
|
38
|
+
// Rewrite $refs to definitions
|
|
39
|
+
specification = traverse(specification, (schema) => {
|
|
40
|
+
if (schema.$ref?.startsWith('#/definitions/')) {
|
|
41
|
+
schema.$ref = schema.$ref.replace(/^#\/definitions\//, '#/components/schemas/');
|
|
42
|
+
}
|
|
43
|
+
return schema;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Paths
|
|
47
|
+
if (specification.paths) {
|
|
48
|
+
for (const path in specification.paths) {
|
|
49
|
+
if (Object.hasOwn(specification.paths, path)) {
|
|
50
|
+
const pathItem = specification.paths[path];
|
|
51
|
+
for (const method in pathItem) {
|
|
52
|
+
if (Object.hasOwn(pathItem, method)) {
|
|
53
|
+
const operationItem = pathItem[method];
|
|
54
|
+
// Request bodies
|
|
55
|
+
if (operationItem.parameters) {
|
|
56
|
+
const bodyParameter = structuredClone(operationItem.parameters.find((parameter) => parameter.in === 'body') ?? {});
|
|
57
|
+
if (bodyParameter && Object.keys(bodyParameter).length) {
|
|
58
|
+
delete bodyParameter.name;
|
|
59
|
+
delete bodyParameter.in;
|
|
60
|
+
const consumes = specification.consumes ??
|
|
61
|
+
operationItem.consumes ?? ['application/json'];
|
|
62
|
+
if (typeof operationItem.requestBody !== 'object') {
|
|
63
|
+
operationItem.requestBody = {};
|
|
64
|
+
}
|
|
65
|
+
if (typeof operationItem.requestBody.content !== 'object') {
|
|
66
|
+
operationItem.requestBody.content = {};
|
|
67
|
+
}
|
|
68
|
+
const { schema, ...requestBody } = bodyParameter;
|
|
69
|
+
operationItem.requestBody = {
|
|
70
|
+
...operationItem.requestBody,
|
|
71
|
+
...requestBody,
|
|
72
|
+
};
|
|
73
|
+
for (const type of consumes) {
|
|
74
|
+
operationItem.requestBody.content[type] = {
|
|
75
|
+
schema: schema,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Delete body parameter
|
|
80
|
+
operationItem.parameters = operationItem.parameters.filter((parameter) => parameter.in !== 'body');
|
|
81
|
+
delete operationItem.consumes;
|
|
82
|
+
// formData parameters
|
|
83
|
+
const formDataParameters = operationItem.parameters.filter((parameter) => parameter.in === 'formData');
|
|
84
|
+
if (formDataParameters.length > 0) {
|
|
85
|
+
if (typeof operationItem.requestBody !== 'object') {
|
|
86
|
+
operationItem.requestBody = {};
|
|
87
|
+
}
|
|
88
|
+
if (typeof operationItem.requestBody.content !== 'object') {
|
|
89
|
+
operationItem.requestBody.content = {};
|
|
90
|
+
}
|
|
91
|
+
operationItem.requestBody.content['application/x-www-form-urlencoded'] = {
|
|
92
|
+
schema: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {},
|
|
95
|
+
required: [], // Initialize required array
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
for (const param of formDataParameters) {
|
|
99
|
+
operationItem.requestBody.content['application/x-www-form-urlencoded'].schema.properties[param.name] = {
|
|
100
|
+
type: param.type,
|
|
101
|
+
description: param.description,
|
|
102
|
+
};
|
|
103
|
+
// Add to required array if param is required
|
|
104
|
+
if (param.required) {
|
|
105
|
+
operationItem.requestBody.content['application/x-www-form-urlencoded'].schema.required.push(param.name);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Remove formData parameters from the parameters array
|
|
109
|
+
operationItem.parameters = operationItem.parameters.filter((parameter) => parameter.in !== 'formData');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Responses
|
|
113
|
+
if (operationItem.responses) {
|
|
114
|
+
for (const response in operationItem.responses) {
|
|
115
|
+
if (Object.hasOwn(operationItem.responses, response)) {
|
|
116
|
+
const responseItem = operationItem.responses[response];
|
|
117
|
+
if (responseItem.schema) {
|
|
118
|
+
const produces = specification.produces ??
|
|
119
|
+
operationItem.produces ?? ['application/json'];
|
|
120
|
+
if (typeof responseItem.content !== 'object') {
|
|
121
|
+
responseItem.content = {};
|
|
122
|
+
}
|
|
123
|
+
for (const type of produces) {
|
|
124
|
+
responseItem.content[type] = {
|
|
125
|
+
schema: responseItem.schema,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
delete responseItem.schema;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
delete operationItem.produces;
|
|
134
|
+
// Delete empty parameters
|
|
135
|
+
if (operationItem.parameters?.length === 0) {
|
|
136
|
+
delete operationItem.parameters;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Upgrade securityDefinitions
|
|
144
|
+
if (specification.securityDefinitions) {
|
|
145
|
+
if (typeof specification.components !== 'object') {
|
|
146
|
+
specification.components = {};
|
|
147
|
+
}
|
|
148
|
+
specification.components.securitySchemes = {};
|
|
149
|
+
for (const [key, securityScheme] of Object.entries(specification.securityDefinitions)) {
|
|
150
|
+
if (typeof securityScheme === 'object') {
|
|
151
|
+
if ('type' in securityScheme && securityScheme.type === 'oauth2') {
|
|
152
|
+
const { flow, authorizationUrl, tokenUrl, scopes } = securityScheme;
|
|
153
|
+
specification.components.securitySchemes[key] = {
|
|
154
|
+
type: 'oauth2',
|
|
155
|
+
flows: {
|
|
156
|
+
[flow]: {
|
|
157
|
+
...(authorizationUrl && { authorizationUrl }),
|
|
158
|
+
...(tokenUrl && { tokenUrl }),
|
|
159
|
+
...(scopes && { scopes }),
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
specification.components.securitySchemes[key] = securityScheme;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
delete specification.securityDefinitions;
|
|
170
|
+
}
|
|
171
|
+
return specification;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { upgradeFromTwoToThree };
|
package/dist/utils/validate.js
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import { makeFilesystem } from './makeFilesystem.js';
|
|
2
|
+
import { Validator } from '../lib/Validator/Validator.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validates an OpenAPI schema.
|
|
6
|
+
*/
|
|
7
|
+
async function validate(value, options) {
|
|
8
|
+
const filesystem = makeFilesystem(value);
|
|
9
|
+
const validator = new Validator();
|
|
10
|
+
const result = await validator.validate(filesystem, options);
|
|
11
|
+
return {
|
|
12
|
+
...result,
|
|
13
|
+
specification: validator.specification,
|
|
14
|
+
version: validator.version,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { validate };
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"parser",
|
|
18
18
|
"typescript"
|
|
19
19
|
],
|
|
20
|
-
"version": "0.8.
|
|
20
|
+
"version": "0.8.8",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=18"
|
|
23
23
|
},
|
|
@@ -26,13 +26,19 @@
|
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
|
-
"import": "./dist/index.js"
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
30
32
|
},
|
|
31
33
|
"./plugins/fetch-urls": {
|
|
32
|
-
"import": "./dist/
|
|
34
|
+
"import": "./dist/plugins/fetch-urls/index.js",
|
|
35
|
+
"types": "./dist/plugins/fetch-urls/index.d.ts",
|
|
36
|
+
"default": "./dist/plugins/fetch-urls/index.js"
|
|
33
37
|
},
|
|
34
38
|
"./plugins/read-files": {
|
|
35
|
-
"import": "./dist/
|
|
39
|
+
"import": "./dist/plugins/read-files/index.js",
|
|
40
|
+
"types": "./dist/plugins/read-files/index.d.ts",
|
|
41
|
+
"default": "./dist/plugins/read-files/index.js"
|
|
36
42
|
}
|
|
37
43
|
},
|
|
38
44
|
"files": [
|
|
@@ -53,27 +59,23 @@
|
|
|
53
59
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
54
60
|
"@babel/code-frame": "^7.24.7",
|
|
55
61
|
"@google-cloud/storage": "^7.12.1",
|
|
56
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
57
|
-
"@rollup/plugin-terser": "^0.4.4",
|
|
58
|
-
"@rollup/plugin-typescript": "^11.1.6",
|
|
59
62
|
"@types/node": "^20.14.10",
|
|
60
63
|
"glob": "^10.3.10",
|
|
61
64
|
"json-to-ast": "^2.1.0",
|
|
62
65
|
"just-diff": "^6.0.2",
|
|
63
|
-
"rollup": "^4.16.4",
|
|
64
|
-
"rollup-plugin-output-size": "^1.4.1",
|
|
65
66
|
"tinybench": "^2.8.0",
|
|
66
|
-
"@scalar/
|
|
67
|
+
"@scalar/build-tooling": "0.1.11",
|
|
68
|
+
"@scalar/openapi-types": "0.1.4"
|
|
67
69
|
},
|
|
68
70
|
"scripts": {
|
|
69
|
-
"build": "
|
|
71
|
+
"build": "scalar-build-rollup",
|
|
70
72
|
"format": "prettier --write .",
|
|
71
73
|
"lint:check": "eslint .",
|
|
72
74
|
"lint:fix": "eslint . --fix",
|
|
73
75
|
"test": "vitest",
|
|
74
76
|
"test:prepare": "vite-node scripts/load-files.ts",
|
|
75
77
|
"test:unit": "vite-node scripts/load-files.ts && vitest",
|
|
76
|
-
"types:build": "
|
|
77
|
-
"types:check": "
|
|
78
|
+
"types:build": "scalar-types-build",
|
|
79
|
+
"types:check": "scalar-types-check"
|
|
78
80
|
}
|
|
79
81
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fetchUrls.d.ts","sourceRoot":"","sources":["../../../../src/utils/load/plugins/fetchUrls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzC,eAAO,MAAM,6BAA6B;;CAEzC,CAAA;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,mBAAmB,CAAC,EAAE;IAC7C;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACtB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3C,KAAK,UAiDL,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const t={limit:20},e=e=>{let s=0;const r={...t,...e};return{check:t=>"string"==typeof t&&!(!t.startsWith("http://")&&!t.startsWith("https://")),async get(t){if(!1!==r?.limit&&s>=r?.limit)console.warn(`[fetchUrls] Maximum number of requests reeached (${r?.limit}), skipping request`);else try{s++;const e=await(r?.fetch?r.fetch(t):fetch(t));return await e.text()}catch(e){console.error("[fetchUrls]",e.message,`(${t})`)}}}};export{e as fetchUrls,t as fetchUrlsDefaultConfiguration};
|