openapi-typesafe 1.0.0-rc.1 → 1.0.0-rc.2

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.
Files changed (203) hide show
  1. package/README.md +12 -5
  2. package/dist/adapters/redocly-loader/configured-headers.cjs +32 -0
  3. package/dist/adapters/redocly-loader/configured-headers.cjs.map +1 -0
  4. package/dist/adapters/redocly-loader/configured-headers.mjs +30 -0
  5. package/dist/adapters/redocly-loader/configured-headers.mjs.map +1 -0
  6. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +19 -6
  7. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  8. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +18 -5
  9. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  10. package/dist/adapters/redocly-loader/ref-access.cjs +37 -4
  11. package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
  12. package/dist/adapters/redocly-loader/ref-access.mjs +37 -4
  13. package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
  14. package/dist/adapters/redocly-loader/ref-resolution-check.cjs +191 -0
  15. package/dist/adapters/redocly-loader/ref-resolution-check.cjs.map +1 -0
  16. package/dist/adapters/redocly-loader/ref-resolution-check.mjs +189 -0
  17. package/dist/adapters/redocly-loader/ref-resolution-check.mjs.map +1 -0
  18. package/dist/adapters/redocly-loader/ref-resolution-steps.cjs +73 -0
  19. package/dist/adapters/redocly-loader/ref-resolution-steps.cjs.map +1 -0
  20. package/dist/adapters/redocly-loader/ref-resolution-steps.mjs +60 -0
  21. package/dist/adapters/redocly-loader/ref-resolution-steps.mjs.map +1 -0
  22. package/dist/adapters/redocly-loader/remote-fetch.cjs +41 -14
  23. package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
  24. package/dist/adapters/redocly-loader/remote-fetch.mjs +40 -14
  25. package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
  26. package/dist/adapters/redocly-loader/schema-load-errors.cjs +23 -5
  27. package/dist/adapters/redocly-loader/schema-load-errors.cjs.map +1 -1
  28. package/dist/adapters/redocly-loader/schema-load-errors.mjs +23 -5
  29. package/dist/adapters/redocly-loader/schema-load-errors.mjs.map +1 -1
  30. package/dist/adapters/redocly-loader/unsupported-keys.cjs +9 -1
  31. package/dist/adapters/redocly-loader/unsupported-keys.cjs.map +1 -1
  32. package/dist/adapters/redocly-loader/unsupported-keys.mjs +9 -1
  33. package/dist/adapters/redocly-loader/unsupported-keys.mjs.map +1 -1
  34. package/dist/adapters/terminal-logger/terminal-logger.cjs +6 -4
  35. package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
  36. package/dist/adapters/terminal-logger/terminal-logger.mjs +6 -4
  37. package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
  38. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs +2 -1
  39. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs.map +1 -1
  40. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs +2 -1
  41. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs.map +1 -1
  42. package/dist/adapters/type-ast-bridge/print-typescript.cjs.map +1 -1
  43. package/dist/adapters/type-ast-bridge/print-typescript.mjs.map +1 -1
  44. package/dist/api/default-ports.cjs +9 -0
  45. package/dist/api/default-ports.cjs.map +1 -1
  46. package/dist/api/default-ports.mjs +8 -1
  47. package/dist/api/default-ports.mjs.map +1 -1
  48. package/dist/api/discriminator-patch.cjs +5 -1
  49. package/dist/api/discriminator-patch.cjs.map +1 -1
  50. package/dist/api/discriminator-patch.mjs +5 -1
  51. package/dist/api/discriminator-patch.mjs.map +1 -1
  52. package/dist/api/document-refs.cjs +15 -12
  53. package/dist/api/document-refs.cjs.map +1 -1
  54. package/dist/api/document-refs.mjs +15 -12
  55. package/dist/api/document-refs.mjs.map +1 -1
  56. package/dist/api/document-run.cjs +4 -2
  57. package/dist/api/document-run.cjs.map +1 -1
  58. package/dist/api/document-run.mjs +4 -2
  59. package/dist/api/document-run.mjs.map +1 -1
  60. package/dist/api/footer-sink.cjs +25 -2
  61. package/dist/api/footer-sink.cjs.map +1 -1
  62. package/dist/api/footer-sink.mjs +25 -2
  63. package/dist/api/footer-sink.mjs.map +1 -1
  64. package/dist/api/legacy-callback-warning.cjs +4 -1
  65. package/dist/api/legacy-callback-warning.cjs.map +1 -1
  66. package/dist/api/legacy-callback-warning.mjs +4 -1
  67. package/dist/api/legacy-callback-warning.mjs.map +1 -1
  68. package/dist/api/legacy-ts-helpers.cjs.map +1 -1
  69. package/dist/api/legacy-ts-helpers.mjs.map +1 -1
  70. package/dist/api/legacy-utils.cjs.map +1 -1
  71. package/dist/api/legacy-utils.mjs.map +1 -1
  72. package/dist/api/openapi-ts.cjs +2 -1
  73. package/dist/api/openapi-ts.cjs.map +1 -1
  74. package/dist/api/openapi-ts.mjs +3 -2
  75. package/dist/api/openapi-ts.mjs.map +1 -1
  76. package/dist/api/transform-context.cjs +1 -1
  77. package/dist/api/transform-context.cjs.map +1 -1
  78. package/dist/api/transform-context.mjs +1 -1
  79. package/dist/api/transform-context.mjs.map +1 -1
  80. package/dist/api/transform-schema.cjs.map +1 -1
  81. package/dist/api/transform-schema.mjs.map +1 -1
  82. package/dist/application/print/type-printer.cjs +2 -1
  83. package/dist/application/print/type-printer.cjs.map +1 -1
  84. package/dist/application/print/type-printer.mjs +2 -1
  85. package/dist/application/print/type-printer.mjs.map +1 -1
  86. package/dist/application/transform/document/parameters.cjs +1 -1
  87. package/dist/application/transform/document/parameters.cjs.map +1 -1
  88. package/dist/application/transform/document/parameters.mjs +1 -1
  89. package/dist/application/transform/document/parameters.mjs.map +1 -1
  90. package/dist/application/transform/document/paths.cjs +1 -1
  91. package/dist/application/transform/document/paths.cjs.map +1 -1
  92. package/dist/application/transform/document/paths.mjs +1 -1
  93. package/dist/application/transform/document/paths.mjs.map +1 -1
  94. package/dist/application/transform/footer/evaluation.cjs +9 -0
  95. package/dist/application/transform/footer/evaluation.cjs.map +1 -1
  96. package/dist/application/transform/footer/evaluation.mjs +8 -1
  97. package/dist/application/transform/footer/evaluation.mjs.map +1 -1
  98. package/dist/application/transform/footer/type-evaluation.cjs +4 -112
  99. package/dist/application/transform/footer/type-evaluation.cjs.map +1 -1
  100. package/dist/application/transform/footer/type-evaluation.mjs +4 -112
  101. package/dist/application/transform/footer/type-evaluation.mjs.map +1 -1
  102. package/dist/application/transform/footer/type-indexing.cjs +107 -0
  103. package/dist/application/transform/footer/type-indexing.cjs.map +1 -0
  104. package/dist/application/transform/footer/type-indexing.mjs +105 -0
  105. package/dist/application/transform/footer/type-indexing.mjs.map +1 -0
  106. package/dist/cli/api-settings.cjs +78 -0
  107. package/dist/cli/api-settings.cjs.map +1 -0
  108. package/dist/cli/api-settings.mjs +73 -0
  109. package/dist/cli/api-settings.mjs.map +1 -0
  110. package/dist/cli/cli-arguments.cjs +1 -0
  111. package/dist/cli/cli-arguments.cjs.map +1 -1
  112. package/dist/cli/cli-arguments.mjs +1 -1
  113. package/dist/cli/cli-arguments.mjs.map +1 -1
  114. package/dist/cli/help-text.cjs +3 -3
  115. package/dist/cli/help-text.cjs.map +1 -1
  116. package/dist/cli/help-text.mjs +3 -3
  117. package/dist/cli/help-text.mjs.map +1 -1
  118. package/dist/cli/redocly-apis.cjs +8 -4
  119. package/dist/cli/redocly-apis.cjs.map +1 -1
  120. package/dist/cli/redocly-apis.mjs +7 -3
  121. package/dist/cli/redocly-apis.mjs.map +1 -1
  122. package/dist/cli/run-cli.cjs +5 -1
  123. package/dist/cli/run-cli.cjs.map +1 -1
  124. package/dist/cli/run-cli.mjs +5 -1
  125. package/dist/cli/run-cli.mjs.map +1 -1
  126. package/dist/domain/errors.cjs +4 -1
  127. package/dist/domain/errors.cjs.map +1 -1
  128. package/dist/domain/errors.mjs +4 -1
  129. package/dist/domain/errors.mjs.map +1 -1
  130. package/dist/domain/ir/constructors.cjs +2 -0
  131. package/dist/domain/ir/constructors.cjs.map +1 -1
  132. package/dist/domain/ir/constructors.mjs +2 -1
  133. package/dist/domain/ir/constructors.mjs.map +1 -1
  134. package/dist/domain/openapi/path-templates.cjs +1 -1
  135. package/dist/domain/openapi/path-templates.cjs.map +1 -1
  136. package/dist/domain/openapi/path-templates.mjs +1 -1
  137. package/dist/domain/openapi/path-templates.mjs.map +1 -1
  138. package/dist/domain/url-credentials.cjs +10 -0
  139. package/dist/domain/url-credentials.cjs.map +1 -0
  140. package/dist/domain/url-credentials.mjs +8 -0
  141. package/dist/domain/url-credentials.mjs.map +1 -0
  142. package/dist/domain/warnings.cjs +9 -1
  143. package/dist/domain/warnings.cjs.map +1 -1
  144. package/dist/domain/warnings.mjs +9 -1
  145. package/dist/domain/warnings.mjs.map +1 -1
  146. package/dist/index.cjs +2 -0
  147. package/dist/index.cjs.map +1 -1
  148. package/dist/index.d.cts +1160 -53
  149. package/dist/index.d.mts +1160 -53
  150. package/dist/index.d.ts +1160 -53
  151. package/dist/index.mjs +1 -0
  152. package/dist/index.mjs.map +1 -1
  153. package/dist/transform/path-item-object.cjs.map +1 -1
  154. package/dist/transform/path-item-object.mjs.map +1 -1
  155. package/package.json +6 -5
  156. package/src/adapters/redocly-loader/configured-headers.ts +65 -0
  157. package/src/adapters/redocly-loader/redocly-schema-loader.ts +22 -7
  158. package/src/adapters/redocly-loader/ref-access.ts +60 -5
  159. package/src/adapters/redocly-loader/ref-resolution-check.ts +257 -0
  160. package/src/adapters/redocly-loader/ref-resolution-steps.ts +113 -0
  161. package/src/adapters/redocly-loader/remote-fetch.ts +77 -21
  162. package/src/adapters/redocly-loader/schema-load-errors.ts +33 -11
  163. package/src/adapters/redocly-loader/unsupported-keys.ts +17 -2
  164. package/src/adapters/terminal-logger/terminal-logger.ts +9 -5
  165. package/src/adapters/type-ast-bridge/ir-to-typescript.ts +2 -1
  166. package/src/adapters/type-ast-bridge/print-typescript.ts +3 -1
  167. package/src/adapters/type-ast-bridge/typescript-types.ts +57 -5
  168. package/src/api/default-ports.ts +16 -0
  169. package/src/api/discriminator-patch.ts +7 -2
  170. package/src/api/document-refs.ts +19 -15
  171. package/src/api/document-run.ts +9 -6
  172. package/src/api/footer-sink.ts +34 -2
  173. package/src/api/legacy-callback-warning.ts +7 -3
  174. package/src/api/legacy-ts-helpers.ts +7 -5
  175. package/src/api/legacy-utils.ts +1 -1
  176. package/src/api/openapi-ts.ts +2 -0
  177. package/src/api/run-context.ts +34 -0
  178. package/src/api/transform-context.ts +10 -8
  179. package/src/api/transform-schema.ts +6 -5
  180. package/src/application/print/type-printer.ts +2 -1
  181. package/src/application/transform/document/parameters.ts +2 -1
  182. package/src/application/transform/document/paths.ts +2 -1
  183. package/src/application/transform/footer/evaluation.ts +10 -0
  184. package/src/application/transform/footer/type-evaluation.ts +6 -133
  185. package/src/application/transform/footer/type-indexing.ts +152 -0
  186. package/src/cli/api-settings.ts +109 -0
  187. package/src/cli/cli-arguments.ts +5 -2
  188. package/src/cli/help-text.ts +4 -4
  189. package/src/cli/redocly-apis.ts +13 -7
  190. package/src/cli/run-cli.ts +5 -1
  191. package/src/domain/errors.ts +8 -2
  192. package/src/domain/ir/constructors.ts +3 -0
  193. package/src/domain/ir/nodes.ts +9 -6
  194. package/src/domain/openapi/path-templates.ts +2 -1
  195. package/src/domain/url-credentials.ts +18 -0
  196. package/src/domain/warnings.ts +8 -0
  197. package/src/index.ts +1 -0
  198. package/src/transform/path-item-object.ts +6 -2
  199. package/src/types/generator-options.ts +12 -32
  200. package/src/types/openapi-common.ts +1 -0
  201. package/src/types/openapi-request.ts +3 -0
  202. package/src/types/openapi-schema.ts +2 -0
  203. package/src/types/openapi-security.ts +1 -0
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { BaseResolver, YamlParseError } from '@redocly/openapi-core';
5
5
  import { ERROR_CODES } from '../../domain/errors.mjs';
