openapi-typesafe 1.0.0-rc.1 → 1.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -5
- package/dist/adapters/node-deprecations/node-deprecations.cjs +14 -0
- package/dist/adapters/node-deprecations/node-deprecations.cjs.map +1 -0
- package/dist/adapters/node-deprecations/node-deprecations.mjs +12 -0
- package/dist/adapters/node-deprecations/node-deprecations.mjs.map +1 -0
- package/dist/adapters/redocly-loader/configured-headers.cjs +32 -0
- package/dist/adapters/redocly-loader/configured-headers.cjs.map +1 -0
- package/dist/adapters/redocly-loader/configured-headers.mjs +30 -0
- package/dist/adapters/redocly-loader/configured-headers.mjs.map +1 -0
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +19 -6
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +18 -5
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.cjs +37 -4
- package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.mjs +37 -4
- package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-resolution-check.cjs +191 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.cjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.mjs +189 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.mjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.cjs +73 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.cjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.mjs +60 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.mjs.map +1 -0
- package/dist/adapters/redocly-loader/remote-fetch.cjs +41 -14
- package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-fetch.mjs +40 -14
- package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-load-errors.cjs +23 -5
- package/dist/adapters/redocly-loader/schema-load-errors.cjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-load-errors.mjs +23 -5
- package/dist/adapters/redocly-loader/schema-load-errors.mjs.map +1 -1
- package/dist/adapters/redocly-loader/unsupported-keys.cjs +9 -1
- package/dist/adapters/redocly-loader/unsupported-keys.cjs.map +1 -1
- package/dist/adapters/redocly-loader/unsupported-keys.mjs +9 -1
- package/dist/adapters/redocly-loader/unsupported-keys.mjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.cjs +6 -4
- package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.mjs +6 -4
- package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs +2 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs.map +1 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs +2 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs.map +1 -1
- package/dist/adapters/type-ast-bridge/print-typescript.cjs.map +1 -1
- package/dist/adapters/type-ast-bridge/print-typescript.mjs.map +1 -1
- package/dist/api/default-ports.cjs +14 -0
- package/dist/api/default-ports.cjs.map +1 -1
- package/dist/api/default-ports.mjs +12 -1
- package/dist/api/default-ports.mjs.map +1 -1
- package/dist/api/discriminator-conflict-warnings.cjs +1 -1
- package/dist/api/discriminator-conflict-warnings.cjs.map +1 -1
- package/dist/api/discriminator-conflict-warnings.mjs +2 -2
- package/dist/api/discriminator-conflict-warnings.mjs.map +1 -1
- package/dist/api/discriminator-patch.cjs +8 -4
- package/dist/api/discriminator-patch.cjs.map +1 -1
- package/dist/api/discriminator-patch.mjs +9 -5
- package/dist/api/discriminator-patch.mjs.map +1 -1
- package/dist/api/discriminator-scan.cjs +2 -2
- package/dist/api/discriminator-scan.cjs.map +1 -1
- package/dist/api/discriminator-scan.mjs +3 -3
- package/dist/api/discriminator-scan.mjs.map +1 -1
- package/dist/api/document-refs.cjs +15 -12
- package/dist/api/document-refs.cjs.map +1 -1
- package/dist/api/document-refs.mjs +15 -12
- package/dist/api/document-refs.mjs.map +1 -1
- package/dist/api/document-run.cjs +6 -3
- package/dist/api/document-run.cjs.map +1 -1
- package/dist/api/document-run.mjs +6 -3
- package/dist/api/document-run.mjs.map +1 -1
- package/dist/api/footer-sink.cjs +25 -2
- package/dist/api/footer-sink.cjs.map +1 -1
- package/dist/api/footer-sink.mjs +25 -2
- package/dist/api/footer-sink.mjs.map +1 -1
- package/dist/api/legacy-callback-warning.cjs +7 -1
- package/dist/api/legacy-callback-warning.cjs.map +1 -1
- package/dist/api/legacy-callback-warning.mjs +8 -2
- package/dist/api/legacy-callback-warning.mjs.map +1 -1
- package/dist/api/legacy-ts-helpers.cjs.map +1 -1
- package/dist/api/legacy-ts-helpers.mjs.map +1 -1
- package/dist/api/legacy-utils.cjs.map +1 -1
- package/dist/api/legacy-utils.mjs.map +1 -1
- package/dist/api/openapi-ts.cjs +3 -2
- package/dist/api/openapi-ts.cjs.map +1 -1
- package/dist/api/openapi-ts.mjs +4 -3
- package/dist/api/openapi-ts.mjs.map +1 -1
- package/dist/api/string-to-ast.cjs +7 -9
- package/dist/api/string-to-ast.cjs.map +1 -1
- package/dist/api/string-to-ast.mjs +8 -9
- package/dist/api/string-to-ast.mjs.map +1 -1
- package/dist/api/transform-context.cjs +1 -1
- package/dist/api/transform-context.cjs.map +1 -1
- package/dist/api/transform-context.mjs +1 -1
- package/dist/api/transform-context.mjs.map +1 -1
- package/dist/api/transform-schema.cjs +2 -2
- package/dist/api/transform-schema.cjs.map +1 -1
- package/dist/api/transform-schema.mjs +3 -3
- package/dist/api/transform-schema.mjs.map +1 -1
- package/dist/application/print/type-printer.cjs +2 -1
- package/dist/application/print/type-printer.cjs.map +1 -1
- package/dist/application/print/type-printer.mjs +2 -1
- package/dist/application/print/type-printer.mjs.map +1 -1
- package/dist/application/transform/document/components.cjs +1 -1
- package/dist/application/transform/document/components.cjs.map +1 -1
- package/dist/application/transform/document/components.mjs +2 -2
- package/dist/application/transform/document/components.mjs.map +1 -1
- package/dist/application/transform/document/parameters.cjs +2 -2
- package/dist/application/transform/document/parameters.cjs.map +1 -1
- package/dist/application/transform/document/parameters.mjs +3 -3
- package/dist/application/transform/document/parameters.mjs.map +1 -1
- package/dist/application/transform/document/paths.cjs +2 -2
- package/dist/application/transform/document/paths.cjs.map +1 -1
- package/dist/application/transform/document/paths.mjs +3 -3
- package/dist/application/transform/document/paths.mjs.map +1 -1
- package/dist/application/transform/footer/evaluation.cjs +9 -0
- package/dist/application/transform/footer/evaluation.cjs.map +1 -1
- package/dist/application/transform/footer/evaluation.mjs +8 -1
- package/dist/application/transform/footer/evaluation.mjs.map +1 -1
- package/dist/application/transform/footer/type-evaluation.cjs +4 -112
- package/dist/application/transform/footer/type-evaluation.cjs.map +1 -1
- package/dist/application/transform/footer/type-evaluation.mjs +4 -112
- package/dist/application/transform/footer/type-evaluation.mjs.map +1 -1
- package/dist/application/transform/footer/type-indexing.cjs +107 -0
- package/dist/application/transform/footer/type-indexing.cjs.map +1 -0
- package/dist/application/transform/footer/type-indexing.mjs +105 -0
- package/dist/application/transform/footer/type-indexing.mjs.map +1 -0
- package/dist/application/transform/ref-pointer/pointer-type.cjs +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.mjs +2 -2
- package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -1
- package/dist/application/transform/schema/access-flags.cjs +1 -1
- package/dist/application/transform/schema/access-flags.cjs.map +1 -1
- package/dist/application/transform/schema/access-flags.mjs +2 -2
- package/dist/application/transform/schema/access-flags.mjs.map +1 -1
- package/dist/application/transform/schema/array-type.cjs +2 -2
- package/dist/application/transform/schema/array-type.cjs.map +1 -1
- package/dist/application/transform/schema/array-type.mjs +3 -3
- package/dist/application/transform/schema/array-type.mjs.map +1 -1
- package/dist/application/transform/schema/composition-type.cjs +3 -3
- package/dist/application/transform/schema/composition-type.cjs.map +1 -1
- package/dist/application/transform/schema/composition-type.mjs +4 -4
- package/dist/application/transform/schema/composition-type.mjs.map +1 -1
- package/dist/application/transform/schema/enum-rule.cjs +3 -5
- package/dist/application/transform/schema/enum-rule.cjs.map +1 -1
- package/dist/application/transform/schema/enum-rule.mjs +4 -6
- package/dist/application/transform/schema/enum-rule.mjs.map +1 -1
- package/dist/application/transform/schema/presence-members.cjs +1 -1
- package/dist/application/transform/schema/presence-members.cjs.map +1 -1
- package/dist/application/transform/schema/presence-members.mjs +2 -2
- package/dist/application/transform/schema/presence-members.mjs.map +1 -1
- package/dist/application/transform/schema/required-variants.cjs +2 -2
- package/dist/application/transform/schema/required-variants.cjs.map +1 -1
- package/dist/application/transform/schema/required-variants.mjs +3 -3
- package/dist/application/transform/schema/required-variants.mjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.cjs +1 -1
- package/dist/application/transform/schema/unevaluated-properties.cjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.mjs +2 -2
- package/dist/application/transform/schema/unevaluated-properties.mjs.map +1 -1
- package/dist/application/transform/schema-comment.cjs +1 -1
- package/dist/application/transform/schema-comment.cjs.map +1 -1
- package/dist/application/transform/schema-comment.mjs +2 -2
- package/dist/application/transform/schema-comment.mjs.map +1 -1
- package/dist/cli/api-settings.cjs +78 -0
- package/dist/cli/api-settings.cjs.map +1 -0
- package/dist/cli/api-settings.mjs +73 -0
- package/dist/cli/api-settings.mjs.map +1 -0
- package/dist/cli/cli-arguments.cjs +1 -0
- package/dist/cli/cli-arguments.cjs.map +1 -1
- package/dist/cli/cli-arguments.mjs +1 -1
- package/dist/cli/cli-arguments.mjs.map +1 -1
- package/dist/cli/help-text.cjs +3 -3
- package/dist/cli/help-text.cjs.map +1 -1
- package/dist/cli/help-text.mjs +3 -3
- package/dist/cli/help-text.mjs.map +1 -1
- package/dist/cli/redocly-apis.cjs +8 -4
- package/dist/cli/redocly-apis.cjs.map +1 -1
- package/dist/cli/redocly-apis.mjs +7 -3
- package/dist/cli/redocly-apis.mjs.map +1 -1
- package/dist/cli/run-cli.cjs +5 -1
- package/dist/cli/run-cli.cjs.map +1 -1
- package/dist/cli/run-cli.mjs +5 -1
- package/dist/cli/run-cli.mjs.map +1 -1
- package/dist/domain/deprecations.cjs +11 -0
- package/dist/domain/deprecations.cjs.map +1 -0
- package/dist/domain/deprecations.mjs +9 -0
- package/dist/domain/deprecations.mjs.map +1 -0
- package/dist/domain/errors.cjs +4 -1
- package/dist/domain/errors.cjs.map +1 -1
- package/dist/domain/errors.mjs +4 -1
- package/dist/domain/errors.mjs.map +1 -1
- package/dist/domain/ir/constructors.cjs +2 -0
- package/dist/domain/ir/constructors.cjs.map +1 -1
- package/dist/domain/ir/constructors.mjs +2 -1
- package/dist/domain/ir/constructors.mjs.map +1 -1
- package/dist/domain/openapi/path-templates.cjs +1 -1
- package/dist/domain/openapi/path-templates.cjs.map +1 -1
- package/dist/domain/openapi/path-templates.mjs +1 -1
- package/dist/domain/openapi/path-templates.mjs.map +1 -1
- package/dist/domain/output-fixes.cjs +11 -14
- package/dist/domain/output-fixes.cjs.map +1 -1
- package/dist/domain/output-fixes.mjs +11 -13
- package/dist/domain/output-fixes.mjs.map +1 -1
- package/dist/domain/url-credentials.cjs +10 -0
- package/dist/domain/url-credentials.cjs.map +1 -0
- package/dist/domain/url-credentials.mjs +8 -0
- package/dist/domain/url-credentials.mjs.map +1 -0
- package/dist/domain/warnings.cjs +8 -1
- package/dist/domain/warnings.cjs.map +1 -1
- package/dist/domain/warnings.mjs +8 -1
- package/dist/domain/warnings.mjs.map +1 -1
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1185 -59
- package/dist/index.d.mts +1185 -59
- package/dist/index.d.ts +1185 -59
- package/dist/index.mjs +4 -2
- package/dist/index.mjs.map +1 -1
- package/dist/transform/path-item-object.cjs.map +1 -1
- package/dist/transform/path-item-object.mjs.map +1 -1
- package/package.json +5 -4
- package/src/adapters/node-deprecations/node-deprecations.ts +14 -0
- package/src/adapters/redocly-loader/configured-headers.ts +65 -0
- package/src/adapters/redocly-loader/redocly-schema-loader.ts +22 -7
- package/src/adapters/redocly-loader/ref-access.ts +60 -5
- package/src/adapters/redocly-loader/ref-resolution-check.ts +257 -0
- package/src/adapters/redocly-loader/ref-resolution-steps.ts +113 -0
- package/src/adapters/redocly-loader/remote-fetch.ts +77 -21
- package/src/adapters/redocly-loader/schema-load-errors.ts +33 -11
- package/src/adapters/redocly-loader/unsupported-keys.ts +17 -2
- package/src/adapters/terminal-logger/terminal-logger.ts +9 -5
- package/src/adapters/type-ast-bridge/ir-to-typescript.ts +2 -1
- package/src/adapters/type-ast-bridge/print-typescript.ts +3 -1
- package/src/adapters/type-ast-bridge/typescript-types.ts +57 -5
- package/src/api/default-ports.ts +23 -0
- package/src/api/discriminator-conflict-warnings.ts +2 -2
- package/src/api/discriminator-patch.ts +11 -7
- package/src/api/discriminator-scan.ts +3 -3
- package/src/api/document-refs.ts +19 -15
- package/src/api/document-run.ts +14 -6
- package/src/api/footer-sink.ts +34 -2
- package/src/api/legacy-callback-warning.ts +16 -5
- package/src/api/legacy-ts-helpers.ts +7 -5
- package/src/api/legacy-utils.ts +1 -1
- package/src/api/openapi-ts.ts +8 -2
- package/src/api/run-context.ts +37 -0
- package/src/api/string-to-ast.ts +11 -15
- package/src/api/transform-context.ts +10 -8
- package/src/api/transform-schema.ts +9 -8
- package/src/application/ports/deprecation-reporter.ts +9 -0
- package/src/application/print/type-printer.ts +2 -1
- package/src/application/transform/document/components.ts +2 -2
- package/src/application/transform/document/parameters.ts +4 -3
- package/src/application/transform/document/paths.ts +4 -3
- package/src/application/transform/footer/evaluation.ts +10 -0
- package/src/application/transform/footer/type-evaluation.ts +6 -133
- package/src/application/transform/footer/type-indexing.ts +152 -0
- package/src/application/transform/ref-pointer/pointer-type.ts +2 -4
- package/src/application/transform/schema/access-flags.ts +2 -2
- package/src/application/transform/schema/array-type.ts +3 -5
- package/src/application/transform/schema/composition-type.ts +4 -4
- package/src/application/transform/schema/enum-rule.ts +4 -6
- package/src/application/transform/schema/presence-members.ts +2 -2
- package/src/application/transform/schema/required-variants.ts +3 -3
- package/src/application/transform/schema/unevaluated-properties.ts +2 -2
- package/src/application/transform/schema-comment.ts +2 -2
- package/src/cli/api-settings.ts +109 -0
- package/src/cli/cli-arguments.ts +5 -2
- package/src/cli/help-text.ts +4 -4
- package/src/cli/redocly-apis.ts +13 -7
- package/src/cli/run-cli.ts +5 -1
- package/src/domain/deprecations.ts +15 -0
- package/src/domain/errors.ts +16 -4
- package/src/domain/ir/constructors.ts +3 -0
- package/src/domain/ir/nodes.ts +9 -6
- package/src/domain/openapi/path-templates.ts +2 -1
- package/src/domain/output-fixes.ts +16 -16
- package/src/domain/url-credentials.ts +18 -0
- package/src/domain/warnings.ts +7 -0
- package/src/index.ts +3 -6
- package/src/transform/path-item-object.ts +6 -2
- package/src/types/generator-options.ts +12 -32
- package/src/types/openapi-common.ts +1 -0
- package/src/types/openapi-request.ts +3 -0
- package/src/types/openapi-schema.ts +2 -0
- package/src/types/openapi-security.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -4,11 +4,13 @@ export { COMMENT_HEADER } from './api/comment-header.mjs';
|
|
|
4
4
|
export { scanDiscriminators } from './api/discriminator-scan.mjs';
|
|
5
5
|
export { JS_ENUM_INVALID_CHARS_RE, JS_PROPERTY_INDEX_INVALID_CHARS_RE, JS_PROPERTY_INDEX_RE, QUESTION_TOKEN, SPECIAL_CHARACTER_MAP, addJSDocComment, enumCache, tsArrayLiteralExpression, tsDedupe, tsEnum, tsEnumMember, tsIsPrimitive, tsModifiers, tsPropertyIndex, tsReadonlyArray } from './api/legacy-ts-helpers.mjs';
|
|
6
6
|
export { c, createDiscriminatorProperty, createRef, debug, error, formatTime, getEntries, resolveRef, walk, warn } from './api/legacy-utils.mjs';
|
|
7
|
-
export {
|
|
7
|
+
export { stringToAST } from './api/string-to-ast.mjs';
|
|
8
8
|
export { default as transformSchema } from './api/transform-schema.mjs';
|
|
9
9
|
export { BOOLEAN, FALSE, NEVER, NULL, NUMBER, STRING, TRUE, UNDEFINED, UNKNOWN, oapiRef, tsIntersection, tsLiteral, tsNullable, tsOmit, tsRecord, tsUnion, tsWithRequired } from './api/type-helpers.mjs';
|
|
10
|
+
export { DEPRECATION_CODES } from './domain/deprecations.mjs';
|
|
10
11
|
export { ERROR_CODES, OpenAPITypesafeError, isOpenAPITypesafeError } from './domain/errors.mjs';
|
|
11
|
-
export {
|
|
12
|
+
export { OUTPUT_FIXES } from './domain/output-fixes.mjs';
|
|
13
|
+
export { WARNING_CODES } from './domain/warnings.mjs';
|
|
12
14
|
export { default as transformComponentsObject } from './transform/components-object.mjs';
|
|
13
15
|
export { default as transformHeaderObject } from './transform/header-object.mjs';
|
|
14
16
|
export { default as transformMediaTypeObject } from './transform/media-type-object.mjs';
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-item-object.cjs","sources":["../../src/transform/path-item-object.ts"],"sourcesContent":["import { createTransformContext, schemaLocation } from \"../api/transform-context.js\";\nimport { pathItemType } from \"../application/transform/document/path-item.js\";\nimport type { IrType } from \"../domain/ir/nodes.js\";\nimport type {
|
|
1
|
+
{"version":3,"file":"path-item-object.cjs","sources":["../../src/transform/path-item-object.ts"],"sourcesContent":["import { createTransformContext, schemaLocation } from \"../api/transform-context.js\";\nimport { pathItemType } from \"../application/transform/document/path-item.js\";\nimport type { IrType } from \"../domain/ir/nodes.js\";\nimport type { PathItemMethod } from \"../domain/openapi/path-item-object.js\";\nimport type { PathItemObject, TransformNodeOptions } from \"../types.js\";\n\n/**\n * The operation methods of a path item: openapi-typescript's eight, plus the OpenAPI 3.2 `query`\n * (ADR-020), as `HttpMethod` of openapi-typesafe-helpers lists them.\n */\nexport type Method = PathItemMethod;\n\n/**\n * Transform PathItem nodes (4.8.9)\n * @see https://spec.openapis.org/oas/v3.1.0#path-item-object\n *\n * Ported to IR (ENGINEERING_PLAN Phase 5, step 5). Same name and parameters as the original;\n * returns IR (task 6.2). For the public API and for users who call it from callbacks.\n */\nexport default function transformPathItemObject(\n pathItem: PathItemObject,\n options: TransformNodeOptions,\n): IrType {\n const context = createTransformContext(options.ctx);\n return pathItemType(pathItem, schemaLocation(options), context);\n}\n"],"names":["pathItem","createTransformContext","pathItemType","schemaLocation"],"mappings":";;;;;AAmBA,SAAwB,uBAAA,CACtBA,YACA,OAAA,EACQ;AACR,EAAA,MAAM,OAAA,GAAUC,uCAAA,CAAuB,OAAA,CAAQ,GAAG,CAAA;AAClD,EAAA,OAAOC,qBAAA,CAAaF,UAAA,EAAUG,+BAAA,CAAe,OAAO,GAAG,OAAO,CAAA;AAChE;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-item-object.mjs","sources":["../../src/transform/path-item-object.ts"],"sourcesContent":["import { createTransformContext, schemaLocation } from \"../api/transform-context.js\";\nimport { pathItemType } from \"../application/transform/document/path-item.js\";\nimport type { IrType } from \"../domain/ir/nodes.js\";\nimport type {
|
|
1
|
+
{"version":3,"file":"path-item-object.mjs","sources":["../../src/transform/path-item-object.ts"],"sourcesContent":["import { createTransformContext, schemaLocation } from \"../api/transform-context.js\";\nimport { pathItemType } from \"../application/transform/document/path-item.js\";\nimport type { IrType } from \"../domain/ir/nodes.js\";\nimport type { PathItemMethod } from \"../domain/openapi/path-item-object.js\";\nimport type { PathItemObject, TransformNodeOptions } from \"../types.js\";\n\n/**\n * The operation methods of a path item: openapi-typescript's eight, plus the OpenAPI 3.2 `query`\n * (ADR-020), as `HttpMethod` of openapi-typesafe-helpers lists them.\n */\nexport type Method = PathItemMethod;\n\n/**\n * Transform PathItem nodes (4.8.9)\n * @see https://spec.openapis.org/oas/v3.1.0#path-item-object\n *\n * Ported to IR (ENGINEERING_PLAN Phase 5, step 5). Same name and parameters as the original;\n * returns IR (task 6.2). For the public API and for users who call it from callbacks.\n */\nexport default function transformPathItemObject(\n pathItem: PathItemObject,\n options: TransformNodeOptions,\n): IrType {\n const context = createTransformContext(options.ctx);\n return pathItemType(pathItem, schemaLocation(options), context);\n}\n"],"names":[],"mappings":";;;AAmBA,SAAwB,uBAAA,CACtB,UACA,OAAA,EACQ;AACR,EAAA,MAAM,OAAA,GAAU,sBAAA,CAAuB,OAAA,CAAQ,GAAG,CAAA;AAClD,EAAA,OAAO,YAAA,CAAa,QAAA,EAAU,cAAA,CAAe,OAAO,GAAG,OAAO,CAAA;AAChE;;"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openapi-typesafe",
|
|
3
|
-
"description": "Convert OpenAPI 3.0 & 3.
|
|
4
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"description": "Convert OpenAPI 3.0, 3.1 & 3.2 schemas to TypeScript",
|
|
4
|
+
"version": "1.0.0-rc.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Fedor Chishchin",
|
|
7
7
|
"url": "https://github.com/fedorello"
|
|
@@ -81,15 +81,16 @@
|
|
|
81
81
|
"yargs-parser": "^22.0.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@types/js-yaml": "4.0.9",
|
|
85
84
|
"@types/yargs-parser": "21.0.3",
|
|
86
85
|
"@typescript/typescript6": "6.0.2",
|
|
87
86
|
"degit": "3.10.1",
|
|
88
|
-
"dependency-cruiser": "18.7.
|
|
87
|
+
"dependency-cruiser": "18.7.2",
|
|
89
88
|
"execa": "^10.2.1",
|
|
90
89
|
"fast-check": "4.10.2",
|
|
91
90
|
"strip-ansi": "7.2.0",
|
|
92
91
|
"typescript": "^6.0.3",
|
|
92
|
+
"typescript-5.4": "npm:typescript@5.4.5",
|
|
93
|
+
"typescript-7.0": "npm:typescript@7.0.2",
|
|
93
94
|
"vite-node": "6.0.0"
|
|
94
95
|
},
|
|
95
96
|
"engines": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { deprecate } from "node:util";
|
|
2
|
+
import type { DeprecationReporter } from "../../application/ports/deprecation-reporter.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Deprecations as Node.js `DeprecationWarning`s. `util.deprecate` warns once per process for each
|
|
6
|
+
* code, also across wrappers, and honours `--no-deprecation` and `--throw-deprecation`.
|
|
7
|
+
*/
|
|
8
|
+
export function createNodeDeprecationReporter(): DeprecationReporter {
|
|
9
|
+
return {
|
|
10
|
+
report(code, message) {
|
|
11
|
+
deprecate(() => undefined, message, code)();
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Config as RedoclyConfig, readFileFromUrl } from "@redocly/openapi-core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which `resolve.http.headers` of the Redocly config go with a request (threat model T4a). The
|
|
5
|
+
* headers often carry credentials, so they are chosen again for every request and every redirect,
|
|
6
|
+
* from the parsed URL, and never sent over plain HTTP to another machine unless the pattern asks
|
|
7
|
+
* for it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** One `resolve.http.headers` entry: a name, a value or an environment variable, a pattern. */
|
|
11
|
+
export type ConfiguredHeader = RedoclyConfig["resolve"]["http"]["headers"][number];
|
|
12
|
+
|
|
13
|
+
/** Redocly matches the protocol only when the pattern starts with one; the same test. */
|
|
14
|
+
const EXPLICIT_HTTP_PATTERN = /^http:\/\//;
|
|
15
|
+
|
|
16
|
+
/** Hosts that never leave the machine: plain HTTP to them cannot be read on the network. */
|
|
17
|
+
const LOOPBACK_HOSTNAMES: ReadonlySet<string> = new Set(["localhost", "[::1]"]);
|
|
18
|
+
// The URL parser has already turned every IPv4 form (`2130706433`, `0x7f.1`) into dotted decimal.
|
|
19
|
+
const LOOPBACK_IPV4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
20
|
+
|
|
21
|
+
function isLoopback(url: URL): boolean {
|
|
22
|
+
return LOOPBACK_HOSTNAMES.has(url.hostname) || LOOPBACK_IPV4.test(url.hostname);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A header goes over HTTPS, over HTTP to this machine (a local dev server), or over HTTP to
|
|
27
|
+
* another machine when its pattern names `http://` itself.
|
|
28
|
+
*/
|
|
29
|
+
function isProtocolAllowed(header: ConfiguredHeader, url: URL): boolean {
|
|
30
|
+
return (
|
|
31
|
+
url.protocol === "https:" ||
|
|
32
|
+
(url.protocol === "http:" && isLoopback(url)) ||
|
|
33
|
+
EXPLICIT_HTTP_PATTERN.test(header.matches)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The URL as a pattern sees it: protocol, host and path. User info, query and fragment are left
|
|
39
|
+
* out, so a `*` in the pattern cannot match across `?`, `#` or `@` into another host's URL.
|
|
40
|
+
*/
|
|
41
|
+
function matchedText(url: URL): string {
|
|
42
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The headers configured for this URL. The patterns are matched by Redocly's own matcher, which
|
|
47
|
+
* also reads `envVariable` values, so a pattern means here what it means in Redocly; its fetch
|
|
48
|
+
* only records the headers it would send and sends nothing.
|
|
49
|
+
*/
|
|
50
|
+
export async function headersFor(
|
|
51
|
+
url: URL,
|
|
52
|
+
configured: readonly ConfiguredHeader[],
|
|
53
|
+
): Promise<Record<string, string>> {
|
|
54
|
+
const candidates = configured.filter((header) => isProtocolAllowed(header, url));
|
|
55
|
+
let selected: Record<string, string> = {};
|
|
56
|
+
if (candidates.length === 0) {
|
|
57
|
+
return selected;
|
|
58
|
+
}
|
|
59
|
+
const recordHeaders = async (_input: string, init: { headers: Record<string, string> }) => {
|
|
60
|
+
selected = init.headers;
|
|
61
|
+
return new Response("");
|
|
62
|
+
};
|
|
63
|
+
await readFileFromUrl(matchedText(url), { headers: [...candidates], customFetch: recordHeaders });
|
|
64
|
+
return selected;
|
|
65
|
+
}
|
|
@@ -10,11 +10,14 @@ import {
|
|
|
10
10
|
import type { Logger } from "../../application/ports/logger.js";
|
|
11
11
|
import type { SchemaLoader, SchemaLoadRequest } from "../../application/ports/schema-loader.js";
|
|
12
12
|
import { withStackOverflowReportedAsync } from "../../application/stack-overflow.js";
|
|
13
|
+
import { WARNING_CODES, warningText } from "../../domain/warnings.js";
|
|
13
14
|
import type { OpenAPI3 } from "../../types.js";
|
|
14
15
|
import { repairBundledRefs } from "./bundled-ref-repair.js";
|
|
15
16
|
import { findMalformedRefs, malformedRefsError } from "./malformed-refs.js";
|
|
16
17
|
import { RefAccessResolver } from "./ref-access.js";
|
|
17
|
-
import {
|
|
18
|
+
import { checkRefsResolve } from "./ref-resolution-check.js";
|
|
19
|
+
import { REF_CYCLE_MESSAGE } from "./ref-resolution-steps.js";
|
|
20
|
+
import { type RemoteDocumentLimits, resolveConfigWithAccess } from "./remote-fetch.js";
|
|
18
21
|
import {
|
|
19
22
|
loadFailedError,
|
|
20
23
|
refCycleError,
|
|
@@ -39,6 +42,7 @@ export interface RedoclySchemaLoaderOptions {
|
|
|
39
42
|
readonly now: () => number;
|
|
40
43
|
/** Time limit of one remote fetch, unless the Redocly config brings its own `customFetch`. */
|
|
41
44
|
readonly remoteRefTimeoutMs: number;
|
|
45
|
+
readonly remoteDocumentLimits: RemoteDocumentLimits;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
const UNSUPPORTED_FORMAT_MESSAGE = "Unsupported schema format, expected `openapi: 3.x`";
|
|
@@ -103,9 +107,6 @@ function sourceFilePath(source: URL): string {
|
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
/** Redocly throws a plain Error for a `$ref` chain that points back to itself. */
|
|
107
|
-
const REF_CYCLE_MESSAGE = "Self-referencing circular pointer";
|
|
108
|
-
|
|
109
110
|
/**
|
|
110
111
|
* The root document and every other document the resolver loaded, as far as they parsed. A root
|
|
111
112
|
* read from a file or URL is in the resolver cache too, and is listed once.
|
|
@@ -176,12 +177,16 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
#resolver(request: SchemaLoadRequest): RefAccessResolver {
|
|
179
|
-
const { redoc, remoteRefTimeoutMs } = this.#options;
|
|
180
|
+
const { redoc, remoteRefTimeoutMs, remoteDocumentLimits } = this.#options;
|
|
180
181
|
const config = resolveConfigWithAccess(redoc.resolve, {
|
|
181
182
|
timeoutMs: remoteRefTimeoutMs,
|
|
182
183
|
access: request.refAccess,
|
|
184
|
+
maxParallelFetches: remoteDocumentLimits.maxParallelFetches,
|
|
185
|
+
});
|
|
186
|
+
return new RefAccessResolver(config, {
|
|
187
|
+
access: request.refAccess,
|
|
188
|
+
maxRemoteDocuments: remoteDocumentLimits.maxDocuments,
|
|
183
189
|
});
|
|
184
|
-
return new RefAccessResolver(config, request.refAccess);
|
|
185
190
|
}
|
|
186
191
|
|
|
187
192
|
async #lintAndBundle(
|
|
@@ -190,6 +195,8 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
190
195
|
shouldLint: boolean,
|
|
191
196
|
): Promise<OpenAPI3> {
|
|
192
197
|
const rethrow = (error: unknown) => rethrowDocumentError(error, document, resolver);
|
|
198
|
+
// Redocly leaves unhandled rejections behind when a `$ref` fails in two documents (ADR-031).
|
|
199
|
+
await checkRefsResolve(document, resolver, this.#options.redoc.styleguide).catch(rethrow);
|
|
193
200
|
if (shouldLint) {
|
|
194
201
|
await this.#lint(document, resolver).catch(rethrow);
|
|
195
202
|
}
|
|
@@ -242,7 +249,7 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
242
249
|
errorMessages.push(message);
|
|
243
250
|
this.#options.logger.error(message);
|
|
244
251
|
} else {
|
|
245
|
-
this.#options.logger.warn(
|
|
252
|
+
this.#options.logger.warn(lintWarningText(problem));
|
|
246
253
|
}
|
|
247
254
|
}
|
|
248
255
|
if (errorMessages.length > 0) {
|
|
@@ -251,6 +258,14 @@ class RedoclySchemaLoader implements SchemaLoader {
|
|
|
251
258
|
}
|
|
252
259
|
}
|
|
253
260
|
|
|
261
|
+
/** A lint warning names its rule, which `redocly.yaml` can turn off, and ends with its code. */
|
|
262
|
+
function lintWarningText(problem: NormalizedProblem): string {
|
|
263
|
+
return warningText(
|
|
264
|
+
`${problemMessage(problem)}, rule ${problem.ruleId}`,
|
|
265
|
+
WARNING_CODES.LINT_PROBLEM,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
254
269
|
function problemMessage(problem: NormalizedProblem): string {
|
|
255
270
|
const location = problem.location?.[0]?.pointer;
|
|
256
271
|
return location ? `${problem.message} at ${location}` : problem.message;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "@redocly/openapi-core";
|
|
11
11
|
import type { RefAccess } from "../../application/ports/schema-loader.js";
|
|
12
12
|
import { ERROR_CODES, type OpenAPITypesafeError } from "../../domain/errors.js";
|
|
13
|
+
import { redactUrlCredentials } from "../../domain/url-credentials.js";
|
|
13
14
|
import { aliasExpansionFailure } from "./alias-limit.js";
|
|
14
15
|
import { isHostAllowed } from "./allowed-hosts.js";
|
|
15
16
|
import { refForbiddenError } from "./schema-load-errors.js";
|
|
@@ -79,20 +80,45 @@ export async function refusalOf(
|
|
|
79
80
|
});
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
/**
|
|
84
|
+
* A remote document may refer only to other URLs (threat model T3). Redocly resolves a `$ref`
|
|
85
|
+
* against a URL base with `new URL()`, so `C:/x` becomes `c:/x` and `file:///x` stays as it is;
|
|
86
|
+
* Redocly then reads either from disk, and on Windows a drive letter is an absolute path.
|
|
87
|
+
*/
|
|
88
|
+
function localFromRemoteRefusal(
|
|
89
|
+
base: string,
|
|
90
|
+
absoluteRef: string,
|
|
91
|
+
): OpenAPITypesafeError | undefined {
|
|
92
|
+
if (!isRemoteRef(base) || isRemoteRef(absoluteRef)) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const shownBase = redactUrlCredentials(base);
|
|
96
|
+
return refForbiddenError(absoluteRef, { reason: "local-ref-from-remote", base: shownBase });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The limits one resolver checks; each load gets its own resolver. */
|
|
100
|
+
export interface RefAccessLimits {
|
|
101
|
+
readonly access: RefAccess;
|
|
102
|
+
/** Remote documents one load may fetch, the root included (threat model T2). */
|
|
103
|
+
readonly maxRemoteDocuments: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
82
106
|
/**
|
|
83
107
|
* A Redocly resolver that checks each document before it loads it. Redocly turns a failed load
|
|
84
108
|
* into an unresolved-`$ref` problem, so the refusals are also kept for the loader to report as
|
|
85
109
|
* the typed error.
|
|
86
110
|
*/
|
|
87
111
|
export class RefAccessResolver extends BaseResolver {
|
|
88
|
-
readonly #
|
|
112
|
+
readonly #limits: RefAccessLimits;
|
|
89
113
|
readonly #refusals: OpenAPITypesafeError[] = [];
|
|
90
114
|
/** The root document's ref: it is chosen by the developer, so it is not limited. */
|
|
91
115
|
#rootRef: string | undefined;
|
|
116
|
+
/** Remote documents asked for so far; Redocly caches each one, so each is counted once. */
|
|
117
|
+
#remoteDocumentCount = 0;
|
|
92
118
|
|
|
93
|
-
constructor(config: ResolveConfig,
|
|
119
|
+
constructor(config: ResolveConfig, limits: RefAccessLimits) {
|
|
94
120
|
super(config);
|
|
95
|
-
this.#
|
|
121
|
+
this.#limits = limits;
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
/** The first document that was refused, if any. */
|
|
@@ -108,6 +134,12 @@ export class RefAccessResolver extends BaseResolver {
|
|
|
108
134
|
if (isRoot) {
|
|
109
135
|
this.#rootRef = this.resolveExternalRef(base, ref);
|
|
110
136
|
}
|
|
137
|
+
const refusal =
|
|
138
|
+
base === null ? undefined : localFromRemoteRefusal(base, this.resolveExternalRef(base, ref));
|
|
139
|
+
if (refusal !== undefined) {
|
|
140
|
+
this.#refusals.push(refusal);
|
|
141
|
+
return Promise.reject(refusal);
|
|
142
|
+
}
|
|
111
143
|
return super.resolveDocument(base, ref, isRoot);
|
|
112
144
|
}
|
|
113
145
|
|
|
@@ -122,8 +154,7 @@ export class RefAccessResolver extends BaseResolver {
|
|
|
122
154
|
}
|
|
123
155
|
|
|
124
156
|
override async loadExternalRef(absoluteRef: string): Promise<Source> {
|
|
125
|
-
const refusal =
|
|
126
|
-
absoluteRef === this.#rootRef ? undefined : await refusalOf(absoluteRef, this.#access);
|
|
157
|
+
const refusal = this.#countRefusal(absoluteRef) ?? (await this.#accessRefusal(absoluteRef));
|
|
127
158
|
if (refusal !== undefined) {
|
|
128
159
|
this.#refusals.push(refusal);
|
|
129
160
|
throw refusal;
|
|
@@ -139,6 +170,30 @@ export class RefAccessResolver extends BaseResolver {
|
|
|
139
170
|
throw error;
|
|
140
171
|
}
|
|
141
172
|
}
|
|
173
|
+
|
|
174
|
+
#accessRefusal(absoluteRef: string): Promise<OpenAPITypesafeError | undefined> {
|
|
175
|
+
return absoluteRef === this.#rootRef
|
|
176
|
+
? Promise.resolve(undefined)
|
|
177
|
+
: refusalOf(absoluteRef, this.#limits.access);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A document that changes all its URLs can fan out without end: 150 `$ref`s per document, three
|
|
182
|
+
* levels deep, made 16,212 requests and exhausted the local ports (audit B2, F4).
|
|
183
|
+
*/
|
|
184
|
+
#countRefusal(absoluteRef: string): OpenAPITypesafeError | undefined {
|
|
185
|
+
if (!isRemoteRef(absoluteRef)) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
this.#remoteDocumentCount += 1;
|
|
189
|
+
const { maxRemoteDocuments } = this.#limits;
|
|
190
|
+
return this.#remoteDocumentCount > maxRemoteDocuments
|
|
191
|
+
? refForbiddenError(absoluteRef, {
|
|
192
|
+
reason: "too-many-remote-documents",
|
|
193
|
+
maxDocuments: maxRemoteDocuments,
|
|
194
|
+
})
|
|
195
|
+
: undefined;
|
|
196
|
+
}
|
|
142
197
|
}
|
|
143
198
|
|
|
144
199
|
function isRefusal(value: unknown): value is OpenAPITypesafeError {
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BaseResolver,
|
|
3
|
+
type Document,
|
|
4
|
+
detectSpec,
|
|
5
|
+
getTypes,
|
|
6
|
+
isRef,
|
|
7
|
+
normalizeTypes,
|
|
8
|
+
type Config as RedoclyConfig,
|
|
9
|
+
} from "@redocly/openapi-core";
|
|
10
|
+
import {
|
|
11
|
+
ANCHOR_REF,
|
|
12
|
+
escapePointer,
|
|
13
|
+
hasRef,
|
|
14
|
+
isExternalValue,
|
|
15
|
+
isNamedType,
|
|
16
|
+
joinPointer,
|
|
17
|
+
nextTick,
|
|
18
|
+
parseRef,
|
|
19
|
+
propertyType,
|
|
20
|
+
REF_CYCLE_MESSAGE,
|
|
21
|
+
type Ref,
|
|
22
|
+
type RefStack,
|
|
23
|
+
type ResolvedRef,
|
|
24
|
+
UNKNOWN_TYPE,
|
|
25
|
+
type WalkType,
|
|
26
|
+
} from "./ref-resolution-steps.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Redocly 1.34 resolves `$ref`s with `Promise.all` over the lookups it has started and stops at
|
|
30
|
+
* the first one that fails. Lookups it starts after that (the walk of a document that was still
|
|
31
|
+
* loading) are never awaited: when one of them fails too, its rejection is unhandled, and Node.js
|
|
32
|
+
* ends the process on an unhandled rejection (ADR-031). Redocly 2.x has the same loop.
|
|
33
|
+
*
|
|
34
|
+
* This is a port of Redocly's `resolveDocument` (`lib/resolve.js`, 1.34.22, MIT) that waits for
|
|
35
|
+
* every lookup and then throws the first failure. The loader runs it before Redocly lints or
|
|
36
|
+
* bundles, so Redocly only resolves documents in which no lookup fails. It walks the same nodes
|
|
37
|
+
* with the same types and throws the same errors: the "Self-referencing circular pointer" Error,
|
|
38
|
+
* the URIError of a malformed percent escape, and the TypeError of a key such as `valueOf`.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** One walk of the documents a root reaches, with every `$ref` lookup it starts. */
|
|
42
|
+
class RefResolutionWalk {
|
|
43
|
+
readonly #resolver: BaseResolver;
|
|
44
|
+
/** Redocly's `SpecExtension` type: its values are walked like values of no known type. */
|
|
45
|
+
readonly specExtension: WalkType;
|
|
46
|
+
/** Walked nodes per type name; a node walked again with the same type starts the same lookups. */
|
|
47
|
+
readonly #seenNodes = new Map<string, WeakSet<object>>();
|
|
48
|
+
readonly #lookups: Promise<void>[] = [];
|
|
49
|
+
|
|
50
|
+
constructor(resolver: BaseResolver, specExtension: WalkType) {
|
|
51
|
+
this.#resolver = resolver;
|
|
52
|
+
this.specExtension = specExtension;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isWalkedLikeUnknown(type: WalkType): boolean {
|
|
56
|
+
return type === UNKNOWN_TYPE || type === this.specExtension;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Wait for every lookup, including those started while waiting; return the first failure. */
|
|
60
|
+
async settle(): Promise<PromiseRejectedResult | undefined> {
|
|
61
|
+
let settled: PromiseSettledResult<void>[];
|
|
62
|
+
do {
|
|
63
|
+
settled = await Promise.allSettled(this.#lookups);
|
|
64
|
+
} while (settled.length !== this.#lookups.length);
|
|
65
|
+
return settled.find((result) => result.status === "rejected");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
walkFrom(node: unknown, document: Document, pointer: string, type: WalkType): void {
|
|
69
|
+
new DocumentWalk(this, document).walk(node, type, document.source.absoluteRef + pointer);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Whether the node was walked with this type before; marks it walked. */
|
|
73
|
+
isSeen(type: WalkType, node: object): boolean {
|
|
74
|
+
let seen = this.#seenNodes.get(type.name);
|
|
75
|
+
if (seen === undefined) {
|
|
76
|
+
seen = new WeakSet();
|
|
77
|
+
this.#seenNodes.set(type.name, seen);
|
|
78
|
+
}
|
|
79
|
+
if (seen.has(node)) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
seen.add(node);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
track(lookup: Promise<ResolvedRef>, type: WalkType): void {
|
|
87
|
+
this.#lookups.push(
|
|
88
|
+
lookup.then((resolved) => {
|
|
89
|
+
if (resolved.resolved && resolved.document !== undefined) {
|
|
90
|
+
this.walkFrom(resolved.node, resolved.document, resolved.nodePointer ?? "#/", type);
|
|
91
|
+
}
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async loadDocument(base: Document, uri: string): Promise<Document | undefined> {
|
|
97
|
+
try {
|
|
98
|
+
return (await this.#resolver.resolveDocument(base.source.absoluteRef, uri)) as Document;
|
|
99
|
+
} catch {
|
|
100
|
+
// Redocly records the failed lookup as an unresolved `$ref`; the lint reports it.
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The walk of one document from one node, with the anchors it collects (as in Redocly). */
|
|
107
|
+
class DocumentWalk {
|
|
108
|
+
readonly #walk: RefResolutionWalk;
|
|
109
|
+
readonly #document: Document;
|
|
110
|
+
readonly #anchors = new Map<string, unknown>();
|
|
111
|
+
|
|
112
|
+
constructor(walk: RefResolutionWalk, document: Document) {
|
|
113
|
+
this.#walk = walk;
|
|
114
|
+
this.#document = document;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
walk(node: unknown, type: WalkType, absoluteRef: string): void {
|
|
118
|
+
if (typeof node !== "object" || node === null || this.#walk.isSeen(type, node)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const anchor = (node as { $anchor?: unknown }).$anchor;
|
|
122
|
+
if (Object.hasOwn(node, "$anchor") && anchor) {
|
|
123
|
+
this.#anchors.set(`#${String(anchor)}`, node);
|
|
124
|
+
}
|
|
125
|
+
if (Array.isArray(node)) {
|
|
126
|
+
this.#walkItems(node, type, absoluteRef);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
for (const name of Object.keys(node)) {
|
|
130
|
+
const value = (node as Record<string, unknown>)[name];
|
|
131
|
+
const [propType, walked] = propertyType(type, name, value, this.#walk.specExtension);
|
|
132
|
+
if (isNamedType(propType) && typeof walked === "object") {
|
|
133
|
+
this.walk(walked, propType, joinPointer(absoluteRef, escapePointer(name)));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (isRef(node)) {
|
|
137
|
+
this.#walk.track(this.#follow(this.#document, node, { prev: null, node }), type);
|
|
138
|
+
}
|
|
139
|
+
if (isExternalValue(node)) {
|
|
140
|
+
const ref = { $ref: node.externalValue };
|
|
141
|
+
this.#walk.track(this.#follow(this.#document, ref, { prev: null, node }), type);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#walkItems(items: readonly unknown[], type: WalkType, absoluteRef: string): void {
|
|
146
|
+
const itemsType = type.items;
|
|
147
|
+
if (itemsType === undefined && !this.#walk.isWalkedLikeUnknown(type)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
items.forEach((item, index) => {
|
|
151
|
+
const pointer = joinPointer(absoluteRef, index);
|
|
152
|
+
const itemType = typeof itemsType === "function" ? itemsType(item, pointer) : itemsType;
|
|
153
|
+
if (itemType === undefined && !this.#walk.isWalkedLikeUnknown(type)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.walk(item, isNamedType(itemType) ? itemType : UNKNOWN_TYPE, pointer);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async #follow(document: Document, ref: Ref, refStack: RefStack): Promise<ResolvedRef> {
|
|
161
|
+
if (hasRef(refStack.prev, ref)) {
|
|
162
|
+
throw new Error(REF_CYCLE_MESSAGE);
|
|
163
|
+
}
|
|
164
|
+
if (ANCHOR_REF.test(ref.$ref)) {
|
|
165
|
+
await nextTick();
|
|
166
|
+
return { resolved: true, node: this.#anchors.get(ref.$ref), document, nodePointer: ref.$ref };
|
|
167
|
+
}
|
|
168
|
+
const { uri, pointer } = parseRef(ref.$ref);
|
|
169
|
+
const targetDocument = uri === null ? document : await this.#walk.loadDocument(document, uri);
|
|
170
|
+
if (targetDocument === undefined) {
|
|
171
|
+
return { resolved: false };
|
|
172
|
+
}
|
|
173
|
+
const found = await this.#followSegments(targetDocument, pointer, refStack);
|
|
174
|
+
if (found.document !== undefined && isRef(found.node)) {
|
|
175
|
+
return this.#follow(found.document, found.node, pushRef(refStack, found.node));
|
|
176
|
+
}
|
|
177
|
+
return found;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Follow the pointer segments; a `$ref` met on the way is resolved first. */
|
|
181
|
+
async #followSegments(
|
|
182
|
+
targetDocument: Document,
|
|
183
|
+
segments: readonly string[],
|
|
184
|
+
refStack: RefStack,
|
|
185
|
+
): Promise<ResolvedRef> {
|
|
186
|
+
let document = targetDocument;
|
|
187
|
+
let nodePointer = "#/";
|
|
188
|
+
let target: unknown = targetDocument.parsed;
|
|
189
|
+
for (const segment of segments) {
|
|
190
|
+
if (typeof target !== "object") {
|
|
191
|
+
return { resolved: true, node: undefined, document, nodePointer };
|
|
192
|
+
}
|
|
193
|
+
// A `null` target throws a TypeError here, as it does in Redocly.
|
|
194
|
+
const step = (target as Record<string, unknown>)[segment];
|
|
195
|
+
if (step !== undefined) {
|
|
196
|
+
target = step;
|
|
197
|
+
} else if (isRef(target)) {
|
|
198
|
+
const resolved = await this.#follow(document, target, pushRef(refStack, target));
|
|
199
|
+
document = resolved.document ?? document;
|
|
200
|
+
if (typeof resolved.node !== "object") {
|
|
201
|
+
return {
|
|
202
|
+
resolved: true,
|
|
203
|
+
node: undefined,
|
|
204
|
+
document,
|
|
205
|
+
nodePointer: resolved.nodePointer ?? nodePointer,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
target = (resolved.node as Record<string, unknown>)[segment];
|
|
209
|
+
nodePointer = resolved.nodePointer ?? nodePointer;
|
|
210
|
+
} else {
|
|
211
|
+
return { resolved: true, node: undefined, document, nodePointer };
|
|
212
|
+
}
|
|
213
|
+
nodePointer = joinPointer(nodePointer, escapePointer(segment));
|
|
214
|
+
}
|
|
215
|
+
return { resolved: true, node: target, document, nodePointer };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function pushRef(head: RefStack, node: unknown): RefStack {
|
|
220
|
+
return { prev: head, node };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Resolve every `$ref` the document reaches, as Redocly's lint and bundle do, and throw the first
|
|
225
|
+
* failure only after all lookups have finished. Documents are loaded through the given resolver,
|
|
226
|
+
* whose cache Redocly then reuses.
|
|
227
|
+
*/
|
|
228
|
+
export async function checkRefsResolve(
|
|
229
|
+
document: Document,
|
|
230
|
+
resolver: BaseResolver,
|
|
231
|
+
styleguide: RedoclyConfig["styleguide"],
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
const specVersion = detectSpec(document.parsed);
|
|
234
|
+
const types = normalizeTypes(
|
|
235
|
+
styleguide.extendTypes(getTypes(specVersion), specVersion),
|
|
236
|
+
styleguide,
|
|
237
|
+
);
|
|
238
|
+
const rootType = types.Root;
|
|
239
|
+
const specExtension = types.SpecExtension;
|
|
240
|
+
if (rootType === undefined || specExtension === undefined) {
|
|
241
|
+
// Redocly gives every spec version both types; a missing one is a Redocly change to port.
|
|
242
|
+
throw new Error(`Redocly has no Root or SpecExtension type for ${specVersion}`);
|
|
243
|
+
}
|
|
244
|
+
const walk = new RefResolutionWalk(resolver, specExtension);
|
|
245
|
+
let rootFailure: PromiseRejectedResult | undefined;
|
|
246
|
+
try {
|
|
247
|
+
walk.walkFrom(document.parsed, document, "#/", rootType);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
// The walk of the root can fail after it started lookups; they are awaited all the same.
|
|
250
|
+
rootFailure = { status: "rejected", reason: error };
|
|
251
|
+
}
|
|
252
|
+
const lookupFailure = await walk.settle();
|
|
253
|
+
const failure = rootFailure ?? lookupFailure;
|
|
254
|
+
if (failure !== undefined) {
|
|
255
|
+
throw failure.reason;
|
|
256
|
+
}
|
|
257
|
+
}
|