Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/sharp-files-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@graphql-inspector/patch': minor
'@graphql-inspector/core': minor
---

Adjust SCHEMA\_\*\_TYPE_CHANGED changes to use null instead of 'unknown' when these types are not
defined and improve the change messages.
5 changes: 5 additions & 0 deletions .changeset/twelve-ties-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-inspector/core': minor
---

diff can be passed null schemas. This lets it output the full list of additions on the new schema.
166 changes: 161 additions & 5 deletions packages/core/__tests__/diff/schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { buildClientSchema, buildSchema, introspectionFromSchema } from 'graphql';
import { Change, CriticalityLevel, diff } from '../../src/index.js';
import { findBestMatch } from '../../src/utils/string.js';
import { findChangesByPath, findFirstChangeByPath } from '../../utils/testing.js';

test('same schema', async () => {
const schemaA = buildSchema(/* GraphQL */ `
Expand Down Expand Up @@ -69,7 +67,7 @@ test('renamed query', async () => {

expect(changed).toBeDefined();
expect(changed.criticality.level).toEqual(CriticalityLevel.Breaking);
expect(changed.message).toEqual(`Schema query root has changed from 'Query' to 'RootQuery'`);
expect(changed.message).toEqual(`Schema query root type was changed from 'Query' to 'RootQuery'`);
});

test('new field and field changed', async () => {
Expand Down Expand Up @@ -767,10 +765,10 @@ test('adding root type should not be breaking', async () => {
"criticality": {
"level": "NON_BREAKING",
},
"message": "Schema subscription root has changed from 'unknown' to 'Subscription'",
"message": "Schema subscription type was set to 'Subscription'.",
"meta": {
"newSubscriptionTypeName": "Subscription",
"oldSubscriptionTypeName": "unknown",
"oldSubscriptionTypeName": null,
},
"type": "SCHEMA_SUBSCRIPTION_TYPE_CHANGED",
},
Expand Down Expand Up @@ -803,3 +801,161 @@ test('adding root type should not be breaking', async () => {
]
`);
});

test('null old schema', async () => {
const schemaA = null;

const schemaB = buildSchema(/* GraphQL */ `
type Query {
foo: String
}

type Subscription {
onFoo: String
}
`);

const changes = await diff(schemaA, schemaB);
expect(changes).toMatchInlineSnapshot(`
[
{
"criticality": {
"level": "NON_BREAKING",
},
"message": "Schema query root type was set to 'Query'.",
"meta": {
"newQueryTypeName": "Query",
"oldQueryTypeName": null,
},
"type": "SCHEMA_QUERY_TYPE_CHANGED",
},
{
"criticality": {
"level": "NON_BREAKING",
},
"message": "Schema subscription type was set to 'Subscription'.",
"meta": {
"newSubscriptionTypeName": "Subscription",
"oldSubscriptionTypeName": null,
},
"type": "SCHEMA_SUBSCRIPTION_TYPE_CHANGED",
},
{
"criticality": {
"level": "NON_BREAKING",
},
"message": "Type 'Query' was added",
"meta": {
"addedTypeKind": "ObjectTypeDefinition",
"addedTypeName": "Query",
},
"path": "Query",
"type": "TYPE_ADDED",
},
{
"criticality": {
"level": "NON_BREAKING",
},
"message": "Field 'foo' was added to object type 'Query'",
"meta": {
"addedFieldName": "foo",
"addedFieldReturnType": "String",
"typeName": "Query",
"typeType": "object type",
},
"path": "Query.foo",
"type": "FIELD_ADDED",
},
{
"criticality": {
"level": "NON_BREAKING",
},
"message": "Type 'Subscription' was added",
"meta": {
"addedTypeKind": "ObjectTypeDefinition",
"addedTypeName": "Subscription",
},
"path": "Subscription",
"type": "TYPE_ADDED",
},
{
"criticality": {
"level": "NON_BREAKING",
},
"message": "Field 'onFoo' was added to object type 'Subscription'",
"meta": {
"addedFieldName": "onFoo",
"addedFieldReturnType": "String",
"typeName": "Subscription",
"typeType": "object type",
},
"path": "Subscription.onFoo",
"type": "FIELD_ADDED",
},
]
`);
});

