Skip to content

Commit

Permalink
Rewrite JSON Schema Visitor (#4216)
Browse files Browse the repository at this point in the history
* Rewrite JSON Schema Visitor

* Better visitor code

* Fix tests

* YES

* Even more
  • Loading branch information
ardatan authored Aug 2, 2022
1 parent 67a7ac3 commit f95036a
Show file tree
Hide file tree
Showing 18 changed files with 2,473 additions and 3,627 deletions.
10 changes: 10 additions & 0 deletions .changeset/red-squids-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"json-machete": patch
"@omnigraph/json-schema": patch
"@omnigraph/openapi": patch
"@graphql-mesh/types": patch
---

Rewrite JSON Schema visitor and support circular dependencies in a better way

Now `visitJSONSchema` takes two different visitor functions instead of `enter` and `leave`, previously we used to handle only `leave`.
149 changes: 75 additions & 74 deletions packages/json-machete/src/compareJSONSchemas.ts
Original file line number Diff line number Diff line change
@@ -1,109 +1,110 @@
import { JSONSchema } from './types';
import { OnCircularReference, visitJSONSchema } from './visitJSONSchema';
import { visitJSONSchema } from './visitJSONSchema';
import { AggregateError } from '@graphql-tools/utils';
import { resolvePath } from './dereferenceObject';
import { process } from '@graphql-mesh/cross-helpers';

export async function compareJSONSchemas(oldSchema: JSONSchema, newSchema: JSONSchema) {
const breakingChanges: string[] = [];
await visitJSONSchema(
oldSchema,
(oldSubSchema, { path }) => {
if (typeof newSchema === 'object') {
const newSubSchema = resolvePath(path, newSchema);
if (typeof oldSubSchema === 'boolean') {
if (newSubSchema !== oldSubSchema) {
breakingChanges.push(`${path} is changed from ${oldSubSchema} to ${newSubSchema}`);
}
} else {
if (oldSubSchema.$ref) {
if (newSubSchema?.$ref !== oldSubSchema.$ref) {
breakingChanges.push(`${path}/$ref is changed from ${oldSubSchema.$ref} to ${newSubSchema?.$ref}`);
{
enter: (oldSubSchema: JSONSchema, { path }) => {
if (typeof newSchema === 'object') {
const newSubSchema = resolvePath(path, newSchema);
if (typeof oldSubSchema === 'boolean') {
if (newSubSchema !== oldSubSchema) {
breakingChanges.push(`${path} is changed from ${oldSubSchema} to ${newSubSchema}`);
}
}
if (oldSubSchema.const) {
if (newSubSchema?.const !== oldSubSchema.const) {
breakingChanges.push(`${path}/const is changed from ${oldSubSchema.const} to ${newSubSchema?.const}`);
} else {
if (oldSubSchema.$ref) {
if (newSubSchema?.$ref !== oldSubSchema.$ref) {
breakingChanges.push(`${path}/$ref is changed from ${oldSubSchema.$ref} to ${newSubSchema?.$ref}`);
}
}
}
if (oldSubSchema.enum) {
for (const enumValue of oldSubSchema.enum) {
if (!newSubSchema?.enum?.includes(enumValue)) {
breakingChanges.push(`${path}/enum doesn't have ${enumValue} anymore`);
if (oldSubSchema.const) {
if (newSubSchema?.const !== oldSubSchema.const) {
breakingChanges.push(`${path}/const is changed from ${oldSubSchema.const} to ${newSubSchema?.const}`);
}
}
}
if (oldSubSchema.format) {
if (newSubSchema?.format !== oldSubSchema.format) {
breakingChanges.push(`${path}/format is changed from ${oldSubSchema.format} to ${newSubSchema?.format}`);
if (oldSubSchema.enum) {
for (const enumValue of oldSubSchema.enum) {
if (!newSubSchema?.enum?.includes(enumValue)) {
breakingChanges.push(`${path}/enum doesn't have ${enumValue} anymore`);
}
}
}
}
if (oldSubSchema.maxLength) {
if (oldSubSchema.maxLength > newSubSchema?.maxLength) {
breakingChanges.push(
`${path}/maxLength is changed from ${oldSubSchema.maxLength} to ${newSubSchema?.maxLength}`
);
if (oldSubSchema.format) {
if (newSubSchema?.format !== oldSubSchema.format) {
breakingChanges.push(
`${path}/format is changed from ${oldSubSchema.format} to ${newSubSchema?.format}`
);
}
}
}
if (oldSubSchema.minLength) {
if (oldSubSchema.minLength < newSubSchema?.minLength) {
breakingChanges.push(
`${path}/minLength is changed from ${oldSubSchema.minLength} to ${newSubSchema?.minLength}`
);
if (oldSubSchema.maxLength) {
if (oldSubSchema.maxLength > newSubSchema?.maxLength) {
breakingChanges.push(
`${path}/maxLength is changed from ${oldSubSchema.maxLength} to ${newSubSchema?.maxLength}`
);
}
}
}
if (oldSubSchema.pattern) {
if (newSubSchema?.pattern?.toString() !== oldSubSchema.pattern.toString()) {
breakingChanges.push(
`${path}/pattern is changed from ${oldSubSchema.pattern} to ${newSubSchema?.pattern}`
);
if (oldSubSchema.minLength) {
if (oldSubSchema.minLength < newSubSchema?.minLength) {
breakingChanges.push(
`${path}/minLength is changed from ${oldSubSchema.minLength} to ${newSubSchema?.minLength}`
);
}
}
if (oldSubSchema.pattern) {
if (newSubSchema?.pattern?.toString() !== oldSubSchema.pattern.toString()) {
breakingChanges.push(
`${path}/pattern is changed from ${oldSubSchema.pattern} to ${newSubSchema?.pattern}`
);
}
}
}

if (oldSubSchema.properties) {
for (const propertyName in oldSubSchema.properties) {
if (newSubSchema?.properties?.[propertyName] == null) {
breakingChanges.push(`${path}/properties doesn't have ${propertyName}`);
if (oldSubSchema.properties) {
for (const propertyName in oldSubSchema.properties) {
if (newSubSchema?.properties?.[propertyName] == null) {
breakingChanges.push(`${path}/properties doesn't have ${propertyName}`);
}
}
}
}

if (newSubSchema?.required) {
for (const propertyName of newSubSchema.required) {
if (!oldSubSchema.required?.includes(propertyName)) {
breakingChanges.push(`${path}/required has ${propertyName} an extra`);
if (newSubSchema?.required) {
for (const propertyName of newSubSchema.required) {
if (!oldSubSchema.required?.includes(propertyName)) {
breakingChanges.push(`${path}/required has ${propertyName} an extra`);
}
}
}
}

if (oldSubSchema.title) {
if (newSubSchema?.title !== oldSubSchema.title) {
breakingChanges.push(`${path}/title is changed from ${oldSubSchema.title} to ${newSubSchema?.title}`);
if (oldSubSchema.title) {
if (newSubSchema?.title !== oldSubSchema.title) {
breakingChanges.push(`${path}/title is changed from ${oldSubSchema.title} to ${newSubSchema?.title}`);
}
}
}

if (oldSubSchema.type) {
if (
typeof newSubSchema?.type === 'string'
? newSubSchema?.type !== oldSubSchema.type
: Array.isArray(newSubSchema?.type)
? Array.isArray(oldSubSchema.type)
? oldSubSchema.type.some(typeName => !newSubSchema?.type.includes(typeName))
: !newSubSchema?.type.includes(oldSubSchema.type)
: true
) {
breakingChanges.push(`${path}/type is changed from ${oldSubSchema.type} to ${newSubSchema?.type}`);
if (oldSubSchema.type) {
if (
typeof newSubSchema?.type === 'string'
? newSubSchema?.type !== oldSubSchema.type
: Array.isArray(newSubSchema?.type)
? Array.isArray(oldSubSchema.type)
? oldSubSchema.type.some(typeName => !newSubSchema?.type.includes(typeName))
: !newSubSchema?.type.includes(oldSubSchema.type)
: true
) {
breakingChanges.push(`${path}/type is changed from ${oldSubSchema.type} to ${newSubSchema?.type}`);
}
}
}
}
}
return oldSubSchema;
return oldSubSchema;
},
},
{
visitedSubschemaResultMap: new WeakMap(),
path: '',
keepObjectRef: true,
onCircularReference: process.env.DEBUG ? OnCircularReference.WARN : OnCircularReference.IGNORE,
}
);
if (breakingChanges.length > 0) {
Expand Down
Loading

1 comment on commit f95036a

@vercel
Copy link

@vercel vercel bot commented on f95036a Aug 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.