openapi-typesafe 1.0.0-rc.1 → 1.0.0-rc.3
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/README.md +12 -5
- package/dist/adapters/node-deprecations/node-deprecations.cjs +14 -0
- package/dist/adapters/node-deprecations/node-deprecations.cjs.map +1 -0
- package/dist/adapters/node-deprecations/node-deprecations.mjs +12 -0
- package/dist/adapters/node-deprecations/node-deprecations.mjs.map +1 -0
- package/dist/adapters/redocly-loader/configured-headers.cjs +32 -0
- package/dist/adapters/redocly-loader/configured-headers.cjs.map +1 -0
- package/dist/adapters/redocly-loader/configured-headers.mjs +30 -0
- package/dist/adapters/redocly-loader/configured-headers.mjs.map +1 -0
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +19 -6
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +18 -5
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.cjs +37 -4
- package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.mjs +37 -4
- package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-resolution-check.cjs +191 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.cjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.mjs +189 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.mjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.cjs +73 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.cjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.mjs +60 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.mjs.map +1 -0
- package/dist/adapters/redocly-loader/remote-fetch.cjs +41 -14
- package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-fetch.mjs +40 -14
- package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-load-errors.cjs +23 -5
- package/dist/adapters/redocly-loader/schema-load-errors.cjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-load-errors.mjs +23 -5
- package/dist/adapters/redocly-loader/schema-load-errors.mjs.map +1 -1
- package/dist/adapters/redocly-loader/unsupported-keys.cjs +9 -1
- package/dist/adapters/redocly-loader/unsupported-keys.cjs.map +1 -1
- package/dist/adapters/redocly-loader/unsupported-keys.mjs +9 -1
- package/dist/adapters/redocly-loader/unsupported-keys.mjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.cjs +6 -4
- package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.mjs +6 -4
- package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs +2 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs.map +1 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs +2 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs.map +1 -1
- package/dist/adapters/type-ast-bridge/print-typescript.cjs.map +1 -1
- package/dist/adapters/type-ast-bridge/print-typescript.mjs.map +1 -1
- package/dist/api/default-ports.cjs +14 -0
- package/dist/api/default-ports.cjs.map +1 -1
- package/dist/api/default-ports.mjs +12 -1
- package/dist/api/default-ports.mjs.map +1 -1
- package/dist/api/discriminator-conflict-warnings.cjs +1 -1
- package/dist/api/discriminator-conflict-warnings.cjs.map +1 -1
- package/dist/api/discriminator-conflict-warnings.mjs +2 -2
- package/dist/api/discriminator-conflict-warnings.mjs.map +1 -1
- package/dist/api/discriminator-patch.cjs +8 -4
- package/dist/api/discriminator-patch.cjs.map +1 -1
- package/dist/api/discriminator-patch.mjs +9 -5
- package/dist/api/discriminator-patch.mjs.map +1 -1
- package/dist/api/discriminator-scan.cjs +2 -2
- package/dist/api/discriminator-scan.cjs.map +1 -1
- package/dist/api/discriminator-scan.mjs +3 -3
- package/dist/api/discriminator-scan.mjs.map +1 -1
- package/dist/api/document-refs.cjs +15 -12
- package/dist/api/document-refs.cjs.map +1 -1
- package/dist/api/document-refs.mjs +15 -12
- package/dist/api/document-refs.mjs.map +1 -1
- package/dist/api/document-run.cjs +6 -3
- package/dist/api/document-run.cjs.map +1 -1
- package/dist/api/document-run.mjs +6 -3
- package/dist/api/document-run.mjs.map +1 -1
- package/dist/api/footer-sink.cjs +25 -2
- package/dist/api/footer-sink.cjs.map +1 -1
- package/dist/api/footer-sink.mjs +25 -2
- package/dist/api/footer-sink.mjs.map +1 -1
- package/dist/api/legacy-callback-warning.cjs +7 -1
- package/dist/api/legacy-callback-warning.cjs.map +1 -1
- package/dist/api/legacy-callback-warning.mjs +8 -2
- package/dist/api/legacy-callback-warning.mjs.map +1 -1
- package/dist/api/legacy-ts-helpers.cjs.map +1 -1
- package/dist/api/legacy-ts-helpers.mjs.map +1 -1
- package/dist/api/legacy-utils.cjs.map +1 -1
- package/dist/api/legacy-utils.mjs.map +1 -1
- package/dist/api/openapi-ts.cjs +3 -2
- package/dist/api/openapi-ts.cjs.map +1 -1
- package/dist/api/openapi-ts.mjs +4 -3
- package/dist/api/openapi-ts.mjs.map +1 -1
- package/dist/api/string-to-ast.cjs +7 -9
- package/dist/api/string-to-ast.cjs.map +1 -1
- package/dist/api/string-to-ast.mjs +8 -9
- package/dist/api/string-to-ast.mjs.map +1 -1
- package/dist/api/transform-context.cjs +1 -1
- package/dist/api/transform-context.cjs.map +1 -1
- package/dist/api/transform-context.mjs +1 -1
- package/dist/api/transform-context.mjs.map +1 -1
- package/dist/api/transform-schema.cjs +2 -2
- package/dist/api/transform-schema.cjs.map +1 -1
- package/dist/api/transform-schema.mjs +3 -3
- package/dist/api/transform-schema.mjs.map +1 -1
- package/dist/application/print/type-printer.cjs +2 -1
- package/dist/application/print/type-printer.cjs.map +1 -1
- package/dist/application/print/type-printer.mjs +2 -1
- package/dist/application/print/type-printer.mjs.map +1 -1
- package/dist/application/transform/document/components.cjs +1 -1
- package/dist/application/transform/document/components.cjs.map +1 -1
- package/dist/application/transform/document/components.mjs +2 -2
- package/dist/application/transform/document/components.mjs.map +1 -1
- package/dist/application/transform/document/parameters.cjs +2 -2
- package/dist/application/transform/document/parameters.cjs.map +1 -1
- package/dist/application/transform/document/parameters.mjs +3 -3
- package/dist/application/transform/document/parameters.mjs.map +1 -1
- package/dist/application/transform/document/paths.cjs +2 -2
- package/dist/application/transform/document/paths.cjs.map +1 -1
- package/dist/application/transform/document/paths.mjs +3 -3
- package/dist/application/transform/document/paths.mjs.map +1 -1
- package/dist/application/transform/footer/evaluation.cjs +9 -0
- package/dist/application/transform/footer/evaluation.cjs.map +1 -1
- package/dist/application/transform/footer/evaluation.mjs +8 -1
- package/dist/application/transform/footer/evaluation.mjs.map +1 -1
- package/dist/application/transform/footer/type-evaluation.cjs +4 -112
- package/dist/application/transform/footer/type-evaluation.cjs.map +1 -1
- package/dist/application/transform/footer/type-evaluation.mjs +4 -112
- package/dist/application/transform/footer/type-evaluation.mjs.map +1 -1
- package/dist/application/transform/footer/type-indexing.cjs +107 -0
- package/dist/application/transform/footer/type-indexing.cjs.map +1 -0
- package/dist/application/transform/footer/type-indexing.mjs +105 -0
- package/dist/application/transform/footer/type-indexing.mjs.map +1 -0
- package/dist/application/transform/ref-pointer/pointer-type.cjs +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.mjs +2 -2
- package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -1
- package/dist/application/transform/schema/access-flags.cjs +1 -1
- package/dist/application/transform/schema/access-flags.cjs.map +1 -1
- package/dist/application/transform/schema/access-flags.mjs +2 -2
- package/dist/application/transform/schema/access-flags.mjs.map +1 -1
- package/dist/application/transform/schema/array-type.cjs +2 -2
- package/dist/application/transform/schema/array-type.cjs.map +1 -1
- package/dist/application/transform/schema/array-type.mjs +3 -3
- package/dist/application/transform/schema/array-type.mjs.map +1 -1
- package/dist/application/transform/schema/composition-type.cjs +3 -3
- package/dist/application/transform/schema/composition-type.cjs.map +1 -1
- package/dist/application/transform/schema/composition-type.mjs +4 -4
- package/dist/application/transform/schema/composition-type.mjs.map +1 -1
- package/dist/application/transform/schema/enum-rule.cjs +3 -5
- package/dist/application/transform/schema/enum-rule.cjs.map +1 -1
- package/dist/application/transform/schema/enum-rule.mjs +4 -6
- package/dist/application/transform/schema/enum-rule.mjs.map +1 -1
- package/dist/application/transform/schema/presence-members.cjs +1 -1
- package/dist/application/transform/schema/presence-members.cjs.map +1 -1
- package/dist/application/transform/schema/presence-members.mjs +2 -2
- package/dist/application/transform/schema/presence-members.mjs.map +1 -1
- package/dist/application/transform/schema/required-variants.cjs +2 -2
- package/dist/application/transform/schema/required-variants.cjs.map +1 -1
- package/dist/application/transform/schema/required-variants.mjs +3 -3
- package/dist/application/transform/schema/required-variants.mjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.cjs +1 -1
- package/dist/application/transform/schema/unevaluated-properties.cjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.mjs +2 -2
- package/dist/application/transform/schema/unevaluated-properties.mjs.map +1 -1
- package/dist/application/transform/schema-comment.cjs +1 -1
- package/dist/application/transform/schema-comment.cjs.map +1 -1
- package/dist/application/transform/schema-comment.mjs +2 -2
- package/dist/application/transform/schema-comment.mjs.map +1 -1
- package/dist/cli/api-settings.cjs +78 -0
- package/dist/cli/api-settings.cjs.map +1 -0
- package/dist/cli/api-settings.mjs +73 -0
- package/dist/cli/api-settings.mjs.map +1 -0
- package/dist/cli/cli-arguments.cjs +1 -0
- package/dist/cli/cli-arguments.cjs.map +1 -1
- package/dist/cli/cli-arguments.mjs +1 -1
- package/dist/cli/cli-arguments.mjs.map +1 -1
- package/dist/cli/help-text.cjs +3 -3
- package/dist/cli/help-text.cjs.map +1 -1
- package/dist/cli/help-text.mjs +3 -3
- package/dist/cli/help-text.mjs.map +1 -1
- package/dist/cli/redocly-apis.cjs +8 -4
- package/dist/cli/redocly-apis.cjs.map +1 -1
- package/dist/cli/redocly-apis.mjs +7 -3
- package/dist/cli/redocly-apis.mjs.map +1 -1
- package/dist/cli/run-cli.cjs +5 -1
- package/dist/cli/run-cli.cjs.map +1 -1
- package/dist/cli/run-cli.mjs +5 -1
- package/dist/cli/run-cli.mjs.map +1 -1
- package/dist/domain/deprecations.cjs +11 -0
- package/dist/domain/deprecations.cjs.map +1 -0
- package/dist/domain/deprecations.mjs +9 -0
- package/dist/domain/deprecations.mjs.map +1 -0
- package/dist/domain/errors.cjs +4 -1
- package/dist/domain/errors.cjs.map +1 -1
- package/dist/domain/errors.mjs +4 -1
- package/dist/domain/errors.mjs.map +1 -1
- package/dist/domain/ir/constructors.cjs +2 -0
- package/dist/domain/ir/constructors.cjs.map +1 -1
- package/dist/domain/ir/constructors.mjs +2 -1
- package/dist/domain/ir/constructors.mjs.map +1 -1
- package/dist/domain/openapi/path-templates.cjs +1 -1
- package/dist/domain/openapi/path-templates.cjs.map +1 -1
- package/dist/domain/openapi/path-templates.mjs +1 -1
- package/dist/domain/openapi/path-templates.mjs.map +1 -1
- package/dist/domain/output-fixes.cjs +11 -14
- package/dist/domain/output-fixes.cjs.map +1 -1
- package/dist/domain/output-fixes.mjs +11 -13
- package/dist/domain/output-fixes.mjs.map +1 -1
- package/dist/domain/url-credentials.cjs +10 -0
- package/dist/domain/url-credentials.cjs.map +1 -0
- package/dist/domain/url-credentials.mjs +8 -0
- package/dist/domain/url-credentials.mjs.map +1 -0
- package/dist/domain/warnings.cjs +8 -1
- package/dist/domain/warnings.cjs.map +1 -1
- package/dist/domain/warnings.mjs +8 -1
- package/dist/domain/warnings.mjs.map +1 -1
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1185 -59
- package/dist/index.d.mts +1185 -59
- package/dist/index.d.ts +1185 -59
- package/dist/index.mjs +4 -2
- package/dist/index.mjs.map +1 -1
- package/dist/transform/path-item-object.cjs.map +1 -1
- package/dist/transform/path-item-object.mjs.map +1 -1
- package/package.json +5 -4
- package/src/adapters/node-deprecations/node-deprecations.ts +14 -0
- package/src/adapters/redocly-loader/configured-headers.ts +65 -0
- package/src/adapters/redocly-loader/redocly-schema-loader.ts +22 -7
- package/src/adapters/redocly-loader/ref-access.ts +60 -5
- package/src/adapters/redocly-loader/ref-resolution-check.ts +257 -0
- package/src/adapters/redocly-loader/ref-resolution-steps.ts +113 -0
- package/src/adapters/redocly-loader/remote-fetch.ts +77 -21
- package/src/adapters/redocly-loader/schema-load-errors.ts +33 -11
- package/src/adapters/redocly-loader/unsupported-keys.ts +17 -2
- package/src/adapters/terminal-logger/terminal-logger.ts +9 -5
- package/src/adapters/type-ast-bridge/ir-to-typescript.ts +2 -1
- package/src/adapters/type-ast-bridge/print-typescript.ts +3 -1
- package/src/adapters/type-ast-bridge/typescript-types.ts +57 -5
- package/src/api/default-ports.ts +23 -0
- package/src/api/discriminator-conflict-warnings.ts +2 -2
- package/src/api/discriminator-patch.ts +11 -7
- package/src/api/discriminator-scan.ts +3 -3
- package/src/api/document-refs.ts +19 -15
- package/src/api/document-run.ts +14 -6
- package/src/api/footer-sink.ts +34 -2
- package/src/api/legacy-callback-warning.ts +16 -5
- package/src/api/legacy-ts-helpers.ts +7 -5
- package/src/api/legacy-utils.ts +1 -1
- package/src/api/openapi-ts.ts +8 -2
- package/src/api/run-context.ts +37 -0
- package/src/api/string-to-ast.ts +11 -15
- package/src/api/transform-context.ts +10 -8
- package/src/api/transform-schema.ts +9 -8
- package/src/application/ports/deprecation-reporter.ts +9 -0
- package/src/application/print/type-printer.ts +2 -1
- package/src/application/transform/document/components.ts +2 -2
- package/src/application/transform/document/parameters.ts +4 -3
- package/src/application/transform/document/paths.ts +4 -3
- package/src/application/transform/footer/evaluation.ts +10 -0
- package/src/application/transform/footer/type-evaluation.ts +6 -133
- package/src/application/transform/footer/type-indexing.ts +152 -0
- package/src/application/transform/ref-pointer/pointer-type.ts +2 -4
- package/src/application/transform/schema/access-flags.ts +2 -2
- package/src/application/transform/schema/array-type.ts +3 -5
- package/src/application/transform/schema/composition-type.ts +4 -4
- package/src/application/transform/schema/enum-rule.ts +4 -6
- package/src/application/transform/schema/presence-members.ts +2 -2
- package/src/application/transform/schema/required-variants.ts +3 -3
- package/src/application/transform/schema/unevaluated-properties.ts +2 -2
- package/src/application/transform/schema-comment.ts +2 -2
- package/src/cli/api-settings.ts +109 -0
- package/src/cli/cli-arguments.ts +5 -2
- package/src/cli/help-text.ts +4 -4
- package/src/cli/redocly-apis.ts +13 -7
- package/src/cli/run-cli.ts +5 -1
- package/src/domain/deprecations.ts +15 -0
- package/src/domain/errors.ts +16 -4
- package/src/domain/ir/constructors.ts +3 -0
- package/src/domain/ir/nodes.ts +9 -6
- package/src/domain/openapi/path-templates.ts +2 -1
- package/src/domain/output-fixes.ts +16 -16
- package/src/domain/url-credentials.ts +18 -0
- package/src/domain/warnings.ts +7 -0
- package/src/index.ts +3 -6
- package/src/transform/path-item-object.ts +6 -2
- package/src/types/generator-options.ts +12 -32
- package/src/types/openapi-common.ts +1 -0
- package/src/types/openapi-request.ts +3 -0
- package/src/types/openapi-schema.ts +2 -0
- package/src/types/openapi-security.ts +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Document, normalizeTypes } from "@redocly/openapi-core";
|
|
2
|
+
import { parseRefPointer } from "../../domain/openapi/json-pointer.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The pieces of Redocly 1.34's `$ref` resolution (`lib/resolve.js`, `lib/ref-utils.js`, MIT) that
|
|
6
|
+
* the resolution check (`ref-resolution-check.ts`, ADR-031) walks with, kept as Redocly has them.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type RedoclyTypes = ReturnType<typeof normalizeTypes>;
|
|
10
|
+
export type WalkType = RedoclyTypes[string];
|
|
11
|
+
type PropTypeOrFunction = WalkType["properties"][string] | WalkType["additionalProperties"];
|
|
12
|
+
/** The type of a property or array item, after Redocly called a type function. */
|
|
13
|
+
export type PropType = Exclude<PropTypeOrFunction, (...args: never[]) => unknown>;
|
|
14
|
+
|
|
15
|
+
/** Redocly's own placeholders: a node of no known type, and a scalar that is a `$ref`. */
|
|
16
|
+
export const UNKNOWN_TYPE: WalkType = { name: "unknown", properties: {} };
|
|
17
|
+
export const RESOLVABLE_SCALAR_TYPE: WalkType = { name: "scalar", properties: {} };
|
|
18
|
+
|
|
19
|
+
/** Redocly's test for an anchor reference (`#name`), which waits for the whole document walk. */
|
|
20
|
+
export const ANCHOR_REF = /^#[A-Za-z][A-Za-z0-9\-_:.]*$/;
|
|
21
|
+
|
|
22
|
+
/** Redocly throws a plain Error with this message for a `$ref` chain that points back to itself. */
|
|
23
|
+
export const REF_CYCLE_MESSAGE = "Self-referencing circular pointer";
|
|
24
|
+
|
|
25
|
+
export interface RefStack {
|
|
26
|
+
readonly prev: RefStack | null;
|
|
27
|
+
readonly node: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ResolvedRef {
|
|
31
|
+
readonly resolved: boolean;
|
|
32
|
+
readonly node?: unknown;
|
|
33
|
+
readonly document?: Document;
|
|
34
|
+
readonly nodePointer?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type Ref = { readonly $ref: string };
|
|
38
|
+
|
|
39
|
+
export function isNamedType(type: PropType): type is WalkType {
|
|
40
|
+
return typeof (type as { name?: unknown } | null | undefined)?.name === "string";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function joinPointer(base: string, key: string | number): string {
|
|
44
|
+
const start = base === "" ? "#/" : base;
|
|
45
|
+
return start.endsWith("/") ? `${start}${key}` : `${start}/${key}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function escapePointer(fragment: string): string {
|
|
49
|
+
return fragment.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Redocly's `parseRef`: the document part (null for a local ref) and the unescaped segments. */
|
|
53
|
+
export function parseRef(ref: string): { uri: string | null; pointer: string[] } {
|
|
54
|
+
const [uri = ""] = ref.split("#/");
|
|
55
|
+
return {
|
|
56
|
+
uri: (uri.endsWith("#") ? uri.slice(0, -1) : uri) || null,
|
|
57
|
+
pointer: parseRefPointer(ref),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function hasRef(head: RefStack | null, node: unknown): boolean {
|
|
62
|
+
for (let entry = head; entry !== null; entry = entry.prev) {
|
|
63
|
+
if (entry.node === node) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isExternalValue(node: object): node is { externalValue: string } {
|
|
71
|
+
return typeof (node as { externalValue?: unknown }).externalValue === "string";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Wait one macrotask, as Redocly does before it reads the anchors of a document. */
|
|
75
|
+
export function nextTick(): Promise<void> {
|
|
76
|
+
return new Promise((resolve) => setTimeout(resolve));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The type Redocly gives one property value, and the value to walk (a `directResolveAs` string
|
|
81
|
+
* becomes a `$ref`). A value whose type has no name is not walked.
|
|
82
|
+
*/
|
|
83
|
+
export function propertyType(
|
|
84
|
+
type: WalkType,
|
|
85
|
+
name: string,
|
|
86
|
+
value: unknown,
|
|
87
|
+
specExtension: WalkType,
|
|
88
|
+
): [PropType, unknown] {
|
|
89
|
+
// `properties[name]` also finds `Object.prototype` members, as in Redocly; for `valueOf` and the
|
|
90
|
+
// like the call below throws the TypeError that the loader turns into SCHEMA_KEY_UNSUPPORTED.
|
|
91
|
+
let declared: PropTypeOrFunction = type.properties[name];
|
|
92
|
+
if (declared === undefined) {
|
|
93
|
+
declared = type.additionalProperties;
|
|
94
|
+
}
|
|
95
|
+
let propType: PropType = typeof declared === "function" ? declared(value, name) : declared;
|
|
96
|
+
if (propType === undefined) {
|
|
97
|
+
propType = UNKNOWN_TYPE;
|
|
98
|
+
}
|
|
99
|
+
if (
|
|
100
|
+
type.extensionsPrefix &&
|
|
101
|
+
name.startsWith(type.extensionsPrefix) &&
|
|
102
|
+
propType === UNKNOWN_TYPE
|
|
103
|
+
) {
|
|
104
|
+
return [specExtension, value];
|
|
105
|
+
}
|
|
106
|
+
if (!isNamedType(propType) && propType?.directResolveAs) {
|
|
107
|
+
return [propType.directResolveAs, { $ref: value }];
|
|
108
|
+
}
|
|
109
|
+
if (propType && !isNamedType(propType) && propType.resolvable !== false) {
|
|
110
|
+
return [RESOLVABLE_SCALAR_TYPE, value];
|
|
111
|
+
}
|
|
112
|
+
return [propType, value];
|
|
113
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { Config as RedoclyConfig } from "@redocly/openapi-core";
|
|
2
2
|
import type { RefAccess } from "../../application/ports/schema-loader.js";
|
|
3
3
|
import { isHostAllowed } from "./allowed-hosts.js";
|
|
4
|
+
import { type ConfiguredHeader, headersFor } from "./configured-headers.js";
|
|
4
5
|
import { refForbiddenError } from "./schema-load-errors.js";
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* How the loader fetches remote documents: a time limit, the host limit and the configured headers
|
|
9
|
+
* checked on every redirect.
|
|
10
|
+
*/
|
|
7
11
|
|
|
8
12
|
type ResolveConfig = RedoclyConfig["resolve"];
|
|
9
13
|
|
|
@@ -11,10 +15,10 @@ type ResolveConfig = RedoclyConfig["resolve"];
|
|
|
11
15
|
export type RemoteFetch = (input: string, init?: RequestInit) => Promise<Response>;
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
|
-
* Redirects followed for
|
|
15
|
-
*
|
|
18
|
+
* Redirects followed for one remote document. The loader follows them itself, so that each hop is
|
|
19
|
+
* checked; 20 is the limit of the Fetch standard, which fetch applied before.
|
|
16
20
|
*/
|
|
17
|
-
export const
|
|
21
|
+
export const MAX_REDIRECTS = 20;
|
|
18
22
|
|
|
19
23
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
20
24
|
|
|
@@ -51,33 +55,78 @@ function fetchWithTimeout(timeoutMs: number): RemoteFetch {
|
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/** What every hop of a remote fetch is checked against. */
|
|
59
|
+
export interface RedirectPolicy {
|
|
60
|
+
/** `resolve.http.headers` of the Redocly config; chosen again for the URL of every hop. */
|
|
61
|
+
readonly headers: readonly ConfiguredHeader[];
|
|
62
|
+
/** `--remote-ref-hosts`; undefined allows every host. */
|
|
63
|
+
readonly hosts: readonly string[] | undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
/**
|
|
55
|
-
* Follow redirects one by one
|
|
56
|
-
*
|
|
57
|
-
*
|
|
67
|
+
* Follow redirects one by one. Fetch drops only `Authorization`, `Cookie` and
|
|
68
|
+
* `Proxy-Authorization` on a redirect to another origin and keeps every other header (an
|
|
69
|
+
* `X-Api-Key`), so the configured headers are chosen for each hop's own URL instead, and a hop to
|
|
70
|
+
* a host that is not listed is refused before it is fetched. The first URL was checked by the
|
|
71
|
+
* resolver (or is the root document). Redocly passes only the configured headers in `init`, and
|
|
72
|
+
* it gets an empty list of them (`resolveConfigWithAccess`), so `init.headers` is replaced.
|
|
58
73
|
*/
|
|
59
74
|
export function fetchWithCheckedRedirects(
|
|
60
75
|
fetchImpl: RemoteFetch,
|
|
61
|
-
|
|
76
|
+
policy: RedirectPolicy,
|
|
62
77
|
): RemoteFetch {
|
|
63
78
|
return async (input, init = {}) => {
|
|
64
|
-
const firstOrigin = new URL(input).origin;
|
|
65
79
|
let url = new URL(input);
|
|
66
|
-
for (let hop = 0; hop <=
|
|
67
|
-
if (hop > 0 && !isHostAllowed(url, hosts)) {
|
|
68
|
-
throw refForbiddenError(url.href, {
|
|
80
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop += 1) {
|
|
81
|
+
if (hop > 0 && policy.hosts !== undefined && !isHostAllowed(url, policy.hosts)) {
|
|
82
|
+
throw refForbiddenError(url.href, {
|
|
83
|
+
reason: "host-not-allowed",
|
|
84
|
+
allowedHosts: [...policy.hosts],
|
|
85
|
+
});
|
|
69
86
|
}
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
url.origin === firstOrigin && headers !== undefined ? { ...rest, headers } : rest;
|
|
73
|
-
const response = await fetchImpl(url.href, { ...hopInit, redirect: "manual" });
|
|
87
|
+
const headers = await headersFor(url, policy.headers);
|
|
88
|
+
const response = await fetchImpl(url.href, { ...init, headers, redirect: "manual" });
|
|
74
89
|
const location = response.headers.get("location");
|
|
75
90
|
if (!REDIRECT_STATUSES.has(response.status) || location === null) {
|
|
76
91
|
return response;
|
|
77
92
|
}
|
|
78
93
|
url = new URL(location, url);
|
|
79
94
|
}
|
|
80
|
-
throw new Error(`More than ${
|
|
95
|
+
throw new Error(`More than ${MAX_REDIRECTS} redirects loading ${input}`);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Bounds on the remote documents of one load (threat model T2, ADR-033). */
|
|
100
|
+
export interface RemoteDocumentLimits {
|
|
101
|
+
/** Remote documents one load may fetch, the root included; one more is refused. */
|
|
102
|
+
readonly maxDocuments: number;
|
|
103
|
+
/** Remote documents fetched at the same time; the others wait for a free slot. */
|
|
104
|
+
readonly maxParallelFetches: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* At most `limit` calls of the fetch run at once; the others wait in order. A finished call hands
|
|
109
|
+
* its slot to the next waiting one.
|
|
110
|
+
*/
|
|
111
|
+
export function fetchWithConcurrencyLimit(fetchImpl: RemoteFetch, limit: number): RemoteFetch {
|
|
112
|
+
let running = 0;
|
|
113
|
+
const waiting: (() => void)[] = [];
|
|
114
|
+
return async (input, init) => {
|
|
115
|
+
if (running < limit) {
|
|
116
|
+
running += 1;
|
|
117
|
+
} else {
|
|
118
|
+
await new Promise<void>((resolve) => waiting.push(resolve));
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
return await fetchImpl(input, init);
|
|
122
|
+
} finally {
|
|
123
|
+
const next = waiting.shift();
|
|
124
|
+
if (next === undefined) {
|
|
125
|
+
running -= 1;
|
|
126
|
+
} else {
|
|
127
|
+
next();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
81
130
|
};
|
|
82
131
|
}
|
|
83
132
|
|
|
@@ -85,11 +134,14 @@ export interface RemoteFetchOptions {
|
|
|
85
134
|
/** Time limit of one fetch and its body, unless the Redocly config has its own `customFetch`. */
|
|
86
135
|
readonly timeoutMs: number;
|
|
87
136
|
readonly access: RefAccess;
|
|
137
|
+
readonly maxParallelFetches: number;
|
|
88
138
|
}
|
|
89
139
|
|
|
90
140
|
/**
|
|
91
141
|
* The resolve config with the fetch the loader uses. A `customFetch` of the Redocly config is
|
|
92
|
-
* kept
|
|
142
|
+
* kept and wrapped, so that each redirect is checked and only a few documents load at once. The
|
|
143
|
+
* configured headers move into that wrapper: Redocly would match them against the raw URL and
|
|
144
|
+
* send them on through redirects.
|
|
93
145
|
*/
|
|
94
146
|
export function resolveConfigWithAccess(
|
|
95
147
|
resolve: ResolveConfig,
|
|
@@ -98,7 +150,11 @@ export function resolveConfigWithAccess(
|
|
|
98
150
|
// Redocly types `customFetch` as `Function`; it is called as fetch is.
|
|
99
151
|
const configured = resolve.http.customFetch as RemoteFetch | undefined;
|
|
100
152
|
const base = configured ?? fetchWithTimeout(options.timeoutMs);
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
153
|
+
const checked = fetchWithCheckedRedirects(base, {
|
|
154
|
+
headers: resolve.http.headers,
|
|
155
|
+
hosts: options.access.remoteHosts,
|
|
156
|
+
});
|
|
157
|
+
// The limit is outside the redirects, so all hops of one document share one slot.
|
|
158
|
+
const customFetch = fetchWithConcurrencyLimit(checked, options.maxParallelFetches);
|
|
159
|
+
return { ...resolve, http: { ...resolve.http, headers: [], customFetch } };
|
|
104
160
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { assertNever } from "../../domain/assert-never.js";
|
|
1
2
|
import { ERROR_CODES, OpenAPITypesafeError } from "../../domain/errors.js";
|
|
3
|
+
import { redactUrlCredentials } from "../../domain/url-credentials.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* The typed errors of loading, validating and bundling a document (ADR-011). Messages are the
|
|
5
|
-
* original messages, so Node API users who match on them keep working
|
|
7
|
+
* original messages, so Node API users who match on them keep working; URLs in them and in the
|
|
8
|
+
* details never show user info.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
/** No source was given, or the source is empty. */
|
|
@@ -48,7 +51,7 @@ export function loadFailedError(absoluteRef: string, cause: unknown): OpenAPITyp
|
|
|
48
51
|
return new OpenAPITypesafeError({
|
|
49
52
|
code: ERROR_CODES.SCHEMA_LOAD_FAILED,
|
|
50
53
|
message: causeMessage(cause),
|
|
51
|
-
details: { source: absoluteRef },
|
|
54
|
+
details: { source: redactUrlCredentials(absoluteRef) },
|
|
52
55
|
cause,
|
|
53
56
|
});
|
|
54
57
|
}
|
|
@@ -62,7 +65,7 @@ export function parseFailedError(
|
|
|
62
65
|
return new OpenAPITypesafeError({
|
|
63
66
|
code: ERROR_CODES.SCHEMA_PARSE_FAILED,
|
|
64
67
|
message: causeMessage(cause),
|
|
65
|
-
details: { format, source: absoluteRef },
|
|
68
|
+
details: { format, source: redactUrlCredentials(absoluteRef) },
|
|
66
69
|
cause,
|
|
67
70
|
});
|
|
68
71
|
}
|
|
@@ -87,7 +90,7 @@ export function schemaInvalidError(problems: readonly string[]): OpenAPITypesafe
|
|
|
87
90
|
return new OpenAPITypesafeError({
|
|
88
91
|
code: ERROR_CODES.SCHEMA_INVALID,
|
|
89
92
|
message: problems.at(-1) ?? "",
|
|
90
|
-
details: { problems:
|
|
93
|
+
details: { problems: problems.map(redactUrlCredentials) },
|
|
91
94
|
});
|
|
92
95
|
}
|
|
93
96
|
|
|
@@ -108,17 +111,36 @@ function hostListText(hosts: readonly string[]): string {
|
|
|
108
111
|
/** Why a `$ref` document was refused, with the limit it broke (ADR-017). */
|
|
109
112
|
export type RefRefusalDetails =
|
|
110
113
|
| { readonly reason: "outside-local-root"; readonly localRoot: string }
|
|
111
|
-
| { readonly reason: "host-not-allowed"; readonly allowedHosts: readonly string[] }
|
|
114
|
+
| { readonly reason: "host-not-allowed"; readonly allowedHosts: readonly string[] }
|
|
115
|
+
| { readonly reason: "local-ref-from-remote"; readonly base: string }
|
|
116
|
+
| { readonly reason: "too-many-remote-documents"; readonly maxDocuments: number };
|
|
112
117
|
|
|
113
|
-
|
|
118
|
+
function limitText(details: RefRefusalDetails): string {
|
|
119
|
+
switch (details.reason) {
|
|
120
|
+
case "outside-local-root":
|
|
121
|
+
return `it is outside the local $ref root ${details.localRoot}`;
|
|
122
|
+
case "host-not-allowed": {
|
|
123
|
+
const hosts = hostListText(details.allowedHosts);
|
|
124
|
+
return `its host is not in the allowed remote $ref hosts (${hosts})`;
|
|
125
|
+
}
|
|
126
|
+
case "local-ref-from-remote":
|
|
127
|
+
return `it is not an http(s) URL, and the $ref is in the remote document ${details.base}`;
|
|
128
|
+
case "too-many-remote-documents":
|
|
129
|
+
return `the document refers to more than ${details.maxDocuments} remote documents`;
|
|
130
|
+
default:
|
|
131
|
+
return assertNever(details);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A `$ref` is outside the local root, its host is not allowed (threat model R1, R2), a remote
|
|
137
|
+
* document refers to a local file (T3), or there are too many remote documents (T2).
|
|
138
|
+
*/
|
|
114
139
|
export function refForbiddenError(ref: string, details: RefRefusalDetails): OpenAPITypesafeError {
|
|
115
|
-
const limit =
|
|
116
|
-
details.reason === "outside-local-root"
|
|
117
|
-
? `it is outside the local $ref root ${details.localRoot}`
|
|
118
|
-
: `its host is not in the allowed remote $ref hosts (${hostListText(details.allowedHosts)})`;
|
|
140
|
+
const limit = limitText(details);
|
|
119
141
|
return new OpenAPITypesafeError({
|
|
120
142
|
code: ERROR_CODES.SCHEMA_REF_FORBIDDEN,
|
|
121
143
|
message: `Refused to load ${ref}: ${limit}`,
|
|
122
|
-
details: { ref, ...details },
|
|
144
|
+
details: { ref: redactUrlCredentials(ref), ...details },
|
|
123
145
|
});
|
|
124
146
|
}
|
|
@@ -4,7 +4,7 @@ import { ERROR_CODES, OpenAPITypesafeError } from "../../domain/errors.js";
|
|
|
4
4
|
* Keys Redocly 1.34 cannot walk. It looks up the type of every key with `type.properties[key]`,
|
|
5
5
|
* which also finds inherited `Object.prototype` members; it then calls a found function with no
|
|
6
6
|
* `this`, and these methods throw a `TypeError` for that. Redocly 2.x looks up own keys only
|
|
7
|
-
* (ADR-005 keeps 1.34). `toString`
|
|
7
|
+
* (ADR-005 keeps 1.34). `toString` does not throw, and loads as it should.
|
|
8
8
|
*
|
|
9
9
|
* Redocly does not walk every value (`default` and `const` are data), so a document with such a
|
|
10
10
|
* key is not rejected up front: the loader looks for them only after Redocly threw a `TypeError`.
|
|
@@ -21,6 +21,21 @@ export const UNSUPPORTED_KEYS: ReadonlySet<string> = new Set([
|
|
|
21
21
|
"__lookupSetter__",
|
|
22
22
|
]);
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* `constructor` finds `Object`, which returns the value it is called with. Redocly then takes the
|
|
26
|
+
* value for a node type when it has a string `name` (a security scheme, a header with a `name`),
|
|
27
|
+
* and fails reading the type's fields. Any other `constructor` key loads.
|
|
28
|
+
*/
|
|
29
|
+
const CONSTRUCTOR_KEY = "constructor";
|
|
30
|
+
|
|
31
|
+
function isUnsupportedEntry(key: string, value: unknown): boolean {
|
|
32
|
+
if (UNSUPPORTED_KEYS.has(key)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const name = (value as { name?: unknown } | null | undefined)?.name;
|
|
36
|
+
return key === CONSTRUCTOR_KEY && typeof value === "object" && typeof name === "string";
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
/** Enough found keys in the message to act on; `details.keys` has all of them. */
|
|
25
40
|
const KEYS_IN_MESSAGE = 5;
|
|
26
41
|
|
|
@@ -66,7 +81,7 @@ function unsupportedKeysOf(document: LoadedDocument): UnsupportedKey[] {
|
|
|
66
81
|
for (let node = pending.pop(); node !== undefined; node = pending.pop()) {
|
|
67
82
|
for (const [key, child] of Object.entries(node.value)) {
|
|
68
83
|
const pointer = `${node.pointer}/${escapePointerSegment(key)}`;
|
|
69
|
-
if (
|
|
84
|
+
if (isUnsupportedEntry(key, child)) {
|
|
70
85
|
found.push({ key, pointer, source: document.source });
|
|
71
86
|
}
|
|
72
87
|
enqueue(child, pointer);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import colors from "ansi-colors";
|
|
2
2
|
import type { DebugGroup, Logger } from "../../application/ports/logger.js";
|
|
3
|
+
import { redactUrlCredentials } from "../../domain/url-credentials.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The Logger port on a terminal, with the original output format: ` ⚠ ` warnings, ` ✘ `
|
|
6
|
-
* errors, and `openapi-ts:<group>` debug lines filtered by `DEBUG` (ENGINEERING_PLAN 4.5).
|
|
7
|
+
* errors, and `openapi-ts:<group>` debug lines filtered by `DEBUG` (ENGINEERING_PLAN 4.5). No
|
|
8
|
+
* line shows the user info of a URL.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
/** Where the lines go: `console` in production, a recording sink in tests. */
|
|
@@ -76,19 +78,21 @@ class TerminalLogger implements Logger {
|
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
78
80
|
const name = groupColor(this.#palette, group)(`openapi-ts:${group ?? "info"}`);
|
|
81
|
+
// The original wrote no space without a duration (`openapi-ts:lintIgnored …`).
|
|
79
82
|
const time =
|
|
80
|
-
durationMs === undefined ? "" : this.#palette.green(` ${formatDuration(durationMs)} `);
|
|
81
|
-
|
|
83
|
+
durationMs === undefined ? " " : this.#palette.green(` ${formatDuration(durationMs)} `);
|
|
84
|
+
const text = redactUrlCredentials(message);
|
|
85
|
+
this.#options.sink.debug(` ${this.#palette.bold(name)}${time}${text}`);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
warn(message: string): void {
|
|
85
89
|
if (!this.#options.silent) {
|
|
86
|
-
this.#options.sink.warn(this.#palette.yellow(` ⚠ ${message}`));
|
|
90
|
+
this.#options.sink.warn(this.#palette.yellow(` ⚠ ${redactUrlCredentials(message)}`));
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
error(message: string): void {
|
|
91
|
-
this.#options.sink.error(this.#palette.red(` ✘ ${message}`));
|
|
95
|
+
this.#options.sink.error(this.#palette.red(` ✘ ${redactUrlCredentials(message)}`));
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type ts from "typescript";
|
|
2
2
|
import { assertNever } from "../../domain/assert-never.js";
|
|
3
|
+
import { DEFAULT_STRING_ENCODING } from "../../domain/ir/constructors.js";
|
|
3
4
|
import type {
|
|
4
5
|
IrComment,
|
|
5
6
|
IrKeyword,
|
|
@@ -90,7 +91,7 @@ function toLeafTypeNode(ts: TypeScriptModule, type: LeafType): ts.TypeNode {
|
|
|
90
91
|
case "keyword":
|
|
91
92
|
return toKeywordType(ts, type.keyword);
|
|
92
93
|
case "stringLiteral":
|
|
93
|
-
return toStringLiteralType(ts, type.value, type.encoding);
|
|
94
|
+
return toStringLiteralType(ts, type.value, type.encoding ?? DEFAULT_STRING_ENCODING);
|
|
94
95
|
case "numberLiteral":
|
|
95
96
|
return ts.factory.createLiteralTypeNode(toNumberExpression(ts, type.value));
|
|
96
97
|
case "booleanLiteral":
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type ts from "typescript";
|
|
2
2
|
import type { TypeScriptModule } from "./load-typescript.js";
|
|
3
|
+
import type { ts as CompilerTypes } from "./typescript-types.js";
|
|
3
4
|
|
|
4
5
|
/** Options of `astToString` for the TypeScript printer, as in the original. */
|
|
5
6
|
export interface AstToStringOptions {
|
|
6
7
|
fileName?: string;
|
|
7
8
|
sourceText?: string;
|
|
8
|
-
|
|
9
|
+
/** The installed compiler's printer options (ADR-032: named without `ts.PrinterOptions`). */
|
|
10
|
+
formatOptions?: CompilerTypes.PrinterOptions;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/** Print TypeScript nodes as the original `astToString` did. */
|
|
@@ -1,8 +1,60 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as TypeScript from "typescript";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The TypeScript compiler's types for code outside the bridge
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The TypeScript compiler's node types for code outside the bridge and for the public
|
|
5
|
+
* declarations (ADR-004, decision 4; ADR-032). Type-only: importing it never loads TypeScript.
|
|
6
|
+
*
|
|
7
|
+
* Each type is read from the compiler API of the `typescript` package the consumer has installed
|
|
8
|
+
* (`factory`, `createPrinter`), not named as `ts.Node`. TypeScript 5.x and 6.x have that API, so
|
|
9
|
+
* the types are the compiler's own. TypeScript 7's `typescript` package exports only `version`;
|
|
10
|
+
* `ts.Node` there is error TS2694 in a consumer that checks declarations (`skipLibCheck: false`).
|
|
7
11
|
*/
|
|
8
|
-
|
|
12
|
+
|
|
13
|
+
type Api = typeof TypeScript;
|
|
14
|
+
type Factory = Api extends { readonly factory: infer F } ? F : undefined;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A compiler type when the installed `typescript` has no compiler API (TypeScript 7). It is `any`
|
|
18
|
+
* because that is what these types already were for such a consumer with `skipLibCheck: true`
|
|
19
|
+
* (an unresolved type in a declaration file is `any`), so callbacks that build or read nodes of
|
|
20
|
+
* `@typescript/typescript6` keep compiling (ADR-032). The cost, as before: a union with a node
|
|
21
|
+
* type, such as the result of `transform`, accepts any value there.
|
|
22
|
+
*/
|
|
23
|
+
// biome-ignore lint/suspicious/noExplicitAny: unresolved compiler types were `any` (ADR-032)
|
|
24
|
+
type WithoutCompilerApi = any;
|
|
25
|
+
|
|
26
|
+
/** The node type that `factory[Name]` returns, or `WithoutCompilerApi`. */
|
|
27
|
+
type Created<Name extends string> = Factory extends {
|
|
28
|
+
readonly [Key in Name]: (...args: never[]) => infer Result;
|
|
29
|
+
}
|
|
30
|
+
? Result
|
|
31
|
+
: WithoutCompilerApi;
|
|
32
|
+
|
|
33
|
+
type ModifierList = Created<"createModifiersFromModifierFlags">;
|
|
34
|
+
|
|
35
|
+
/** The compiler node types the package names; see the module comment. */
|
|
36
|
+
export declare namespace ts {
|
|
37
|
+
type Node = Api extends { isTypeNode(node: infer N): boolean } ? N : WithoutCompilerApi;
|
|
38
|
+
type TypeNode = Factory extends { createArrayTypeNode(elementType: infer T): unknown }
|
|
39
|
+
? T
|
|
40
|
+
: WithoutCompilerApi;
|
|
41
|
+
type QuestionToken = Factory extends {
|
|
42
|
+
createPropertySignature(modifiers: never, name: never, token: infer Q, type: never): unknown;
|
|
43
|
+
}
|
|
44
|
+
? NonNullable<Q>
|
|
45
|
+
: WithoutCompilerApi;
|
|
46
|
+
type Modifier = ModifierList extends readonly (infer M)[] | undefined ? M : WithoutCompilerApi;
|
|
47
|
+
type PrinterOptions = Api extends { createPrinter(options?: infer O, ...rest: never[]): unknown }
|
|
48
|
+
? NonNullable<O>
|
|
49
|
+
: WithoutCompilerApi;
|
|
50
|
+
type PropertySignature = Created<"createPropertySignature">;
|
|
51
|
+
type LiteralTypeNode = Created<"createLiteralTypeNode">;
|
|
52
|
+
type TypeLiteralNode = Created<"createTypeLiteralNode">;
|
|
53
|
+
type InterfaceDeclaration = Created<"createInterfaceDeclaration">;
|
|
54
|
+
type EnumDeclaration = Created<"createEnumDeclaration">;
|
|
55
|
+
type EnumMember = Created<"createEnumMember">;
|
|
56
|
+
type VariableStatement = Created<"createVariableStatement">;
|
|
57
|
+
type Identifier = Created<"createIdentifier">;
|
|
58
|
+
type NumericLiteral = Created<"createNumericLiteral">;
|
|
59
|
+
type StringLiteral = Created<"createStringLiteral">;
|
|
60
|
+
}
|
package/src/api/default-ports.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { performance } from "node:perf_hooks";
|
|
2
2
|
import { createConfig, type Config as RedoclyConfig } from "@redocly/openapi-core";
|
|
3
3
|
import supportsColor from "supports-color";
|
|
4
|
+
import { createNodeDeprecationReporter } from "../adapters/node-deprecations/node-deprecations.js";
|
|
5
|
+
import type { RemoteDocumentLimits } from "../adapters/redocly-loader/remote-fetch.js";
|
|
4
6
|
import { createTerminalLogger } from "../adapters/terminal-logger/terminal-logger.js";
|
|
7
|
+
import type { DeprecationReporter } from "../application/ports/deprecation-reporter.js";
|
|
5
8
|
import type { Logger } from "../application/ports/logger.js";
|
|
6
9
|
|
|
7
10
|
/**
|
|
@@ -25,6 +28,11 @@ export function defaultLogger(silent: boolean): Logger {
|
|
|
25
28
|
});
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
/** Deprecations as Node.js `DeprecationWarning`s, each code once per process. */
|
|
32
|
+
export function defaultDeprecationReporter(): DeprecationReporter {
|
|
33
|
+
return createNodeDeprecationReporter();
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
/**
|
|
29
37
|
* How long one remote `$ref` (or a remote source) may take to load. Large documents (the 27 MB
|
|
30
38
|
* Cloudflare spec) download in seconds; a minute covers slow hosts, while Node's own fetch would
|
|
@@ -32,6 +40,21 @@ export function defaultLogger(silent: boolean): Logger {
|
|
|
32
40
|
*/
|
|
33
41
|
export const REMOTE_REF_TIMEOUT_MS = 60_000;
|
|
34
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Bounds on the remote documents of one run (ADR-033). Large split documents have a few thousand
|
|
45
|
+
* files, so 10,000 leaves room and still ends an endless fan-out. 16 parallel fetches keep a
|
|
46
|
+
* multi-file document fast without exhausting local ports.
|
|
47
|
+
*/
|
|
48
|
+
export const DEFAULT_REMOTE_DOCUMENT_LIMITS: RemoteDocumentLimits = {
|
|
49
|
+
maxDocuments: 10_000,
|
|
50
|
+
maxParallelFetches: 16,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** The working directory of the process: the default `cwd` of an inline source. */
|
|
54
|
+
export function processCwd(): string {
|
|
55
|
+
return process.cwd();
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
/** A monotonic clock in milliseconds, for debug durations. */
|
|
36
59
|
export function monotonicNow(): number {
|
|
37
60
|
return performance.now();
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type DiscriminatorValueConflict,
|
|
4
4
|
findDiscriminatorValueConflicts,
|
|
5
5
|
} from "../domain/openapi/discriminator-conflicts.js";
|
|
6
|
-
import { OutputFix } from "../domain/output-fixes.js";
|
|
6
|
+
import { FIX, type OutputFix } from "../domain/output-fixes.js";
|
|
7
7
|
import { WARNING_CODES, warningText } from "../domain/warnings.js";
|
|
8
8
|
|
|
9
9
|
const DOCS_BASE_URL = "https://github.com/openapi-typesafe/openapi-typesafe/blob/main/docs";
|
|
@@ -36,7 +36,7 @@ export function warnDiscriminatorValueConflicts(
|
|
|
36
36
|
fixes: readonly OutputFix[],
|
|
37
37
|
logger: Logger,
|
|
38
38
|
): void {
|
|
39
|
-
const isUnionReused = fixes.includes(
|
|
39
|
+
const isUnionReused = fixes.includes(FIX.ALLOF_DISCRIMINATED_UNION);
|
|
40
40
|
for (const conflict of findDiscriminatorValueConflicts(document, { isUnionReused })) {
|
|
41
41
|
logger.warn(warningText(warningOf(conflict), WARNING_CODES.DISCRIMINATOR_VALUE_NOT_ALLOWED));
|
|
42
42
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Logger } from "../application/ports/logger.js";
|
|
2
|
-
import { OutputFix } from "../domain/output-fixes.js";
|
|
2
|
+
import { FIX, type OutputFix } from "../domain/output-fixes.js";
|
|
3
|
+
import { WARNING_CODES, warningText } from "../domain/warnings.js";
|
|
3
4
|
import type { DiscriminatorObject, OpenAPI3, SchemaObject } from "../types.js";
|
|
4
5
|
import { addEnumProperty, declaresValues, type EnumPatch } from "./discriminator-enum.js";
|
|
5
6
|
import {
|
|
@@ -45,7 +46,7 @@ export interface ScanState extends DiscriminatorScanOptions {
|
|
|
45
46
|
|
|
46
47
|
/** Whether `discriminator-values` is on: values of two discriminators merge into one enum. */
|
|
47
48
|
export function mergesValues(state: ScanState): boolean {
|
|
48
|
-
return state.fixes.has(
|
|
49
|
+
return state.fixes.has(FIX.DISCRIMINATOR_VALUES);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
function patchedSchema(state: ScanState, patch: EnumPatch): SchemaObject | undefined {
|
|
@@ -59,8 +60,7 @@ function patchedSchema(state: ScanState, patch: EnumPatch): SchemaObject | undef
|
|
|
59
60
|
*/
|
|
60
61
|
function isDeclared(state: ScanState, patch: EnumPatch): boolean {
|
|
61
62
|
return (
|
|
62
|
-
state.fixes.has(
|
|
63
|
-
declaresValues(patchedSchema(state, patch), patch)
|
|
63
|
+
state.fixes.has(FIX.DISCRIMINATOR_VALUES) && declaresValues(patchedSchema(state, patch), patch)
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -69,7 +69,7 @@ function isDeclared(state: ScanState, patch: EnumPatch): boolean {
|
|
|
69
69
|
* added, or a discriminator object that a schema inherited.
|
|
70
70
|
*/
|
|
71
71
|
export function recordEnumParent(state: ScanState, owner: DiscriminatorOwner, key: object): void {
|
|
72
|
-
if (!state.fixes.has(
|
|
72
|
+
if (!state.fixes.has(FIX.DISCRIMINATOR_VALUES)) {
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
const { silent, logger } = state;
|
|
@@ -141,7 +141,11 @@ export function markHandled(state: ScanState, ref: string): void {
|
|
|
141
141
|
function warnInvalidMapping(patch: EnumPatch, logger: Logger): void {
|
|
142
142
|
const mapped = `${patch.ref} => ${patch.values.join(", ")}`;
|
|
143
143
|
logger.warn(
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
warningText(
|
|
145
|
+
"Discriminator mapping has an invalid schema " +
|
|
146
|
+
"(neither an object schema nor an allOf array): " +
|
|
147
|
+
`${mapped} (Discriminator: ${patch.discriminatorRef})`,
|
|
148
|
+
WARNING_CODES.DISCRIMINATOR_MAPPING_INVALID,
|
|
149
|
+
),
|
|
146
150
|
);
|
|
147
151
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Logger } from "../application/ports/logger.js";
|
|
2
2
|
import { isUnionMember, unionAt } from "../domain/openapi/discriminated-union.js";
|
|
3
|
-
import {
|
|
3
|
+
import { FIX } from "../domain/output-fixes.js";
|
|
4
4
|
import type {
|
|
5
5
|
DiscriminatorObject,
|
|
6
6
|
GeneratorOptions,
|
|
@@ -121,7 +121,7 @@ function valuesMappedTo(discriminator: DiscriminatorObject, ref: string): string
|
|
|
121
121
|
* schema that the union's mapping names is a member.
|
|
122
122
|
*/
|
|
123
123
|
function inheritsDiscriminator(state: ScanState, parentRef: string, ref: string): boolean {
|
|
124
|
-
if (!state.fixes.has(
|
|
124
|
+
if (!state.fixes.has(FIX.ALLOF_DISCRIMINATED_UNION)) {
|
|
125
125
|
return true;
|
|
126
126
|
}
|
|
127
127
|
const resolve = (target: string) => resolveDocumentRef(state.schema, target, state);
|
|
@@ -141,7 +141,7 @@ function recordInherited(
|
|
|
141
141
|
discriminator: DiscriminatorObject,
|
|
142
142
|
ownerRef: string,
|
|
143
143
|
): void {
|
|
144
|
-
if (state.fixes.has(
|
|
144
|
+
if (state.fixes.has(FIX.DISCRIMINATOR_VALUES) && state.owners.has(ref)) {
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
147
|
const inherited = { ...discriminator };
|