test('null new schema', async () => {
const schemaA = buildSchema(/* GraphQL */ `
type Query {
foo: String
}

type Subscription {
onFoo: String
}
`);

const schemaB = null;

const changes = await diff(schemaA, schemaB);
expect(changes).toMatchInlineSnapshot(`
[
{
"criticality": {
"level": "BREAKING",
},
"message": "Schema query root type 'Query' was removed.",
"meta": {
"newQueryTypeName": null,
"oldQueryTypeName": "Query",
},
"type": "SCHEMA_QUERY_TYPE_CHANGED",
},
{
"criticality": {
"level": "BREAKING",
},
"message": "Schema subscription type 'Subscription' was removed.",
"meta": {
"newSubscriptionTypeName": null,
"oldSubscriptionTypeName": "Subscription",
},
"type": "SCHEMA_SUBSCRIPTION_TYPE_CHANGED",
},
{
"criticality": {
"level": "BREAKING",
},
"message": "Type 'Query' was removed",
"meta": {
"removedTypeName": "Query",
},
"path": "Query",
"type": "TYPE_REMOVED",
},
{
"criticality": {
"level": "BREAKING",
},
"message": "Type 'Subscription' was removed",
"meta": {
"removedTypeName": "Subscription",
},
"path": "Subscription",
"type": "TYPE_REMOVED",
},
]
`);
});
12 changes: 6 additions & 6 deletions packages/core/src/diff/changes/change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,24 +564,24 @@ export type ObjectTypeInterfaceRemovedChange = {
export type SchemaQueryTypeChangedChange = {
type: typeof ChangeType.SchemaQueryTypeChanged;
meta: {
oldQueryTypeName: string;
newQueryTypeName: string;
oldQueryTypeName: string | null;
newQueryTypeName: string | null;
};
};

export type SchemaMutationTypeChangedChange = {
type: typeof ChangeType.SchemaMutationTypeChanged;
meta: {
oldMutationTypeName: string;
newMutationTypeName: string;
oldMutationTypeName: string | null;
newMutationTypeName: string | null;
};
};

export type SchemaSubscriptionTypeChangedChange = {
type: typeof ChangeType.SchemaSubscriptionTypeChanged;
meta: {
oldSubscriptionTypeName: string;
newSubscriptionTypeName: string;
oldSubscriptionTypeName: string | null;
newSubscriptionTypeName: string | null;
};
};

Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/diff/changes/directive-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ type KindToPayload = {
change: DirectiveUsageEnumValueAddedChange | DirectiveUsageEnumValueRemovedChange;
};
[Kind.SCHEMA_DEFINITION]: {
input: GraphQLSchema;
input: GraphQLSchema | null;
change: DirectiveUsageSchemaAddedChange | DirectiveUsageSchemaRemovedChange;
};
[Kind.SCALAR_TYPE_DEFINITION]: {
Expand Down Expand Up @@ -836,9 +836,9 @@ export function directiveUsageAdded<K extends keyof KindToPayload>(
type: ChangeType.DirectiveUsageSchemaAdded,
meta: {
addedDirectiveName: directive.name.value,
schemaTypeName: payload.getQueryType()?.name || '',
schemaTypeName: payload?.getQueryType()?.name || '',
addedToNewType,
directiveRepeatedTimes: directiveRepeatTimes(payload.astNode?.directives ?? [], directive),
directiveRepeatedTimes: directiveRepeatTimes(payload?.astNode?.directives ?? [], directive),
},
});
}
Expand Down Expand Up @@ -1016,8 +1016,8 @@ export function directiveUsageRemoved<K extends keyof KindToPayload>(
type: ChangeType.DirectiveUsageSchemaRemoved,
meta: {
removedDirectiveName: directive.name.value,
schemaTypeName: payload.getQueryType()?.name || '',
directiveRepeatedTimes: directiveRepeatTimes(payload.astNode?.directives ?? [], directive),
schemaTypeName: payload?.getQueryType()?.name || '',
directiveRepeatedTimes: directiveRepeatTimes(payload?.astNode?.directives ?? [], directive),
},
});
}
Expand Down
54 changes: 36 additions & 18 deletions packages/core/src/diff/changes/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ import {
} from './change.js';

function buildSchemaQueryTypeChangedMessage(args: SchemaQueryTypeChangedChange['meta']): string {
return `Schema query root has changed from '${args.oldQueryTypeName}' to '${args.newQueryTypeName}'`;
if (args.oldQueryTypeName === null) {
return `Schema query root type was set to '${args.newQueryTypeName}'.`;
}
if (args.newQueryTypeName === null) {
return `Schema query root type '${args.oldQueryTypeName}' was removed.`;
}
return `Schema query root type was changed from '${args.oldQueryTypeName}' to '${args.newQueryTypeName}'`;
}