6
+ import { redactUrlCredentials } from '../../domain/url-credentials.mjs';
6
7
  import { aliasExpansionFailure } from './alias-limit.mjs';
7
8
  import { isHostAllowed } from './allowed-hosts.mjs';
8
9
  import { refForbiddenError } from './schema-load-errors.mjs';
@@ -46,14 +47,23 @@ async function refusalOf(absoluteRef, access) {
46
47
  allowedHosts: [...access.remoteHosts]
47
48
  });
48
49
  }
50
+ function localFromRemoteRefusal(base, absoluteRef) {
51
+ if (!isRemoteRef(base) || isRemoteRef(absoluteRef)) {
52
+ return void 0;
53
+ }
54
+ const shownBase = redactUrlCredentials(base);
55
+ return refForbiddenError(absoluteRef, { reason: "local-ref-from-remote", base: shownBase });
56
+ }
49
57
  class RefAccessResolver extends BaseResolver {
50
- #access;
58
+ #limits;
51
59
  #refusals = [];
52
60
  /** The root document's ref: it is chosen by the developer, so it is not limited. */
53
61
  #rootRef;
54
- constructor(config, access) {
62
+ /** Remote documents asked for so far; Redocly caches each one, so each is counted once. */
63
+ #remoteDocumentCount = 0;
64
+ constructor(config, limits) {
55
65
  super(config);
56
- this.#access = access;
66
+ this.#limits = limits;
57
67
  }
58
68
  /** The first document that was refused, if any. */
59
69
  get firstRefusal() {
@@ -63,6 +73,11 @@ class RefAccessResolver extends BaseResolver {
63
73
  if (isRoot) {
64
74
  this.#rootRef = this.resolveExternalRef(base, ref);
65
75
  }
76
+ const refusal = base === null ? void 0 : localFromRemoteRefusal(base, this.resolveExternalRef(base, ref));
77
+ if (refusal !== void 0) {
78
+ this.#refusals.push(refusal);
79
+ return Promise.reject(refusal);
80
+ }
66
81
  return super.resolveDocument(base, ref, isRoot);
67
82
  }
68
83
  /** Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). */
@@ -75,7 +90,7 @@ class RefAccessResolver extends BaseResolver {
75
90
  return document;
76
91
  }
77
92
  async loadExternalRef(absoluteRef) {
78
- const refusal = absoluteRef === this.#rootRef ? void 0 : await refusalOf(absoluteRef, this.#access);
93
+ const refusal = this.#countRefusal(absoluteRef) ?? await this.#accessRefusal(absoluteRef);
79
94
  if (refusal !== void 0) {
80
95
  this.#refusals.push(refusal);
81
96
  throw refusal;
@@ -90,6 +105,24 @@ class RefAccessResolver extends BaseResolver {
90
105
  throw error;
91
106
  }
92
107
  }
108
+ #accessRefusal(absoluteRef) {
109
+ return absoluteRef === this.#rootRef ? Promise.resolve(void 0) : refusalOf(absoluteRef, this.#limits.access);
110
+ }
111
+ /**
112
+ * A document that changes all its URLs can fan out without end: 150 `$ref`s per document, three
113
+ * levels deep, made 16,212 requests and exhausted the local ports (audit B2, F4).
114
+ */
115
+ #countRefusal(absoluteRef) {
116
+ if (!isRemoteRef(absoluteRef)) {
117
+ return void 0;
118
+ }
119
+ this.#remoteDocumentCount += 1;
120
+ const { maxRemoteDocuments } = this.#limits;
121
+ return this.#remoteDocumentCount > maxRemoteDocuments ? refForbiddenError(absoluteRef, {
122
+ reason: "too-many-remote-documents",
123
+ maxDocuments: maxRemoteDocuments
124
+ }) : void 0;
125
+ }
93
126
  }
