json-machete 0.0.17 → 0.0.18-alpha-22f3e156f.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 (3) hide show
  1. package/index.js +64 -2
  2. package/index.mjs +64 -2
  3. 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 deduplicateJSONSchema(schema, seenMap = new Map()) {
296
+ if (typeof schema === 'object' && schema != null) {
297
+ const stringified = JSON.stringify(schema);
298
+ const seen = seenMap.get(stringified);
299
+ if (seen) {
300
+ return seen;
301
+ }
302
+ seenMap.set(stringified, schema);
303
+ for (const key in schema) {
304
+ schema[key] = deduplicateJSONSchema(schema[key], seenMap);
305
+ }
306
+ }
307
+ return schema;
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
- return visitJSONSchema(schema, (subSchema, { path }) => {
331
+ const deduplicatedSchema = deduplicateJSONSchema(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 deduplicateJSONSchema(schema, seenMap = new Map()) {
293
+ if (typeof schema === 'object' && schema != null) {
294
+ const stringified = JSON.stringify(schema);
295
+ const seen = seenMap.get(stringified);
296
+ if (seen) {
297
+ return seen;
298
+ }
299
+ seenMap.set(stringified, schema);
300
+ for (const key in schema) {
301
+ schema[key] = deduplicateJSONSchema(schema[key], seenMap);
302
+ }
303
+ }
304
+ return schema;
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: OnCircularReference.IGNORE,
324
+ });
325
+ return duplicatedTypeNames;
326
+ }
292
327
  async function healJSONSchema(schema) {
293
- return visitJSONSchema(schema, (subSchema, { path }) => {
328
+ const deduplicatedSchema = deduplicateJSONSchema(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.17",
3
+ "version": "0.0.18-alpha-22f3e156f.0",
4
4
  "sideEffects": false,
5
5
  "dependencies": {
6
- "@graphql-mesh/utils": "0.17.2",
6
+ "@graphql-mesh/utils": "0.17.3-alpha-22f3e156f.0",
7
7
  "@json-schema-tools/meta-schema": "1.6.19",
8
8
  "json-ptr": "2.2.0"
9
9
  },