export function schemaQueryTypeChangedFromMeta(args: SchemaQueryTypeChangedChange) {
return {
type: ChangeType.SchemaQueryTypeChanged,
criticality: {
level:
args.meta.oldQueryTypeName === 'unknown'
args.meta.oldQueryTypeName === null
? CriticalityLevel.NonBreaking
: CriticalityLevel.Breaking,
},
Expand All @@ -27,11 +33,11 @@ export function schemaQueryTypeChangedFromMeta(args: SchemaQueryTypeChangedChang
}

export function schemaQueryTypeChanged(
oldSchema: GraphQLSchema,
newSchema: GraphQLSchema,
oldSchema: GraphQLSchema | null,
newSchema: GraphQLSchema | null,
): Change<typeof ChangeType.SchemaQueryTypeChanged> {
const oldName = (oldSchema.getQueryType() || ({} as any)).name || 'unknown';
const newName = (newSchema.getQueryType() || ({} as any)).name || 'unknown';
const oldName = oldSchema?.getQueryType()?.name || null;
const newName = newSchema?.getQueryType()?.name || null;

return schemaQueryTypeChangedFromMeta({
type: ChangeType.SchemaQueryTypeChanged,
Expand All @@ -45,15 +51,21 @@ export function schemaQueryTypeChanged(
function buildSchemaMutationTypeChangedMessage(
args: SchemaMutationTypeChangedChange['meta'],
): string {
return `Schema mutation root has changed from '${args.oldMutationTypeName}' to '${args.newMutationTypeName}'`;
if (args.oldMutationTypeName === null) {
return `Schema mutation type was set to '${args.newMutationTypeName}'.`;
}
if (args.newMutationTypeName === null) {
return `Schema mutation type '${args.oldMutationTypeName}' was removed.`;
}
return `Schema mutation type was changed from '${args.oldMutationTypeName}' to '${args.newMutationTypeName}'`;
}

export function schemaMutationTypeChangedFromMeta(args: SchemaMutationTypeChangedChange) {
return {
type: ChangeType.SchemaMutationTypeChanged,
criticality: {
level:
args.meta.oldMutationTypeName === 'unknown'
args.meta.oldMutationTypeName === null
? CriticalityLevel.NonBreaking
: CriticalityLevel.Breaking,
},
Expand All @@ -63,11 +75,11 @@ export function schemaMutationTypeChangedFromMeta(args: SchemaMutationTypeChange
}

export function schemaMutationTypeChanged(
oldSchema: GraphQLSchema,
newSchema: GraphQLSchema,
oldSchema: GraphQLSchema | null,
newSchema: GraphQLSchema | null,
): Change<typeof ChangeType.SchemaMutationTypeChanged> {
const oldName = (oldSchema.getMutationType() || ({} as any)).name || 'unknown';
const newName = (newSchema.getMutationType() || ({} as any)).name || 'unknown';
const oldName = oldSchema?.getMutationType()?.name || null;
const newName = newSchema?.getMutationType()?.name || null;

return schemaMutationTypeChangedFromMeta({
type: ChangeType.SchemaMutationTypeChanged,
Expand All @@ -81,15 +93,21 @@ export function schemaMutationTypeChanged(
function buildSchemaSubscriptionTypeChangedMessage(
args: SchemaSubscriptionTypeChangedChange['meta'],
): string {
return `Schema subscription root has changed from '${args.oldSubscriptionTypeName}' to '${args.newSubscriptionTypeName}'`;
if (args.oldSubscriptionTypeName === null) {
return `Schema subscription type was set to '${args.newSubscriptionTypeName}'.`;
}
if (args.newSubscriptionTypeName === null) {
return `Schema subscription type '${args.oldSubscriptionTypeName}' was removed.`;
}
return `Schema subscription type was changed from '${args.oldSubscriptionTypeName}' to '${args.newSubscriptionTypeName}'`;
}

export function schemaSubscriptionTypeChangedFromMeta(args: SchemaSubscriptionTypeChangedChange) {
return {
type: ChangeType.SchemaSubscriptionTypeChanged,
criticality: {
level:
args.meta.oldSubscriptionTypeName === 'unknown'
args.meta.oldSubscriptionTypeName === null
? CriticalityLevel.NonBreaking
: CriticalityLevel.Breaking,
},
Expand All @@ -99,11 +117,11 @@ export function schemaSubscriptionTypeChangedFromMeta(args: SchemaSubscriptionTy
}

export function schemaSubscriptionTypeChanged(
oldSchema: GraphQLSchema,
newSchema: GraphQLSchema,
oldSchema: GraphQLSchema | null,
newSchema: GraphQLSchema | null,
): Change<typeof ChangeType.SchemaSubscriptionTypeChanged> {
const oldName = (oldSchema.getSubscriptionType() || ({} as any)).name || 'unknown';
const newName = (newSchema.getSubscriptionType() || ({} as any)).name || 'unknown';
const oldName = oldSchema?.getSubscriptionType()?.name || null;
const newName = newSchema?.getSubscriptionType()?.name || null;

return schemaSubscriptionTypeChangedFromMeta({
type: ChangeType.SchemaSubscriptionTypeChanged,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/diff/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export * from './onComplete/types.js';
export type { UsageHandler } from './rules/consider-usage.js';

export function diff(
oldSchema: GraphQLSchema,
newSchema: GraphQLSchema,
oldSchema: GraphQLSchema | null,
newSchema: GraphQLSchema | null,
rules: Rule[] = [],
config?: rules.ConsiderUsageConfig,
): Promise<Change[]> {
Expand Down
Loading
Loading