@trojs/openapi-dereference 1.1.2 → 1.2.0

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/dereference.js +66 -35
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@trojs/openapi-dereference",
3
3
  "description": "OpenAPI dereference",
4
- "version": "1.1.2",
4
+ "version": "1.2.0",
5
5
  "author": {
6
6
  "name": "Pieter Wigboldus",
7
7
  "url": "https://trojs.org/"
@@ -1,6 +1,3 @@
1
- /* eslint-disable sonarjs/cognitive-complexity */
2
- /* eslint-disable no-param-reassign */
3
- /* eslint-disable no-restricted-syntax */
4
1
  import { klona } from './klona.js'
5
2
  import { resolveRefSync } from './resolveRef.js'
6
3
 
@@ -9,51 +6,85 @@ import { resolveRefSync } from './resolveRef.js'
9
6
  * @typedef {import('./types').DereferencedJSONSchema} DereferencedJSONSchema
10
7
  */
11
8
 
9
+ const PROHIBITED_KEYS = new Set(['__proto__', 'constructor', 'prototype'])
12
10
  const cache = new Map()
13
11
 
12
+ /**
13
+ * Removes prohibited keys from an object (shallow).
14
+ * @param {object} obj
15
+ * @returns {object}
16
+ */
17
+ function filterProhibitedKeys (obj) {
18
+ return Object.fromEntries(
19
+ Object.entries(obj).filter(([key]) => !PROHIBITED_KEYS.has(key))
20
+ )
21
+ }
22
+
14
23
  /**
15
24
  * Resolves all $ref pointers in a schema and returns a new schema without any $ref pointers.
16
- * @param {JSONSchema} schema
17
- * @returns {DereferencedJSONSchema}
25
+ * Handles circular references and deeply nested $refs.
26
+ * @param {JSONSchema} schema - The JSON schema to dereference.
27
+ * @returns {DereferencedJSONSchema} The dereferenced schema.
18
28
  */
19
29
  export const dereferenceSync = (schema) => {
20
- if (cache.has(schema)) {
21
- return cache.get(schema)
22
- }
30
+ if (cache.has(schema)) return cache.get(schema)
31
+
32
+ // Filter prohibited keys at the root level
33
+ const filtered = typeof schema === 'object' && schema !== null && !Array.isArray(schema)
34
+ ? filterProhibitedKeys(schema)
35
+ : schema
23
36
 
24
- const visitedNodes = new Set()
25
- const cloned = klona(schema)
37
+ const cloned = klona(filtered)
38
+ const seen = new WeakMap()
26
39
 
40
+ /**
41
+ * Recursively resolves a value (object, array, or primitive).
42
+ * @param {any} current - The current value to resolve.
43
+ * @param {string} path - The current JSON pointer path.
44
+ * @returns {any} The resolved value.
45
+ */
27
46
  const resolve = (current, path) => {
28
- if (typeof current === 'object' && current !== null) {
29
- // make sure we don't visit the same node twice
30
- if (visitedNodes.has(current)) {
31
- return current
47
+ if (typeof current !== 'object' || current === null) return current
48
+
49
+ // Handle circular references
50
+ if (seen.has(current)) return seen.get(current)
51
+
52
+ // Handle arrays
53
+ if (Array.isArray(current)) {
54
+ const arr = current.map((item, i) => resolve(item, `${path}/${i}`))
55
+ seen.set(current, arr)
56
+ return arr
57
+ }
58
+
59
+ // Handle $ref
60
+ if ('$ref' in current && typeof current.$ref === 'string') {
61
+ const ref = resolveRefSync(cloned, current.$ref)
62
+ if (!ref) {
63
+ return null
64
+ }
65
+ if (seen.has(ref)) {
66
+ return seen.get(ref)
32
67
  }
33
- visitedNodes.add(current)
34
-
35
- if (Array.isArray(current)) {
36
- // array
37
- for (let index = 0; index < current.length; index++) {
38
- current[index] = resolve(current[index], `${path}/${index}`)
39
- }
40
- } else {
41
- // object
42
- if ('$ref' in current && typeof current.$ref === 'string') {
43
- let ref = current
44
- do {
45
- ref = resolveRefSync(cloned, ref.$ref)
46
- } while (ref?.$ref)
47
- return ref
48
- }
49
-
50
- for (const key in current) {
51
- current[key] = resolve(current[key], `${path}/${key}`)
52
- }
68
+ if (Array.isArray(ref)) {
69
+ const resolvedArray = resolve(ref, current.$ref)
70
+ seen.set(ref, resolvedArray)
71
+ return resolvedArray
53
72
  }
73
+ const placeholder = {}
74
+ seen.set(current, placeholder)
75
+ const resolved = resolve(ref, current.$ref)
76
+ Object.assign(placeholder, resolved)
77
+ return resolved
54
78
  }
55
79
 
56
- return current
80
+ // Handle objects
81
+ const obj = Object.fromEntries(
82
+ Object.entries(current)
83
+ .filter(([key]) => !PROHIBITED_KEYS.has(key))
84
+ .map(([key, value]) => [key, resolve(value, `${path}/${key}`)])
85
+ )
86
+ seen.set(current, obj)
87
+ return obj
57
88
  }
58
89
 
59
90
  const result = resolve(cloned, '#')