openapi-typesafe 1.0.0-rc.4 → 1.0.0-rc.6
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 +214 -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-document-names.cjs +50 -0
- package/dist/adapters/redocly-loader/query-document-names.cjs.map +1 -0
- package/dist/adapters/redocly-loader/query-document-names.mjs +48 -0
- package/dist/adapters/redocly-loader/query-document-names.mjs.map +1 -0
- package/dist/adapters/redocly-loader/query-named-components.cjs +129 -0
- package/dist/adapters/redocly-loader/query-named-components.cjs.map +1 -0
- package/dist/adapters/redocly-loader/query-named-components.mjs +126 -0
- package/dist/adapters/redocly-loader/query-named-components.mjs.map +1 -0
- package/dist/adapters/redocly-loader/redocly-config-source.cjs +3 -0
- package/dist/adapters/redocly-loader/redocly-config-source.cjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-config-source.mjs +4 -1
- package/dist/adapters/redocly-loader/redocly-config-source.mjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +39 -18
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +40 -19
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.cjs +25 -4
- package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.mjs +26 -5
- 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 +20 -3
- package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-fetch.mjs +20 -4
- 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/cwd-option.cjs +58 -0
- package/dist/api/cwd-option.cjs.map +1 -0
- package/dist/api/cwd-option.mjs +52 -0
- package/dist/api/cwd-option.mjs.map +1 -0
- package/dist/api/default-ports.cjs +15 -7
- package/dist/api/default-ports.cjs.map +1 -1
- package/dist/api/default-ports.mjs +14 -8
- package/dist/api/default-ports.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 +6 -26
- package/dist/api/document-run.cjs.map +1 -1
- package/dist/api/document-run.mjs +5 -21
- package/dist/api/document-run.mjs.map +1 -1
- package/dist/api/legacy-utils.cjs +1 -1
- package/dist/api/legacy-utils.cjs.map +1 -1
- package/dist/api/legacy-utils.mjs +1 -1
- package/dist/api/legacy-utils.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/option-errors.cjs +24 -0
- package/dist/api/option-errors.cjs.map +1 -0
- package/dist/api/option-errors.mjs +21 -0
- package/dist/api/option-errors.mjs.map +1 -0
- package/dist/api/ref-limit-options.cjs +58 -0
- package/dist/api/ref-limit-options.cjs.map +1 -0
- package/dist/api/ref-limit-options.mjs +52 -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 +80 -0
- package/dist/application/transform/document/path-template-refs.cjs.map +1 -0
- package/dist/application/transform/document/path-template-refs.mjs +77 -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 +63 -64
- package/dist/application/transform/ref-pointer/generated-shape.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/generated-shape.mjs +57 -53
- 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 +69 -14
- package/dist/application/transform/ref-pointer/original-index.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/original-index.mjs +71 -16
- 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 +57 -0
- package/dist/application/transform/ref-pointer/property-shape.cjs.map +1 -0
- package/dist/application/transform/ref-pointer/property-shape.mjs +49 -0
- package/dist/application/transform/ref-pointer/property-shape.mjs.map +1 -0
- package/dist/application/transform/ref-pointer/typed-walk.cjs +90 -22
- package/dist/application/transform/ref-pointer/typed-walk.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/typed-walk.mjs +87 -19
- 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 +10 -17
- package/dist/cli/api-settings.cjs.map +1 -1
- package/dist/cli/api-settings.mjs +10 -17
- package/dist/cli/api-settings.mjs.map +1 -1
- package/dist/cli/api-writes.cjs +7 -1
- package/dist/cli/api-writes.cjs.map +1 -1
- package/dist/cli/api-writes.mjs +7 -1
- package/dist/cli/api-writes.mjs.map +1 -1
- package/dist/cli/cli-arguments.cjs +32 -74
- package/dist/cli/cli-arguments.cjs.map +1 -1
- package/dist/cli/cli-arguments.mjs +28 -70
- package/dist/cli/cli-arguments.mjs.map +1 -1
- package/dist/cli/cli-errors.cjs +18 -0
- package/dist/cli/cli-errors.cjs.map +1 -1
- package/dist/cli/cli-errors.mjs +18 -1
- package/dist/cli/cli-errors.mjs.map +1 -1
- package/dist/cli/cli-flag-names.cjs +77 -0
- package/dist/cli/cli-flag-names.cjs.map +1 -0
- package/dist/cli/cli-flag-names.mjs +64 -0
- package/dist/cli/cli-flag-names.mjs.map +1 -0
- package/dist/cli/cli-output.cjs +13 -6
- package/dist/cli/cli-output.cjs.map +1 -1
- package/dist/cli/cli-output.mjs +14 -7
- package/dist/cli/cli-output.mjs.map +1 -1
- package/dist/cli/main.cjs +11 -5
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.mjs +11 -5
- package/dist/cli/main.mjs.map +1 -1
- package/dist/cli/redocly-apis.cjs +33 -12
- package/dist/cli/redocly-apis.cjs.map +1 -1
- package/dist/cli/redocly-apis.mjs +35 -14
- 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/cli/written-flags.cjs +83 -0
- package/dist/cli/written-flags.cjs.map +1 -0
- package/dist/cli/written-flags.mjs +77 -0
- package/dist/cli/written-flags.mjs.map +1 -0
- 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 +3 -0
- package/dist/domain/errors.cjs.map +1 -1
- package/dist/domain/errors.mjs +3 -0
- package/dist/domain/errors.mjs.map +1 -1
- package/dist/domain/location-text.cjs +15 -0
- package/dist/domain/location-text.cjs.map +1 -0
- package/dist/domain/location-text.mjs +13 -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 +29 -18
- package/dist/index.d.mts +29 -18
- package/dist/index.d.ts +29 -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-document-names.ts +79 -0
- package/src/adapters/redocly-loader/query-named-components.ts +200 -0
- package/src/adapters/redocly-loader/redocly-config-source.ts +11 -1
- package/src/adapters/redocly-loader/redocly-schema-loader.ts +63 -17
- package/src/adapters/redocly-loader/ref-access.ts +32 -5
- package/src/adapters/redocly-loader/ref-holders.ts +75 -14
- package/src/adapters/redocly-loader/remote-fetch.ts +37 -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/cwd-option.ts +76 -0
- package/src/api/default-ports.ts +33 -10
- package/src/api/document-refs.ts +29 -1
- package/src/api/document-run.ts +7 -32
- package/src/api/legacy-utils.ts +1 -1
- package/src/api/openapi-ts.ts +20 -4
- package/src/api/option-errors.ts +22 -0
- package/src/api/ref-limit-options.ts +69 -0
- package/src/api/transform-schema.ts +20 -5
- package/src/application/ports/file-system.ts +8 -0
- package/src/application/ports/redocly-config-source.ts +5 -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 +127 -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 +115 -83
- 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 +106 -20
- 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 +106 -0
- package/src/application/transform/ref-pointer/typed-walk.ts +143 -29
- 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/api-writes.ts +11 -3
- package/src/cli/cli-arguments.ts +57 -92
- package/src/cli/cli-dependencies.ts +7 -2
- package/src/cli/cli-errors.ts +23 -0
- package/src/cli/cli-flag-names.ts +99 -0
- package/src/cli/cli-output.ts +31 -16
- package/src/cli/main.ts +12 -5
- package/src/cli/redocly-apis.ts +56 -20
- package/src/cli/ref-limits.ts +21 -2
- package/src/cli/run-cli.ts +39 -7
- package/src/cli/written-flags.ts +131 -0
- package/src/domain/cause-secrets.ts +59 -25
- package/src/domain/errors.ts +3 -0
- package/src/domain/location-text.ts +27 -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
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendPointer,
|
|
3
|
+
canonicalRef,
|
|
4
|
+
parseRefPointer,
|
|
5
|
+
ROOT_POINTER,
|
|
6
|
+
} from "../../domain/openapi/json-pointer.js";
|
|
7
|
+
import { type DocumentNames, queryDocumentNames } from "./query-document-names.js";
|
|
8
|
+
import { findMappings, findRefHolders } from "./ref-holders.js";
|
|
9
|
+
import type { LoadedDocument } from "./unsupported-keys.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Redocly 1.x bundles an external `$ref` into a component named after the last segment of the
|
|
13
|
+
* document's URL, without its extension, when the pointer gives no free name (a `$ref` to a whole
|
|
14
|
+
* document, or two targets with the same pointer). The query stays in that name, and a query holds
|
|
15
|
+
* API keys and signatures, so `https://host/schemas/pet?token=…` became the component
|
|
16
|
+
* `pet?token=…` in the generated file and in messages (threat model T4b). This renames such a
|
|
17
|
+
* component after the URL without its query: `pet`, with a `-2` suffix when that name is taken.
|
|
18
|
+
* Each component is matched to its document by its node, since two URLs can give one name.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type Components = Record<string, Record<string, unknown>>;
|
|
22
|
+
|
|
23
|
+
/** The keys of each `components` group before bundling: the bundle edits the document in place. */
|
|
24
|
+
export type ComponentKeys = ReadonlyMap<string, ReadonlySet<string>>;
|
|
25
|
+
|
|
26
|
+
/** Old and new name of each renamed component, per `components` group. */
|
|
27
|
+
type Renames = Map<string, Map<string, string>>;
|
|
28
|
+
|
|
29
|
+
const COMPONENTS_KEY = "components";
|
|
30
|
+
/** What Redocly puts between the base name and the rest of a component name. */
|
|
31
|
+
const NAME_SUFFIX_STARTS = ["_", "-"];
|
|
32
|
+
const FIRST_SERIAL = 2;
|
|
33
|
+
/**
|
|
34
|
+
* The serial Redocly adds when a name is taken (`pet-2`). Two URLs that differ only in the query
|
|
35
|
+
* took one name; without the query they may not, so the serial is chosen again (`freeName`).
|
|
36
|
+
*/
|
|
37
|
+
const REDOCLY_SERIAL_RE = /-\d+$/;
|
|
38
|
+
|
|
39
|
+
function isContainer(value: unknown): value is Record<string, unknown> {
|
|
40
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function componentsOf(document: unknown): Components {
|
|
44
|
+
const components = isContainer(document) ? document[COMPONENTS_KEY] : undefined;
|
|
45
|
+
if (!isContainer(components)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
const groups: Components = {};
|
|
49
|
+
for (const [group, entries] of Object.entries(components)) {
|
|
50
|
+
if (isContainer(entries)) {
|
|
51
|
+
groups[group] = entries;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return groups;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The keys of every `components` group of a document. */
|
|
58
|
+
export function componentKeysOf(document: unknown): ComponentKeys {
|
|
59
|
+
const keys = new Map<string, ReadonlySet<string>>();
|
|
60
|
+
for (const [group, entries] of Object.entries(componentsOf(document))) {
|
|
61
|
+
keys.set(group, new Set(Object.keys(entries)));
|
|
62
|
+
}
|
|
63
|
+
return keys;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The name a component of a document with a query gets; undefined for any other component. */
|
|
67
|
+
function queryFreeName(key: string, names: DocumentNames | undefined): string | undefined {
|
|
68
|
+
if (names === undefined) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const { named, clean } = names;
|
|
72
|
+
const hasBaseName =
|
|
73
|
+
key === named || NAME_SUFFIX_STARTS.some((start) => key.startsWith(`${named}${start}`));
|
|
74
|
+
return hasBaseName
|
|
75
|
+
? `${clean}${key.slice(named.length).replace(REDOCLY_SERIAL_RE, "")}`
|
|
76
|
+
: undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function freeName(name: string, taken: ReadonlySet<string>): string {
|
|
80
|
+
let free = name;
|
|
81
|
+
for (let serial = FIRST_SERIAL; taken.has(free); serial += 1) {
|
|
82
|
+
free = `${name}-${serial}`;
|
|
83
|
+
}
|
|
84
|
+
return free;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Rename the bundled components of one group in place, keeping their order. */
|
|
88
|
+
function renameGroup(
|
|
89
|
+
entries: Record<string, unknown>,
|
|
90
|
+
keysBefore: ReadonlySet<string>,
|
|
91
|
+
documentNames: ReadonlyMap<object, DocumentNames>,
|
|
92
|
+
): Map<string, string> {
|
|
93
|
+
const renames = new Map<string, string>();
|
|
94
|
+
const taken = new Set(Object.keys(entries));
|
|
95
|
+
for (const key of Object.keys(entries)) {
|
|
96
|
+
const node = entries[key];
|
|
97
|
+
const names = typeof node === "object" && node !== null ? documentNames.get(node) : undefined;
|
|
98
|
+
const name = keysBefore.has(key) ? undefined : queryFreeName(key, names);
|
|
99
|
+
if (name !== undefined) {
|
|
100
|
+
taken.delete(key);
|
|
101
|
+
const free = freeName(name, taken);
|
|
102
|
+
taken.add(free);
|
|
103
|
+
renames.set(key, free);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
107
|
+
delete entries[key];
|
|
108
|
+
entries[renames.get(key) ?? key] = value;
|
|
109
|
+
}
|
|
110
|
+
return renames;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Rewrites pointers and messages that name a renamed component. */
|
|
114
|
+
export interface ComponentRenaming {
|
|
115
|
+
/** The pointer with a renamed component's new name; any other text as it is. */
|
|
116
|
+
readonly pointer: (pointer: string) => string;
|
|
117
|
+
/** The text with each renamed component's old name replaced by its new name. */
|
|
118
|
+
readonly text: (text: string) => string;
|
|
119
|
+
/**
|
|
120
|
+
* Whether the problem is Redocly's report of two components with one name that the new names
|
|
121
|
+
* tell apart: it renamed `new-api` to `new-api-2`, and the documents are now `pet` and `owner`.
|
|
122
|
+
*/
|
|
123
|
+
readonly isResolvedCollision: (message: string) => boolean;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The warning Redocly 1.34 gives when it adds a serial to a component name. */
|
|
127
|
+
const COLLISION_RE =
|
|
128
|
+
/^Two schemas are referenced with the same name but different content\. Renamed (.+) to (.+)\.$/;
|
|
129
|
+
const SERIAL_RE = /^-\d+$/;
|
|
130
|
+
|
|
131
|
+
function resolvesCollision(message: string, renames: Renames): boolean {
|
|
132
|
+
const [, first, second] = COLLISION_RE.exec(message) ?? [];
|
|
133
|
+
if (first === undefined || second === undefined) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const renamed = new Map([...renames.values()].flatMap((group) => [...group]));
|
|
137
|
+
const [newFirst, newSecond] = [renamed.get(first) ?? first, renamed.get(second) ?? second];
|
|
138
|
+
return !(newSecond.startsWith(newFirst) && SERIAL_RE.test(newSecond.slice(newFirst.length)));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function renamedPointer(pointer: string, renames: Renames): string {
|
|
142
|
+
if (!pointer.startsWith(`${ROOT_POINTER}/`)) {
|
|
143
|
+
return pointer;
|
|
144
|
+
}
|
|
145
|
+
const [components, group = "", name = "", ...rest] = parseRefPointer(canonicalRef(pointer));
|
|
146
|
+
const renamed = components === COMPONENTS_KEY ? renames.get(group)?.get(name) : undefined;
|
|
147
|
+
return renamed === undefined
|
|
148
|
+
? pointer
|
|
149
|
+
: appendPointer(ROOT_POINTER, [COMPONENTS_KEY, group, renamed, ...rest]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function renamedText(text: string, renames: Renames): string {
|
|
153
|
+
const pairs = [...renames.values()].flatMap((group) => [...group]);
|
|
154
|
+
// The longest old name first, so a name that starts another one does not split it.
|
|
155
|
+
pairs.sort(([a], [b]) => b.length - a.length);
|
|
156
|
+
return pairs.reduce((shown, [old, renamed]) => shown.replaceAll(old, renamed), text);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function rewriteRefs(document: unknown, renaming: ComponentRenaming): void {
|
|
160
|
+
for (const { node } of findRefHolders(document)) {
|
|
161
|
+
node.$ref = renaming.pointer(node.$ref);
|
|
162
|
+
}
|
|
163
|
+
for (const mapping of findMappings(document)) {
|
|
164
|
+
for (const [name, value] of Object.entries(mapping)) {
|
|
165
|
+
if (typeof value === "string") {
|
|
166
|
+
mapping[name] = renaming.pointer(value);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Rename, in place, the components the bundle named after a URL with a query, and every local
|
|
174
|
+
* `$ref` and `mapping` value that names them. `documents` are the documents the load read.
|
|
175
|
+
*/
|
|
176
|
+
export function renameQueryNamedComponents(
|
|
177
|
+
bundled: unknown,
|
|
178
|
+
keysBefore: ComponentKeys,
|
|
179
|
+
documents: readonly LoadedDocument[],
|
|
180
|
+
): ComponentRenaming {
|
|
181
|
+
const documentNames = queryDocumentNames(documents);
|
|
182
|
+
const renames: Renames = new Map();
|
|
183
|
+
if (documentNames.size > 0) {
|
|
184
|
+
for (const [group, entries] of Object.entries(componentsOf(bundled))) {
|
|
185
|
+
const groupRenames = renameGroup(entries, keysBefore.get(group) ?? new Set(), documentNames);
|
|
186
|
+
if (groupRenames.size > 0) {
|
|
187
|
+
renames.set(group, groupRenames);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const renaming: ComponentRenaming = {
|
|
192
|
+
pointer: (pointer) => renamedPointer(pointer, renames),
|
|
193
|
+
text: (text) => renamedText(text, renames),
|
|
194
|
+
isResolvedCollision: (message) => resolvesCollision(message, renames),
|
|
195
|
+
};
|
|
196
|
+
if (renames.size > 0) {
|
|
197
|
+
rewriteRefs(bundled, renaming);
|
|
198
|
+
}
|
|
199
|
+
return renaming;
|
|
200
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createConfig,
|
|
5
|
+
findConfig,
|
|
6
|
+
getMergedConfig,
|
|
7
|
+
IGNORE_FILE,
|
|
8
|
+
loadConfig,
|
|
9
|
+
} from "@redocly/openapi-core";
|
|
4
10
|
import type {
|
|
5
11
|
RedoclyConfig,
|
|
6
12
|
RedoclyConfigSource,
|
|
@@ -70,6 +76,10 @@ class RedoclyConfigFiles implements RedoclyConfigSource {
|
|
|
70
76
|
);
|
|
71
77
|
return loadedConfig(create, undefined);
|
|
72
78
|
}
|
|
79
|
+
|
|
80
|
+
forApi(config: RedoclyConfig, apiName: string): RedoclyConfig {
|
|
81
|
+
return getMergedConfig(config, apiName);
|
|
82
|
+
}
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
/** Find and load `redocly.yaml` relative to the directory `cwd`. */
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type BaseResolver,
|
|
4
4
|
bundle,
|
|
5
5
|
type Document,
|
|
6
|
+
type LocationObject,
|
|
6
7
|
lintDocument,
|
|
7
8
|
type NormalizedProblem,
|
|
8
9
|
type Config as RedoclyConfig,
|
|
@@ -14,16 +15,27 @@ import type {
|
|
|
14
15
|
SchemaLoadRequest,
|
|
15
16
|
} from "../../application/ports/schema-loader.js";
|
|
16
17
|
import { withStackOverflowReportedAsync } from "../../application/stack-overflow.js";
|
|
18
|
+
import { redactUrlSecrets } from "../../domain/url-secrets.js";
|
|
17
19
|
import { WARNING_CODES, warningText } from "../../domain/warnings.js";
|
|
18
20
|
import type { OpenAPI3 } from "../../types.js";
|
|
19
21
|
import { removeBundledMappingCopies, schemaKeysOf } from "./bundled-mapping-copies.js";
|
|
20
22
|
import { repairBundledRefs } from "./bundled-ref-repair.js";
|
|
21
23
|
import { loadedDocuments, localFilesRead } from "./loaded-documents.js";
|
|
22
24
|
import { findMalformedRefs, malformedRefsError } from "./malformed-refs.js";
|
|
25
|
+
import { pointMappingNamesAtSchemas } from "./mapping-schema-names.js";
|
|
26
|
+
import {
|
|
27
|
+
type ComponentRenaming,
|
|
28
|
+
componentKeysOf,
|
|
29
|
+
renameQueryNamedComponents,
|
|
30
|
+
} from "./query-named-components.js";
|
|
23
31
|
import { RefAccessResolver } from "./ref-access.js";
|
|
24
32
|
import { checkRefsResolve } from "./ref-resolution-check.js";
|
|
25
33
|
import { REF_CYCLE_MESSAGE } from "./ref-resolution-steps.js";
|
|
26
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
RedirectedUrls,
|
|
36
|
+
type RemoteDocumentLimits,
|
|
37
|
+
resolveConfigWithAccess,
|
|
38
|
+
} from "./remote-fetch.js";
|
|
27
39
|
import {
|
|
28
40
|
loadFailedError,
|
|
29
41
|
refCycleError,
|
|
@@ -160,6 +172,7 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
160
172
|
const document = await parseSchema(request.source, { absoluteRef, resolver });
|
|
161
173
|
logger.debug("Parsed schema", "redoc", now() - parseStartedAt);
|
|
162
174
|
assertOpenAPI3(document.parsed);
|
|
175
|
+
pointMappingNamesAtSchemas(document.parsed);
|
|
163
176
|
const bundled = await this.#lintAndBundle(document, resolver, request.shouldLint);
|
|
164
177
|
const isRootAFile = request.source instanceof URL && request.source.protocol === "file:";
|
|
165
178
|
const localFiles = await localFilesRead(document, resolver, isRootAFile);
|
|
@@ -168,16 +181,21 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
168
181
|
|
|
169
182
|
#resolver(request: SchemaLoadRequest): RefAccessResolver {
|
|
170
183
|
const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;
|
|
184
|
+
// One record per load, as the resolver and its cache are.
|
|
185
|
+
const redirects = new RedirectedUrls();
|
|
171
186
|
const config = resolveConfigWithAccess(redoc.resolve, {
|
|
172
187
|
timeoutMs: remoteRefTimeoutMs,
|
|
173
188
|
access: request.refAccess,
|
|
189
|
+
rootUrl: request.source instanceof URL ? request.source.href : undefined,
|
|
174
190
|
maxParallelFetches: remoteDocumentLimits.maxParallelFetches,
|
|
175
191
|
bodyLimits: remoteDocumentLimits,
|
|
192
|
+
redirects,
|
|
176
193
|
});
|
|
177
194
|
return new RefAccessResolver(config, {
|
|
178
195
|
access: request.refAccess,
|
|
179
196
|
maxRemoteDocuments: remoteDocumentLimits.maxDocuments,
|
|
180
197
|
maxRemoteNodes: remoteDocumentLimits.maxNodes,
|
|
198
|
+
redirects,
|
|
181
199
|
});
|
|
182
200
|
}
|
|
183
201
|
|
|
@@ -202,13 +220,14 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
202
220
|
config: this.#options.redoc.styleguide,
|
|
203
221
|
externalRefResolver: resolver,
|
|
204
222
|
});
|
|
205
|
-
this.#report(problems);
|
|
223
|
+
this.#report(problems, document.source.absoluteRef);
|
|
206
224
|
this.#options.logger.debug("Linted schema", "lint", this.#options.now() - startedAt);
|
|
207
225
|
}
|
|
208
226
|
|
|
209
227
|
async #bundle(document: Document, resolver: BaseResolver): Promise<OpenAPI3> {
|
|
210
228
|
const startedAt = this.#options.now();
|
|
211
229
|
const schemaKeys = schemaKeysOf(document.parsed);
|
|
230
|
+
const componentKeys = componentKeysOf(document.parsed);
|
|
212
231
|
// The lint resolver: documents it already loaded are not fetched again.
|
|
213
232
|
const bundled = await bundle({
|
|
214
233
|
config: this.#options.redoc,
|
|
@@ -216,9 +235,12 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
216
235
|
doc: document,
|
|
217
236
|
externalRefResolver: resolver,
|
|
218
237
|
});
|
|
219
|
-
this.#report(bundled.problems);
|
|
220
238
|
removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);
|
|
221
239
|
repairBundledRefs(bundled.bundle.parsed);
|
|
240
|
+
const documents = await loadedDocuments(document, resolver);
|
|
241
|
+
const renaming = renameQueryNamedComponents(bundled.bundle.parsed, componentKeys, documents);
|
|
242
|
+
// The problems name the components the bundle made, so they are shown by their new names.
|
|
243
|
+
this.#report(bundled.problems, document.source.absoluteRef, renaming);
|
|
222
244
|
this.#options.logger.debug("Bundled schema", "bundle", this.#options.now() - startedAt);
|
|
223
245
|
return bundled.bundle.parsed;
|
|
224
246
|
}
|
|
@@ -226,24 +248,26 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
226
248
|
/**
|
|
227
249
|
* Report every problem; then throw if any of them is an error. A problem that the Redocly
|
|
228
250
|
* config's `.redocly.lint-ignore.yaml` lists is left out, as `redocly lint` leaves it out
|
|
229
|
-
* (upstream #2189).
|
|
251
|
+
* (upstream #2189). A name collision that the renamed components no longer have is dropped.
|
|
230
252
|
*/
|
|
231
|
-
#report(problems: readonly NormalizedProblem[]): void {
|
|
253
|
+
#report(problems: readonly NormalizedProblem[], rootRef: string, renaming = UNRENAMED): void {
|
|
232
254
|
const errorMessages: string[] = [];
|
|
233
|
-
for (const problem of problems
|
|
255
|
+
for (const problem of problems.filter(
|
|
256
|
+
(found) => !renaming.isResolvedCollision(found.message),
|
|
257
|
+
)) {
|
|
234
258
|
if (problem.ignored === true) {
|
|
235
259
|
this.#options.logger.debug(
|
|
236
|
-
`Ignored by the lint ignore file: ${problemMessage(problem)}`,
|
|
260
|
+
`Ignored by the lint ignore file: ${problemMessage(problem, renaming, rootRef)}`,
|
|
237
261
|
"lint",
|
|
238
262
|
);
|
|
239
263
|
continue;
|
|
240
264
|
}
|
|
241
|
-
const message = problemMessage(problem);
|
|
265
|
+
const message = problemMessage(problem, renaming, rootRef);
|
|
242
266
|
if (problem.severity === "error") {
|
|
243
267
|
errorMessages.push(message);
|
|
244
268
|
this.#options.logger.error(message);
|
|
245
269
|
} else {
|
|
246
|
-
this.#options.logger.warn(lintWarningText(problem));
|
|
270
|
+
this.#options.logger.warn(lintWarningText(message, problem.ruleId));
|
|
247
271
|
}
|
|
248
272
|
}
|
|
249
273
|
if (errorMessages.length > 0) {
|
|
@@ -253,16 +277,38 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
253
277
|
}
|
|
254
278
|
|
|
255
279
|
/** A lint warning names its rule, which `redocly.yaml` can turn off, and ends with its code. */
|
|
256
|
-
function lintWarningText(
|
|
257
|
-
return warningText(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
280
|
+
function lintWarningText(message: string, ruleId: string): string {
|
|
281
|
+
return warningText(`${message}, rule ${ruleId}`, WARNING_CODES.LINT_PROBLEM);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Problems found before the bundle named any component. */
|
|
285
|
+
const UNRENAMED: ComponentRenaming = {
|
|
286
|
+
pointer: (pointer) => pointer,
|
|
287
|
+
text: (text) => text,
|
|
288
|
+
isResolvedCollision: () => false,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/** A problem location that names a JSON Pointer, not a line and column. */
|
|
292
|
+
type PointerLocation = Extract<LocationObject, { pointer: string }>;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Where a problem is: its pointer, and the document when it is not the root (URL secrets left
|
|
296
|
+
* out).
|
|
297
|
+
*/
|
|
298
|
+
function locationText(location: PointerLocation, renaming: ComponentRenaming, rootRef: string) {
|
|
299
|
+
const pointer = renaming.pointer(location.pointer);
|
|
300
|
+
const documentRef = location.source.absoluteRef;
|
|
301
|
+
return documentRef === rootRef ? pointer : `${pointer} in ${redactUrlSecrets(documentRef)}`;
|
|
261
302
|
}
|
|
262
303
|
|
|
263
|
-
function problemMessage(
|
|
264
|
-
|
|
265
|
-
|
|
304
|
+
function problemMessage(
|
|
305
|
+
problem: NormalizedProblem,
|
|
306
|
+
renaming: ComponentRenaming,
|
|
307
|
+
rootRef: string,
|
|
308
|
+
): string {
|
|
309
|
+
const location = problem.location?.[0];
|
|
310
|
+
const message = renaming.text(problem.message);
|
|
311
|
+
return location?.pointer ? `${message} at ${locationText(location, renaming, rootRef)}` : message;
|
|
266
312
|
}
|
|
267
313
|
|
|
268
314
|
/** A schema loader with one Redocly config and logger, for one or more loads. */
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
BaseResolver,
|
|
6
6
|
type Document,
|
|
7
7
|
type Config as RedoclyConfig,
|
|
8
|
-
|
|
8
|
+
Source,
|
|
9
9
|
YamlParseError,
|
|
10
10
|
} from "@redocly/openapi-core";
|
|
11
11
|
import type { RefAccess } from "../../application/ports/schema-loader.js";
|
|
@@ -13,6 +13,8 @@ import { ERROR_CODES, type OpenAPITypesafeError } from "../../domain/errors.js";
|
|
|
13
13
|
import { redactUrlSecrets } from "../../domain/url-secrets.js";
|
|
14
14
|
import { aliasExpansionFailure } from "./alias-limit.js";
|
|
15
15
|
import { isHostAllowed } from "./allowed-hosts.js";
|
|
16
|
+
import { pointMappingNamesAtSchemas } from "./mapping-schema-names.js";
|
|
17
|
+
import type { RedirectedUrls } from "./remote-fetch.js";
|
|
16
18
|
import { isParsedSource, RemoteNodeLimit } from "./remote-node-limit.js";
|
|
17
19
|
import { refForbiddenError } from "./schema-load-errors.js";
|
|
18
20
|
|
|
@@ -107,6 +109,8 @@ export interface RefAccessLimits {
|
|
|
107
109
|
readonly maxRemoteDocuments: number;
|
|
108
110
|
/** Nodes the remote documents of one load may parse into together (threat model T2). */
|
|
109
111
|
readonly maxRemoteNodes: number;
|
|
112
|
+
/** The final URLs of redirected documents, which their relative `$ref`s resolve against. */
|
|
113
|
+
readonly redirects: RedirectedUrls;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
/**
|
|
@@ -162,21 +166,33 @@ export class RefAccessResolver extends BaseResolver {
|
|
|
162
166
|
return super.resolveDocument(base, ref, isRoot);
|
|
163
167
|
}
|
|
164
168
|
|
|
165
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). A document a
|
|
171
|
+
* `$ref` loads has its own `components.schemas`, so its mapping names point at those schemas
|
|
172
|
+
* before Redocly resolves them, as the root's do (`pointMappingNamesAtSchemas`).
|
|
173
|
+
*/
|
|
166
174
|
override parseDocument(source: Source, isRoot = false): Document {
|
|
167
175
|
const document = this.#parsed(source, isRoot);
|
|
168
176
|
const failure = aliasExpansionFailure(document.parsed);
|
|
169
177
|
if (failure !== undefined) {
|
|
170
178
|
throw new YamlParseError(failure, source);
|
|
171
179
|
}
|
|
180
|
+
pointMappingNamesAtSchemas(document.parsed);
|
|
172
181
|
return document;
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* A remote document is parsed within the node limit; a local one as Redocly parses it. Whether
|
|
186
|
+
* a remote one is parsed at all is decided here (`isParsedSource`), since Redocly read the
|
|
187
|
+
* extension of a URL with its query.
|
|
188
|
+
*/
|
|
176
189
|
#parsed(source: Source, isRoot: boolean): Document {
|
|
177
|
-
if (!isRemoteRef(source.absoluteRef)
|
|
190
|
+
if (!isRemoteRef(source.absoluteRef)) {
|
|
178
191
|
return super.parseDocument(source, isRoot);
|
|
179
192
|
}
|
|
193
|
+
if (!isParsedSource(source, isRoot)) {
|
|
194
|
+
return { source, parsed: source.body };
|
|
195
|
+
}
|
|
180
196
|
try {
|
|
181
197
|
return this.#nodeLimit.parse(source);
|
|
182
198
|
} catch (error) {
|
|
@@ -194,7 +210,7 @@ export class RefAccessResolver extends BaseResolver {
|
|
|
194
210
|
throw refusal;
|
|
195
211
|
}
|
|
196
212
|
try {
|
|
197
|
-
return await super.loadExternalRef(absoluteRef);
|
|
213
|
+
return this.#atFinalUrl(await super.loadExternalRef(absoluteRef));
|
|
198
214
|
} catch (error) {
|
|
199
215
|
// A refused redirect throws inside the fetch, and Redocly wraps it in a ResolveError.
|
|
200
216
|
const cause = (error as { originalError?: unknown }).originalError;
|
|
@@ -205,6 +221,17 @@ export class RefAccessResolver extends BaseResolver {
|
|
|
205
221
|
}
|
|
206
222
|
}
|
|
207
223
|
|
|
224
|
+
/**
|
|
225
|
+
* A redirected document is named by the URL it came from, so its relative `$ref`s, its
|
|
226
|
+
* component names and its problems use that URL. Redocly still finds it by the requested one.
|
|
227
|
+
*/
|
|
228
|
+
#atFinalUrl(source: Source): Source {
|
|
229
|
+
const finalUrl = this.#limits.redirects.finalUrlOf(source.absoluteRef);
|
|
230
|
+
return finalUrl === source.absoluteRef
|
|
231
|
+
? source
|
|
232
|
+
: new Source(finalUrl, source.body, source.mimeType);
|
|
233
|
+
}
|
|
234
|
+
|
|
208
235
|
#accessRefusal(absoluteRef: string): Promise<OpenAPITypesafeError | undefined> {
|
|
209
236
|
return absoluteRef === this.#rootRef
|
|
210
237
|
? Promise.resolve(undefined)
|
|
@@ -6,38 +6,99 @@ export interface RefHolder {
|
|
|
6
6
|
readonly pointer: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Keywords whose value is data, not OpenAPI: a `$ref` or a `discriminator` in an example, a
|
|
11
|
+
* default, a `const` or an `enum` value, or an extension is part of the data and stays as written.
|
|
12
|
+
*/
|
|
13
|
+
const DATA_KEYWORDS = new Set(["example", "examples", "default", "const", "enum"]);
|
|
14
|
+
const EXTENSION_PREFIX = "x-";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Keywords whose value maps names to objects: a property, a schema or a response may be named
|
|
18
|
+
* `default` or `example`, and is OpenAPI all the same.
|
|
19
|
+
*/
|
|
20
|
+
const NAME_MAPS = new Set([
|
|
21
|
+
"properties",
|
|
22
|
+
"patternProperties",
|
|
23
|
+
"dependentSchemas",
|
|
24
|
+
"$defs",
|
|
25
|
+
"definitions",
|
|
26
|
+
"schemas",
|
|
27
|
+
"responses",
|
|
28
|
+
"parameters",
|
|
29
|
+
"requestBodies",
|
|
30
|
+
"headers",
|
|
31
|
+
"securitySchemes",
|
|
32
|
+
"links",
|
|
33
|
+
"callbacks",
|
|
34
|
+
"pathItems",
|
|
35
|
+
"mediaTypes",
|
|
36
|
+
"content",
|
|
37
|
+
"encoding",
|
|
38
|
+
"paths",
|
|
39
|
+
"webhooks",
|
|
40
|
+
]);
|
|
41
|
+
|
|
9
42
|
interface PendingNode {
|
|
10
43
|
readonly value: object;
|
|
11
44
|
readonly pointer: string;
|
|
45
|
+
/** Whether the keys of `value` are names (of properties, schemas, responses), not keywords. */
|
|
46
|
+
readonly hasNameKeys: boolean;
|
|
12
47
|
}
|
|
13
48
|
|
|
14
|
-
function
|
|
15
|
-
return
|
|
49
|
+
function isDataKey(key: string): boolean {
|
|
50
|
+
return DATA_KEYWORDS.has(key) || key.startsWith(EXTENSION_PREFIX);
|
|
16
51
|
}
|
|
17
52
|
|
|
18
53
|
/**
|
|
19
|
-
* Every object
|
|
20
|
-
*
|
|
21
|
-
* YAML aliases (or a cycle of them) is visited
|
|
54
|
+
* Every object of a parsed document that is OpenAPI, not data (`DATA_KEYWORDS`), with its JSON
|
|
55
|
+
* Pointer, in no particular order. The walk uses its own stack, so a deeply nested document does
|
|
56
|
+
* not run out of call stack, and a node shared by YAML aliases (or a cycle of them) is visited
|
|
57
|
+
* once.
|
|
22
58
|
*/
|
|
23
|
-
|
|
24
|
-
const found:
|
|
59
|
+
function openApiNodes(document: unknown): PendingNode[] {
|
|
60
|
+
const found: PendingNode[] = [];
|
|
25
61
|
const visited = new WeakSet<object>();
|
|
26
62
|
const pending: PendingNode[] = [];
|
|
27
|
-
const enqueue = (value: unknown, pointer: string): void => {
|
|
63
|
+
const enqueue = (value: unknown, pointer: string, hasNameKeys: boolean): void => {
|
|
28
64
|
if (typeof value === "object" && value !== null && !visited.has(value)) {
|
|
29
65
|
visited.add(value);
|
|
30
|
-
pending.push({ value, pointer });
|
|
66
|
+
pending.push({ value, pointer, hasNameKeys });
|
|
31
67
|
}
|
|
32
68
|
};
|
|
33
|
-
enqueue(document, ROOT_POINTER);
|
|
69
|
+
enqueue(document, ROOT_POINTER, false);
|
|
34
70
|
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
71
|
+
found.push(next);
|
|
72
|
+
// One push per child: spreading an array of millions of children overflows the stack.
|
|
38
73
|
for (const [key, child] of Object.entries(next.value)) {
|
|
39
|
-
|
|
74
|
+
if (next.hasNameKeys || !isDataKey(key)) {
|
|
75
|
+
enqueue(child, childPointer(next.pointer, key), !next.hasNameKeys && NAME_MAPS.has(key));
|
|
76
|
+
}
|
|
40
77
|
}
|
|
41
78
|
}
|
|
42
79
|
return found;
|
|
43
80
|
}
|
|
81
|
+
|
|
82
|
+
function hasStringRef(value: object): value is { $ref: string } {
|
|
83
|
+
return !Array.isArray(value) && typeof (value as { $ref?: unknown }).$ref === "string";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Every object with a string `$ref` in a parsed document, outside data, in no particular order. */
|
|
87
|
+
export function findRefHolders(document: unknown): RefHolder[] {
|
|
88
|
+
return openApiNodes(document).flatMap(({ value, pointer, hasNameKeys }) =>
|
|
89
|
+
!hasNameKeys && hasStringRef(value) ? [{ node: value, pointer }] : [],
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isMapping(value: unknown): value is Record<string, unknown> {
|
|
94
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Every `discriminator.mapping` object of a parsed document, outside data. */
|
|
98
|
+
export function findMappings(document: unknown): Record<string, unknown>[] {
|
|
99
|
+
return openApiNodes(document).flatMap(({ value, hasNameKeys }) => {
|
|
100
|
+
const { discriminator } = value as { discriminator?: unknown };
|
|
101
|
+
const mapping = !hasNameKeys && isMapping(discriminator) ? discriminator.mapping : undefined;
|
|
102
|
+
return isMapping(mapping) ? [mapping] : [];
|
|
103
|
+
});
|
|
104
|
+
}
|