@scalar/openapi-validator 0.1.10 → 0.1.12
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 +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/validate-cookie-parameters.d.ts +9 -0
- package/dist/validate-cookie-parameters.d.ts.map +1 -0
- package/dist/validate-cookie-parameters.js +103 -0
- package/dist/validate-path-parameters.d.ts.map +1 -1
- package/dist/validate-path-parameters.js +14 -10
- package/dist/validate.d.ts +7 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +8 -5
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @scalar/openapi-validator
|
|
2
2
|
|
|
3
|
+
## 0.1.12
|
|
4
|
+
|
|
5
|
+
## 0.1.11
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- [#10489](https://github.com/scalar/scalar/pull/10489): Report invalid non-exploded structured cookies in OpenAPI 3.2 documents and prevent invalid request and code sample generation. Recommend `style: cookie` with `explode: true`.
|
|
10
|
+
- [#10485](https://github.com/scalar/scalar/pull/10485): Validate missing and mismatched path parameters for QUERY and methods under additionalOperations, including inherited path-level parameters.
|
|
11
|
+
|
|
3
12
|
## 0.1.10
|
|
4
13
|
|
|
5
14
|
## 0.1.9
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { OpenApiVersions as supportedVersions } from './specifications.js';
|
|
2
2
|
export type { ErrorObject, OpenApiVersion, ThrowOnErrorOption, ValidationOutcome } from './types.js';
|
|
3
3
|
export { type ValidateOptions, validate } from './validate.js';
|
|
4
|
+
export { validateCookieParameters } from './validate-cookie-parameters.js';
|
|
4
5
|
export { validatePathParameters } from './validate-path-parameters.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACvE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AACjG,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACvE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AACjG,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAA;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AnyObject } from '@scalar/types/utils';
|
|
2
|
+
import type { ErrorObject } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Checks cookie serialization declarations in OpenAPI 3.2 documents.
|
|
5
|
+
* Resolve references before calling to also check referenced parameters and schemas.
|
|
6
|
+
* Only OpenAPI parameter locations are visited; examples and extensions are data.
|
|
7
|
+
*/
|
|
8
|
+
export declare const validateCookieParameters: (specification: AnyObject) => ErrorObject[];
|
|
9
|
+
//# sourceMappingURL=validate-cookie-parameters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-cookie-parameters.d.ts","sourceRoot":"","sources":["../src/validate-cookie-parameters.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE1C;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,eAAe,SAAS,KAAG,WAAW,EAoG9E,CAAA"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { getCookieSerializationError } from '@scalar/helpers/http/get-cookie-serialization-error';
|
|
2
|
+
import { isHttpMethod } from '@scalar/helpers/http/is-http-method';
|
|
3
|
+
import { isObject } from '@scalar/helpers/object/is-object';
|
|
4
|
+
/**
|
|
5
|
+
* Checks cookie serialization declarations in OpenAPI 3.2 documents.
|
|
6
|
+
* Resolve references before calling to also check referenced parameters and schemas.
|
|
7
|
+
* Only OpenAPI parameter locations are visited; examples and extensions are data.
|
|
8
|
+
*/
|
|
9
|
+
export const validateCookieParameters = (specification) => {
|
|
10
|
+
const [major, minor] = String(specification.openapi).split('.');
|
|
11
|
+
if (major !== '3' || minor !== '2') {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const errors = [];
|
|
15
|
+
const ancestors = new WeakSet();
|
|
16
|
+
const checkParameter = (parameter, path) => {
|
|
17
|
+
if (!isObject(parameter) || 'content' in parameter || typeof parameter.name !== 'string') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const message = getCookieSerializationError({
|
|
21
|
+
name: parameter.name,
|
|
22
|
+
in: String(parameter.in),
|
|
23
|
+
style: typeof parameter.style === 'string' ? parameter.style : undefined,
|
|
24
|
+
explode: typeof parameter.explode === 'boolean' ? parameter.explode : undefined,
|
|
25
|
+
schema: parameter.schema,
|
|
26
|
+
});
|
|
27
|
+
if (message) {
|
|
28
|
+
errors.push({ path: [...path, 'explode'], message });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const checkParameters = (parameters, path) => {
|
|
32
|
+
if (Array.isArray(parameters)) {
|
|
33
|
+
parameters.forEach((parameter, index) => checkParameter(parameter, [...path, String(index)]));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const visitCallback = (callback, path) => {
|
|
37
|
+
if (!isObject(callback) || ancestors.has(callback)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
ancestors.add(callback);
|
|
41
|
+
for (const [expression, pathItem] of Object.entries(callback)) {
|
|
42
|
+
if (!expression.startsWith('x-') && expression !== '$ref') {
|
|
43
|
+
visitPathItem(pathItem, [...path, expression]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
ancestors.delete(callback);
|
|
47
|
+
};
|
|
48
|
+
const visitOperation = (operation, path) => {
|
|
49
|
+
if (!isObject(operation)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
checkParameters(operation.parameters, [...path, 'parameters']);
|
|
53
|
+
if (isObject(operation.callbacks)) {
|
|
54
|
+
for (const [name, callback] of Object.entries(operation.callbacks)) {
|
|
55
|
+
visitCallback(callback, [...path, 'callbacks', name]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const visitPathItem = (pathItem, path) => {
|
|
60
|
+
if (!isObject(pathItem) || ancestors.has(pathItem)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
ancestors.add(pathItem);
|
|
64
|
+
checkParameters(pathItem.parameters, [...path, 'parameters']);
|
|
65
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
66
|
+
if (isHttpMethod(method)) {
|
|
67
|
+
visitOperation(operation, [...path, method]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (isObject(pathItem.additionalOperations)) {
|
|
71
|
+
for (const [method, operation] of Object.entries(pathItem.additionalOperations)) {
|
|
72
|
+
visitOperation(operation, [...path, 'additionalOperations', method]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
ancestors.delete(pathItem);
|
|
76
|
+
};
|
|
77
|
+
for (const key of ['paths', 'webhooks']) {
|
|
78
|
+
const items = specification[key];
|
|
79
|
+
if (isObject(items)) {
|
|
80
|
+
for (const [name, pathItem] of Object.entries(items)) {
|
|
81
|
+
if (!name.startsWith('x-')) {
|
|
82
|
+
visitPathItem(pathItem, [key, name]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const components = specification.components;
|
|
88
|
+
if (isObject(components)) {
|
|
89
|
+
for (const [key, visit] of [
|
|
90
|
+
['parameters', checkParameter],
|
|
91
|
+
['pathItems', visitPathItem],
|
|
92
|
+
['callbacks', visitCallback],
|
|
93
|
+
]) {
|
|
94
|
+
const items = components[key];
|
|
95
|
+
if (isObject(items)) {
|
|
96
|
+
for (const [name, item] of Object.entries(items)) {
|
|
97
|
+
visit(item, ['components', key, name]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return errors;
|
|
103
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-path-parameters.d.ts","sourceRoot":"","sources":["../src/validate-path-parameters.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-path-parameters.d.ts","sourceRoot":"","sources":["../src/validate-path-parameters.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAmB1C;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,SAAS,GAAG,WAAW,EAAE,CAyE9E"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HTTP_METHODS } from '@scalar/helpers/http/http-methods';
|
|
1
2
|
import { isObjectLike } from '@scalar/helpers/object/is-object';
|
|
2
3
|
import { deduplicateErrors } from '@scalar/json-schema-validator';
|
|
3
4
|
/**
|
|
@@ -8,7 +9,7 @@ import { deduplicateErrors } from '@scalar/json-schema-validator';
|
|
|
8
9
|
* hand-constructed document rather than validating them.
|
|
9
10
|
*/
|
|
10
11
|
const isRecord = (value) => isObjectLike(value) && !Array.isArray(value);
|
|
11
|
-
const OPERATION_KEYS = new Set(
|
|
12
|
+
const OPERATION_KEYS = new Set(HTTP_METHODS);
|
|
12
13
|
const PATH_PARAMETER_PATTERN = /{([^}]+)}/g;
|
|
13
14
|
/**
|
|
14
15
|
* Validates path-template semantics that are not enforced by the JSON schema.
|
|
@@ -23,7 +24,15 @@ export function validatePathParameters(specification) {
|
|
|
23
24
|
if (!isRecord(pathItem)) {
|
|
24
25
|
continue;
|
|
25
26
|
}
|
|
26
|
-
const operations = Object.entries(pathItem).
|
|
27
|
+
const operations = Object.entries(pathItem).flatMap(([key, value]) => {
|
|
28
|
+
if (OPERATION_KEYS.has(key) && isRecord(value)) {
|
|
29
|
+
return [{ operation: value, path: ['paths', pathName, key] }];
|
|
30
|
+
}
|
|
31
|
+
if (key === 'additionalOperations' && isRecord(value)) {
|
|
32
|
+
return Object.entries(value).flatMap(([method, operation]) => isRecord(operation) ? [{ operation, path: ['paths', pathName, 'additionalOperations', method] }] : []);
|
|
33
|
+
}
|
|
34
|
+
return [];
|
|
35
|
+
});
|
|
27
36
|
// Preserve the repo's current behaviour for empty path items.
|
|
28
37
|
if (operations.length === 0) {
|
|
29
38
|
continue;
|
|
@@ -38,13 +47,8 @@ export function validatePathParameters(specification) {
|
|
|
38
47
|
});
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
|
-
for (const
|
|
42
|
-
const operationParameters = getPathParameters(operation.parameters, [
|
|
43
|
-
'paths',
|
|
44
|
-
pathName,
|
|
45
|
-
operationKey,
|
|
46
|
-
'parameters',
|
|
47
|
-
]);
|
|
50
|
+
for (const { operation, path } of operations) {
|
|
51
|
+
const operationParameters = getPathParameters(operation.parameters, [...path, 'parameters']);
|
|
48
52
|
for (const parameter of operationParameters) {
|
|
49
53
|
if (!templateParameters.has(parameter.name)) {
|
|
50
54
|
errors.push({
|
|
@@ -57,7 +61,7 @@ export function validatePathParameters(specification) {
|
|
|
57
61
|
for (const templateParameter of templateParameters) {
|
|
58
62
|
if (!effectiveParameters.has(templateParameter)) {
|
|
59
63
|
errors.push({
|
|
60
|
-
path
|
|
64
|
+
path,
|
|
61
65
|
message: `Declared path parameter "${templateParameter}" needs to be defined as a path parameter at either the path or operation level`,
|
|
62
66
|
});
|
|
63
67
|
}
|
package/dist/validate.d.ts
CHANGED
|
@@ -15,12 +15,18 @@ export type ValidateOptions = ThrowOnErrorOption & {
|
|
|
15
15
|
* @default false
|
|
16
16
|
*/
|
|
17
17
|
checkPathParameters?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Check OpenAPI 3.2 cookie serialization declarations.
|
|
20
|
+
* Resolve references first for complete coverage.
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
checkCookieParameters?: boolean;
|
|
18
24
|
};
|
|
19
25
|
/**
|
|
20
26
|
* Validates a single OpenAPI document against the OpenAPI Specification.
|
|
21
27
|
*
|
|
22
28
|
* Schema validation is delegated to `@scalar/json-schema-validator`; version
|
|
23
|
-
* detection and
|
|
29
|
+
* detection and parameter semantic checks are OpenAPI-specific.
|
|
24
30
|
*
|
|
25
31
|
* This validator is strict about the specification: every required field,
|
|
26
32
|
* including `info.version`, must be present. Callers that want to be lenient
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAMpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAMpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAIpE,MAAM,MAAM,eAAe,GAAG,kBAAkB,GAAG;IACjD;;;;;;;;;;;;OAYG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC,CAAA;AAiBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,iBAAiB,CAEnG"}
|
package/dist/validate.js
CHANGED
|
@@ -3,6 +3,7 @@ import { detectVersion } from './detect-version.js';
|
|
|
3
3
|
import { ERRORS } from './errors.js';
|
|
4
4
|
import { explainComponentReferences } from './explain-component-references.js';
|
|
5
5
|
import { OpenApiSpecifications } from './specifications.js';
|
|
6
|
+
import { validateCookieParameters } from './validate-cookie-parameters.js';
|
|
6
7
|
import { validatePathParameters } from './validate-path-parameters.js';
|
|
7
8
|
const validateDocument = createSpecificationValidator({
|
|
8
9
|
schemas: OpenApiSpecifications,
|
|
@@ -11,16 +12,18 @@ const validateDocument = createSpecificationValidator({
|
|
|
11
12
|
formats: (version) => (version === '3.1' || version === '3.2' ? { 'media-range': true } : undefined),
|
|
12
13
|
errors: { emptyOrInvalid: ERRORS.EMPTY_OR_INVALID, versionNotSupported: ERRORS.OPENAPI_VERSION_NOT_SUPPORTED },
|
|
13
14
|
transformErrors: explainComponentReferences,
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// Cookie declarations can be checked without resolving references. Path-template
|
|
16
|
+
// checks require a resolved document to avoid reporting referenced parameters as missing.
|
|
17
|
+
postValidate: (specification, _version, options) => [
|
|
18
|
+
...(options?.checkPathParameters ? validatePathParameters(specification) : []),
|
|
19
|
+
...(options?.checkCookieParameters === false ? [] : validateCookieParameters(specification)),
|
|
20
|
+
],
|
|
18
21
|
});
|
|
19
22
|
/**
|
|
20
23
|
* Validates a single OpenAPI document against the OpenAPI Specification.
|
|
21
24
|
*
|
|
22
25
|
* Schema validation is delegated to `@scalar/json-schema-validator`; version
|
|
23
|
-
* detection and
|
|
26
|
+
* detection and parameter semantic checks are OpenAPI-specific.
|
|
24
27
|
*
|
|
25
28
|
* This validator is strict about the specification: every required field,
|
|
26
29
|
* including `info.version`, must be present. Callers that want to be lenient
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"validator",
|
|
18
18
|
"typescript"
|
|
19
19
|
],
|
|
20
|
-
"version": "0.1.
|
|
20
|
+
"version": "0.1.12",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=22"
|
|
23
23
|
},
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
],
|
|
39
39
|
"sideEffects": false,
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@scalar/helpers": "0.16.
|
|
42
|
-
"@scalar/json-schema-validator": "0.1.
|
|
43
|
-
"@scalar/types": "0.23.
|
|
41
|
+
"@scalar/helpers": "0.16.3",
|
|
42
|
+
"@scalar/json-schema-validator": "0.1.12",
|
|
43
|
+
"@scalar/types": "0.23.2",
|
|
44
44
|
"yaml": "^2.9.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@scalar/themes": "0.
|
|
47
|
+
"@scalar/themes": "0.19.1",
|
|
48
48
|
"@scalar/use-codemirror": "0.14.15",
|
|
49
49
|
"@tailwindcss/vite": "^4.3.3",
|
|
50
50
|
"@types/node": "^24.1.0",
|