Skip to content

Commit

Permalink
Detect directive argument changes
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Nov 1, 2024
1 parent 51e53d7 commit 756f929
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 1 deletion.
148 changes: 147 additions & 1 deletion src/utilities/__tests__/findSchemaChanges-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { expect } from 'chai';
import { describe, it } from 'mocha';

import { buildSchema } from '../buildASTSchema.js';
import { findSchemaChanges, SafeChangeType } from '../findSchemaChanges.js';
import {
BreakingChangeType,
DangerousChangeType,
findSchemaChanges,
SafeChangeType,
} from '../findSchemaChanges.js';

describe('findSchemaChanges', () => {
it('should detect if a type was added', () => {
Expand Down Expand Up @@ -171,6 +176,147 @@ describe('findSchemaChanges', () => {
]);
});

it('should detect if a changes argument safely', () => {
const oldSchema = buildSchema(`
directive @Foo(foo: String!) on FIELD_DEFINITION
type Query {
foo: String
}
`);

const newSchema = buildSchema(`
directive @Foo(foo: String) on FIELD_DEFINITION
type Query {
foo: String
}
`);
expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([
{
description:
'Argument @Foo(foo:) has changed type from String! to String.',
type: SafeChangeType.ARG_CHANGED_KIND_SAFE,
},
]);
});

it('should detect if a default value is added to an argument', () => {
const oldSchema = buildSchema(`
directive @Foo(foo: String) on FIELD_DEFINITION
type Query {
foo: String
}
`);

const newSchema = buildSchema(`
directive @Foo(foo: String = "Foo") on FIELD_DEFINITION
type Query {
foo: String
}
`);
expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([
{
description: '@Foo(foo:) added a defaultValue "Foo".',
type: SafeChangeType.ARG_DEFAULT_VALUE_ADDED,
},
]);
});

it('should detect if a default value is removed from an argument', () => {
const newSchema = buildSchema(`
directive @Foo(foo: String) on FIELD_DEFINITION
type Query {
foo: String
}
`);

const oldSchema = buildSchema(`
directive @Foo(foo: String = "Foo") on FIELD_DEFINITION
type Query {
foo: String
}
`);
expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([
{
description: '@Foo(foo:) defaultValue was removed.',
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
},
]);
});

it('should detect if a default value is changed in an argument', () => {
const oldSchema = buildSchema(`
directive @Foo(foo: String = "Bar") on FIELD_DEFINITION
type Query {
foo: String
}
`);

const newSchema = buildSchema(`
directive @Foo(foo: String = "Foo") on FIELD_DEFINITION
type Query {
foo: String
}
`);
expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([
{
description: '@Foo(foo:) has changed defaultValue from "Bar" to "Foo".',
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
},
]);
});

it('should detect if a directive argument does a breaking change', () => {
const oldSchema = buildSchema(`
directive @Foo(foo: String) on FIELD_DEFINITION
type Query {
foo: String
}
`);

const newSchema = buildSchema(`
directive @Foo(foo: String!) on FIELD_DEFINITION
type Query {
foo: String
}
`);
expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([
{
description:
'Argument @Foo(foo:) has changed type from String to String!.',
type: BreakingChangeType.ARG_CHANGED_KIND,
},
]);
});

it('should not detect if a directive argument default value does not change', () => {
const oldSchema = buildSchema(`
directive @Foo(foo: String = "FOO") on FIELD_DEFINITION
type Query {
foo: String
}
`);

const newSchema = buildSchema(`
directive @Foo(foo: String = "FOO") on FIELD_DEFINITION
type Query {
foo: String
}
`);
expect(findSchemaChanges(oldSchema, newSchema)).to.deep.equal([]);
});

it('should detect if a directive changes description', () => {
const oldSchema = buildSchema(`
directive @Foo on FIELD_DEFINITION
Expand Down
50 changes: 50 additions & 0 deletions src/utilities/findSchemaChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,56 @@ function findDirectiveChanges(
}

for (const [oldArg, newArg] of argsDiff.persisted) {
const isSafe = isChangeSafeForInputObjectFieldOrFieldArg(
oldArg.type,
newArg.type,
);

if (!isSafe) {
schemaChanges.push({
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
`Argument @${oldDirective.name}(${oldArg.name}:) has changed type from ` +
`${String(oldArg.type)} to ${String(newArg.type)}.`,
});
} else if (oldArg.defaultValue !== undefined) {
if (newArg.defaultValue === undefined) {
schemaChanges.push({
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
description: `@${oldDirective.name}(${oldArg.name}:) defaultValue was removed.`,
});
} else {
// Since we looking only for client's observable changes we should
// compare default values in the same representation as they are
// represented inside introspection.
const oldValueStr = stringifyValue(oldArg.defaultValue, oldArg.type);
const newValueStr = stringifyValue(newArg.defaultValue, newArg.type);

if (oldValueStr !== newValueStr) {
schemaChanges.push({
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
description: `@${oldDirective.name}(${oldArg.name}:) has changed defaultValue from ${oldValueStr} to ${newValueStr}.`,
});
}
}
} else if (
newArg.defaultValue !== undefined &&
oldArg.defaultValue === undefined
) {
const newValueStr = stringifyValue(newArg.defaultValue, newArg.type);
schemaChanges.push({
type: SafeChangeType.ARG_DEFAULT_VALUE_ADDED,
description: `@${oldDirective.name}(${oldArg.name}:) added a defaultValue ${newValueStr}.`,
});
} else if (oldArg.type.toString() !== newArg.type.toString()) {
schemaChanges.push({
type: SafeChangeType.ARG_CHANGED_KIND_SAFE,
description:
`Argument @${oldDirective.name}(${oldArg.name}:) has changed type from ` +
`${String(oldArg.type)} to ${String(newArg.type)}.`,
});
}

if (oldArg.description !== newArg.description) {
schemaChanges.push({
type: SafeChangeType.DESCRIPTION_CHANGED,
Expand Down

0 comments on commit 756f929

Please sign in to comment.