@redocly/openapi-core 1.0.0 → 1.0.1
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 +7 -0
- package/__tests__/utils.ts +88 -0
- package/lib/config/all.js +0 -1
- package/lib/config/minimal.js +0 -1
- package/lib/config/recommended.js +0 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/bundle.test.ts.snap +437 -0
- package/src/__tests__/bundle.test.ts +236 -0
- package/src/__tests__/codeframes.test.ts +530 -0
- package/src/__tests__/fixtures/.redocly.lint-ignore.yaml +5 -0
- package/src/__tests__/fixtures/extension.js +24 -0
- package/src/__tests__/fixtures/refs/definitions.yaml +3 -0
- package/src/__tests__/fixtures/refs/examples.yaml +8 -0
- package/src/__tests__/fixtures/refs/external-request-body.yaml +13 -0
- package/src/__tests__/fixtures/refs/externalref.yaml +35 -0
- package/src/__tests__/fixtures/refs/hosted.yaml +35 -0
- package/src/__tests__/fixtures/refs/openapi-with-external-refs-conflicting-names.yaml +21 -0
- package/src/__tests__/fixtures/refs/openapi-with-external-refs.yaml +33 -0
- package/src/__tests__/fixtures/refs/openapi-with-url-refs.yaml +18 -0
- package/src/__tests__/fixtures/refs/param-b.yaml +1 -0
- package/src/__tests__/fixtures/refs/param-c.yaml +1 -0
- package/src/__tests__/fixtures/refs/rename.yaml +1 -0
- package/src/__tests__/fixtures/refs/requestBody.yaml +9 -0
- package/src/__tests__/fixtures/refs/schema-a.yaml +1 -0
- package/src/__tests__/fixtures/refs/simple.yaml +1 -0
- package/src/__tests__/fixtures/refs/vendor.schema.yaml +20 -0
- package/src/__tests__/fixtures/resolve/External.yaml +10 -0
- package/src/__tests__/fixtures/resolve/External2.yaml +4 -0
- package/src/__tests__/fixtures/resolve/description.md +3 -0
- package/src/__tests__/fixtures/resolve/externalInfo.yaml +4 -0
- package/src/__tests__/fixtures/resolve/externalLicense.yaml +1 -0
- package/src/__tests__/fixtures/resolve/openapi-with-back.yaml +13 -0
- package/src/__tests__/fixtures/resolve/openapi-with-md-description.yaml +5 -0
- package/src/__tests__/fixtures/resolve/openapi.yaml +28 -0
- package/src/__tests__/fixtures/resolve/schemas/type-a.yaml +10 -0
- package/src/__tests__/fixtures/resolve/schemas/type-b.yaml +6 -0
- package/src/__tests__/fixtures/resolve/transitive/a.yaml +1 -0
- package/src/__tests__/fixtures/resolve/transitive/components.yaml +5 -0
- package/src/__tests__/fixtures/resolve/transitive/schemas.yaml +3 -0
- package/src/__tests__/format.test.ts +76 -0
- package/src/__tests__/js-yaml.test.ts +73 -0
- package/src/__tests__/lint.test.ts +392 -0
- package/src/__tests__/logger-browser.test.ts +53 -0
- package/src/__tests__/logger.test.ts +47 -0
- package/src/__tests__/login.test.ts +17 -0
- package/src/__tests__/normalizeVisitors.test.ts +151 -0
- package/src/__tests__/output-browser.test.ts +18 -0
- package/src/__tests__/output.test.ts +15 -0
- package/src/__tests__/ref-utils.test.ts +120 -0
- package/src/__tests__/resolve-http.test.ts +77 -0
- package/src/__tests__/resolve.test.ts +431 -0
- package/src/__tests__/utils-browser.test.ts +11 -0
- package/src/__tests__/utils.test.ts +144 -0
- package/src/__tests__/walk.test.ts +1545 -0
- package/src/benchmark/benches/lint-with-many-rules.bench.ts +35 -0
- package/src/benchmark/benches/lint-with-nested-rule.bench.ts +39 -0
- package/src/benchmark/benches/lint-with-no-rules.bench.ts +20 -0
- package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +35 -0
- package/src/benchmark/benches/lint-with-top-level-rule.bench.ts +32 -0
- package/src/benchmark/benches/rebilly.yaml +32275 -0
- package/src/benchmark/benches/recommended-oas3.bench.ts +22 -0
- package/src/benchmark/benches/resolve-with-no-external.bench.ts +23 -0
- package/src/benchmark/benchmark.js +311 -0
- package/src/benchmark/colors.js +29 -0
- package/src/benchmark/fork.js +83 -0
- package/src/benchmark/utils.ts +36 -0
- package/src/bundle.ts +417 -0
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +164 -0
- package/src/config/__tests__/__snapshots__/config.test.ts.snap +144 -0
- package/src/config/__tests__/config-resolvers.test.ts +491 -0
- package/src/config/__tests__/config.test.ts +312 -0
- package/src/config/__tests__/fixtures/ingore-file.ts +8 -0
- package/src/config/__tests__/fixtures/load-redocly.yaml +2 -0
- package/src/config/__tests__/fixtures/plugin-config.yaml +2 -0
- package/src/config/__tests__/fixtures/plugin.js +56 -0
- package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -0
- package/src/config/__tests__/fixtures/resolve-config/api/plugin.js +69 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +17 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +15 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -0
- package/src/config/__tests__/fixtures/resolve-config/plugin.js +80 -0
- package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -0
- package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -0
- package/src/config/__tests__/load.test.ts +167 -0
- package/src/config/__tests__/resolve-plugins.test.ts +27 -0
- package/src/config/__tests__/utils.test.ts +204 -0
- package/src/config/all.ts +74 -0
- package/src/config/builtIn.ts +37 -0
- package/src/config/config-resolvers.ts +474 -0
- package/src/config/config.ts +332 -0
- package/src/config/index.ts +7 -0
- package/src/config/load.ts +144 -0
- package/src/config/minimal.ts +61 -0
- package/src/config/recommended.ts +61 -0
- package/src/config/rules.ts +54 -0
- package/src/config/types.ts +231 -0
- package/src/config/utils.ts +349 -0
- package/src/decorators/__tests__/filter-in.test.ts +310 -0
- package/src/decorators/__tests__/filter-out.test.ts +335 -0
- package/src/decorators/__tests__/media-type-examples-override.test.ts +665 -0
- package/src/decorators/__tests__/remove-x-internal.test.ts +316 -0
- package/src/decorators/__tests__/resources/request.yaml +3 -0
- package/src/decorators/__tests__/resources/response.yaml +3 -0
- package/src/decorators/common/filters/filter-helper.ts +72 -0
- package/src/decorators/common/filters/filter-in.ts +18 -0
- package/src/decorators/common/filters/filter-out.ts +18 -0
- package/src/decorators/common/info-description-override.ts +24 -0
- package/src/decorators/common/info-override.ts +15 -0
- package/src/decorators/common/media-type-examples-override.ts +79 -0
- package/src/decorators/common/operation-description-override.ts +30 -0
- package/src/decorators/common/registry-dependencies.ts +25 -0
- package/src/decorators/common/remove-x-internal.ts +59 -0
- package/src/decorators/common/tag-description-override.ts +25 -0
- package/src/decorators/oas2/index.ts +20 -0
- package/src/decorators/oas3/index.ts +22 -0
- package/src/env.ts +5 -0
- package/src/format/codeframes.ts +216 -0
- package/src/format/format.ts +375 -0
- package/src/index.ts +71 -0
- package/src/js-yaml/index.ts +14 -0
- package/src/lint.ts +148 -0
- package/src/logger.ts +34 -0
- package/src/oas-types.ts +57 -0
- package/src/output.ts +7 -0
- package/src/redocly/__tests__/redocly-client.test.ts +146 -0
- package/src/redocly/index.ts +187 -0
- package/src/redocly/redocly-client-types.ts +10 -0
- package/src/redocly/registry-api-types.ts +32 -0
- package/src/redocly/registry-api.ts +150 -0
- package/src/ref-utils.ts +85 -0
- package/src/resolve.ts +417 -0
- package/src/rules/__tests__/fixtures/code-sample.php +9 -0
- package/src/rules/__tests__/fixtures/invalid-yaml.yaml +1 -0
- package/src/rules/__tests__/fixtures/ref.yaml +1 -0
- package/src/rules/__tests__/no-unresolved-refs.test.ts +257 -0
- package/src/rules/__tests__/utils.test.ts +160 -0
- package/src/rules/ajv.ts +102 -0
- package/src/rules/common/__tests__/info-license.test.ts +62 -0
- package/src/rules/common/__tests__/license-url.test.ts +63 -0
- package/src/rules/common/__tests__/no-ambiguous-paths.test.ts +96 -0
- package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +210 -0
- package/src/rules/common/__tests__/no-identical-paths.test.ts +58 -0
- package/src/rules/common/__tests__/no-path-trailing-slash.test.ts +85 -0
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +192 -0
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +231 -0
- package/src/rules/common/__tests__/operation-operationId-unique.test.ts +76 -0
- package/src/rules/common/__tests__/operation-operationId-url-safe.test.ts +45 -0
- package/src/rules/common/__tests__/operation-parameters-unique.test.ts +167 -0
- package/src/rules/common/__tests__/operation-singular-tag.test.ts +72 -0
- package/src/rules/common/__tests__/path-http-verbs-order.test.ts +95 -0
- package/src/rules/common/__tests__/path-not-include-query.test.ts +64 -0
- package/src/rules/common/__tests__/path-params-defined.test.ts +202 -0
- package/src/rules/common/__tests__/paths-kebab-case.test.ts +108 -0
- package/src/rules/common/__tests__/scalar-property-missing-example.test.ts +264 -0
- package/src/rules/common/__tests__/security-defined.test.ts +175 -0
- package/src/rules/common/__tests__/spec-strict-refs.test.ts +69 -0
- package/src/rules/common/__tests__/spec.test.ts +610 -0
- package/src/rules/common/__tests__/tag-description.test.ts +65 -0
- package/src/rules/common/__tests__/tags-alphabetical.test.ts +64 -0
- package/src/rules/common/assertions/__tests__/asserts.test.ts +869 -0
- package/src/rules/common/assertions/__tests__/index.test.ts +100 -0
- package/src/rules/common/assertions/__tests__/utils.test.ts +236 -0
- package/src/rules/common/assertions/asserts.ts +357 -0
- package/src/rules/common/assertions/index.ts +53 -0
- package/src/rules/common/assertions/utils.ts +331 -0
- package/src/rules/common/info-contact.ts +15 -0
- package/src/rules/common/info-license-url.ts +10 -0
- package/src/rules/common/info-license.ts +15 -0
- package/src/rules/common/no-ambiguous-paths.ts +50 -0
- package/src/rules/common/no-enum-type-mismatch.ts +52 -0
- package/src/rules/common/no-http-verbs-in-paths.ts +36 -0
- package/src/rules/common/no-identical-paths.ts +24 -0
- package/src/rules/common/no-invalid-parameter-examples.ts +36 -0
- package/src/rules/common/no-invalid-schema-examples.ts +27 -0
- package/src/rules/common/no-path-trailing-slash.ts +15 -0
- package/src/rules/common/operation-2xx-response.ts +24 -0
- package/src/rules/common/operation-4xx-response.ts +24 -0
- package/src/rules/common/operation-description.ts +13 -0
- package/src/rules/common/operation-operationId-unique.ts +21 -0
- package/src/rules/common/operation-operationId-url-safe.ts +19 -0
- package/src/rules/common/operation-operationId.ts +17 -0
- package/src/rules/common/operation-parameters-unique.ts +48 -0
- package/src/rules/common/operation-singular-tag.ts +17 -0
- package/src/rules/common/operation-summary.ts +13 -0
- package/src/rules/common/operation-tag-defined.ts +26 -0
- package/src/rules/common/parameter-description.ts +22 -0
- package/src/rules/common/path-declaration-must-exist.ts +15 -0
- package/src/rules/common/path-excludes-patterns.ts +23 -0
- package/src/rules/common/path-http-verbs-order.ts +30 -0
- package/src/rules/common/path-not-include-query.ts +17 -0
- package/src/rules/common/path-params-defined.ts +65 -0
- package/src/rules/common/path-segment-plural.ts +31 -0
- package/src/rules/common/paths-kebab-case.ts +19 -0
- package/src/rules/common/required-string-property-missing-min-length.ts +44 -0
- package/src/rules/common/response-contains-header.ts +35 -0
- package/src/rules/common/scalar-property-missing-example.ts +58 -0
- package/src/rules/common/security-defined.ts +65 -0
- package/src/rules/common/spec-strict-refs.ts +30 -0
- package/src/rules/common/spec.ts +175 -0
- package/src/rules/common/tag-description.ts +10 -0
- package/src/rules/common/tags-alphabetical.ts +20 -0
- package/src/rules/no-unresolved-refs.ts +51 -0
- package/src/rules/oas2/__tests__/boolean-parameter-prefixes.test.ts +110 -0
- package/src/rules/oas2/__tests__/response-contains-header.test.ts +174 -0
- package/src/rules/oas2/__tests__/response-contains-property.test.ts +155 -0
- package/src/rules/oas2/__tests__/spec/fixtures/description.md +1 -0
- package/src/rules/oas2/__tests__/spec/info.test.ts +355 -0
- package/src/rules/oas2/__tests__/spec/operation.test.ts +123 -0
- package/src/rules/oas2/__tests__/spec/paths.test.ts +245 -0
- package/src/rules/oas2/__tests__/spec/referenceableScalars.test.ts +35 -0
- package/src/rules/oas2/__tests__/spec/utils.ts +32 -0
- package/src/rules/oas2/boolean-parameter-prefixes.ts +26 -0
- package/src/rules/oas2/index.ts +91 -0
- package/src/rules/oas2/remove-unused-components.ts +81 -0
- package/src/rules/oas2/request-mime-type.ts +16 -0
- package/src/rules/oas2/response-contains-property.ts +36 -0
- package/src/rules/oas2/response-mime-type.ts +16 -0
- package/src/rules/oas3/__tests__/boolean-parameter-prefixes.test.ts +111 -0
- package/src/rules/oas3/__tests__/component-name-unique.test.ts +823 -0
- package/src/rules/oas3/__tests__/fixtures/common.yaml +11 -0
- package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +205 -0
- package/src/rules/oas3/__tests__/no-example-value-and-externalValue.test.ts +65 -0
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +473 -0
- package/src/rules/oas3/__tests__/no-server-example.com.test.ts +60 -0
- package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +79 -0
- package/src/rules/oas3/__tests__/no-unused-components.test.ts +131 -0
- package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
- package/src/rules/oas3/__tests__/response-contains-header.test.ts +389 -0
- package/src/rules/oas3/__tests__/response-contains-property.test.ts +403 -0
- package/src/rules/oas3/__tests__/spec/callbacks.test.ts +41 -0
- package/src/rules/oas3/__tests__/spec/fixtures/description.md +1 -0
- package/src/rules/oas3/__tests__/spec/info.test.ts +391 -0
- package/src/rules/oas3/__tests__/spec/operation.test.ts +253 -0
- package/src/rules/oas3/__tests__/spec/paths.test.ts +284 -0
- package/src/rules/oas3/__tests__/spec/referenceableScalars.test.ts +77 -0
- package/src/rules/oas3/__tests__/spec/servers.test.ts +505 -0
- package/src/rules/oas3/__tests__/spec/spec.test.ts +298 -0
- package/src/rules/oas3/__tests__/spec/utils.ts +32 -0
- package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +276 -0
- package/src/rules/oas3/__tests__/utils/lint-document-for-test.ts +23 -0
- package/src/rules/oas3/boolean-parameter-prefixes.ts +28 -0
- package/src/rules/oas3/component-name-unique.ts +158 -0
- package/src/rules/oas3/index.ts +113 -0
- package/src/rules/oas3/no-empty-servers.ts +22 -0
- package/src/rules/oas3/no-example-value-and-externalValue.ts +14 -0
- package/src/rules/oas3/no-invalid-media-type-examples.ts +49 -0
- package/src/rules/oas3/no-server-example.com.ts +14 -0
- package/src/rules/oas3/no-server-trailing-slash.ts +15 -0
- package/src/rules/oas3/no-server-variables-empty-enum.ts +66 -0
- package/src/rules/oas3/no-undefined-server-variable.ts +30 -0
- package/src/rules/oas3/no-unused-components.ts +75 -0
- package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +35 -0
- package/src/rules/oas3/remove-unused-components.ts +95 -0
- package/src/rules/oas3/request-mime-type.ts +30 -0
- package/src/rules/oas3/response-contains-property.ts +38 -0
- package/src/rules/oas3/response-mime-type.ts +30 -0
- package/src/rules/oas3/spec-components-invalid-map-name.ts +69 -0
- package/src/rules/other/stats.ts +73 -0
- package/src/rules/utils.ts +193 -0
- package/src/types/config-external-schemas.ts +917 -0
- package/src/types/index.ts +149 -0
- package/src/types/oas2.ts +478 -0
- package/src/types/oas3.ts +597 -0
- package/src/types/oas3_1.ts +258 -0
- package/src/types/redocly-yaml.ts +1040 -0
- package/src/typings/common.ts +17 -0
- package/src/typings/openapi.ts +298 -0
- package/src/typings/swagger.ts +236 -0
- package/src/utils.ts +276 -0
- package/src/visitors.ts +491 -0
- package/src/walk.ts +439 -0
- package/tsconfig.json +8 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { asserts, AssertionFn } from './asserts';
|
|
2
|
+
import { buildSubjectVisitor, buildVisitorObject } from './utils';
|
|
3
|
+
import { Oas2Visitor, Oas3Visitor } from '../../../visitors';
|
|
4
|
+
import { RuleSeverity } from '../../../config';
|
|
5
|
+
import { isString } from '../../../utils';
|
|
6
|
+
|
|
7
|
+
export type AssertionLocators = {
|
|
8
|
+
filterInParentKeys?: (string | number)[];
|
|
9
|
+
filterOutParentKeys?: (string | number)[];
|
|
10
|
+
matchParentKeys?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AssertionDefinition = {
|
|
14
|
+
subject: {
|
|
15
|
+
type: string;
|
|
16
|
+
property?: string | string[];
|
|
17
|
+
} & AssertionLocators;
|
|
18
|
+
assertions: { [name in keyof typeof asserts]?: AssertionFn };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type RawAssertion = AssertionDefinition & {
|
|
22
|
+
where?: AssertionDefinition[];
|
|
23
|
+
message?: string;
|
|
24
|
+
suggest?: string[];
|
|
25
|
+
severity?: RuleSeverity;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type Assertion = RawAssertion & { assertionId: string };
|
|
29
|
+
|
|
30
|
+
export const Assertions = (opts: Record<string, Assertion>) => {
|
|
31
|
+
const visitors: (Oas2Visitor | Oas3Visitor)[] = [];
|
|
32
|
+
|
|
33
|
+
// As 'Assertions' has an array of asserts,
|
|
34
|
+
// that array spreads into an 'opts' object on init rules phase here
|
|
35
|
+
// https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/config/config.ts#L311
|
|
36
|
+
// that is why we need to iterate through 'opts' values;
|
|
37
|
+
// before - filter only object 'opts' values
|
|
38
|
+
const assertions: Assertion[] = Object.values(opts).filter(
|
|
39
|
+
(opt: unknown) => typeof opt === 'object' && opt !== null
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
for (const [_, assertion] of assertions.entries()) {
|
|
43
|
+
if (!isString(assertion.subject.type)) {
|
|
44
|
+
throw new Error(`${assertion.assertionId}: 'type' (String) is required`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const subjectVisitor = buildSubjectVisitor(assertion.assertionId, assertion);
|
|
48
|
+
const visitorObject = buildVisitorObject(assertion, subjectVisitor);
|
|
49
|
+
visitors.push(visitorObject);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return visitors;
|
|
53
|
+
};
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { asserts, runOnKeysSet, runOnValuesSet, Asserts } from './asserts';
|
|
2
|
+
import { colorize } from '../../../logger';
|
|
3
|
+
import { isRef } from '../../../ref-utils';
|
|
4
|
+
import { isTruthy, keysOf, isString } from '../../../utils';
|
|
5
|
+
import type { AssertionContext, AssertResult } from '../../../config';
|
|
6
|
+
import type { Assertion, AssertionDefinition, AssertionLocators } from '.';
|
|
7
|
+
import type {
|
|
8
|
+
Oas2Visitor,
|
|
9
|
+
Oas3Visitor,
|
|
10
|
+
SkipFunctionContext,
|
|
11
|
+
VisitFunction,
|
|
12
|
+
} from '../../../visitors';
|
|
13
|
+
import { UserContext } from 'core/src/walk';
|
|
14
|
+
|
|
15
|
+
export type OrderDirection = 'asc' | 'desc';
|
|
16
|
+
|
|
17
|
+
export type OrderOptions = {
|
|
18
|
+
direction: OrderDirection;
|
|
19
|
+
property: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AssertToApply = {
|
|
23
|
+
name: keyof Asserts;
|
|
24
|
+
conditions: any;
|
|
25
|
+
runsOnKeys: boolean;
|
|
26
|
+
runsOnValues: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type RunAssertionParams = {
|
|
30
|
+
ctx: AssertionContext;
|
|
31
|
+
assert: AssertToApply;
|
|
32
|
+
assertionProperty?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const assertionMessageTemplates = {
|
|
36
|
+
problems: '{{problems}}',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function getPredicatesFromLocators(
|
|
40
|
+
locators: AssertionLocators
|
|
41
|
+
): ((key: string | number) => boolean)[] {
|
|
42
|
+
const { filterInParentKeys, filterOutParentKeys, matchParentKeys } = locators;
|
|
43
|
+
|
|
44
|
+
const keyMatcher = matchParentKeys && regexFromString(matchParentKeys);
|
|
45
|
+
const matchKeysPredicate =
|
|
46
|
+
keyMatcher && ((key: string | number) => keyMatcher.test(key.toString()));
|
|
47
|
+
|
|
48
|
+
const filterInPredicate =
|
|
49
|
+
Array.isArray(filterInParentKeys) &&
|
|
50
|
+
((key: string | number) => filterInParentKeys.includes(key.toString()));
|
|
51
|
+
|
|
52
|
+
const filterOutPredicate =
|
|
53
|
+
Array.isArray(filterOutParentKeys) &&
|
|
54
|
+
((key: string | number) => !filterOutParentKeys.includes(key.toString()));
|
|
55
|
+
|
|
56
|
+
return [matchKeysPredicate, filterInPredicate, filterOutPredicate].filter(isTruthy);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getAssertsToApply(assertion: AssertionDefinition): AssertToApply[] {
|
|
60
|
+
const assertsToApply = keysOf(asserts)
|
|
61
|
+
.filter((assertName) => assertion.assertions[assertName] !== undefined)
|
|
62
|
+
.map((assertName) => {
|
|
63
|
+
return {
|
|
64
|
+
name: assertName,
|
|
65
|
+
conditions: assertion.assertions[assertName],
|
|
66
|
+
runsOnKeys: runOnKeysSet.has(assertName),
|
|
67
|
+
runsOnValues: runOnValuesSet.has(assertName),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const shouldRunOnKeys: AssertToApply | undefined = assertsToApply.find(
|
|
72
|
+
(assert: AssertToApply) => assert.runsOnKeys && !assert.runsOnValues
|
|
73
|
+
);
|
|
74
|
+
const shouldRunOnValues: AssertToApply | undefined = assertsToApply.find(
|
|
75
|
+
(assert: AssertToApply) => assert.runsOnValues && !assert.runsOnKeys
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (shouldRunOnValues && !assertion.subject.property) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`${shouldRunOnValues.name} can't be used on all keys. Please provide a single property`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (shouldRunOnKeys && assertion.subject.property) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return assertsToApply;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getAssertionProperties({ subject }: AssertionDefinition): string[] {
|
|
94
|
+
return (Array.isArray(subject.property) ? subject.property : [subject?.property]).filter(
|
|
95
|
+
Boolean
|
|
96
|
+
) as string[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function applyAssertions(
|
|
100
|
+
assertionDefinition: AssertionDefinition,
|
|
101
|
+
asserts: AssertToApply[],
|
|
102
|
+
ctx: AssertionContext
|
|
103
|
+
): AssertResult[] {
|
|
104
|
+
const properties = getAssertionProperties(assertionDefinition);
|
|
105
|
+
const assertResults: Array<AssertResult[]> = [];
|
|
106
|
+
|
|
107
|
+
for (const assert of asserts) {
|
|
108
|
+
if (properties.length) {
|
|
109
|
+
for (const property of properties) {
|
|
110
|
+
assertResults.push(
|
|
111
|
+
runAssertion({
|
|
112
|
+
assert,
|
|
113
|
+
ctx,
|
|
114
|
+
assertionProperty: property,
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
assertResults.push(
|
|
120
|
+
runAssertion({
|
|
121
|
+
assert,
|
|
122
|
+
ctx,
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return assertResults.flat();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function buildVisitorObject(
|
|
132
|
+
assertion: Assertion,
|
|
133
|
+
subjectVisitor: VisitFunction<any>
|
|
134
|
+
): Oas2Visitor | Oas3Visitor {
|
|
135
|
+
const targetVisitorLocatorPredicates = getPredicatesFromLocators(assertion.subject);
|
|
136
|
+
const targetVisitorSkipFunction = targetVisitorLocatorPredicates.length
|
|
137
|
+
? (_: any, key: string | number) =>
|
|
138
|
+
!targetVisitorLocatorPredicates.every((predicate) => predicate(key))
|
|
139
|
+
: undefined;
|
|
140
|
+
const targetVisitor: Oas2Visitor | Oas3Visitor = {
|
|
141
|
+
[assertion.subject.type]: {
|
|
142
|
+
enter: subjectVisitor,
|
|
143
|
+
...(targetVisitorSkipFunction && { skip: targetVisitorSkipFunction }),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (!Array.isArray(assertion.where)) {
|
|
148
|
+
return targetVisitor;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let currentVisitorLevel: Record<string, any> = {};
|
|
152
|
+
const visitor: Record<string, any> = currentVisitorLevel;
|
|
153
|
+
const context = assertion.where;
|
|
154
|
+
|
|
155
|
+
for (let index = 0; index < context.length; index++) {
|
|
156
|
+
const assertionDefinitionNode = context[index];
|
|
157
|
+
|
|
158
|
+
if (!isString(assertionDefinitionNode.subject?.type)) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`${assertion.assertionId} -> where -> [${index}]: 'type' (String) is required`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const locatorPredicates = getPredicatesFromLocators(assertionDefinitionNode.subject);
|
|
165
|
+
const assertsToApply = getAssertsToApply(assertionDefinitionNode);
|
|
166
|
+
|
|
167
|
+
const skipFunction = (node: unknown, key: string | number, ctx: SkipFunctionContext): boolean =>
|
|
168
|
+
!locatorPredicates.every((predicate) => predicate(key)) ||
|
|
169
|
+
!!applyAssertions(assertionDefinitionNode, assertsToApply, { ...ctx, node }).length;
|
|
170
|
+
|
|
171
|
+
const nodeVisitor = {
|
|
172
|
+
...((locatorPredicates.length || assertsToApply.length) && { skip: skipFunction }),
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
assertionDefinitionNode.subject.type === assertion.subject.type &&
|
|
177
|
+
index === context.length - 1
|
|
178
|
+
) {
|
|
179
|
+
// We have to merge the visitors if the last node inside the `where` is the same as the subject.
|
|
180
|
+
targetVisitor[assertion.subject.type] = {
|
|
181
|
+
enter: subjectVisitor,
|
|
182
|
+
...((nodeVisitor.skip && { skip: nodeVisitor.skip }) ||
|
|
183
|
+
(targetVisitorSkipFunction && {
|
|
184
|
+
skip: (
|
|
185
|
+
node,
|
|
186
|
+
key,
|
|
187
|
+
ctx // We may have locators defined on assertion level and on where level for the same node type
|
|
188
|
+
) => !!(nodeVisitor.skip?.(node, key, ctx) || targetVisitorSkipFunction?.(node, key)),
|
|
189
|
+
})),
|
|
190
|
+
};
|
|
191
|
+
} else {
|
|
192
|
+
currentVisitorLevel = currentVisitorLevel[assertionDefinitionNode.subject?.type] =
|
|
193
|
+
nodeVisitor;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
currentVisitorLevel[assertion.subject.type] = targetVisitor[assertion.subject.type];
|
|
198
|
+
|
|
199
|
+
return visitor;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function buildSubjectVisitor(assertId: string, assertion: Assertion): VisitFunction<any> {
|
|
203
|
+
return (node: any, ctx: UserContext) => {
|
|
204
|
+
const properties = getAssertionProperties(assertion);
|
|
205
|
+
|
|
206
|
+
const defaultMessage = `${colorize.blue(assertId)} failed because the ${colorize.blue(
|
|
207
|
+
assertion.subject.type
|
|
208
|
+
)} ${colorize.blue(properties.join(', '))} didn't meet the assertions: ${
|
|
209
|
+
assertionMessageTemplates.problems
|
|
210
|
+
}`.replace(/ +/g, ' ');
|
|
211
|
+
|
|
212
|
+
const problems = applyAssertions(assertion, getAssertsToApply(assertion), {
|
|
213
|
+
...ctx,
|
|
214
|
+
node,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (problems.length) {
|
|
218
|
+
for (const problemGroup of groupProblemsByPointer(problems)) {
|
|
219
|
+
const message = assertion.message || defaultMessage;
|
|
220
|
+
const problemMessage = getProblemsMessage(problemGroup);
|
|
221
|
+
ctx.report({
|
|
222
|
+
message: message.replace(assertionMessageTemplates.problems, problemMessage),
|
|
223
|
+
location: getProblemsLocation(problemGroup) || ctx.location,
|
|
224
|
+
forceSeverity: assertion.severity || 'error',
|
|
225
|
+
suggest: assertion.suggest || [],
|
|
226
|
+
ruleId: assertId,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function groupProblemsByPointer(problems: AssertResult[]): AssertResult[][] {
|
|
234
|
+
const groups: Record<string, AssertResult[]> = {};
|
|
235
|
+
for (const problem of problems) {
|
|
236
|
+
if (!problem.location) continue;
|
|
237
|
+
const pointer = problem.location.pointer;
|
|
238
|
+
groups[pointer] = groups[pointer] || [];
|
|
239
|
+
groups[pointer].push(problem);
|
|
240
|
+
}
|
|
241
|
+
return Object.values(groups);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getProblemsLocation(problems: AssertResult[]) {
|
|
245
|
+
return problems.length ? problems[0].location : undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getProblemsMessage(problems: AssertResult[]) {
|
|
249
|
+
return problems.length === 1
|
|
250
|
+
? problems[0].message ?? ''
|
|
251
|
+
: problems.map((problem) => `\n- ${problem.message ?? ''}`).join('');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function getIntersectionLength(keys: string[], properties: string[]): number {
|
|
255
|
+
const props = new Set(properties);
|
|
256
|
+
let count = 0;
|
|
257
|
+
for (const key of keys) {
|
|
258
|
+
if (props.has(key)) {
|
|
259
|
+
count++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return count;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function isOrdered(value: any[], options: OrderOptions | OrderDirection): boolean {
|
|
266
|
+
const direction = (options as OrderOptions).direction || (options as OrderDirection);
|
|
267
|
+
const property = (options as OrderOptions).property;
|
|
268
|
+
for (let i = 1; i < value.length; i++) {
|
|
269
|
+
let currValue = value[i];
|
|
270
|
+
let prevVal = value[i - 1];
|
|
271
|
+
|
|
272
|
+
if (property) {
|
|
273
|
+
const currPropValue = value[i][property];
|
|
274
|
+
const prevPropValue = value[i - 1][property];
|
|
275
|
+
|
|
276
|
+
if (!currPropValue || !prevPropValue) {
|
|
277
|
+
return false; // property doesn't exist, so collection is not ordered
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
currValue = currPropValue;
|
|
281
|
+
prevVal = prevPropValue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (typeof currValue === 'string' && typeof prevVal === 'string') {
|
|
285
|
+
currValue = currValue.toLowerCase();
|
|
286
|
+
prevVal = prevVal.toLowerCase();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const result = direction === 'asc' ? currValue >= prevVal : currValue <= prevVal;
|
|
290
|
+
if (!result) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function runAssertion({
|
|
298
|
+
assert,
|
|
299
|
+
ctx,
|
|
300
|
+
assertionProperty,
|
|
301
|
+
}: RunAssertionParams): AssertResult[] {
|
|
302
|
+
const currentLocation = assert.name === 'ref' ? ctx.rawLocation : ctx.location;
|
|
303
|
+
|
|
304
|
+
if (assertionProperty) {
|
|
305
|
+
const values = isRef(ctx.node[assertionProperty])
|
|
306
|
+
? ctx.resolve(ctx.node[assertionProperty])?.node
|
|
307
|
+
: ctx.node[assertionProperty];
|
|
308
|
+
const rawValues = ctx.rawNode[assertionProperty];
|
|
309
|
+
|
|
310
|
+
const location = currentLocation.child(assertionProperty);
|
|
311
|
+
|
|
312
|
+
return asserts[assert.name](values, assert.conditions, {
|
|
313
|
+
...ctx,
|
|
314
|
+
baseLocation: location,
|
|
315
|
+
rawValue: rawValues,
|
|
316
|
+
});
|
|
317
|
+
} else {
|
|
318
|
+
const value = Array.isArray(ctx.node) ? ctx.node : Object.keys(ctx.node);
|
|
319
|
+
|
|
320
|
+
return asserts[assert.name](value, assert.conditions, {
|
|
321
|
+
...ctx,
|
|
322
|
+
rawValue: ctx.rawNode,
|
|
323
|
+
baseLocation: currentLocation,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function regexFromString(input: string): RegExp | null {
|
|
329
|
+
const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
|
|
330
|
+
return matches && new RegExp(matches[1] || matches[3], matches[2]);
|
|
331
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
+
import { missingRequiredField } from '../utils';
|
|
3
|
+
|
|
4
|
+
export const InfoContact: Oas3Rule | Oas2Rule = () => {
|
|
5
|
+
return {
|
|
6
|
+
Info(info, { report, location }) {
|
|
7
|
+
if (!info.contact) {
|
|
8
|
+
report({
|
|
9
|
+
message: missingRequiredField('Info', 'contact'),
|
|
10
|
+
location: location.child('contact').key(),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
+
import { validateDefinedAndNonEmpty } from '../utils';
|
|
3
|
+
|
|
4
|
+
export const InfoLicenseUrl: Oas3Rule | Oas2Rule = () => {
|
|
5
|
+
return {
|
|
6
|
+
License(license, ctx) {
|
|
7
|
+
validateDefinedAndNonEmpty('url', license, ctx);
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
+
import { missingRequiredField } from '../utils';
|
|
3
|
+
|
|
4
|
+
export const InfoLicense: Oas3Rule | Oas2Rule = () => {
|
|
5
|
+
return {
|
|
6
|
+
Info(info, { report }) {
|
|
7
|
+
if (!info.license) {
|
|
8
|
+
report({
|
|
9
|
+
message: missingRequiredField('Info', 'license'),
|
|
10
|
+
location: { reportOnKey: true },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
+
import { UserContext } from '../../walk';
|
|
3
|
+
import { Oas3Paths } from '../../typings/openapi';
|
|
4
|
+
import { Oas2Paths } from '../../typings/swagger';
|
|
5
|
+
|
|
6
|
+
export const NoAmbiguousPaths: Oas3Rule | Oas2Rule = () => {
|
|
7
|
+
return {
|
|
8
|
+
Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
|
|
9
|
+
const seenPaths: string[] = [];
|
|
10
|
+
|
|
11
|
+
for (const currentPath of Object.keys(pathMap)) {
|
|
12
|
+
const ambiguousPath = seenPaths.find((seenPath) =>
|
|
13
|
+
arePathsAmbiguous(seenPath, currentPath)
|
|
14
|
+
);
|
|
15
|
+
if (ambiguousPath) {
|
|
16
|
+
report({
|
|
17
|
+
message: `Paths should resolve unambiguously. Found two ambiguous paths: \`${ambiguousPath}\` and \`${currentPath}\`.`,
|
|
18
|
+
location: location.child([currentPath]).key(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
seenPaths.push(currentPath);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function arePathsAmbiguous(a: string, b: string) {
|
|
28
|
+
const partsA = a.split('/');
|
|
29
|
+
const partsB = b.split('/');
|
|
30
|
+
|
|
31
|
+
if (partsA.length !== partsB.length) return false;
|
|
32
|
+
|
|
33
|
+
let aVars = 0;
|
|
34
|
+
let bVars = 0;
|
|
35
|
+
let ambiguous = true;
|
|
36
|
+
for (let i = 0; i < partsA.length; i++) {
|
|
37
|
+
const aIsVar = partsA[i].match(/^{.+?}$/);
|
|
38
|
+
const bIsVar = partsB[i].match(/^{.+?}$/);
|
|
39
|
+
|
|
40
|
+
if (aIsVar || bIsVar) {
|
|
41
|
+
if (aIsVar) aVars++;
|
|
42
|
+
if (bIsVar) bVars++;
|
|
43
|
+
continue;
|
|
44
|
+
} else if (partsA[i] !== partsB[i]) {
|
|
45
|
+
ambiguous = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return ambiguous && aVars === bVars;
|
|
50
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
+
import { matchesJsonSchemaType, oasTypeOf } from '../utils';
|
|
3
|
+
import { Oas2Schema } from '../../typings/swagger';
|
|
4
|
+
import { Oas3Schema } from '../../typings/openapi';
|
|
5
|
+
import { UserContext } from '../../walk';
|
|
6
|
+
|
|
7
|
+
export const NoEnumTypeMismatch: Oas3Rule | Oas2Rule = () => {
|
|
8
|
+
return {
|
|
9
|
+
Schema(schema: Oas2Schema | Oas3Schema, { report, location }: UserContext) {
|
|
10
|
+
if (schema.enum && !Array.isArray(schema.enum)) return;
|
|
11
|
+
if (schema.enum && schema.type && !Array.isArray(schema.type)) {
|
|
12
|
+
const typeMismatchedValues = schema.enum.filter(
|
|
13
|
+
(item) => !matchesJsonSchemaType(item, schema.type as string, schema.nullable as boolean)
|
|
14
|
+
);
|
|
15
|
+
for (const mismatchedValue of typeMismatchedValues) {
|
|
16
|
+
report({
|
|
17
|
+
message: `All values of \`enum\` field must be of the same type as the \`type\` field: expected "${
|
|
18
|
+
schema.type
|
|
19
|
+
}" but received "${oasTypeOf(mismatchedValue)}".`,
|
|
20
|
+
location: location.child(['enum', schema.enum.indexOf(mismatchedValue)]),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (schema.enum && schema.type && Array.isArray(schema.type)) {
|
|
26
|
+
const mismatchedResults: { [key: string]: string[] } = {};
|
|
27
|
+
for (const enumValue of schema.enum) {
|
|
28
|
+
mismatchedResults[enumValue] = [];
|
|
29
|
+
|
|
30
|
+
for (const type of schema.type) {
|
|
31
|
+
const valid = matchesJsonSchemaType(
|
|
32
|
+
enumValue,
|
|
33
|
+
type as string,
|
|
34
|
+
schema.nullable as boolean
|
|
35
|
+
);
|
|
36
|
+
if (!valid) mismatchedResults[enumValue].push(type);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (mismatchedResults[enumValue].length !== schema.type.length)
|
|
40
|
+
delete mismatchedResults[enumValue];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const mismatchedKey of Object.keys(mismatchedResults)) {
|
|
44
|
+
report({
|
|
45
|
+
message: `Enum value \`${mismatchedKey}\` must be of allowed types: \`${schema.type}\`.`,
|
|
46
|
+
location: location.child(['enum', schema.enum.indexOf(mismatchedKey)]),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
+
import { Oas2PathItem } from '../../typings/swagger';
|
|
3
|
+
import { Oas3PathItem } from '../../typings/openapi';
|
|
4
|
+
import { UserContext } from '../../walk';
|
|
5
|
+
import { isPathParameter, splitCamelCaseIntoWords } from '../../utils';
|
|
6
|
+
|
|
7
|
+
const httpMethods = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];
|
|
8
|
+
|
|
9
|
+
export const NoHttpVerbsInPaths: Oas3Rule | Oas2Rule = ({ splitIntoWords }) => {
|
|
10
|
+
return {
|
|
11
|
+
PathItem(_path: Oas2PathItem | Oas3PathItem, { key, report, location }: UserContext) {
|
|
12
|
+
const pathKey = key.toString();
|
|
13
|
+
if (!pathKey.startsWith('/')) return;
|
|
14
|
+
const pathSegments = pathKey.split('/');
|
|
15
|
+
|
|
16
|
+
for (const pathSegment of pathSegments) {
|
|
17
|
+
if (!pathSegment || isPathParameter(pathSegment)) continue;
|
|
18
|
+
|
|
19
|
+
const isHttpMethodIncluded = (method: string) => {
|
|
20
|
+
return splitIntoWords
|
|
21
|
+
? splitCamelCaseIntoWords(pathSegment).has(method)
|
|
22
|
+
: pathSegment.toLocaleLowerCase().includes(method);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
for (const method of httpMethods) {
|
|
26
|
+
if (isHttpMethodIncluded(method)) {
|
|
27
|
+
report({
|
|
28
|
+
message: `path \`${pathKey}\` should not contain http verb ${method}`,
|
|
29
|
+
location: location.key(),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
+
import { UserContext } from '../../walk';
|
|
3
|
+
import { Oas3Paths } from '../../typings/openapi';
|
|
4
|
+
import { Oas2Paths } from '../../typings/swagger';
|
|
5
|
+
|
|
6
|
+
export const NoIdenticalPaths: Oas3Rule | Oas2Rule = () => {
|
|
7
|
+
return {
|
|
8
|
+
Paths(pathMap: Oas3Paths | Oas2Paths, { report, location }: UserContext) {
|
|
9
|
+
const Paths = new Map<string, string>();
|
|
10
|
+
for (const pathName of Object.keys(pathMap)) {
|
|
11
|
+
const id = pathName.replace(/{.+?}/g, '{VARIABLE}');
|
|
12
|
+
const existingSamePath = Paths.get(id);
|
|
13
|
+
if (existingSamePath) {
|
|
14
|
+
report({
|
|
15
|
+
message: `The path already exists which differs only by path parameter name(s): \`${existingSamePath}\` and \`${pathName}\`.`,
|
|
16
|
+
location: location.child([pathName]).key(),
|
|
17
|
+
});
|
|
18
|
+
} else {
|
|
19
|
+
Paths.set(id, pathName);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { UserContext } from '../../walk';
|
|
2
|
+
import { Oas3Parameter } from '../../typings/openapi';
|
|
3
|
+
import { getAdditionalPropertiesOption, validateExample } from '../utils';
|
|
4
|
+
|
|
5
|
+
export const NoInvalidParameterExamples: any = (opts: any) => {
|
|
6
|
+
const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false;
|
|
7
|
+
return {
|
|
8
|
+
Parameter: {
|
|
9
|
+
leave(parameter: Oas3Parameter, ctx: UserContext) {
|
|
10
|
+
if (parameter.example) {
|
|
11
|
+
validateExample(
|
|
12
|
+
parameter.example,
|
|
13
|
+
parameter.schema!,
|
|
14
|
+
ctx.location.child('example'),
|
|
15
|
+
ctx,
|
|
16
|
+
allowAdditionalProperties
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (parameter.examples) {
|
|
21
|
+
for (const [key, example] of Object.entries(parameter.examples)) {
|
|
22
|
+
if ('value' in example) {
|
|
23
|
+
validateExample(
|
|
24
|
+
example.value,
|
|
25
|
+
parameter.schema!,
|
|
26
|
+
ctx.location.child(['examples', key]),
|
|
27
|
+
ctx,
|
|
28
|
+
true
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { UserContext } from '../../walk';
|
|
2
|
+
import { Oas3_1Schema } from '../../typings/openapi';
|
|
3
|
+
import { getAdditionalPropertiesOption, validateExample } from '../utils';
|
|
4
|
+
|
|
5
|
+
export const NoInvalidSchemaExamples: any = (opts: any) => {
|
|
6
|
+
const allowAdditionalProperties = getAdditionalPropertiesOption(opts) ?? false;
|
|
7
|
+
return {
|
|
8
|
+
Schema: {
|
|
9
|
+
leave(schema: Oas3_1Schema, ctx: UserContext) {
|
|
10
|
+
if (schema.examples) {
|
|
11
|
+
for (const example of schema.examples) {
|
|
12
|
+
validateExample(
|
|
13
|
+
example,
|
|
14
|
+
schema,
|
|
15
|
+
ctx.location.child(['examples', schema.examples.indexOf(example)]),
|
|
16
|
+
ctx,
|
|
17
|
+
allowAdditionalProperties
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (schema.example) {
|
|
22
|
+
validateExample(schema.example, schema, ctx.location.child('example'), ctx, true);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Oas3Rule, Oas2Rule } from '../../visitors';
|
|
2
|
+
import { UserContext } from '../../walk';
|
|
3
|
+
|
|
4
|
+
export const NoPathTrailingSlash: Oas3Rule | Oas2Rule = () => {
|
|
5
|
+
return {
|
|
6
|
+
PathItem(_path: any, { report, key, location }: UserContext) {
|
|
7
|
+
if ((key as string).endsWith('/') && key !== '/') {
|
|
8
|
+
report({
|
|
9
|
+
message: `\`${key}\` should not have a trailing slash.`,
|
|
10
|
+
location: location.key(),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
};
|