openapi-typesafe 1.0.0-rc.4 → 1.0.0-rc.5
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 +141 -0
- package/dist/adapters/node-file-system/node-file-system.cjs +7 -0
- package/dist/adapters/node-file-system/node-file-system.cjs.map +1 -1
- package/dist/adapters/node-file-system/node-file-system.mjs +7 -0
- package/dist/adapters/node-file-system/node-file-system.mjs.map +1 -1
- package/dist/adapters/pipe-safe-writer/pipe-safe-writer.cjs +19 -0
- package/dist/adapters/pipe-safe-writer/pipe-safe-writer.cjs.map +1 -0
- package/dist/adapters/pipe-safe-writer/pipe-safe-writer.mjs +17 -0
- package/dist/adapters/pipe-safe-writer/pipe-safe-writer.mjs.map +1 -0
- package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs +5 -32
- package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs.map +1 -1
- package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs +6 -33
- package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs.map +1 -1
- package/dist/adapters/redocly-loader/component-schemas.cjs +17 -0
- package/dist/adapters/redocly-loader/component-schemas.cjs.map +1 -0
- package/dist/adapters/redocly-loader/component-schemas.mjs +14 -0
- package/dist/adapters/redocly-loader/component-schemas.mjs.map +1 -0
- package/dist/adapters/redocly-loader/mapping-schema-names.cjs +22 -0
- package/dist/adapters/redocly-loader/mapping-schema-names.cjs.map +1 -0
- package/dist/adapters/redocly-loader/mapping-schema-names.mjs +20 -0
- package/dist/adapters/redocly-loader/mapping-schema-names.mjs.map +1 -0
- package/dist/adapters/redocly-loader/query-named-components.cjs +137 -0
- package/dist/adapters/redocly-loader/query-named-components.cjs.map +1 -0
- package/dist/adapters/redocly-loader/query-named-components.mjs +134 -0
- package/dist/adapters/redocly-loader/query-named-components.mjs.map +1 -0
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +26 -14
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +26 -14
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.cjs +16 -3
- package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.mjs +16 -3
- package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-holders.cjs +52 -10
- package/dist/adapters/redocly-loader/ref-holders.cjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-holders.mjs +52 -11
- package/dist/adapters/redocly-loader/ref-holders.mjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-fetch.cjs +5 -3
- package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-fetch.mjs +5 -3
- package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-node-limit.cjs +17 -3
- package/dist/adapters/redocly-loader/remote-node-limit.cjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-node-limit.mjs +17 -3
- package/dist/adapters/redocly-loader/remote-node-limit.mjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-source.cjs +7 -2
- package/dist/adapters/redocly-loader/schema-source.cjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-source.mjs +8 -3
- package/dist/adapters/redocly-loader/schema-source.mjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.cjs +7 -3
- package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.mjs +7 -3
- package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
- package/dist/api/document-refs.cjs +16 -1
- package/dist/api/document-refs.cjs.map +1 -1
- package/dist/api/document-refs.mjs +16 -1
- package/dist/api/document-refs.mjs.map +1 -1
- package/dist/api/document-run.cjs +4 -10
- package/dist/api/document-run.cjs.map +1 -1
- package/dist/api/document-run.mjs +4 -10
- package/dist/api/document-run.mjs.map +1 -1
- package/dist/api/openapi-ts.cjs +1 -2
- package/dist/api/openapi-ts.cjs.map +1 -1
- package/dist/api/openapi-ts.mjs +2 -3
- package/dist/api/openapi-ts.mjs.map +1 -1
- package/dist/api/ref-limit-options.cjs +63 -0
- package/dist/api/ref-limit-options.cjs.map +1 -0
- package/dist/api/ref-limit-options.mjs +57 -0
- package/dist/api/ref-limit-options.mjs.map +1 -0
- package/dist/api/transform-schema.cjs +12 -5
- package/dist/api/transform-schema.cjs.map +1 -1
- package/dist/api/transform-schema.mjs +12 -5
- package/dist/api/transform-schema.mjs.map +1 -1
- package/dist/application/transform/declared-value-types.cjs +36 -0
- package/dist/application/transform/declared-value-types.cjs.map +1 -0
- package/dist/application/transform/declared-value-types.mjs +34 -0
- package/dist/application/transform/declared-value-types.mjs.map +1 -0
- package/dist/application/transform/document/document.cjs +3 -1
- package/dist/application/transform/document/document.cjs.map +1 -1
- package/dist/application/transform/document/document.mjs +3 -1
- package/dist/application/transform/document/document.mjs.map +1 -1
- package/dist/application/transform/document/exploded-query-values.cjs +63 -0
- package/dist/application/transform/document/exploded-query-values.cjs.map +1 -0
- package/dist/application/transform/document/exploded-query-values.mjs +59 -0
- package/dist/application/transform/document/exploded-query-values.mjs.map +1 -0
- package/dist/application/transform/document/header.cjs +11 -3
- package/dist/application/transform/document/header.cjs.map +1 -1
- package/dist/application/transform/document/header.mjs +10 -2
- package/dist/application/transform/document/header.mjs.map +1 -1
- package/dist/application/transform/document/parameter.cjs +1 -1
- package/dist/application/transform/document/parameter.cjs.map +1 -1
- package/dist/application/transform/document/parameter.mjs +1 -1
- package/dist/application/transform/document/parameter.mjs.map +1 -1
- package/dist/application/transform/document/parameters.cjs +23 -5
- package/dist/application/transform/document/parameters.cjs.map +1 -1
- package/dist/application/transform/document/parameters.mjs +23 -5
- package/dist/application/transform/document/parameters.mjs.map +1 -1
- package/dist/application/transform/document/path-item.cjs +4 -3
- package/dist/application/transform/document/path-item.cjs.map +1 -1
- package/dist/application/transform/document/path-item.mjs +4 -4
- package/dist/application/transform/document/path-item.mjs.map +1 -1
- package/dist/application/transform/document/path-template-key.cjs +84 -0
- package/dist/application/transform/document/path-template-key.cjs.map +1 -0
- package/dist/application/transform/document/path-template-key.mjs +79 -0
- package/dist/application/transform/document/path-template-key.mjs.map +1 -0
- package/dist/application/transform/document/path-template-refs.cjs +70 -0
- package/dist/application/transform/document/path-template-refs.cjs.map +1 -0
- package/dist/application/transform/document/path-template-refs.mjs +67 -0
- package/dist/application/transform/document/path-template-refs.mjs.map +1 -0
- package/dist/application/transform/document/paths-enum.cjs +7 -6
- package/dist/application/transform/document/paths-enum.cjs.map +1 -1
- package/dist/application/transform/document/paths-enum.mjs +7 -6
- package/dist/application/transform/document/paths-enum.mjs.map +1 -1
- package/dist/application/transform/document/paths.cjs +38 -66
- package/dist/application/transform/document/paths.cjs.map +1 -1
- package/dist/application/transform/document/paths.mjs +36 -66
- package/dist/application/transform/document/paths.mjs.map +1 -1
- package/dist/application/transform/document/webhooks.cjs +10 -3
- package/dist/application/transform/document/webhooks.cjs.map +1 -1
- package/dist/application/transform/document/webhooks.mjs +9 -2
- package/dist/application/transform/document/webhooks.mjs.map +1 -1
- package/dist/application/transform/footer/document-footer.cjs +6 -3
- package/dist/application/transform/footer/document-footer.cjs.map +1 -1
- package/dist/application/transform/footer/document-footer.mjs +6 -3
- package/dist/application/transform/footer/document-footer.mjs.map +1 -1
- package/dist/application/transform/helper-types.cjs +3 -3
- package/dist/application/transform/helper-types.cjs.map +1 -1
- package/dist/application/transform/helper-types.mjs +3 -3
- package/dist/application/transform/helper-types.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-location.cjs +4 -4
- package/dist/application/transform/ref-pointer/generated-location.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-location.mjs +1 -1
- package/dist/application/transform/ref-pointer/generated-location.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-shape.cjs +47 -66
- package/dist/application/transform/ref-pointer/generated-shape.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-shape.mjs +36 -50
- package/dist/application/transform/ref-pointer/generated-shape.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/inline-cycles.cjs +2 -2
- package/dist/application/transform/ref-pointer/inline-cycles.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/inline-cycles.mjs +1 -1
- package/dist/application/transform/ref-pointer/inline-cycles.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/located-node.cjs +23 -0
- package/dist/application/transform/ref-pointer/located-node.cjs.map +1 -0
- package/dist/application/transform/ref-pointer/located-node.mjs +18 -0
- package/dist/application/transform/ref-pointer/located-node.mjs.map +1 -0
- package/dist/application/transform/ref-pointer/original-index.cjs +68 -13
- package/dist/application/transform/ref-pointer/original-index.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/original-index.mjs +70 -15
- package/dist/application/transform/ref-pointer/original-index.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.cjs +2 -2
- package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.mjs +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-walk.cjs +54 -13
- package/dist/application/transform/ref-pointer/pointer-walk.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-walk.mjs +46 -7
- package/dist/application/transform/ref-pointer/pointer-walk.mjs.map +1 -1
- package/dist/application/transform/ref-pointer/property-shape.cjs +52 -0
- package/dist/application/transform/ref-pointer/property-shape.cjs.map +1 -0
- package/dist/application/transform/ref-pointer/property-shape.mjs +45 -0
- package/dist/application/transform/ref-pointer/property-shape.mjs.map +1 -0
- package/dist/application/transform/ref-pointer/typed-walk.cjs +79 -21
- package/dist/application/transform/ref-pointer/typed-walk.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/typed-walk.mjs +76 -18
- package/dist/application/transform/ref-pointer/typed-walk.mjs.map +1 -1
- package/dist/application/transform/schema/access-flags.cjs.map +1 -1
- package/dist/application/transform/schema/access-flags.mjs.map +1 -1
- package/dist/application/transform/schema/index-signature-type.cjs +21 -0
- package/dist/application/transform/schema/index-signature-type.cjs.map +1 -0
- package/dist/application/transform/schema/index-signature-type.mjs +19 -0
- package/dist/application/transform/schema/index-signature-type.mjs.map +1 -0
- package/dist/application/transform/schema/input-location.cjs +23 -0
- package/dist/application/transform/schema/input-location.cjs.map +1 -0
- package/dist/application/transform/schema/input-location.mjs +21 -0
- package/dist/application/transform/schema/input-location.mjs.map +1 -0
- package/dist/application/transform/schema/object-type.cjs +15 -5
- package/dist/application/transform/schema/object-type.cjs.map +1 -1
- package/dist/application/transform/schema/object-type.mjs +15 -5
- package/dist/application/transform/schema/object-type.mjs.map +1 -1
- package/dist/application/transform/schema/schema-transform.cjs +2 -1
- package/dist/application/transform/schema/schema-transform.cjs.map +1 -1
- package/dist/application/transform/schema/schema-transform.mjs +2 -1
- package/dist/application/transform/schema/schema-transform.mjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.cjs +2 -30
- package/dist/application/transform/schema/unevaluated-properties.cjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.mjs +3 -31
- package/dist/application/transform/schema/unevaluated-properties.mjs.map +1 -1
- package/dist/cli/api-logger.cjs +13 -0
- package/dist/cli/api-logger.cjs.map +1 -0
- package/dist/cli/api-logger.mjs +11 -0
- package/dist/cli/api-logger.mjs.map +1 -0
- package/dist/cli/api-settings.cjs +7 -15
- package/dist/cli/api-settings.cjs.map +1 -1
- package/dist/cli/api-settings.mjs +8 -16
- package/dist/cli/api-settings.mjs.map +1 -1
- package/dist/cli/cli-arguments.cjs +51 -11
- package/dist/cli/cli-arguments.cjs.map +1 -1
- package/dist/cli/cli-arguments.mjs +51 -11
- package/dist/cli/cli-arguments.mjs.map +1 -1
- package/dist/cli/cli-errors.cjs +17 -0
- package/dist/cli/cli-errors.cjs.map +1 -1
- package/dist/cli/cli-errors.mjs +17 -1
- package/dist/cli/cli-errors.mjs.map +1 -1
- package/dist/cli/cli-output.cjs +6 -4
- package/dist/cli/cli-output.cjs.map +1 -1
- package/dist/cli/cli-output.mjs +7 -5
- package/dist/cli/cli-output.mjs.map +1 -1
- package/dist/cli/main.cjs +10 -4
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.mjs +10 -4
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/redocly-apis.cjs +20 -9
- package/dist/cli/redocly-apis.cjs.map +1 -1
- package/dist/cli/redocly-apis.mjs +22 -11
- package/dist/cli/redocly-apis.mjs.map +1 -1
- package/dist/cli/ref-limits.cjs +15 -1
- package/dist/cli/ref-limits.cjs.map +1 -1
- package/dist/cli/ref-limits.mjs +15 -2
- package/dist/cli/ref-limits.mjs.map +1 -1
- package/dist/cli/run-cli.cjs +24 -7
- package/dist/cli/run-cli.cjs.map +1 -1
- package/dist/cli/run-cli.mjs +25 -8
- package/dist/cli/run-cli.mjs.map +1 -1
- package/dist/domain/cause-secrets.cjs +31 -16
- package/dist/domain/cause-secrets.cjs.map +1 -1
- package/dist/domain/cause-secrets.mjs +31 -16
- package/dist/domain/cause-secrets.mjs.map +1 -1
- package/dist/domain/errors.cjs +2 -0
- package/dist/domain/errors.cjs.map +1 -1
- package/dist/domain/errors.mjs +2 -0
- package/dist/domain/errors.mjs.map +1 -1
- package/dist/domain/location-text.cjs +9 -0
- package/dist/domain/location-text.cjs.map +1 -0
- package/dist/domain/location-text.mjs +7 -0
- package/dist/domain/location-text.mjs.map +1 -0
- package/dist/domain/openapi/document-places.cjs +55 -0
- package/dist/domain/openapi/document-places.cjs.map +1 -0
- package/dist/domain/openapi/document-places.mjs +53 -0
- package/dist/domain/openapi/document-places.mjs.map +1 -0
- package/dist/domain/output-fixes.cjs +8 -2
- package/dist/domain/output-fixes.cjs.map +1 -1
- package/dist/domain/output-fixes.mjs +8 -2
- package/dist/domain/output-fixes.mjs.map +1 -1
- package/dist/domain/terminal-text.cjs +31 -0
- package/dist/domain/terminal-text.cjs.map +1 -0
- package/dist/domain/terminal-text.mjs +29 -0
- package/dist/domain/terminal-text.mjs.map +1 -0
- package/dist/domain/warnings.cjs +1 -0
- package/dist/domain/warnings.cjs.map +1 -1
- package/dist/domain/warnings.mjs +1 -0
- package/dist/domain/warnings.mjs.map +1 -1
- package/dist/index.d.cts +26 -18
- package/dist/index.d.mts +26 -18
- package/dist/index.d.ts +26 -18
- package/package.json +1 -1
- package/src/adapters/node-file-system/node-file-system.ts +14 -1
- package/src/adapters/pipe-safe-writer/pipe-safe-writer.ts +29 -0
- package/src/adapters/redocly-loader/bundled-mapping-copies.ts +7 -42
- package/src/adapters/redocly-loader/component-schemas.ts +19 -0
- package/src/adapters/redocly-loader/mapping-schema-names.ts +25 -0
- package/src/adapters/redocly-loader/query-named-components.ts +201 -0
- package/src/adapters/redocly-loader/redocly-schema-loader.ts +46 -14
- package/src/adapters/redocly-loader/ref-access.ts +16 -3
- package/src/adapters/redocly-loader/ref-holders.ts +75 -14
- package/src/adapters/redocly-loader/remote-fetch.ts +11 -2
- package/src/adapters/redocly-loader/remote-node-limit.ts +44 -4
- package/src/adapters/redocly-loader/schema-source.ts +15 -3
- package/src/adapters/terminal-logger/terminal-logger.ts +10 -4
- package/src/api/document-refs.ts +29 -1
- package/src/api/document-run.ts +6 -16
- package/src/api/openapi-ts.ts +6 -3
- package/src/api/ref-limit-options.ts +76 -0
- package/src/api/transform-schema.ts +20 -5
- package/src/application/ports/file-system.ts +8 -0
- package/src/application/transform/declared-value-types.ts +42 -0
- package/src/application/transform/document/document.ts +4 -2
- package/src/application/transform/document/exploded-query-values.ts +88 -0
- package/src/application/transform/document/header.ts +25 -5
- package/src/application/transform/document/parameter.ts +4 -4
- package/src/application/transform/document/parameters.ts +45 -10
- package/src/application/transform/document/path-item.ts +3 -3
- package/src/application/transform/document/path-template-key.ts +152 -0
- package/src/application/transform/document/path-template-refs.ts +106 -0
- package/src/application/transform/document/paths-enum.ts +23 -9
- package/src/application/transform/document/paths.ts +53 -123
- package/src/application/transform/document/webhooks.ts +18 -5
- package/src/application/transform/footer/document-footer.ts +18 -8
- package/src/application/transform/helper-types.ts +4 -4
- package/src/application/transform/ref-pointer/generated-location.ts +1 -1
- package/src/application/transform/ref-pointer/generated-shape.ts +67 -78
- package/src/application/transform/ref-pointer/inline-cycles.ts +1 -1
- package/src/application/transform/ref-pointer/located-node.ts +38 -0
- package/src/application/transform/ref-pointer/original-index.ts +100 -17
- package/src/application/transform/ref-pointer/pointer-type.ts +1 -1
- package/src/application/transform/ref-pointer/pointer-walk.ts +82 -8
- package/src/application/transform/ref-pointer/property-shape.ts +97 -0
- package/src/application/transform/ref-pointer/typed-walk.ts +117 -25
- package/src/application/transform/schema/access-flags.ts +5 -2
- package/src/application/transform/schema/index-signature-type.ts +31 -0
- package/src/application/transform/schema/input-location.ts +35 -0
- package/src/application/transform/schema/object-type.ts +18 -8
- package/src/application/transform/schema/schema-transform.ts +2 -1
- package/src/application/transform/schema/unevaluated-properties.ts +4 -44
- package/src/cli/api-logger.ts +17 -0
- package/src/cli/api-settings.ts +12 -20
- package/src/cli/cli-arguments.ts +90 -14
- package/src/cli/cli-dependencies.ts +7 -2
- package/src/cli/cli-errors.ts +22 -0
- package/src/cli/cli-output.ts +11 -10
- package/src/cli/main.ts +10 -4
- package/src/cli/redocly-apis.ts +37 -17
- package/src/cli/ref-limits.ts +21 -2
- package/src/cli/run-cli.ts +39 -7
- package/src/domain/cause-secrets.ts +59 -25
- package/src/domain/errors.ts +2 -0
- package/src/domain/location-text.ts +12 -0
- package/src/domain/openapi/document-places.ts +78 -0
- package/src/domain/output-fixes.ts +9 -2
- package/src/domain/terminal-text.ts +44 -0
- package/src/domain/warnings.ts +1 -0
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import colors from "ansi-colors";
|
|
2
2
|
import type { DebugGroup, Logger } from "../../application/ports/logger.js";
|
|
3
|
+
import { terminalSafeText } from "../../domain/terminal-text.js";
|
|
3
4
|
import { redactUrlSecrets } from "../../domain/url-secrets.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The Logger port on a terminal, with the original output format: ` ⚠ ` warnings, ` ✘ `
|
|
7
8
|
* errors, and `openapi-ts:<group>` debug lines filtered by `DEBUG` (ENGINEERING_PLAN 4.5). No
|
|
8
|
-
* line shows the
|
|
9
|
+
* line shows the secrets of a URL, and each message prints as one line: a message quotes the
|
|
10
|
+
* document, whose control characters could forge CI commands (terminal-text.ts).
|
|
9
11
|
*/
|
|
10
12
|
|
|
13
|
+
function shownText(message: string): string {
|
|
14
|
+
return terminalSafeText(redactUrlSecrets(message));
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
/** Where the lines go: `console` in production, a recording sink in tests. */
|
|
12
18
|
export interface TerminalSink {
|
|
13
19
|
debug(line: string): void;
|
|
@@ -84,19 +90,19 @@ class TerminalLogger implements Logger {
|
|
|
84
90
|
// The original wrote no space without a duration (`openapi-ts:lintIgnored …`).
|
|
85
91
|
const time =
|
|
86
92
|
durationMs === undefined ? " " : this.#palette.green(` ${formatDuration(durationMs)} `);
|
|
87
|
-
const text =
|
|
93
|
+
const text = shownText(message);
|
|
88
94
|
this.#options.sink.debug(` ${this.#palette.bold(name)}${time}${text}`);
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
warn(message: string): void {
|
|
92
98
|
if (!this.#options.silent) {
|
|
93
|
-
this.#options.sink.warn(this.#palette.yellow(` ⚠ ${
|
|
99
|
+
this.#options.sink.warn(this.#palette.yellow(` ⚠ ${shownText(message)}`));
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
error(message: string): void {
|
|
98
104
|
if (!this.#options.silent) {
|
|
99
|
-
this.#options.sink.error(this.#palette.red(` ✘ ${
|
|
105
|
+
this.#options.sink.error(this.#palette.red(` ✘ ${shownText(message)}`));
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
108
|
}
|
package/src/api/document-refs.ts
CHANGED
|
@@ -16,6 +16,34 @@ function isObject(value: unknown): value is Record<string, unknown> {
|
|
|
16
16
|
return typeof value === "object" && value !== null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The child of a node under a pointer key. A pointer may pass through a `$ref`
|
|
21
|
+
* (`#/components/schemas/Wrap/properties/o/properties/v`, where `o` is a `$ref`): a key the node
|
|
22
|
+
* does not have is read from the node's target, as the generated types index it (ADR-026). A key
|
|
23
|
+
* the node has is read from it, so a pointer to a sibling of a `$ref` (OpenAPI 3.1) stays as
|
|
24
|
+
* written.
|
|
25
|
+
*/
|
|
26
|
+
function childOf(
|
|
27
|
+
schema: unknown,
|
|
28
|
+
node: unknown,
|
|
29
|
+
key: string,
|
|
30
|
+
options: ResolveRefOptions & { logger: Logger },
|
|
31
|
+
): unknown {
|
|
32
|
+
if (!isObject(node)) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const { visited = [] } = options;
|
|
36
|
+
const target = node.$ref;
|
|
37
|
+
if (node[key] || typeof target !== "string" || visited.includes(target)) {
|
|
38
|
+
return node[key];
|
|
39
|
+
}
|
|
40
|
+
const resolved = resolveDocumentRef(schema, target, {
|
|
41
|
+
...options,
|
|
42
|
+
visited: [...visited, target],
|
|
43
|
+
});
|
|
44
|
+
return isObject(resolved) ? resolved[key] : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
19
47
|
/** Resolve a `$ref` in a document; warns and returns undefined when it cannot. */
|
|
20
48
|
export function resolveDocumentRef<T>(
|
|
21
49
|
schema: unknown,
|
|
@@ -30,7 +58,7 @@ export function resolveDocumentRef<T>(
|
|
|
30
58
|
}
|
|
31
59
|
let node: unknown = schema;
|
|
32
60
|
for (const key of pointer) {
|
|
33
|
-
const child =
|
|
61
|
+
const child = childOf(schema, node, key, { ...options, logger });
|
|
34
62
|
if (!child) {
|
|
35
63
|
logger.warn(warningText(`Could not resolve $ref "${$ref}"`, WARNING_CODES.REF_NOT_FOUND));
|
|
36
64
|
return undefined;
|
package/src/api/document-run.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
3
|
import type { Config as RedoclyConfig } from "@redocly/openapi-core";
|
|
4
4
|
import { sourceEmptyError } from "../adapters/redocly-loader/schema-load-errors.js";
|
|
5
5
|
import type { DeprecationReporter } from "../application/ports/deprecation-reporter.js";
|
|
6
6
|
import type { Logger } from "../application/ports/logger.js";
|
|
7
|
-
import type {
|
|
7
|
+
import type { SchemaLoader } from "../application/ports/schema-loader.js";
|
|
8
8
|
import { withStackOverflowReported } from "../application/stack-overflow.js";
|
|
9
9
|
import {
|
|
10
10
|
ineffectiveFixNotices,
|
|
@@ -19,6 +19,7 @@ import { warnDiscriminatorValueConflicts } from "./discriminator-conflict-warnin
|
|
|
19
19
|
import { findDiscriminators } from "./discriminator-scan.js";
|
|
20
20
|
import { resolveDocumentRef } from "./document-refs.js";
|
|
21
21
|
import { warnIdenticalPathTemplates } from "./path-template-warnings.js";
|
|
22
|
+
import { refAccessOf } from "./ref-limit-options.js";
|
|
22
23
|
import type { RunContext } from "./run-context.js";
|
|
23
24
|
import { warnUnsupportedFeatures } from "./unsupported-feature-warnings.js";
|
|
24
25
|
|
|
@@ -68,19 +69,6 @@ function sourceCwd(options: GeneratorOptions): URL {
|
|
|
68
69
|
return pathToFileURL(path.join(directory, path.sep));
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
/** The `$ref` limits of the options; a relative local root resolves against the cwd. */
|
|
72
|
-
function refAccessOf(options: GeneratorOptions, cwd: URL): RefAccess {
|
|
73
|
-
const { localRefRoot, remoteRefHosts } = options;
|
|
74
|
-
const localRoot =
|
|
75
|
-
typeof localRefRoot === "string"
|
|
76
|
-
? pathToFileURL(path.resolve(fileURLToPath(cwd), localRefRoot))
|
|
77
|
-
: localRefRoot;
|
|
78
|
-
return {
|
|
79
|
-
...(localRoot === undefined ? {} : { localRoot }),
|
|
80
|
-
...(remoteRefHosts === undefined ? {} : { remoteHosts: remoteRefHosts }),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
72
|
function callbackOptions(options: GeneratorOptions) {
|
|
85
73
|
return {
|
|
86
74
|
irCallbacks: options.irCallbacks === true,
|
|
@@ -126,7 +114,9 @@ function contextFlags(options: GeneratorOptions): ContextFlags {
|
|
|
126
114
|
*/
|
|
127
115
|
function outputFixesOf(options: GeneratorOptions, logger: Logger): readonly OutputFix[] {
|
|
128
116
|
const names = outputFixNamesOf(options.fix ?? []);
|
|
129
|
-
|
|
117
|
+
// The options with their defaults: `defaultNonNullable` is on unless set to false.
|
|
118
|
+
const flags = contextFlags(options);
|
|
119
|
+
for (const notice of ineffectiveFixNotices(names, (option) => flags[option])) {
|
|
130
120
|
logger.warn(warningText(notice, WARNING_CODES.FIX_NEEDS_OPTION));
|
|
131
121
|
}
|
|
132
122
|
return [...resolveOutputFixes(names)];
|
package/src/api/openapi-ts.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRedoclySchemaLoader } from "../adapters/redocly-loader/redocly-schema-loader.js";
|
|
2
|
+
import type { Logger } from "../application/ports/logger.js";
|
|
2
3
|
import { withStackOverflowReported } from "../application/stack-overflow.js";
|
|
3
4
|
import type { IrDeclaration } from "../domain/ir/nodes.js";
|
|
4
5
|
import type { GeneratorOptions, SchemaSource } from "../types.js";
|
|
@@ -52,10 +53,12 @@ export async function generateDeclarations(
|
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
55
|
* The default ports: the terminal logger, Node.js deprecation warnings, the given or default
|
|
55
|
-
* Redocly config, Redocly loading.
|
|
56
|
+
* Redocly config, Redocly loading. A given `logger` takes every line, the loader's included.
|
|
56
57
|
*/
|
|
57
|
-
export async function defaultGeneratorPorts(
|
|
58
|
-
|
|
58
|
+
export async function defaultGeneratorPorts(
|
|
59
|
+
options: GeneratorOptions,
|
|
60
|
+
logger: Logger = defaultLogger(options.silent ?? false),
|
|
61
|
+
): Promise<GeneratorPorts> {
|
|
59
62
|
const redoc = options.redocly ?? (await defaultRedoclyConfig());
|
|
60
63
|
const loader = createRedoclySchemaLoader({
|
|
61
64
|
redoc,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
import type { RefAccess } from "../application/ports/schema-loader.js";
|
|
4
|
+
import { ERROR_CODES, OpenAPITypesafeError } from "../domain/errors.js";
|
|
5
|
+
import { hasUrlScheme } from "../domain/location-text.js";
|
|
6
|
+
import type { GeneratorOptions } from "../types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The `$ref` limits of the options (threat model R1, R2, ADR-017), checked before anything loads.
|
|
10
|
+
* A value of another type from JavaScript (`remoteRefHosts: "a.com"`, `localRefRoot: new
|
|
11
|
+
* URL("https://…")`) used to fail later as an error of the document.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const FILE_PROTOCOL = "file:";
|
|
15
|
+
|
|
16
|
+
/** What a value is, for the error; a URL is named by its protocol only, as it may hold secrets. */
|
|
17
|
+
function kindOf(value: unknown): string {
|
|
18
|
+
if (value instanceof URL) {
|
|
19
|
+
return `a ${value.protocol} URL`;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return "a list";
|
|
23
|
+
}
|
|
24
|
+
return value === null ? "null" : `a ${typeof value}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function optionInvalid(option: string, expected: string, got: string): OpenAPITypesafeError {
|
|
28
|
+
return new OpenAPITypesafeError({
|
|
29
|
+
code: ERROR_CODES.OPTION_INVALID,
|
|
30
|
+
message: `The ${option} option must be ${expected}; got ${got}.`,
|
|
31
|
+
details: { option },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const LOCAL_ROOT_EXPECTED = "a directory path or a file: URL";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A string as the CLI reads `--local-ref-root`: a URL when it has a scheme (`file:///specs/`),
|
|
39
|
+
* else a path that resolves against the cwd. Undefined when it does not parse as a URL.
|
|
40
|
+
*/
|
|
41
|
+
function localRootUrlOf(value: string, cwd: URL): URL | undefined {
|
|
42
|
+
if (!hasUrlScheme(value)) {
|
|
43
|
+
return pathToFileURL(path.resolve(fileURLToPath(cwd), value));
|
|
44
|
+
}
|
|
45
|
+
return URL.parse(value, cwd.href) ?? undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The local root as a `file:` URL; the option is set, so it is not undefined. */
|
|
49
|
+
function localRootOf(value: unknown, cwd: URL): URL {
|
|
50
|
+
const root = typeof value === "string" ? localRootUrlOf(value, cwd) : value;
|
|
51
|
+
if (root instanceof URL && root.protocol === FILE_PROTOCOL) {
|
|
52
|
+
return root;
|
|
53
|
+
}
|
|
54
|
+
const got = root === undefined ? "a string that is no valid URL" : kindOf(root);
|
|
55
|
+
throw optionInvalid("localRefRoot", LOCAL_ROOT_EXPECTED, got);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function remoteHostsOf(value: unknown): readonly string[] {
|
|
59
|
+
if (!Array.isArray(value)) {
|
|
60
|
+
throw optionInvalid("remoteRefHosts", "a list of hosts", kindOf(value));
|
|
61
|
+
}
|
|
62
|
+
const other = value.find((entry) => typeof entry !== "string");
|
|
63
|
+
if (other !== undefined) {
|
|
64
|
+
throw optionInvalid("remoteRefHosts", "a list of hosts", `a list with ${kindOf(other)}`);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The limits of the options; fails with `OPTION_INVALID` for a value of another type. */
|
|
70
|
+
export function refAccessOf(options: GeneratorOptions, cwd: URL): RefAccess {
|
|
71
|
+
const { localRefRoot, remoteRefHosts } = options;
|
|
72
|
+
return {
|
|
73
|
+
...(localRefRoot === undefined ? {} : { localRoot: localRootOf(localRefRoot, cwd) }),
|
|
74
|
+
...(remoteRefHosts === undefined ? {} : { remoteHosts: remoteHostsOf(remoteRefHosts) }),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
withEnumsRenamed,
|
|
7
7
|
} from "../application/transform/document/enum-names-given-back.js";
|
|
8
8
|
import { operationKeysWritten } from "../application/transform/document/operation-keys-written.js";
|
|
9
|
+
import {
|
|
10
|
+
unmatchedTemplateKeys,
|
|
11
|
+
withTemplatePathRefs,
|
|
12
|
+
} from "../application/transform/document/path-template-refs.js";
|
|
9
13
|
import { renameTakenAliases } from "../application/transform/document/root-type-aliases.js";
|
|
10
14
|
import { RunFooter, type RunFooterNames } from "../application/transform/footer/document-footer.js";
|
|
11
15
|
import { EnumNameClaims } from "../application/transform/footer/enum-name-claims.js";
|
|
@@ -144,6 +148,20 @@ function enumValuesChecked(
|
|
|
144
148
|
return checked.footer;
|
|
145
149
|
}
|
|
146
150
|
|
|
151
|
+
/** The document with its references into templated paths indexed by their templates. */
|
|
152
|
+
function withTemplateRefs(
|
|
153
|
+
{ body, footer, closing }: TransformedDocument,
|
|
154
|
+
keys: ReadonlyMap<string, string>,
|
|
155
|
+
): TransformedDocument {
|
|
156
|
+
return {
|
|
157
|
+
body: withTemplatePathRefs(body, keys),
|
|
158
|
+
footer: footer.map((entry) =>
|
|
159
|
+
isIrDeclaration(entry) ? (withTemplatePathRefs([entry], keys)[0] ?? entry) : entry,
|
|
160
|
+
),
|
|
161
|
+
closing: withTemplatePathRefs(closing, keys),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
147
165
|
/** @internal Root types, the footer, then the closing declarations (Phase 5, step 6). */
|
|
148
166
|
export function transformDocument(schema: OpenAPI3, ctx: RunContext): TransformedDocument {
|
|
149
167
|
const document = schema as DocumentObject;
|
|
@@ -160,11 +178,8 @@ export function transformDocument(schema: OpenAPI3, ctx: RunContext): Transforme
|
|
|
160
178
|
const rawClosing = documentClosing(document, context, rawFooter.some(isOperations));
|
|
161
179
|
const parts = { body: rawBody, footer: rawFooter, closing: rawClosing };
|
|
162
180
|
const { body, footer, closing } = withEnumNamesGivenBack(parts, names);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
footer,
|
|
166
|
-
closing,
|
|
167
|
-
};
|
|
181
|
+
const named = { body: renameTakenAliases(body, footer.filter(isIrDeclaration), closing), footer };
|
|
182
|
+
return withTemplateRefs({ ...named, closing }, unmatchedTemplateKeys(document.paths, context));
|
|
168
183
|
}
|
|
169
184
|
|
|
170
185
|
/**
|
|
@@ -29,6 +29,14 @@ export interface DirectoryCheck {
|
|
|
29
29
|
isDirectory(file: URL): boolean;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Whether a `file:` location is an existing file. A schema argument with a `*` is a glob pattern
|
|
34
|
+
* only when no file has that name.
|
|
35
|
+
*/
|
|
36
|
+
export interface FileCheck {
|
|
37
|
+
isFile(file: URL): boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
/**
|
|
33
41
|
* An output file that exists before its content is written: the claim created it, empty, when it
|
|
34
42
|
* was missing, so the file system has given it its own identity.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { irKeyword, irReference } from "../../domain/ir/constructors.js";
|
|
2
|
+
import type { IrMember, IrType } from "../../domain/ir/nodes.js";
|
|
3
|
+
import { HELPER_TYPES } from "./helper-types.js";
|
|
4
|
+
import type { FooterDeclarations } from "./schema/schema-context.js";
|
|
5
|
+
|
|
6
|
+
/** The footer part that declares the `PropertyValues` helper type. */
|
|
7
|
+
export type PropertyValuesFooter = Pick<FooterDeclarations, "requirePropertyValues">;
|
|
8
|
+
|
|
9
|
+
function memberValueTypes(member: IrMember): IrType[] {
|
|
10
|
+
if (member.kind === "indexSignature") {
|
|
11
|
+
return [member.type];
|
|
12
|
+
}
|
|
13
|
+
return member.isOptional ? [member.type, irKeyword("undefined")] : [member.type];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The value types a key of the type can have, read from its IR: the members of object types,
|
|
18
|
+
* through unions and intersections. A reference (a `$ref`, `Omit`, `WithRequired`) or a raw
|
|
19
|
+
* type is read with `PropertyValues`. Primitives, literals and arrays declare no keys.
|
|
20
|
+
*/
|
|
21
|
+
export function declaredValueTypes(type: IrType, footer: PropertyValuesFooter): IrType[] {
|
|
22
|
+
switch (type.kind) {
|
|
23
|
+
case "object":
|
|
24
|
+
return type.members.flatMap(memberValueTypes);
|
|
25
|
+
case "union":
|
|
26
|
+
case "intersection":
|
|
27
|
+
return type.types.flatMap((member) => declaredValueTypes(member, footer));
|
|
28
|
+
case "reference":
|
|
29
|
+
case "indexedAccess":
|
|
30
|
+
case "raw":
|
|
31
|
+
footer.requirePropertyValues();
|
|
32
|
+
return [irReference(HELPER_TYPES.propertyValues.name, [type])];
|
|
33
|
+
case "keyword":
|
|
34
|
+
case "stringLiteral":
|
|
35
|
+
case "numberLiteral":
|
|
36
|
+
case "booleanLiteral":
|
|
37
|
+
case "array":
|
|
38
|
+
case "tuple":
|
|
39
|
+
case "readonly":
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -9,13 +9,14 @@ import type {
|
|
|
9
9
|
} from "../../../domain/ir/nodes.js";
|
|
10
10
|
import type { ComponentsObject } from "../../../domain/openapi/components-object.js";
|
|
11
11
|
import type { DocumentObject } from "../../../domain/openapi/document-object.js";
|
|
12
|
-
import type { PathsObject } from "../../../domain/openapi/path-item-object.js";
|
|
12
|
+
import type { OperationEntry, PathsObject } from "../../../domain/openapi/path-item-object.js";
|
|
13
13
|
import { READ_WRITE_MARKER_TYPES } from "../helper-types.js";
|
|
14
14
|
import type { TransformContext, TransformOptions } from "../transform-context.js";
|
|
15
15
|
import { componentsType } from "./components.js";
|
|
16
16
|
import { type DeclaredTypeLookup, declaredTypeLookup } from "./declared-types.js";
|
|
17
17
|
import { defsRootType } from "./defs-root.js";
|
|
18
18
|
import { keptDeprecatedComponents } from "./kept-deprecated.js";
|
|
19
|
+
import { isIncludedOperation } from "./path-item.js";
|
|
19
20
|
import { pathMembers, pathsObjectType } from "./paths.js";
|
|
20
21
|
import { apiPathsEnumDeclaration } from "./paths-enum.js";
|
|
21
22
|
import { webhooksType } from "./webhooks.js";
|
|
@@ -210,7 +211,8 @@ export function documentClosing(
|
|
|
210
211
|
declarations.push(emptyAlias(OPERATIONS_NAME));
|
|
211
212
|
}
|
|
212
213
|
if (context.options.makePathsEnum && document.paths) {
|
|
213
|
-
|
|
214
|
+
const isIncluded = (operation: OperationEntry) => isIncludedOperation(operation, context);
|
|
215
|
+
declarations.push(apiPathsEnumDeclaration(document.paths, isIncluded));
|
|
214
216
|
}
|
|
215
217
|
return declarations;
|
|
216
218
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { IrMember, IrType } from "../../../domain/ir/nodes.js";
|
|
2
|
+
import type { Schema } from "../../../domain/openapi/schema-object.js";
|
|
3
|
+
import { transformSchema } from "../schema/schema-transform.js";
|
|
4
|
+
import type { TransformContext } from "../transform-context.js";
|
|
5
|
+
import { unionOf } from "../type-combinators.js";
|
|
6
|
+
import type { ResolvedEntry } from "./parameter-entries.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `exploded-query-objects` and index signatures. The keys of an exploded object are keys of
|
|
10
|
+
* `query`, next to the other parameters. TypeScript checks an index signature against every key
|
|
11
|
+
* of a value, so `{ page?: number } & { [key: string]: string }` admits no `page`, and no value
|
|
12
|
+
* with another parameter at all. An index signature of an exploded object therefore also takes
|
|
13
|
+
* the value types of the other keys of `query`: those keys carry their own values.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
function refOf(value: unknown): string | undefined {
|
|
17
|
+
const ref = (value as { $ref?: unknown } | undefined)?.$ref;
|
|
18
|
+
return typeof ref === "string" ? ref : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A schema `$ref` chain followed to its end, with the pointer where that schema is declared. */
|
|
22
|
+
function declaredSchema(schema: Schema | undefined, ref: string, context: TransformContext) {
|
|
23
|
+
let declared = { schema, path: ref };
|
|
24
|
+
const seen = new Set<string>();
|
|
25
|
+
for (let next = refOf(schema); next !== undefined && !seen.has(next); ) {
|
|
26
|
+
seen.add(next);
|
|
27
|
+
declared = { schema: context.refs.resolve(next) as Schema | undefined, path: next };
|
|
28
|
+
next = refOf(declared.schema);
|
|
29
|
+
}
|
|
30
|
+
return declared;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The type of an exploded parameter whose own type is a reference (its `$ref`, or the `$ref` of
|
|
35
|
+
* its schema), written out where its schema is declared: the index signature of a referenced type
|
|
36
|
+
* cannot be changed. The location is the one `components` types it at, so the enums it declares
|
|
37
|
+
* are the ones the reference names. Null when no `$ref` is involved.
|
|
38
|
+
*/
|
|
39
|
+
export function declaredObjectType(entry: ResolvedEntry, context: TransformContext): IrType | null {
|
|
40
|
+
const { original, resolved } = entry;
|
|
41
|
+
const schemaRef = refOf(resolved.schema);
|
|
42
|
+
if (schemaRef !== undefined) {
|
|
43
|
+
const { schema, path } = declaredSchema(resolved.schema, schemaRef, context);
|
|
44
|
+
return transformSchema(schema, { path, schema }, context);
|
|
45
|
+
}
|
|
46
|
+
const parameterRef = refOf(original);
|
|
47
|
+
return parameterRef === undefined
|
|
48
|
+
? null
|
|
49
|
+
: transformSchema(resolved.schema, { path: parameterRef, schema: resolved }, context);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isIndexSignature(member: IrMember): boolean {
|
|
53
|
+
return member.kind === "indexSignature";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Whether the type has an index signature in its objects, through unions and intersections. */
|
|
57
|
+
export function hasIndexSignature(type: IrType): boolean {
|
|
58
|
+
switch (type.kind) {
|
|
59
|
+
case "object":
|
|
60
|
+
return type.members.some(isIndexSignature);
|
|
61
|
+
case "union":
|
|
62
|
+
case "intersection":
|
|
63
|
+
return type.types.some(hasIndexSignature);
|
|
64
|
+
default:
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function widenedMember(member: IrMember, values: readonly IrType[]): IrMember {
|
|
70
|
+
// `unknown` takes every value already.
|
|
71
|
+
const isUnknown = member.type.kind === "keyword" && member.type.keyword === "unknown";
|
|
72
|
+
return isIndexSignature(member) && !isUnknown
|
|
73
|
+
? { ...member, type: unionOf([member.type, ...values]) }
|
|
74
|
+
: member;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The type with the values added to each index signature of its objects. */
|
|
78
|
+
export function withIndexValues(type: IrType, values: readonly IrType[]): IrType {
|
|
79
|
+
switch (type.kind) {
|
|
80
|
+
case "object":
|
|
81
|
+
return { ...type, members: type.members.map((member) => widenedMember(member, values)) };
|
|
82
|
+
case "union":
|
|
83
|
+
case "intersection":
|
|
84
|
+
return { ...type, types: type.types.map((member) => withIndexValues(member, values)) };
|
|
85
|
+
default:
|
|
86
|
+
return type;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -2,6 +2,8 @@ import { irObject } from "../../../domain/ir/constructors.js";
|
|
|
2
2
|
import type { IrType } from "../../../domain/ir/nodes.js";
|
|
3
3
|
import type { HeaderObject } from "../../../domain/openapi/header-object.js";
|
|
4
4
|
import { escapePointerSegment } from "../../../domain/openapi/json-pointer.js";
|
|
5
|
+
import type { ContentMap } from "../../../domain/openapi/media-type-object.js";
|
|
6
|
+
import { FIX } from "../../../domain/output-fixes.js";
|
|
5
7
|
import { documentEntries } from "../document-entries.js";
|
|
6
8
|
import type { SchemaContext, SchemaLocation } from "../schema/schema-context.js";
|
|
7
9
|
import { transformSchema } from "../schema/schema-transform.js";
|
|
@@ -10,8 +12,10 @@ import { documentMember } from "./document-member.js";
|
|
|
10
12
|
import { contentEntryType } from "./media-type.js";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
* A header's type (OpenAPI 3.1 §4.8.21): its schema, else
|
|
14
|
-
*
|
|
15
|
+
* A header's type (OpenAPI 3.1 §4.8.21): its schema, else the type of its `content`, else
|
|
16
|
+
* `unknown`. `content` holds one media type whose schema describes the value; the original typed
|
|
17
|
+
* it as an object with one property per media type, which the `parameter-header-content` fix
|
|
18
|
+
* replaces by the media type's type, as for a parameter.
|
|
15
19
|
*/
|
|
16
20
|
export function headerType(
|
|
17
21
|
header: HeaderObject,
|
|
@@ -24,11 +28,27 @@ export function headerType(
|
|
|
24
28
|
if (!header.content) {
|
|
25
29
|
return UNKNOWN_TYPE;
|
|
26
30
|
}
|
|
31
|
+
if (context.options.fixes.has(FIX.PARAMETER_HEADER_CONTENT)) {
|
|
32
|
+
// The first media type in document order, as for a parameter: `--alphabetize` sorts only
|
|
33
|
+
// the members it writes.
|
|
34
|
+
const first = Object.entries(header.content)[0];
|
|
35
|
+
return first === undefined ? UNKNOWN_TYPE : mediaType(...first, location, context);
|
|
36
|
+
}
|
|
27
37
|
const members = documentEntries(header.content, context.options).map(([contentType, entry]) => {
|
|
28
|
-
|
|
29
|
-
const path = `${location.path ?? "#"}/${escapePointerSegment(contentType)}`;
|
|
30
|
-
const type = contentEntryType(entry, { ...location, path }, context);
|
|
38
|
+
const type = mediaType(contentType, entry, location, context);
|
|
31
39
|
return documentMember(contentType, type, entry, { isReadonly: context.options.immutable });
|
|
32
40
|
});
|
|
33
41
|
return irObject(members);
|
|
34
42
|
}
|
|
43
|
+
|
|
44
|
+
/** The type of one media type of a header, at the path the original gave it. */
|
|
45
|
+
function mediaType(
|
|
46
|
+
contentType: string,
|
|
47
|
+
entry: ContentMap[string],
|
|
48
|
+
location: SchemaLocation,
|
|
49
|
+
context: SchemaContext,
|
|
50
|
+
): IrType {
|
|
51
|
+
// Unlike other content maps, the original joins this path by hand: a missing path is `#`.
|
|
52
|
+
const path = `${location.path ?? "#"}/${escapePointerSegment(contentType)}`;
|
|
53
|
+
return contentEntryType(entry, { ...location, path }, context);
|
|
54
|
+
}
|
|
@@ -13,7 +13,7 @@ import { contentEntryType } from "./media-type.js";
|
|
|
13
13
|
/**
|
|
14
14
|
* A parameter typed by the schema of its one `content` media type. An `in: querystring` parameter
|
|
15
15
|
* (OpenAPI 3.2) is the whole query string (ADR-020); without a media type it is `string`, the
|
|
16
|
-
* query string as text. With the `parameter-content` fix, any other parameter with `content`
|
|
16
|
+
* query string as text. With the `parameter-header-content` fix, any other parameter with `content`
|
|
17
17
|
* (OpenAPI 3.0 and later, for a JSON value in a query parameter) is typed the same way.
|
|
18
18
|
*/
|
|
19
19
|
function contentParameterType(
|
|
@@ -34,14 +34,14 @@ function isTypedByContent(parameter: ParameterObject, context: SchemaContext): b
|
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
36
36
|
const hasContent = parameter.schema === undefined && parameter.content !== undefined;
|
|
37
|
-
return hasContent && context.options.fixes.has(FIX.
|
|
37
|
+
return hasContent && context.options.fixes.has(FIX.PARAMETER_HEADER_CONTENT);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* A parameter's type (OpenAPI 3.2 §4.8.12). Without a schema it is `string`, not `unknown`:
|
|
42
42
|
* query, path, header and cookie values all arrive as text. `content` is read for
|
|
43
|
-
* `in: querystring`, and with the `parameter-content` fix for every parameter; without
|
|
44
|
-
* other parameters with `content` keep the original's `string`.
|
|
43
|
+
* `in: querystring`, and with the `parameter-header-content` fix for every parameter; without
|
|
44
|
+
* the fix other parameters with `content` keep the original's `string`.
|
|
45
45
|
*/
|
|
46
46
|
export function parameterType(
|
|
47
47
|
parameter: ParameterObject,
|
|
@@ -16,10 +16,12 @@ import {
|
|
|
16
16
|
QUERYSTRING_LOCATION,
|
|
17
17
|
} from "../../../domain/openapi/parameter-object.js";
|
|
18
18
|
import { FIX } from "../../../domain/output-fixes.js";
|
|
19
|
+
import { declaredValueTypes } from "../declared-value-types.js";
|
|
19
20
|
import type { SchemaLocation } from "../schema/schema-context.js";
|
|
20
21
|
import { withSchemaComment } from "../schema-comment.js";
|
|
21
22
|
import type { TransformContext } from "../transform-context.js";
|
|
22
23
|
import { isExplodedQueryObject } from "./exploded-query.js";
|
|
24
|
+
import { declaredObjectType, hasIndexSignature, withIndexValues } from "./exploded-query-values.js";
|
|
23
25
|
import { parameterType } from "./parameter.js";
|
|
24
26
|
import {
|
|
25
27
|
type ParameterEntry,
|
|
@@ -126,17 +128,50 @@ function isExplodedEntry(entry: ResolvedEntry, context: TransformContext): boole
|
|
|
126
128
|
);
|
|
127
129
|
}
|
|
128
130
|
|
|
131
|
+
/** An exploded object parameter with its type, typed in document order. */
|
|
132
|
+
interface ExplodedEntry {
|
|
133
|
+
readonly entry: ResolvedEntry;
|
|
134
|
+
readonly type: IrType;
|
|
135
|
+
}
|
|
136
|
+
|
|
129
137
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
138
|
+
* The object type of an exploded parameter, written out when it is a reference to a type with an
|
|
139
|
+
* index signature and `query` has other keys, which that signature must then admit. Without
|
|
140
|
+
* other keys the reference is kept.
|
|
132
141
|
*/
|
|
133
|
-
function
|
|
134
|
-
entry:
|
|
135
|
-
|
|
142
|
+
function explodedObjectType(
|
|
143
|
+
{ entry, type }: ExplodedEntry,
|
|
144
|
+
hasOtherKeys: boolean,
|
|
136
145
|
context: TransformContext,
|
|
137
146
|
): IrType {
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
if (!hasOtherKeys) {
|
|
148
|
+
return type;
|
|
149
|
+
}
|
|
150
|
+
const declared = declaredObjectType(entry, context);
|
|
151
|
+
return declared !== null && hasIndexSignature(declared) ? declared : type;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The exploded object parameters' parts of `query`: their object types, each index signature
|
|
156
|
+
* widened by the value types of the other keys (see `exploded-query-values.ts`). A parameter that
|
|
157
|
+
* may be left out sends none of its keys then, so they are optional: `Partial<…>`.
|
|
158
|
+
*/
|
|
159
|
+
function explodedParts(
|
|
160
|
+
exploded: readonly ExplodedEntry[],
|
|
161
|
+
members: readonly IrPropertySignature[],
|
|
162
|
+
context: TransformContext,
|
|
163
|
+
): IrType[] {
|
|
164
|
+
const hasOtherKeys = members.length > 0 || exploded.length > 1;
|
|
165
|
+
const types = exploded.map((part) => explodedObjectType(part, hasOtherKeys, context));
|
|
166
|
+
const others = (index: number) => [
|
|
167
|
+
...(members.length > 0 ? [irObject(members)] : []),
|
|
168
|
+
...types.filter((_type, other) => other !== index),
|
|
169
|
+
];
|
|
170
|
+
return types.map((type, index) => {
|
|
171
|
+
const values = others(index).flatMap((other) => declaredValueTypes(other, context.footer));
|
|
172
|
+
const widened = withIndexValues(type, values);
|
|
173
|
+
return exploded[index]?.entry.resolved.required ? widened : irReference("Partial", [widened]);
|
|
174
|
+
});
|
|
140
175
|
}
|
|
141
176
|
|
|
142
177
|
/** A location's parameters as members, and the object types of its exploded parameters. */
|
|
@@ -153,11 +188,11 @@ function locationParts(
|
|
|
153
188
|
context: TransformContext,
|
|
154
189
|
): LocationParts {
|
|
155
190
|
const members: IrPropertySignature[] = [];
|
|
156
|
-
const exploded:
|
|
191
|
+
const exploded: ExplodedEntry[] = [];
|
|
157
192
|
let isOptional = true;
|
|
158
193
|
for (const entry of entries) {
|
|
159
194
|
if (isExplodedEntry(entry, context)) {
|
|
160
|
-
exploded.push(
|
|
195
|
+
exploded.push({ entry, type: entryType(entry, location, context) });
|
|
161
196
|
isOptional &&= !entry.resolved.required;
|
|
162
197
|
continue;
|
|
163
198
|
}
|
|
@@ -165,7 +200,7 @@ function locationParts(
|
|
|
165
200
|
members.push(member);
|
|
166
201
|
isOptional &&= member.isOptional;
|
|
167
202
|
}
|
|
168
|
-
return { members, exploded, isOptional };
|
|
203
|
+
return { members, exploded: explodedParts(exploded, members, context), isOptional };
|
|
169
204
|
}
|
|
170
205
|
|
|
171
206
|
/** The members' object, intersected with the exploded parameters' types; `never` for none. */
|
|
@@ -29,7 +29,7 @@ export function operationKeyOf(operationId: string): string {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Whether the method has an operation, not removed by `--exclude-deprecated`. */
|
|
32
|
-
function
|
|
32
|
+
export function isIncludedOperation(
|
|
33
33
|
operation: OperationEntry | undefined,
|
|
34
34
|
context: TransformContext,
|
|
35
35
|
): operation is OperationEntry {
|
|
@@ -94,7 +94,7 @@ function methodMember(
|
|
|
94
94
|
context: TransformContext,
|
|
95
95
|
): IrPropertySignature {
|
|
96
96
|
const operation = pathItem[method];
|
|
97
|
-
if (!
|
|
97
|
+
if (!isIncludedOperation(operation, context)) {
|
|
98
98
|
const isReadonly = context.options.immutable;
|
|
99
99
|
return irProperty(toPropertyName(method), irKeyword("never"), { isOptional: true, isReadonly });
|
|
100
100
|
}
|
|
@@ -113,7 +113,7 @@ function additionalOperationsMember(
|
|
|
113
113
|
): IrPropertySignature | null {
|
|
114
114
|
const entries = documentEntries(pathItem.additionalOperations ?? {}, context.options);
|
|
115
115
|
const members = entries
|
|
116
|
-
.filter(([, operation]) =>
|
|
116
|
+
.filter(([, operation]) => isIncludedOperation(operation, context))
|
|
117
117
|
.map(([method, operation]) => {
|
|
118
118
|
const path = appendPointer(location.path, [ADDITIONAL_OPERATIONS_KEY, method]);
|
|
119
119
|
return operationMember({ name: method, operation, path }, pathItem, location, context);
|