fast-json-stringify 7.1.3 → 7.1.4
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/index.js +54 -2
- package/package.json +1 -1
- package/test/ref.test.js +227 -0
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/* eslint no-prototype-builtins: 0 */
|
|
4
4
|
|
|
5
5
|
const { RefResolver } = require('json-schema-ref-resolver')
|
|
6
|
+
const { MergeError } = require('@fastify/merge-json-schemas')
|
|
6
7
|
|
|
7
8
|
const Serializer = require('./lib/serializer')
|
|
8
9
|
const Validator = require('./lib/validator')
|
|
@@ -87,6 +88,19 @@ const validLargeArrayMechanisms = new Set([
|
|
|
87
88
|
'json-stringify'
|
|
88
89
|
])
|
|
89
90
|
|
|
91
|
+
// Keywords that do not change the output, so a $ref carrying only these
|
|
92
|
+
// can still be dereferenced directly instead of merged with its target.
|
|
93
|
+
const IGNORED_REF_SIBLING_KEYWORDS = new Set([
|
|
94
|
+
'$ref',
|
|
95
|
+
'$comment',
|
|
96
|
+
'title',
|
|
97
|
+
'description',
|
|
98
|
+
'examples',
|
|
99
|
+
'deprecated',
|
|
100
|
+
'readOnly',
|
|
101
|
+
'writeOnly'
|
|
102
|
+
])
|
|
103
|
+
|
|
90
104
|
let schemaIdCounter = 0
|
|
91
105
|
|
|
92
106
|
function getMaxDepth (options) {
|
|
@@ -223,10 +237,12 @@ function isValidSchema (schema, name) {
|
|
|
223
237
|
|
|
224
238
|
function resolveRef (context, location) {
|
|
225
239
|
const seen = new Set()
|
|
240
|
+
const siblingLocations = []
|
|
226
241
|
let depth = 0
|
|
227
242
|
|
|
228
243
|
while (location.schema.$ref !== undefined) {
|
|
229
|
-
const
|
|
244
|
+
const refSchema = location.schema
|
|
245
|
+
const ref = refSchema.$ref
|
|
230
246
|
const locationRef = location.getSchemaRef()
|
|
231
247
|
|
|
232
248
|
if (seen.has(locationRef)) {
|
|
@@ -251,10 +267,45 @@ function resolveRef (context, location) {
|
|
|
251
267
|
throw new Error(`Cannot find reference "${ref}"`)
|
|
252
268
|
}
|
|
253
269
|
|
|
270
|
+
const siblingSchema = {}
|
|
271
|
+
for (const key in refSchema) {
|
|
272
|
+
if (!IGNORED_REF_SIBLING_KEYWORDS.has(key)) {
|
|
273
|
+
siblingSchema[key] = refSchema[key]
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (Object.keys(siblingSchema).length !== 0) {
|
|
278
|
+
siblingLocations.unshift(new Location(
|
|
279
|
+
cloneOriginSchema(context, siblingSchema, location.schemaId),
|
|
280
|
+
location.schemaId,
|
|
281
|
+
location.jsonPointer
|
|
282
|
+
))
|
|
283
|
+
}
|
|
284
|
+
|
|
254
285
|
location = new Location(schema, schemaId, jsonPointer)
|
|
255
286
|
}
|
|
256
287
|
|
|
257
|
-
|
|
288
|
+
if (siblingLocations.length === 0) {
|
|
289
|
+
return location
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Keyed by content, not by identity: a merged schema is cloned on every
|
|
293
|
+
// merge, so a recursive $ref would otherwise be merged again on each level.
|
|
294
|
+
const mergedSchemaKey = location.getSchemaRef() + JSON.stringify(siblingLocations.map((l) => l.schema))
|
|
295
|
+
|
|
296
|
+
let mergedSchemaId = context.mergedRefsIds.get(mergedSchemaKey)
|
|
297
|
+
if (mergedSchemaId === undefined) {
|
|
298
|
+
mergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
299
|
+
try {
|
|
300
|
+
mergeLocations(context, mergedSchemaId, [location, ...siblingLocations])
|
|
301
|
+
} catch (err) {
|
|
302
|
+
if (!(err instanceof MergeError)) throw err
|
|
303
|
+
return location
|
|
304
|
+
}
|
|
305
|
+
context.mergedRefsIds.set(mergedSchemaKey, mergedSchemaId)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return getMergedLocation(context, mergedSchemaId)
|
|
258
309
|
}
|
|
259
310
|
|
|
260
311
|
function getSchemaDependencies (refResolver, schemaId) {
|
|
@@ -375,6 +426,7 @@ function build (schema, options) {
|
|
|
375
426
|
validatorSchemasIds: new Set(),
|
|
376
427
|
validatorSchemaRefs: new Set(),
|
|
377
428
|
mergedSchemasIds: new Map(),
|
|
429
|
+
mergedRefsIds: new Map(),
|
|
378
430
|
maxDepth,
|
|
379
431
|
recursiveSchemas: new Set(),
|
|
380
432
|
recursivePaths: new Set(),
|
package/package.json
CHANGED
package/test/ref.test.js
CHANGED
|
@@ -2075,3 +2075,230 @@ test('ref nested', (t) => {
|
|
|
2075
2075
|
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
2076
2076
|
t.assert.equal(output, '{"str":"test"}')
|
|
2077
2077
|
})
|
|
2078
|
+
|
|
2079
|
+
test('ref internal - sibling keywords', (t) => {
|
|
2080
|
+
t.plan(3)
|
|
2081
|
+
|
|
2082
|
+
const schema = {
|
|
2083
|
+
definitions: {
|
|
2084
|
+
def: {
|
|
2085
|
+
type: 'object',
|
|
2086
|
+
properties: {
|
|
2087
|
+
str: {
|
|
2088
|
+
type: 'string'
|
|
2089
|
+
},
|
|
2090
|
+
num: {
|
|
2091
|
+
type: 'integer'
|
|
2092
|
+
}
|
|
2093
|
+
},
|
|
2094
|
+
required: ['str']
|
|
2095
|
+
}
|
|
2096
|
+
},
|
|
2097
|
+
type: 'object',
|
|
2098
|
+
properties: {
|
|
2099
|
+
obj: {
|
|
2100
|
+
$ref: '#/definitions/def',
|
|
2101
|
+
required: ['num']
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
const object = {
|
|
2107
|
+
obj: {
|
|
2108
|
+
str: 'test',
|
|
2109
|
+
num: 42
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
const stringify = build(schema)
|
|
2114
|
+
const output = stringify(object)
|
|
2115
|
+
|
|
2116
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
2117
|
+
t.assert.equal(output, '{"obj":{"str":"test","num":42}}')
|
|
2118
|
+
|
|
2119
|
+
t.assert.throws(() => {
|
|
2120
|
+
stringify({
|
|
2121
|
+
obj: {
|
|
2122
|
+
str: 'test'
|
|
2123
|
+
}
|
|
2124
|
+
})
|
|
2125
|
+
}, { message: '"num" is required!' })
|
|
2126
|
+
})
|
|
2127
|
+
|
|
2128
|
+
test('ref external - sibling keywords', (t) => {
|
|
2129
|
+
t.plan(3)
|
|
2130
|
+
|
|
2131
|
+
const externalSchema = {
|
|
2132
|
+
external: {
|
|
2133
|
+
definitions: {
|
|
2134
|
+
def: {
|
|
2135
|
+
type: 'object',
|
|
2136
|
+
properties: {
|
|
2137
|
+
str: {
|
|
2138
|
+
type: 'string'
|
|
2139
|
+
},
|
|
2140
|
+
num: {
|
|
2141
|
+
type: 'integer'
|
|
2142
|
+
}
|
|
2143
|
+
},
|
|
2144
|
+
required: ['str']
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
const schema = {
|
|
2151
|
+
type: 'object',
|
|
2152
|
+
properties: {
|
|
2153
|
+
obj: {
|
|
2154
|
+
$ref: 'external#/definitions/def',
|
|
2155
|
+
required: ['num']
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
const object = {
|
|
2161
|
+
obj: {
|
|
2162
|
+
str: 'test',
|
|
2163
|
+
num: 42
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
const stringify = build(schema, { schema: externalSchema })
|
|
2168
|
+
const output = stringify(object)
|
|
2169
|
+
|
|
2170
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
2171
|
+
t.assert.equal(output, '{"obj":{"str":"test","num":42}}')
|
|
2172
|
+
|
|
2173
|
+
t.assert.throws(() => {
|
|
2174
|
+
stringify({
|
|
2175
|
+
obj: {
|
|
2176
|
+
str: 'test'
|
|
2177
|
+
}
|
|
2178
|
+
})
|
|
2179
|
+
}, { message: '"num" is required!' })
|
|
2180
|
+
})
|
|
2181
|
+
|
|
2182
|
+
test('ref external - recursive sibling keywords', (t) => {
|
|
2183
|
+
t.plan(3)
|
|
2184
|
+
|
|
2185
|
+
const externalSchema = {
|
|
2186
|
+
node: {
|
|
2187
|
+
$id: 'node',
|
|
2188
|
+
type: 'object',
|
|
2189
|
+
properties: {
|
|
2190
|
+
str: {
|
|
2191
|
+
type: 'string'
|
|
2192
|
+
},
|
|
2193
|
+
next: {
|
|
2194
|
+
$ref: 'node#',
|
|
2195
|
+
required: ['str']
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
const schema = {
|
|
2202
|
+
type: 'object',
|
|
2203
|
+
properties: {
|
|
2204
|
+
root: {
|
|
2205
|
+
$ref: 'node#',
|
|
2206
|
+
required: ['str']
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
const object = {
|
|
2212
|
+
root: {
|
|
2213
|
+
str: 'test',
|
|
2214
|
+
next: {
|
|
2215
|
+
str: 'nested'
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
const stringify = build(schema, { schema: externalSchema })
|
|
2221
|
+
const output = stringify(object)
|
|
2222
|
+
|
|
2223
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
2224
|
+
t.assert.equal(output, '{"root":{"str":"test","next":{"str":"nested"}}}')
|
|
2225
|
+
|
|
2226
|
+
t.assert.throws(() => {
|
|
2227
|
+
stringify({
|
|
2228
|
+
root: {
|
|
2229
|
+
str: 'test',
|
|
2230
|
+
next: {}
|
|
2231
|
+
}
|
|
2232
|
+
})
|
|
2233
|
+
}, { message: '"str" is required!' })
|
|
2234
|
+
})
|
|
2235
|
+
|
|
2236
|
+
test('ref internal - conflicting sibling keywords', (t) => {
|
|
2237
|
+
t.plan(2)
|
|
2238
|
+
|
|
2239
|
+
const schema = {
|
|
2240
|
+
definitions: {
|
|
2241
|
+
def: {
|
|
2242
|
+
type: 'string'
|
|
2243
|
+
}
|
|
2244
|
+
},
|
|
2245
|
+
type: 'object',
|
|
2246
|
+
properties: {
|
|
2247
|
+
value: {
|
|
2248
|
+
$ref: '#/definitions/def',
|
|
2249
|
+
type: 'integer'
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
const object = {
|
|
2255
|
+
value: 42
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
const stringify = build(schema)
|
|
2259
|
+
const output = stringify(object)
|
|
2260
|
+
|
|
2261
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
2262
|
+
t.assert.equal(output, '{"value":"42"}')
|
|
2263
|
+
})
|
|
2264
|
+
|
|
2265
|
+
test('ref external - sibling keywords with a duplicated anchor', (t) => {
|
|
2266
|
+
t.plan(1)
|
|
2267
|
+
|
|
2268
|
+
const externalSchema = {
|
|
2269
|
+
external: {
|
|
2270
|
+
$id: 'external',
|
|
2271
|
+
definitions: {
|
|
2272
|
+
def: {
|
|
2273
|
+
type: 'object',
|
|
2274
|
+
properties: {
|
|
2275
|
+
str: {
|
|
2276
|
+
$id: '#anchor',
|
|
2277
|
+
type: 'string'
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
const schema = {
|
|
2286
|
+
type: 'object',
|
|
2287
|
+
properties: {
|
|
2288
|
+
obj: {
|
|
2289
|
+
$ref: 'external#/definitions/def',
|
|
2290
|
+
properties: {
|
|
2291
|
+
num: {
|
|
2292
|
+
$id: '#anchor',
|
|
2293
|
+
type: 'integer'
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
t.assert.throws(
|
|
2301
|
+
() => build(schema, { schema: externalSchema }),
|
|
2302
|
+
{ message: /There is already another anchor "#anchor"/ }
|
|
2303
|
+
)
|
|
2304
|
+
})
|