94
127
  function isRefusal(value) {
95
128
  return value?.code === ERROR_CODES.SCHEMA_REF_FORBIDDEN;
@@ -1 +1 @@
1
- {"version":3,"file":"ref-access.mjs","sources":["../../../src/adapters/redocly-loader/ref-access.ts"],"sourcesContent":["import { realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n BaseResolver,\n type Document,\n type Config as RedoclyConfig,\n type Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport type { RefAccess } from \"../../application/ports/schema-loader.js\";\nimport { ERROR_CODES, type OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport { isHostAllowed } from \"./allowed-hosts.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Limits on where `$ref` documents may be loaded from (threat model R1 and R2, ADR-017). The root\n * document is chosen by the developer and is always loaded; every other document must pass.\n */\n\ntype ResolveConfig = RedoclyConfig[\"resolve\"];\n\n/** Redocly treats only these prefixes as remote; any other `$ref` is a local file path. */\nfunction isRemoteRef(absoluteRef: string): boolean {\n return absoluteRef.startsWith(\"http://\") || absoluteRef.startsWith(\"https://\");\n}\n\n/** Whether `target` is `root` or below it; both are absolute paths. */\nexport function isInsideDirectory(target: string, root: string): boolean {\n const relative = path.relative(root, target);\n // Compare the first segment: a directory named `..hidden` is inside, `../x` is not.\n const isParent = relative === \"..\" || relative.startsWith(`..${path.sep}`);\n return !isParent && !path.isAbsolute(relative);\n}\n\n/** The real path, so a link inside the root that points outside it is caught. */\nasync function realPathOrSelf(file: string): Promise<string> {\n const absolute = path.resolve(file);\n try {\n return await realpath(absolute);\n } catch (error) {\n // A missing file is never read, and Redocly reports it. Resolve its nearest existing\n // directory instead, so it compares with the root's real path: the root may be reached\n // through a link (macOS `/tmp` is one), and the missing file must not look outside it.\n const parent = path.dirname(absolute);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\" && parent !== absolute) {\n return path.join(await realPathOrSelf(parent), path.basename(absolute));\n }\n throw error;\n }\n}\n\nasync function localRefusal(\n absoluteRef: string,\n localRoot: URL,\n): Promise<OpenAPITypesafeError | undefined> {\n const root = await realPathOrSelf(fileURLToPath(localRoot));\n if (isInsideDirectory(await realPathOrSelf(absoluteRef), root)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, { reason: \"outside-local-root\", localRoot: root });\n}\n\n/** The refusal for one document, or undefined when the limits allow it. */\nexport async function refusalOf(\n absoluteRef: string,\n access: RefAccess,\n): Promise<OpenAPITypesafeError | undefined> {\n if (!isRemoteRef(absoluteRef)) {\n return access.localRoot === undefined ? undefined : localRefusal(absoluteRef, access.localRoot);\n }\n if (access.remoteHosts === undefined || isHostAllowed(new URL(absoluteRef), access.remoteHosts)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, {\n reason: \"host-not-allowed\",\n allowedHosts: [...access.remoteHosts],\n });\n}\n\n/**\n * A Redocly resolver that checks each document before it loads it. Redocly turns a failed load\n * into an unresolved-`$ref` problem, so the refusals are also kept for the loader to report as\n * the typed error.\n */\nexport class RefAccessResolver extends BaseResolver {\n readonly #access: RefAccess;\n readonly #refusals: OpenAPITypesafeError[] = [];\n /** The root document's ref: it is chosen by the developer, so it is not limited. */\n #rootRef: string | undefined;\n\n constructor(config: ResolveConfig, access: RefAccess) {\n super(config);\n this.#access = access;\n }\n\n /** The first document that was refused, if any. */\n get firstRefusal(): OpenAPITypesafeError | undefined {\n return this.#refusals[0];\n }\n\n override resolveDocument(\n base: string | null,\n ref: string,\n isRoot = false,\n ): ReturnType<BaseResolver[\"resolveDocument\"]> {\n if (isRoot) {\n this.#rootRef = this.resolveExternalRef(base, ref);\n }\n return super.resolveDocument(base, ref, isRoot);\n }\n\n /** Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). */\n override parseDocument(source: Source, isRoot = false): Document {\n const document = super.parseDocument(source, isRoot);\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw new YamlParseError(failure, source);\n }\n return document;\n }\n\n override async loadExternalRef(absoluteRef: string): Promise<Source> {\n const refusal =\n absoluteRef === this.#rootRef ? undefined : await refusalOf(absoluteRef, this.#access);\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n throw refusal;\n }\n try {\n return await super.loadExternalRef(absoluteRef);\n } catch (error) {\n // A refused redirect throws inside the fetch, and Redocly wraps it in a ResolveError.\n const cause = (error as { originalError?: unknown }).originalError;\n if (isRefusal(cause)) {\n this.#refusals.push(cause);\n }\n throw error;\n }\n }\n}\n\nfunction isRefusal(value: unknown): value is OpenAPITypesafeError {\n return (value as { code?: unknown } | null)?.code === ERROR_CODES.SCHEMA_REF_FORBIDDEN;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ref-access.mjs","sources":["../../../src/adapters/redocly-loader/ref-access.ts"],"sourcesContent":["import { realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n BaseResolver,\n type Document,\n type Config as RedoclyConfig,\n type Source,\n YamlParseError,\n} from \"@redocly/openapi-core\";\nimport type { RefAccess } from \"../../application/ports/schema-loader.js\";\nimport { ERROR_CODES, type OpenAPITypesafeError } from \"../../domain/errors.js\";\nimport { redactUrlCredentials } from \"../../domain/url-credentials.js\";\nimport { aliasExpansionFailure } from \"./alias-limit.js\";\nimport { isHostAllowed } from \"./allowed-hosts.js\";\nimport { refForbiddenError } from \"./schema-load-errors.js\";\n\n/**\n * Limits on where `$ref` documents may be loaded from (threat model R1 and R2, ADR-017). The root\n * document is chosen by the developer and is always loaded; every other document must pass.\n */\n\ntype ResolveConfig = RedoclyConfig[\"resolve\"];\n\n/** Redocly treats only these prefixes as remote; any other `$ref` is a local file path. */\nfunction isRemoteRef(absoluteRef: string): boolean {\n return absoluteRef.startsWith(\"http://\") || absoluteRef.startsWith(\"https://\");\n}\n\n/** Whether `target` is `root` or below it; both are absolute paths. */\nexport function isInsideDirectory(target: string, root: string): boolean {\n const relative = path.relative(root, target);\n // Compare the first segment: a directory named `..hidden` is inside, `../x` is not.\n const isParent = relative === \"..\" || relative.startsWith(`..${path.sep}`);\n return !isParent && !path.isAbsolute(relative);\n}\n\n/** The real path, so a link inside the root that points outside it is caught. */\nasync function realPathOrSelf(file: string): Promise<string> {\n const absolute = path.resolve(file);\n try {\n return await realpath(absolute);\n } catch (error) {\n // A missing file is never read, and Redocly reports it. Resolve its nearest existing\n // directory instead, so it compares with the root's real path: the root may be reached\n // through a link (macOS `/tmp` is one), and the missing file must not look outside it.\n const parent = path.dirname(absolute);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\" && parent !== absolute) {\n return path.join(await realPathOrSelf(parent), path.basename(absolute));\n }\n throw error;\n }\n}\n\nasync function localRefusal(\n absoluteRef: string,\n localRoot: URL,\n): Promise<OpenAPITypesafeError | undefined> {\n const root = await realPathOrSelf(fileURLToPath(localRoot));\n if (isInsideDirectory(await realPathOrSelf(absoluteRef), root)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, { reason: \"outside-local-root\", localRoot: root });\n}\n\n/** The refusal for one document, or undefined when the limits allow it. */\nexport async function refusalOf(\n absoluteRef: string,\n access: RefAccess,\n): Promise<OpenAPITypesafeError | undefined> {\n if (!isRemoteRef(absoluteRef)) {\n return access.localRoot === undefined ? undefined : localRefusal(absoluteRef, access.localRoot);\n }\n if (access.remoteHosts === undefined || isHostAllowed(new URL(absoluteRef), access.remoteHosts)) {\n return undefined;\n }\n return refForbiddenError(absoluteRef, {\n reason: \"host-not-allowed\",\n allowedHosts: [...access.remoteHosts],\n });\n}\n\n/**\n * A remote document may refer only to other URLs (threat model T3). Redocly resolves a `$ref`\n * against a URL base with `new URL()`, so `C:/x` becomes `c:/x` and `file:///x` stays as it is;\n * Redocly then reads either from disk, and on Windows a drive letter is an absolute path.\n */\nfunction localFromRemoteRefusal(\n base: string,\n absoluteRef: string,\n): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(base) || isRemoteRef(absoluteRef)) {\n return undefined;\n }\n const shownBase = redactUrlCredentials(base);\n return refForbiddenError(absoluteRef, { reason: \"local-ref-from-remote\", base: shownBase });\n}\n\n/** The limits one resolver checks; each load gets its own resolver. */\nexport interface RefAccessLimits {\n readonly access: RefAccess;\n /** Remote documents one load may fetch, the root included (threat model T2). */\n readonly maxRemoteDocuments: number;\n}\n\n/**\n * A Redocly resolver that checks each document before it loads it. Redocly turns a failed load\n * into an unresolved-`$ref` problem, so the refusals are also kept for the loader to report as\n * the typed error.\n */\nexport class RefAccessResolver extends BaseResolver {\n readonly #limits: RefAccessLimits;\n readonly #refusals: OpenAPITypesafeError[] = [];\n /** The root document's ref: it is chosen by the developer, so it is not limited. */\n #rootRef: string | undefined;\n /** Remote documents asked for so far; Redocly caches each one, so each is counted once. */\n #remoteDocumentCount = 0;\n\n constructor(config: ResolveConfig, limits: RefAccessLimits) {\n super(config);\n this.#limits = limits;\n }\n\n /** The first document that was refused, if any. */\n get firstRefusal(): OpenAPITypesafeError | undefined {\n return this.#refusals[0];\n }\n\n override resolveDocument(\n base: string | null,\n ref: string,\n isRoot = false,\n ): ReturnType<BaseResolver[\"resolveDocument\"]> {\n if (isRoot) {\n this.#rootRef = this.resolveExternalRef(base, ref);\n }\n const refusal =\n base === null ? undefined : localFromRemoteRefusal(base, this.resolveExternalRef(base, ref));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n return Promise.reject(refusal);\n }\n return super.resolveDocument(base, ref, isRoot);\n }\n\n /** Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). */\n override parseDocument(source: Source, isRoot = false): Document {\n const document = super.parseDocument(source, isRoot);\n const failure = aliasExpansionFailure(document.parsed);\n if (failure !== undefined) {\n throw new YamlParseError(failure, source);\n }\n return document;\n }\n\n override async loadExternalRef(absoluteRef: string): Promise<Source> {\n const refusal = this.#countRefusal(absoluteRef) ?? (await this.#accessRefusal(absoluteRef));\n if (refusal !== undefined) {\n this.#refusals.push(refusal);\n throw refusal;\n }\n try {\n return await super.loadExternalRef(absoluteRef);\n } catch (error) {\n // A refused redirect throws inside the fetch, and Redocly wraps it in a ResolveError.\n const cause = (error as { originalError?: unknown }).originalError;\n if (isRefusal(cause)) {\n this.#refusals.push(cause);\n }\n throw error;\n }\n }\n\n #accessRefusal(absoluteRef: string): Promise<OpenAPITypesafeError | undefined> {\n return absoluteRef === this.#rootRef\n ? Promise.resolve(undefined)\n : refusalOf(absoluteRef, this.#limits.access);\n }\n\n /**\n * A document that changes all its URLs can fan out without end: 150 `$ref`s per document, three\n * levels deep, made 16,212 requests and exhausted the local ports (audit B2, F4).\n */\n #countRefusal(absoluteRef: string): OpenAPITypesafeError | undefined {\n if (!isRemoteRef(absoluteRef)) {\n return undefined;\n }\n this.#remoteDocumentCount += 1;\n const { maxRemoteDocuments } = this.#limits;\n return this.#remoteDocumentCount > maxRemoteDocuments\n ? refForbiddenError(absoluteRef, {\n reason: \"too-many-remote-documents\",\n maxDocuments: maxRemoteDocuments,\n })\n : undefined;\n }\n}\n\nfunction isRefusal(value: unknown): value is OpenAPITypesafeError {\n return (value as { code?: unknown } | null)?.code === ERROR_CODES.SCHEMA_REF_FORBIDDEN;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ 'use strict';
2
+
3
+ const openapiCore = require('@redocly/openapi-core');
4
+ const refResolutionSteps = require('./ref-resolution-steps.cjs');
5
+
6
+ class RefResolutionWalk {
7
+ #resolver;
8
+ /** Redocly's `SpecExtension` type: its values are walked like values of no known type. */
9
+ specExtension;
10
+ /** Walked nodes per type name; a node walked again with the same type starts the same lookups. */
11
+ #seenNodes = /* @__PURE__ */ new Map();
12
+ #lookups = [];
13
+ constructor(resolver, specExtension) {
14
+ this.#resolver = resolver;
15
+ this.specExtension = specExtension;
16
+ }
17
+ isWalkedLikeUnknown(type) {
18
+ return type === refResolutionSteps.UNKNOWN_TYPE || type === this.specExtension;
19
+ }
20
+ /** Wait for every lookup, including those started while waiting; return the first failure. */
21
+ async settle() {
22
+ let settled;
23
+ do {
24
+ settled = await Promise.allSettled(this.#lookups);
25
+ } while (settled.length !== this.#lookups.length);
26
+ return settled.find((result) => result.status === "rejected");
27
+ }
28
+ walkFrom(node, document, pointer, type) {
29
+ new DocumentWalk(this, document).walk(node, type, document.source.absoluteRef + pointer);
30
+ }
31
+ /** Whether the node was walked with this type before; marks it walked. */
32
+ isSeen(type, node) {
33
+ let seen = this.#seenNodes.get(type.name);
34
+ if (seen === void 0) {
35
+ seen = /* @__PURE__ */ new WeakSet();
36
+ this.#seenNodes.set(type.name, seen);
37
+ }
38
+ if (seen.has(node)) {
39
+ return true;
40
+ }
41
+ seen.add(node);
42
+ return false;
43
+ }
44
+ track(lookup, type) {
45
+ this.#lookups.push(
46
+ lookup.then((resolved) => {
47
+ if (resolved.resolved && resolved.document !== void 0) {
48
+ this.walkFrom(resolved.node, resolved.document, resolved.nodePointer ?? "#/", type);
49
+ }
50
+ })
51
+ );
52
+ }
53
+ async loadDocument(base, uri) {
54
+ try {
55
+ return await this.#resolver.resolveDocument(base.source.absoluteRef, uri);
56
+ } catch {
57
+ return void 0;
58
+ }
59
+ }
60
+ }
61
+ class DocumentWalk {
62
+ #walk;
63
+ #document;
64
+ #anchors = /* @__PURE__ */ new Map();
65
+ constructor(walk, document) {
66
+ this.#walk = walk;
67
+ this.#document = document;
68
+ }
69
+ walk(node, type, absoluteRef) {
70
+ if (typeof node !== "object" || node === null || this.#walk.isSeen(type, node)) {
71
+ return;
72
+ }
73
+ const anchor = node.$anchor;
74
+ if (Object.hasOwn(node, "$anchor") && anchor) {
75
+ this.#anchors.set(`#${String(anchor)}`, node);
76
+ }
77
+ if (Array.isArray(node)) {
78
+ this.#walkItems(node, type, absoluteRef);
79
+ return;
80
+ }
81
+ for (const name of Object.keys(node)) {
82
+ const value = node[name];
83
+ const [propType, walked] = refResolutionSteps.propertyType(type, name, value, this.#walk.specExtension);
84
+ if (refResolutionSteps.isNamedType(propType) && typeof walked === "object") {
85
+ this.walk(walked, propType, refResolutionSteps.joinPointer(absoluteRef, refResolutionSteps.escapePointer(name)));
86
+ }
87
+ }
88
+ if (openapiCore.isRef(node)) {
89
+ this.#walk.track(this.#follow(this.#document, node, { prev: null, node }), type);
90
+ }
91
+ if (refResolutionSteps.isExternalValue(node)) {
92
+ const ref = { $ref: node.externalValue };
93
+ this.#walk.track(this.#follow(this.#document, ref, { prev: null, node }), type);
94
+ }
95
+ }
96
+ #walkItems(items, type, absoluteRef) {
97
+ const itemsType = type.items;
98
+ if (itemsType === void 0 && !this.#walk.isWalkedLikeUnknown(type)) {
99
+ return;
100
+ }
101
+ items.forEach((item, index) => {
102
+ const pointer = refResolutionSteps.joinPointer(absoluteRef, index);
103
+ const itemType = typeof itemsType === "function" ? itemsType(item, pointer) : itemsType;
104
+ if (itemType === void 0 && !this.#walk.isWalkedLikeUnknown(type)) {
105
+ return;
106
+ }
107
+ this.walk(item, refResolutionSteps.isNamedType(itemType) ? itemType : refResolutionSteps.UNKNOWN_TYPE, pointer);
108
+ });
109
+ }
110
+ async #follow(document, ref, refStack) {
111
+ if (refResolutionSteps.hasRef(refStack.prev, ref)) {
112
+ throw new Error(refResolutionSteps.REF_CYCLE_MESSAGE);
113
+ }
114
+ if (refResolutionSteps.ANCHOR_REF.test(ref.$ref)) {
115
+ await refResolutionSteps.nextTick();
116
+ return { resolved: true, node: this.#anchors.get(ref.$ref), document, nodePointer: ref.$ref };
117
+ }
118
+ const { uri, pointer } = refResolutionSteps.parseRef(ref.$ref);
119
+ const targetDocument = uri === null ? document : await this.#walk.loadDocument(document, uri);
120
+ if (targetDocument === void 0) {
121
+ return { resolved: false };
122
+ }
123
+ const found = await this.#followSegments(targetDocument, pointer, refStack);
124
+ if (found.document !== void 0 && openapiCore.isRef(found.node)) {
125
+ return this.#follow(found.document, found.node, pushRef(refStack, found.node));
126
+ }
127
+ return found;
128
+ }
129
+ /** Follow the pointer segments; a `$ref` met on the way is resolved first. */
130
+ async #followSegments(targetDocument, segments, refStack) {
131
+ let document = targetDocument;
132
+ let nodePointer = "#/";
133
+ let target = targetDocument.parsed;
134
+ for (const segment of segments) {
135
+ if (typeof target !== "object") {
136
+ return { resolved: true, node: void 0, document, nodePointer };
137
+ }
138
+ const step = target[segment];
139
+ if (step !== void 0) {
140
+ target = step;
141
+ } else if (openapiCore.isRef(target)) {
142
+ const resolved = await this.#follow(document, target, pushRef(refStack, target));
143
+ document = resolved.document ?? document;
144
+ if (typeof resolved.node !== "object") {
145
+ return {
146
+ resolved: true,
147
+ node: void 0,
148
+ document,
149
+ nodePointer: resolved.nodePointer ?? nodePointer
150
+ };
151
+ }
152
+ target = resolved.node[segment];
153
+ nodePointer = resolved.nodePointer ?? nodePointer;
154
+ } else {
155
+ return { resolved: true, node: void 0, document, nodePointer };
156
+ }
157
+ nodePointer = refResolutionSteps.joinPointer(nodePointer, refResolutionSteps.escapePointer(segment));
158
+ }
159
+ return { resolved: true, node: target, document, nodePointer };
160
+ }
161
+ }
162
+ function pushRef(head, node) {
163
+ return { prev: head, node };
164
+ }
165
+ async function checkRefsResolve(document, resolver, styleguide) {
166
+ const specVersion = openapiCore.detectSpec(document.parsed);
167
+ const types = openapiCore.normalizeTypes(
168
+ styleguide.extendTypes(openapiCore.getTypes(specVersion), specVersion),
169
+ styleguide
170
+ );
171
+ const rootType = types.Root;
172
+ const specExtension = types.SpecExtension;
173
+ if (rootType === void 0 || specExtension === void 0) {
174
+ throw new Error(`Redocly has no Root or SpecExtension type for ${specVersion}`);
175
+ }
176
+ const walk = new RefResolutionWalk(resolver, specExtension);
177
+ let rootFailure;
178
+ try {
179
+ walk.walkFrom(document.parsed, document, "#/", rootType);
180
+ } catch (error) {
181
+ rootFailure = { status: "rejected", reason: error };
182
+ }
183
+ const lookupFailure = await walk.settle();
184
+ const failure = rootFailure ?? lookupFailure;
185
+ if (failure !== void 0) {
186
+ throw failure.reason;
187
+ }
188
+ }
189
+
190
+ exports.checkRefsResolve = checkRefsResolve;
191
+ //# sourceMappingURL=ref-resolution-check.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref-resolution-check.cjs","sources":["../../../src/adapters/redocly-loader/ref-resolution-check.ts"],"sourcesContent":["import {\n type BaseResolver,\n type Document,\n detectSpec,\n getTypes,\n isRef,\n normalizeTypes,\n type Config as RedoclyConfig,\n} from \"@redocly/openapi-core\";\nimport {\n ANCHOR_REF,\n escapePointer,\n hasRef,\n isExternalValue,\n isNamedType,\n joinPointer,\n nextTick,\n parseRef,\n propertyType,\n REF_CYCLE_MESSAGE,\n type Ref,\n type RefStack,\n type ResolvedRef,\n UNKNOWN_TYPE,\n type WalkType,\n} from \"./ref-resolution-steps.js\";\n\n/**\n * Redocly 1.34 resolves `$ref`s with `Promise.all` over the lookups it has started and stops at\n * the first one that fails. Lookups it starts after that (the walk of a document that was still\n * loading) are never awaited: when one of them fails too, its rejection is unhandled, and Node.js\n * ends the process on an unhandled rejection (ADR-031). Redocly 2.x has the same loop.\n *\n * This is a port of Redocly's `resolveDocument` (`lib/resolve.js`, 1.34.22, MIT) that waits for\n * every lookup and then throws the first failure. The loader runs it before Redocly lints or\n * bundles, so Redocly only resolves documents in which no lookup fails. It walks the same nodes\n * with the same types and throws the same errors: the \"Self-referencing circular pointer\" Error,\n * the URIError of a malformed percent escape, and the TypeError of a key such as `valueOf`.\n */\n\n/** One walk of the documents a root reaches, with every `$ref` lookup it starts. */\nclass RefResolutionWalk {\n readonly #resolver: BaseResolver;\n /** Redocly's `SpecExtension` type: its values are walked like values of no known type. */\n readonly specExtension: WalkType;\n /** Walked nodes per type name; a node walked again with the same type starts the same lookups. */\n readonly #seenNodes = new Map<string, WeakSet<object>>();\n readonly #lookups: Promise<void>[] = [];\n\n constructor(resolver: BaseResolver, specExtension: WalkType) {\n this.#resolver = resolver;\n this.specExtension = specExtension;\n }\n\n isWalkedLikeUnknown(type: WalkType): boolean {\n return type === UNKNOWN_TYPE || type === this.specExtension;\n }\n\n /** Wait for every lookup, including those started while waiting; return the first failure. */\n async settle(): Promise<PromiseRejectedResult | undefined> {\n let settled: PromiseSettledResult<void>[];\n do {\n settled = await Promise.allSettled(this.#lookups);\n } while (settled.length !== this.#lookups.length);\n return settled.find((result) => result.status === \"rejected\");\n }\n\n walkFrom(node: unknown, document: Document, pointer: string, type: WalkType): void {\n new DocumentWalk(this, document).walk(node, type, document.source.absoluteRef + pointer);\n }\n\n /** Whether the node was walked with this type before; marks it walked. */\n isSeen(type: WalkType, node: object): boolean {\n let seen = this.#seenNodes.get(type.name);\n if (seen === undefined) {\n seen = new WeakSet();\n this.#seenNodes.set(type.name, seen);\n }\n if (seen.has(node)) {\n return true;\n }\n seen.add(node);\n return false;\n }\n\n track(lookup: Promise<ResolvedRef>, type: WalkType): void {\n this.#lookups.push(\n lookup.then((resolved) => {\n if (resolved.resolved && resolved.document !== undefined) {\n this.walkFrom(resolved.node, resolved.document, resolved.nodePointer ?? \"#/\", type);\n }\n }),\n );\n }\n\n async loadDocument(base: Document, uri: string): Promise<Document | undefined> {\n try {\n return (await this.#resolver.resolveDocument(base.source.absoluteRef, uri)) as Document;\n } catch {\n // Redocly records the failed lookup as an unresolved `$ref`; the lint reports it.\n return undefined;\n }\n }\n}\n\n/** The walk of one document from one node, with the anchors it collects (as in Redocly). */\nclass DocumentWalk {\n readonly #walk: RefResolutionWalk;\n readonly #document: Document;\n readonly #anchors = new Map<string, unknown>();\n\n constructor(walk: RefResolutionWalk, document: Document) {\n this.#walk = walk;\n this.#document = document;\n }\n\n walk(node: unknown, type: WalkType, absoluteRef: string): void {\n if (typeof node !== \"object\" || node === null || this.#walk.isSeen(type, node)) {\n return;\n }\n const anchor = (node as { $anchor?: unknown }).$anchor;\n if (Object.hasOwn(node, \"$anchor\") && anchor) {\n this.#anchors.set(`#${String(anchor)}`, node);\n }\n if (Array.isArray(node)) {\n this.#walkItems(node, type, absoluteRef);\n return;\n }\n for (const name of Object.keys(node)) {\n const value = (node as Record<string, unknown>)[name];\n const [propType, walked] = propertyType(type, name, value, this.#walk.specExtension);\n if (isNamedType(propType) && typeof walked === \"object\") {\n this.walk(walked, propType, joinPointer(absoluteRef, escapePointer(name)));\n }\n }\n if (isRef(node)) {\n this.#walk.track(this.#follow(this.#document, node, { prev: null, node }), type);\n }\n if (isExternalValue(node)) {\n const ref = { $ref: node.externalValue };\n this.#walk.track(this.#follow(this.#document, ref, { prev: null, node }), type);\n }\n }\n\n #walkItems(items: readonly unknown[], type: WalkType, absoluteRef: string): void {\n const itemsType = type.items;\n if (itemsType === undefined && !this.#walk.isWalkedLikeUnknown(type)) {\n return;\n }\n items.forEach((item, index) => {\n const pointer = joinPointer(absoluteRef, index);\n const itemType = typeof itemsType === \"function\" ? itemsType(item, pointer) : itemsType;\n if (itemType === undefined && !this.#walk.isWalkedLikeUnknown(type)) {\n return;\n }\n this.walk(item, isNamedType(itemType) ? itemType : UNKNOWN_TYPE, pointer);\n });\n }\n\n async #follow(document: Document, ref: Ref, refStack: RefStack): Promise<ResolvedRef> {\n if (hasRef(refStack.prev, ref)) {\n throw new Error(REF_CYCLE_MESSAGE);\n }\n if (ANCHOR_REF.test(ref.$ref)) {\n await nextTick();\n return { resolved: true, node: this.#anchors.get(ref.$ref), document, nodePointer: ref.$ref };\n }\n const { uri, pointer } = parseRef(ref.$ref);\n const targetDocument = uri === null ? document : await this.#walk.loadDocument(document, uri);\n if (targetDocument === undefined) {\n return { resolved: false };\n }\n const found = await this.#followSegments(targetDocument, pointer, refStack);\n if (found.document !== undefined && isRef(found.node)) {\n return this.#follow(found.document, found.node, pushRef(refStack, found.node));\n }\n return found;\n }\n\n /** Follow the pointer segments; a `$ref` met on the way is resolved first. */\n async #followSegments(\n targetDocument: Document,\n segments: readonly string[],\n refStack: RefStack,\n ): Promise<ResolvedRef> {\n let document = targetDocument;\n let nodePointer = \"#/\";\n let target: unknown = targetDocument.parsed;\n for (const segment of segments) {\n if (typeof target !== \"object\") {\n return { resolved: true, node: undefined, document, nodePointer };\n }\n // A `null` target throws a TypeError here, as it does in Redocly.\n const step = (target as Record<string, unknown>)[segment];\n if (step !== undefined) {\n target = step;\n } else if (isRef(target)) {\n const resolved = await this.#follow(document, target, pushRef(refStack, target));\n document = resolved.document ?? document;\n if (typeof resolved.node !== \"object\") {\n return {\n resolved: true,\n node: undefined,\n document,\n nodePointer: resolved.nodePointer ?? nodePointer,\n };\n }\n target = (resolved.node as Record<string, unknown>)[segment];\n nodePointer = resolved.nodePointer ?? nodePointer;\n } else {\n return { resolved: true, node: undefined, document, nodePointer };\n }\n nodePointer = joinPointer(nodePointer, escapePointer(segment));\n }\n return { resolved: true, node: target, document, nodePointer };\n }\n}\n\nfunction pushRef(head: RefStack, node: unknown): RefStack {\n return { prev: head, node };\n}\n\n/**\n * Resolve every `$ref` the document reaches, as Redocly's lint and bundle do, and throw the first\n * failure only after all lookups have finished. Documents are loaded through the given resolver,\n * whose cache Redocly then reuses.\n */\nexport async function checkRefsResolve(\n document: Document,\n resolver: BaseResolver,\n styleguide: RedoclyConfig[\"styleguide\"],\n): Promise<void> {\n const specVersion = detectSpec(document.parsed);\n const types = normalizeTypes(\n styleguide.extendTypes(getTypes(specVersion), specVersion),\n styleguide,\n );\n const rootType = types.Root;\n const specExtension = types.SpecExtension;\n if (rootType === undefined || specExtension === undefined) {\n // Redocly gives every spec version both types; a missing one is a Redocly change to port.\n throw new Error(`Redocly has no Root or SpecExtension type for ${specVersion}`);\n }\n const walk = new RefResolutionWalk(resolver, specExtension);\n let rootFailure: PromiseRejectedResult | undefined;\n try {\n walk.walkFrom(document.parsed, document, \"#/\", rootType);\n } catch (error) {\n // The walk of the root can fail after it started lookups; they are awaited all the same.\n rootFailure = { status: \"rejected\", reason: error };\n }\n const lookupFailure = await walk.settle();\n const failure = rootFailure ?? lookupFailure;\n if (failure !== undefined) {\n throw failure.reason;\n }\n}\n"],"names":["UNKNOWN_TYPE","propertyType","isNamedType","joinPointer","escapePointer","isRef","isExternalValue","hasRef","REF_CYCLE_MESSAGE","ANCHOR_REF","nextTick","parseRef","detectSpec","normalizeTypes","getTypes"],"mappings":"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"}
@@ -0,0 +1,189 @@
1
+ import { detectSpec, normalizeTypes, getTypes, isRef } from '@redocly/openapi-core';
2
+ import { UNKNOWN_TYPE, propertyType, isNamedType, joinPointer, escapePointer, isExternalValue, hasRef, REF_CYCLE_MESSAGE, ANCHOR_REF, nextTick, parseRef } from './ref-resolution-steps.mjs';
3
+
4
+ class RefResolutionWalk {
5
+ #resolver;
6
+ /** Redocly's `SpecExtension` type: its values are walked like values of no known type. */
7
+ specExtension;
8
+ /** Walked nodes per type name; a node walked again with the same type starts the same lookups. */
9
+ #seenNodes = /* @__PURE__ */ new Map();
10
+ #lookups = [];
11
+ constructor(resolver, specExtension) {
12
+ this.#resolver = resolver;
13
+ this.specExtension = specExtension;
14
+ }
15
+ isWalkedLikeUnknown(type) {
16
+ return type === UNKNOWN_TYPE || type === this.specExtension;
17
+ }
18
+ /** Wait for every lookup, including those started while waiting; return the first failure. */
19
+ async settle() {
20
+ let settled;
21
+ do {
22
+ settled = await Promise.allSettled(this.#lookups);
23
+ } while (settled.length !== this.#lookups.length);
24
+ return settled.find((result) => result.status === "rejected");
25
+ }
26
+ walkFrom(node, document, pointer, type) {
27
+ new DocumentWalk(this, document).walk(node, type, document.source.absoluteRef + pointer);
28
+ }
29
+ /** Whether the node was walked with this type before; marks it walked. */
30
+ isSeen(type, node) {
31
+ let seen = this.#seenNodes.get(type.name);
32
+ if (seen === void 0) {
33
+ seen = /* @__PURE__ */ new WeakSet();
34
+ this.#seenNodes.set(type.name, seen);
35
+ }
36
+ if (seen.has(node)) {
37
+ return true;
38
+ }
39
+ seen.add(node);
40
+ return false;
41
+ }
42
+ track(lookup, type) {
43
+ this.#lookups.push(
44
+ lookup.then((resolved) => {
45
+ if (resolved.resolved && resolved.document !== void 0) {
46
+ this.walkFrom(resolved.node, resolved.document, resolved.nodePointer ?? "#/", type);
47
+ }
48
+ })
49
+ );
50
+ }
51
+ async loadDocument(base, uri) {
52
+ try {
53
+ return await this.#resolver.resolveDocument(base.source.absoluteRef, uri);
54
+ } catch {
55
+ return void 0;
56
+ }
57
+ }
58
+ }
59
+ class DocumentWalk {
60
+ #walk;
61
+ #document;
62
+ #anchors = /* @__PURE__ */ new Map();
63
+ constructor(walk, document) {
64
+ this.#walk = walk;
65
+ this.#document = document;
66
+ }
67
+ walk(node, type, absoluteRef) {
68
+ if (typeof node !== "object" || node === null || this.#walk.isSeen(type, node)) {
69
+ return;
70
+ }
71
+ const anchor = node.$anchor;
72
+ if (Object.hasOwn(node, "$anchor") && anchor) {
73
+ this.#anchors.set(`#${String(anchor)}`, node);
74
+ }
75
+ if (Array.isArray(node)) {
76
+ this.#walkItems(node, type, absoluteRef);
77
+ return;
78
+ }
79
+ for (const name of Object.keys(node)) {
80
+ const value = node[name];
81
+ const [propType, walked] = propertyType(type, name, value, this.#walk.specExtension);
82
+ if (isNamedType(propType) && typeof walked === "object") {
83
+ this.walk(walked, propType, joinPointer(absoluteRef, escapePointer(name)));
84
+ }
85
+ }
86
+ if (isRef(node)) {
87
+ this.#walk.track(this.#follow(this.#document, node, { prev: null, node }), type);
88
+ }
89
+ if (isExternalValue(node)) {
90
+ const ref = { $ref: node.externalValue };
91
+ this.#walk.track(this.#follow(this.#document, ref, { prev: null, node }), type);
92
+ }
93
+ }
94
+ #walkItems(items, type, absoluteRef) {
95
+ const itemsType = type.items;
96
+ if (itemsType === void 0 && !this.#walk.isWalkedLikeUnknown(type)) {
97
+ return;
98
+ }
99
+ items.forEach((item, index) => {
100
+ const pointer = joinPointer(absoluteRef, index);
101
+ const itemType = typeof itemsType === "function" ? itemsType(item, pointer) : itemsType;
102
+ if (itemType === void 0 && !this.#walk.isWalkedLikeUnknown(type)) {
103
+ return;
104
+ }
105
+ this.walk(item, isNamedType(itemType) ? itemType : UNKNOWN_TYPE, pointer);
106
+ });
107
+ }
108
+ async #follow(document, ref, refStack) {
109
+ if (hasRef(refStack.prev, ref)) {
110
+ throw new Error(REF_CYCLE_MESSAGE);
111
+ }
112
+ if (ANCHOR_REF.test(ref.$ref)) {
113
+ await nextTick();
114
+ return { resolved: true, node: this.#anchors.get(ref.$ref), document, nodePointer: ref.$ref };
115
+ }
116
+ const { uri, pointer } = parseRef(ref.$ref);
117
+ const targetDocument = uri === null ? document : await this.#walk.loadDocument(document, uri);
118
+ if (targetDocument === void 0) {
119
+ return { resolved: false };
120
+ }
121
+ const found = await this.#followSegments(targetDocument, pointer, refStack);
122
+ if (found.document !== void 0 && isRef(found.node)) {
123
+ return this.#follow(found.document, found.node, pushRef(refStack, found.node));
124
+ }
125
+ return found;
126
+ }
127
+ /** Follow the pointer segments; a `$ref` met on the way is resolved first. */
128
+ async #followSegments(targetDocument, segments, refStack) {
129
+ let document = targetDocument;
130
+ let nodePointer = "#/";
131
+ let target = targetDocument.parsed;
132
+ for (const segment of segments) {
133
+ if (typeof target !== "object") {
134
+ return { resolved: true, node: void 0, document, nodePointer };
135
+ }
136
+ const step = target[segment];
137
+ if (step !== void 0) {
138
+ target = step;
139
+ } else if (isRef(target)) {
140
+ const resolved = await this.#follow(document, target, pushRef(refStack, target));
141
+ document = resolved.document ?? document;
142
+ if (typeof resolved.node !== "object") {
143
+ return {
144
+ resolved: true,
145
+ node: void 0,
146
+ document,
147
+ nodePointer: resolved.nodePointer ?? nodePointer
148
+ };
149
+ }
150
+ target = resolved.node[segment];
151
+ nodePointer = resolved.nodePointer ?? nodePointer;
152
+ } else {
153
+ return { resolved: true, node: void 0, document, nodePointer };
154
+ }
155
+ nodePointer = joinPointer(nodePointer, escapePointer(segment));
156
+ }
157
+ return { resolved: true, node: target, document, nodePointer };
158
+ }
159
+ }
160
+ function pushRef(head, node) {
161
+ return { prev: head, node };
162
+ }
163
+ async function checkRefsResolve(document, resolver, styleguide) {
164
+ const specVersion = detectSpec(document.parsed);
165
+ const types = normalizeTypes(
166
+ styleguide.extendTypes(getTypes(specVersion), specVersion),
167
+ styleguide
168
+ );
169
+ const rootType = types.Root;
170
+ const specExtension = types.SpecExtension;
171
+ if (rootType === void 0 || specExtension === void 0) {
172
+ throw new Error(`Redocly has no Root or SpecExtension type for ${specVersion}`);
173
+ }
174
+ const walk = new RefResolutionWalk(resolver, specExtension);
175
+ let rootFailure;
176
+ try {
177
+ walk.walkFrom(document.parsed, document, "#/", rootType);
178
+ } catch (error) {
179
+ rootFailure = { status: "rejected", reason: error };
180
+ }
181
+ const lookupFailure = await walk.settle();
182
+ const failure = rootFailure ?? lookupFailure;
183
+ if (failure !== void 0) {
184
+ throw failure.reason;
185
+ }
186
+ }
187
+
188
+ export { checkRefsResolve };
189
+ //# sourceMappingURL=ref-resolution-check.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref-resolution-check.mjs","sources":["../../../src/adapters/redocly-loader/ref-resolution-check.ts"],"sourcesContent":["import {\n type BaseResolver,\n type Document,\n detectSpec,\n getTypes,\n isRef,\n normalizeTypes,\n type Config as RedoclyConfig,\n} from \"@redocly/openapi-core\";\nimport {\n ANCHOR_REF,\n escapePointer,\n hasRef,\n isExternalValue,\n isNamedType,\n joinPointer,\n nextTick,\n parseRef,\n propertyType,\n REF_CYCLE_MESSAGE,\n type Ref,\n type RefStack,\n type ResolvedRef,\n UNKNOWN_TYPE,\n type WalkType,\n} from \"./ref-resolution-steps.js\";\n\n/**\n * Redocly 1.34 resolves `$ref`s with `Promise.all` over the lookups it has started and stops at\n * the first one that fails. Lookups it starts after that (the walk of a document that was still\n * loading) are never awaited: when one of them fails too, its rejection is unhandled, and Node.js\n * ends the process on an unhandled rejection (ADR-031). Redocly 2.x has the same loop.\n *\n * This is a port of Redocly's `resolveDocument` (`lib/resolve.js`, 1.34.22, MIT) that waits for\n * every lookup and then throws the first failure. The loader runs it before Redocly lints or\n * bundles, so Redocly only resolves documents in which no lookup fails. It walks the same nodes\n * with the same types and throws the same errors: the \"Self-referencing circular pointer\" Error,\n * the URIError of a malformed percent escape, and the TypeError of a key such as `valueOf`.\n */\n\n/** One walk of the documents a root reaches, with every `$ref` lookup it starts. */\nclass RefResolutionWalk {\n readonly #resolver: BaseResolver;\n /** Redocly's `SpecExtension` type: its values are walked like values of no known type. */\n readonly specExtension: WalkType;\n /** Walked nodes per type name; a node walked again with the same type starts the same lookups. */\n readonly #seenNodes = new Map<string, WeakSet<object>>();\n readonly #lookups: Promise<void>[] = [];\n\n constructor(resolver: BaseResolver, specExtension: WalkType) {\n this.#resolver = resolver;\n this.specExtension = specExtension;\n }\n\n isWalkedLikeUnknown(type: WalkType): boolean {\n return type === UNKNOWN_TYPE || type === this.specExtension;\n }\n\n /** Wait for every lookup, including those started while waiting; return the first failure. */\n async settle(): Promise<PromiseRejectedResult | undefined> {\n let settled: PromiseSettledResult<void>[];\n do {\n settled = await Promise.allSettled(this.#lookups);\n } while (settled.length !== this.#lookups.length);\n return settled.find((result) => result.status === \"rejected\");\n }\n\n walkFrom(node: unknown, document: Document, pointer: string, type: WalkType): void {\n new DocumentWalk(this, document).walk(node, type, document.source.absoluteRef + pointer);\n }\n\n /** Whether the node was walked with this type before; marks it walked. */\n isSeen(type: WalkType, node: object): boolean {\n let seen = this.#seenNodes.get(type.name);\n if (seen === undefined) {\n seen = new WeakSet();\n this.#seenNodes.set(type.name, seen);\n }\n if (seen.has(node)) {\n return true;\n }\n seen.add(node);\n return false;\n }\n\n track(lookup: Promise<ResolvedRef>, type: WalkType): void {\n this.#lookups.push(\n lookup.then((resolved) => {\n if (resolved.resolved && resolved.document !== undefined) {\n this.walkFrom(resolved.node, resolved.document, resolved.nodePointer ?? \"#/\", type);\n }\n }),\n );\n }\n\n async loadDocument(base: Document, uri: string): Promise<Document | undefined> {\n try {\n return (await this.#resolver.resolveDocument(base.source.absoluteRef, uri)) as Document;\n } catch {\n // Redocly records the failed lookup as an unresolved `$ref`; the lint reports it.\n return undefined;\n }\n }\n}\n\n/** The walk of one document from one node, with the anchors it collects (as in Redocly). */\nclass DocumentWalk {\n readonly #walk: RefResolutionWalk;\n readonly #document: Document;\n readonly #anchors = new Map<string, unknown>();\n\n constructor(walk: RefResolutionWalk, document: Document) {\n this.#walk = walk;\n this.#document = document;\n }\n\n walk(node: unknown, type: WalkType, absoluteRef: string): void {\n if (typeof node !== \"object\" || node === null || this.#walk.isSeen(type, node)) {\n return;\n }\n const anchor = (node as { $anchor?: unknown }).$anchor;\n if (Object.hasOwn(node, \"$anchor\") && anchor) {\n this.#anchors.set(`#${String(anchor)}`, node);\n }\n if (Array.isArray(node)) {\n this.#walkItems(node, type, absoluteRef);\n return;\n }\n for (const name of Object.keys(node)) {\n const value = (node as Record<string, unknown>)[name];\n const [propType, walked] = propertyType(type, name, value, this.#walk.specExtension);\n if (isNamedType(propType) && typeof walked === \"object\") {\n this.walk(walked, propType, joinPointer(absoluteRef, escapePointer(name)));\n }\n }\n if (isRef(node)) {\n this.#walk.track(this.#follow(this.#document, node, { prev: null, node }), type);\n }\n if (isExternalValue(node)) {\n const ref = { $ref: node.externalValue };\n this.#walk.track(this.#follow(this.#document, ref, { prev: null, node }), type);\n }\n }\n\n #walkItems(items: readonly unknown[], type: WalkType, absoluteRef: string): void {\n const itemsType = type.items;\n if (itemsType === undefined && !this.#walk.isWalkedLikeUnknown(type)) {\n return;\n }\n items.forEach((item, index) => {\n const pointer = joinPointer(absoluteRef, index);\n const itemType = typeof itemsType === \"function\" ? itemsType(item, pointer) : itemsType;\n if (itemType === undefined && !this.#walk.isWalkedLikeUnknown(type)) {\n return;\n }\n this.walk(item, isNamedType(itemType) ? itemType : UNKNOWN_TYPE, pointer);\n });\n }\n\n async #follow(document: Document, ref: Ref, refStack: RefStack): Promise<ResolvedRef> {\n if (hasRef(refStack.prev, ref)) {\n throw new Error(REF_CYCLE_MESSAGE);\n }\n if (ANCHOR_REF.test(ref.$ref)) {\n await nextTick();\n return { resolved: true, node: this.#anchors.get(ref.$ref), document, nodePointer: ref.$ref };\n }\n const { uri, pointer } = parseRef(ref.$ref);\n const targetDocument = uri === null ? document : await this.#walk.loadDocument(document, uri);\n if (targetDocument === undefined) {\n return { resolved: false };\n }\n const found = await this.#followSegments(targetDocument, pointer, refStack);\n if (found.document !== undefined && isRef(found.node)) {\n return this.#follow(found.document, found.node, pushRef(refStack, found.node));\n }\n return found;\n }\n\n /** Follow the pointer segments; a `$ref` met on the way is resolved first. */\n async #followSegments(\n targetDocument: Document,\n segments: readonly string[],\n refStack: RefStack,\n ): Promise<ResolvedRef> {\n let document = targetDocument;\n let nodePointer = \"#/\";\n let target: unknown = targetDocument.parsed;\n for (const segment of segments) {\n if (typeof target !== \"object\") {\n return { resolved: true, node: undefined, document, nodePointer };\n }\n // A `null` target throws a TypeError here, as it does in Redocly.\n const step = (target as Record<string, unknown>)[segment];\n if (step !== undefined) {\n target = step;\n } else if (isRef(target)) {\n const resolved = await this.#follow(document, target, pushRef(refStack, target));\n document = resolved.document ?? document;\n if (typeof resolved.node !== \"object\") {\n return {\n resolved: true,\n node: undefined,\n document,\n nodePointer: resolved.nodePointer ?? nodePointer,\n };\n }\n target = (resolved.node as Record<string, unknown>)[segment];\n nodePointer = resolved.nodePointer ?? nodePointer;\n } else {\n return { resolved: true, node: undefined, document, nodePointer };\n }\n nodePointer = joinPointer(nodePointer, escapePointer(segment));\n }\n return { resolved: true, node: target, document, nodePointer };\n }\n}\n\nfunction pushRef(head: RefStack, node: unknown): RefStack {\n return { prev: head, node };\n}\n\n/**\n * Resolve every `$ref` the document reaches, as Redocly's lint and bundle do, and throw the first\n * failure only after all lookups have finished. Documents are loaded through the given resolver,\n * whose cache Redocly then reuses.\n */\nexport async function checkRefsResolve(\n document: Document,\n resolver: BaseResolver,\n styleguide: RedoclyConfig[\"styleguide\"],\n): Promise<void> {\n const specVersion = detectSpec(document.parsed);\n const types = normalizeTypes(\n styleguide.extendTypes(getTypes(specVersion), specVersion),\n styleguide,\n );\n const rootType = types.Root;\n const specExtension = types.SpecExtension;\n if (rootType === undefined || specExtension === undefined) {\n // Redocly gives every spec version both types; a missing one is a Redocly change to port.\n throw new Error(`Redocly has no Root or SpecExtension type for ${specVersion}`);\n }\n const walk = new RefResolutionWalk(resolver, specExtension);\n let rootFailure: PromiseRejectedResult | undefined;\n try {\n walk.walkFrom(document.parsed, document, \"#/\", rootType);\n } catch (error) {\n // The walk of the root can fail after it started lookups; they are awaited all the same.\n rootFailure = { status: \"rejected\", reason: error };\n }\n const lookupFailure = await walk.settle();\n const failure = rootFailure ?? lookupFailure;\n if (failure !== undefined) {\n throw failure.reason;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const jsonPointer = require('../../domain/openapi/json-pointer.cjs');
4
+
5
+ const UNKNOWN_TYPE = { name: "unknown", properties: {} };
6
+ const RESOLVABLE_SCALAR_TYPE = { name: "scalar", properties: {} };
7
+ const ANCHOR_REF = /^#[A-Za-z][A-Za-z0-9\-_:.]*$/;
8
+ const REF_CYCLE_MESSAGE = "Self-referencing circular pointer";
9
+ function isNamedType(type) {
10
+ return typeof type?.name === "string";
11
+ }
12
+ function joinPointer(base, key) {
13
+ const start = base === "" ? "#/" : base;
14
+ return start.endsWith("/") ? `${start}${key}` : `${start}/${key}`;
15
+ }
16
+ function escapePointer(fragment) {
17
+ return fragment.replaceAll("~", "~0").replaceAll("/", "~1");
18
+ }
19
+ function parseRef(ref) {
20
+ const [uri = ""] = ref.split("#/");
21
+ return {
22
+ uri: (uri.endsWith("#") ? uri.slice(0, -1) : uri) || null,
23
+ pointer: jsonPointer.parseRefPointer(ref)
24
+ };
25
+ }
26
+ function hasRef(head, node) {
27
+ for (let entry = head; entry !== null; entry = entry.prev) {
28
+ if (entry.node === node) {
29
+ return true;
30
+ }
31
+ }
32
+ return false;
33
+ }
34
+ function isExternalValue(node) {
35
+ return typeof node.externalValue === "string";
36
+ }
37
+ function nextTick() {
38
+ return new Promise((resolve) => setTimeout(resolve));
39
+ }
40
+ function propertyType(type, name, value, specExtension) {
41
+ let declared = type.properties[name];
42
+ if (declared === void 0) {
43
+ declared = type.additionalProperties;
44
+ }
45
+ let propType = typeof declared === "function" ? declared(value, name) : declared;
46
+ if (propType === void 0) {
47
+ propType = UNKNOWN_TYPE;
48
+ }
49
+ if (type.extensionsPrefix && name.startsWith(type.extensionsPrefix) && propType === UNKNOWN_TYPE) {
50
+ return [specExtension, value];
51
+ }
52
+ if (!isNamedType(propType) && propType?.directResolveAs) {
53
+ return [propType.directResolveAs, { $ref: value }];
54
+ }
55
+ if (propType && !isNamedType(propType) && propType.resolvable !== false) {
56
+ return [RESOLVABLE_SCALAR_TYPE, value];
57
+ }
58
+ return [propType, value];
59
+ }
60
+
61
+ exports.ANCHOR_REF = ANCHOR_REF;
62
+ exports.REF_CYCLE_MESSAGE = REF_CYCLE_MESSAGE;
63
+ exports.RESOLVABLE_SCALAR_TYPE = RESOLVABLE_SCALAR_TYPE;
64
+ exports.UNKNOWN_TYPE = UNKNOWN_TYPE;
65
+ exports.escapePointer = escapePointer;
66
+ exports.hasRef = hasRef;
67
+ exports.isExternalValue = isExternalValue;
68
+ exports.isNamedType = isNamedType;
69
+ exports.joinPointer = joinPointer;
70
+ exports.nextTick = nextTick;
71
+ exports.parseRef = parseRef;
72
+ exports.propertyType = propertyType;
73
+ //# sourceMappingURL=ref-resolution-steps.cjs.map