json-machete 0.0.15 → 0.0.16-alpha-5fdf08f65.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.
- package/index.js +64 -2
- package/index.mjs +64 -2
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -292,37 +292,99 @@ async function compareJSONSchemas(oldSchema, newSchema) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
const reservedTypeNames = ['Query', 'Mutation', 'Subscription'];
|
|
295
|
+
function deduplicateObject(object, seenMap = new Map()) {
|
|
296
|
+
if (typeof object === 'object' && object != null) {
|
|
297
|
+
const stringified = JSON.stringify(object);
|
|
298
|
+
const seen = seenMap.get(stringified);
|
|
299
|
+
if (seen) {
|
|
300
|
+
return seen;
|
|
301
|
+
}
|
|
302
|
+
seenMap.set(stringified, object);
|
|
303
|
+
for (const key in object) {
|
|
304
|
+
object[key] = deduplicateObject(object[key], seenMap);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return object;
|
|
308
|
+
}
|
|
309
|
+
async function getDeduplicatedTitles(schema) {
|
|
310
|
+
const duplicatedTypeNames = new Set();
|
|
311
|
+
const seenTypeNames = new Set();
|
|
312
|
+
await visitJSONSchema(schema, subSchema => {
|
|
313
|
+
if (typeof subSchema === 'object' && subSchema.title) {
|
|
314
|
+
if (seenTypeNames.has(subSchema.title)) {
|
|
315
|
+
duplicatedTypeNames.add(subSchema.title);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
seenTypeNames.add(subSchema.title);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return subSchema;
|
|
322
|
+
}, {
|
|
323
|
+
visitedSubschemaResultMap: new WeakMap(),
|
|
324
|
+
path: '',
|
|
325
|
+
keepObjectRef: true,
|
|
326
|
+
onCircularReference: exports.OnCircularReference.IGNORE,
|
|
327
|
+
});
|
|
328
|
+
return duplicatedTypeNames;
|
|
329
|
+
}
|
|
295
330
|
async function healJSONSchema(schema) {
|
|
296
|
-
|
|
331
|
+
const deduplicatedSchema = deduplicateObject(schema);
|
|
332
|
+
const duplicatedTypeNames = await getDeduplicatedTitles(deduplicatedSchema);
|
|
333
|
+
return visitJSONSchema(deduplicatedSchema, (subSchema, { path }) => {
|
|
297
334
|
if (typeof subSchema === 'object') {
|
|
335
|
+
if (duplicatedTypeNames.has(subSchema.title)) {
|
|
336
|
+
delete subSchema.title;
|
|
337
|
+
}
|
|
298
338
|
if (!subSchema.title && !subSchema.$ref) {
|
|
299
|
-
// Try to get definition name
|
|
339
|
+
// Try to get definition name if missing
|
|
300
340
|
const maybeDefinitionBasedPath = path.includes('/definitions/') ? path.split('/definitions/')[1] : path;
|
|
301
341
|
let pathBasedName = maybeDefinitionBasedPath.split('/properties').join('').split('/').join('_');
|
|
302
342
|
switch (subSchema.type) {
|
|
303
343
|
case 'string':
|
|
344
|
+
// If it has special pattern, use path based name because it is specific
|
|
304
345
|
if (subSchema.pattern || subSchema.maxLength || subSchema.minLength || subSchema.enum) {
|
|
305
346
|
subSchema.title = pathBasedName;
|
|
347
|
+
// Otherwise use the format name
|
|
306
348
|
}
|
|
307
349
|
else if (subSchema.format) {
|
|
308
350
|
subSchema.title = subSchema.format;
|
|
309
351
|
}
|
|
310
352
|
break;
|
|
311
353
|
case 'integer':
|
|
354
|
+
// Use format name
|
|
312
355
|
if (subSchema.format) {
|
|
313
356
|
subSchema.title = subSchema.format;
|
|
314
357
|
}
|
|
315
358
|
break;
|
|
316
359
|
}
|
|
360
|
+
// If type name is reserved, add a suffix
|
|
317
361
|
if (reservedTypeNames.includes(pathBasedName)) {
|
|
318
362
|
pathBasedName += '_';
|
|
319
363
|
}
|
|
320
364
|
subSchema.title = subSchema.title || pathBasedName;
|
|
321
365
|
}
|
|
366
|
+
// Try to find the type
|
|
322
367
|
if (!subSchema.type) {
|
|
368
|
+
// If required exists without properties
|
|
369
|
+
if (subSchema.required && !subSchema.properties) {
|
|
370
|
+
// Add properties
|
|
371
|
+
subSchema.properties = {};
|
|
372
|
+
for (const missingPropertyName of subSchema.required) {
|
|
373
|
+
subSchema.properties[missingPropertyName] = {
|
|
374
|
+
oneOf: [
|
|
375
|
+
{ type: 'string' },
|
|
376
|
+
{ type: 'integer' },
|
|
377
|
+
{ type: 'boolean' },
|
|
378
|
+
{ type: 'object', additionalProperties: true },
|
|
379
|
+
],
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Properties only exist in objects
|
|
323
384
|
if (subSchema.properties) {
|
|
324
385
|
subSchema.type = 'object';
|
|
325
386
|
}
|
|
387
|
+
// Items only exist in arrays
|
|
326
388
|
if (subSchema.items) {
|
|
327
389
|
subSchema.type = 'array';
|
|
328
390
|
}
|
package/index.mjs
CHANGED
|
@@ -289,37 +289,99 @@ async function compareJSONSchemas(oldSchema, newSchema) {
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
const reservedTypeNames = ['Query', 'Mutation', 'Subscription'];
|
|
292
|
+
function deduplicateObject(object, seenMap = new Map()) {
|
|
293
|
+
if (typeof object === 'object' && object != null) {
|
|
294
|
+
const stringified = JSON.stringify(object);
|
|
295
|
+
const seen = seenMap.get(stringified);
|
|
296
|
+
if (seen) {
|
|
297
|
+
return seen;
|
|
298
|
+
}
|
|
299
|
+
seenMap.set(stringified, object);
|
|
300
|
+
for (const key in object) {
|
|
301
|
+
object[key] = deduplicateObject(object[key], seenMap);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return object;
|
|
305
|
+
}
|
|
306
|
+
async function getDeduplicatedTitles(schema) {
|
|
307
|
+
const duplicatedTypeNames = new Set();
|
|
308
|
+
const seenTypeNames = new Set();
|
|
309
|
+
await visitJSONSchema(schema, subSchema => {
|
|
310
|
+
if (typeof subSchema === 'object' && subSchema.title) {
|
|
311
|
+
if (seenTypeNames.has(subSchema.title)) {
|
|
312
|
+
duplicatedTypeNames.add(subSchema.title);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
seenTypeNames.add(subSchema.title);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return subSchema;
|
|
319
|
+
}, {
|
|
320
|
+
visitedSubschemaResultMap: new WeakMap(),
|
|
321
|
+
path: '',
|
|
322
|
+
keepObjectRef: true,
|
|
323
|
+
onCircularReference: exports.OnCircularReference.IGNORE,
|
|
324
|
+
});
|
|
325
|
+
return duplicatedTypeNames;
|
|
326
|
+
}
|
|
292
327
|
async function healJSONSchema(schema) {
|
|
293
|
-
|
|
328
|
+
const deduplicatedSchema = deduplicateObject(schema);
|
|
329
|
+
const duplicatedTypeNames = await getDeduplicatedTitles(deduplicatedSchema);
|
|
330
|
+
return visitJSONSchema(deduplicatedSchema, (subSchema, { path }) => {
|
|
294
331
|
if (typeof subSchema === 'object') {
|
|
332
|
+
if (duplicatedTypeNames.has(subSchema.title)) {
|
|
333
|
+
delete subSchema.title;
|
|
334
|
+
}
|
|
295
335
|
if (!subSchema.title && !subSchema.$ref) {
|
|
296
|
-
// Try to get definition name
|
|
336
|
+
// Try to get definition name if missing
|
|
297
337
|
const maybeDefinitionBasedPath = path.includes('/definitions/') ? path.split('/definitions/')[1] : path;
|
|
298
338
|
let pathBasedName = maybeDefinitionBasedPath.split('/properties').join('').split('/').join('_');
|
|
299
339
|
switch (subSchema.type) {
|
|
300
340
|
case 'string':
|
|
341
|
+
// If it has special pattern, use path based name because it is specific
|
|
301
342
|
if (subSchema.pattern || subSchema.maxLength || subSchema.minLength || subSchema.enum) {
|
|
302
343
|
subSchema.title = pathBasedName;
|
|
344
|
+
// Otherwise use the format name
|
|
303
345
|
}
|
|
304
346
|
else if (subSchema.format) {
|
|
305
347
|
subSchema.title = subSchema.format;
|
|
306
348
|
}
|
|
307
349
|
break;
|
|
308
350
|
case 'integer':
|
|
351
|
+
// Use format name
|
|
309
352
|
if (subSchema.format) {
|
|
310
353
|
subSchema.title = subSchema.format;
|
|
311
354
|
}
|
|
312
355
|
break;
|
|
313
356
|
}
|
|
357
|
+
// If type name is reserved, add a suffix
|
|
314
358
|
if (reservedTypeNames.includes(pathBasedName)) {
|
|
315
359
|
pathBasedName += '_';
|
|
316
360
|
}
|
|
317
361
|
subSchema.title = subSchema.title || pathBasedName;
|
|
318
362
|
}
|
|
363
|
+
// Try to find the type
|
|
319
364
|
if (!subSchema.type) {
|
|
365
|
+
// If required exists without properties
|
|
366
|
+
if (subSchema.required && !subSchema.properties) {
|
|
367
|
+
// Add properties
|
|
368
|
+
subSchema.properties = {};
|
|
369
|
+
for (const missingPropertyName of subSchema.required) {
|
|
370
|
+
subSchema.properties[missingPropertyName] = {
|
|
371
|
+
oneOf: [
|
|
372
|
+
{ type: 'string' },
|
|
373
|
+
{ type: 'integer' },
|
|
374
|
+
{ type: 'boolean' },
|
|
375
|
+
{ type: 'object', additionalProperties: true },
|
|
376
|
+
],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Properties only exist in objects
|
|
320
381
|
if (subSchema.properties) {
|
|
321
382
|
subSchema.type = 'object';
|
|
322
383
|
}
|
|
384
|
+
// Items only exist in arrays
|
|
323
385
|
if (subSchema.items) {
|
|
324
386
|
subSchema.type = 'array';
|
|
325
387
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-machete",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16-alpha-5fdf08f65.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@graphql-mesh/utils": "0.17.0",
|
|
6
|
+
"@graphql-mesh/utils": "0.17.1-alpha-5fdf08f65.0",
|
|
7
7
|
"@json-schema-tools/meta-schema": "1.6.19",
|
|
8
8
|
"json-ptr": "2.2.0"
|
|
9
9
|
},
|