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
5 changes: 5 additions & 0 deletions .changeset/large-walls-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-inspector/core': minor
---

Escape single quotes in all enum descriptions and deprecation reasons
101 changes: 101 additions & 0 deletions packages/core/__tests__/diff/enum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,105 @@ describe('enum', () => {
expect(change.criticality.reason).toBeDefined();
expect(change.message).toEqual(`Enum value 'C' was added to enum 'enumA'`);
});

describe('string escaping', () => {
test('deprecation reason changed with escaped single quotes', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
fieldA: String
}

enum enumA {
A @deprecated(reason: "It's old")
B
}
`);

const b = buildSchema(/* GraphQL */ `
type Query {
fieldA: String
}

enum enumA {
A @deprecated(reason: "It's new")
B
}
`);

const changes = await diff(a, b);
const change = findFirstChangeByPath(changes, 'enumA.A');

expect(changes.length).toEqual(1);
expect(change.criticality.level).toEqual(CriticalityLevel.NonBreaking);
expect(change.message).toEqual(
`Enum value 'enumA.A' deprecation reason changed from 'It\\'s old' to 'It\\'s new'`,
);
});

test('deprecation reason added with escaped single quotes', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
fieldA: String
}

enum enumA {
A
B
}
`);

const b = buildSchema(/* GraphQL */ `
type Query {
fieldA: String
}

enum enumA {
A @deprecated(reason: "Don't use this")
B
}
`);

const changes = await diff(a, b);
const change = findFirstChangeByPath(changes, 'enumA.A');

expect(changes.length).toEqual(2);
expect(change.criticality.level).toEqual(CriticalityLevel.NonBreaking);
expect(change.message).toEqual(
`Enum value 'enumA.A' was deprecated with reason 'Don\\'t use this'`,
);
});

test('deprecation reason without single quotes is unchanged', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
fieldA: String
}

enum enumA {
A @deprecated(reason: "Old Reason")
B
}
`);

const b = buildSchema(/* GraphQL */ `
type Query {
fieldA: String
}

enum enumA {
A @deprecated(reason: "New Reason")
B
}
`);

const changes = await diff(a, b);
const change = findFirstChangeByPath(changes, 'enumA.A');

expect(changes.length).toEqual(1);
expect(change.criticality.level).toEqual(CriticalityLevel.NonBreaking);
expect(change.message).toEqual(
`Enum value 'enumA.A' deprecation reason changed from 'Old Reason' to 'New Reason'`,
);
});
});
});
17 changes: 16 additions & 1 deletion packages/core/__tests__/utils/string.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { safeString } from '../../src/utils/string.js';
import { fmt, safeString } from '../../src/utils/string.js';

test('scalars', () => {
expect(safeString(0)).toBe('0');
Expand Down Expand Up @@ -33,3 +33,18 @@ test('array', () => {
'[ { foo: 42 } ]',
);
});

describe('fmt', () => {
test('escapes single quotes in strings', () => {
expect(fmt("It's a test")).toBe("It\\'s a test");
expect(fmt("Don't do this")).toBe("Don\\'t do this");
expect(fmt("'quoted'")).toBe("\\'quoted\\'");
});

test('handles strings without single quotes', () => {
expect(fmt('test')).toBe('test');
expect(fmt('Old Reason')).toBe('Old Reason');
expect(fmt('enumA.B')).toBe('enumA.B');
expect(fmt('')).toBe('');
});
});
18 changes: 10 additions & 8 deletions packages/core/src/diff/changes/enum.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GraphQLEnumType, GraphQLEnumValue } from 'graphql';
import { isDeprecated } from '../../utils/is-deprecated.js';
import { fmt } from '../../utils/string.js';
import {
Change,
ChangeType,
Expand Down Expand Up @@ -80,13 +81,11 @@ export function enumValueAdded(
}

function buildEnumValueDescriptionChangedMessage(args: EnumValueDescriptionChangedChange['meta']) {
const oldDesc = fmt(args.oldEnumValueDescription ?? 'undefined');
const newDesc = fmt(args.newEnumValueDescription ?? 'undefined');
return args.oldEnumValueDescription === null
? `Description '${args.newEnumValueDescription ?? 'undefined'}' was added to enum value '${
args.enumName
}.${args.enumValueName}'`
: `Description for enum value '${args.enumName}.${args.enumValueName}' changed from '${
args.oldEnumValueDescription ?? 'undefined'
}' to '${args.newEnumValueDescription ?? 'undefined'}'`;
? `Description '${newDesc}' was added to enum value '${args.enumName}.${args.enumValueName}'`
: `Description for enum value '${args.enumName}.${args.enumValueName}' changed from '${oldDesc}' to '${newDesc}'`;
}

export function enumValueDescriptionChangedFromMeta(
Expand Down Expand Up @@ -122,7 +121,9 @@ export function enumValueDescriptionChanged(
function buildEnumValueDeprecationChangedMessage(
args: EnumValueDeprecationReasonChangedChange['meta'],
) {
return `Enum value '${args.enumName}.${args.enumValueName}' deprecation reason changed from '${args.oldEnumValueDeprecationReason}' to '${args.newEnumValueDeprecationReason}'`;
const oldReason = fmt(args.oldEnumValueDeprecationReason);
const newReason = fmt(args.newEnumValueDeprecationReason);
return `Enum value '${args.enumName}.${args.enumValueName}' deprecation reason changed from '${oldReason}' to '${newReason}'`;
}

export function enumValueDeprecationReasonChangedFromMeta(
Expand Down Expand Up @@ -158,7 +159,8 @@ export function enumValueDeprecationReasonChanged(
function buildEnumValueDeprecationReasonAddedMessage(
args: EnumValueDeprecationReasonAddedChange['meta'],
) {
return `Enum value '${args.enumName}.${args.enumValueName}' was deprecated with reason '${args.addedValueDeprecationReason}'`;
const reason = fmt(args.addedValueDeprecationReason);
return `Enum value '${args.enumName}.${args.enumValueName}' was deprecated with reason '${reason}'`;
}

export function enumValueDeprecationReasonAddedFromMeta(
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/utils/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,7 @@ export function safeString(obj: unknown) {
.replace(/\[Object: null prototype\] /g, '')
.replace(/(^')|('$)/g, '');
}

export function fmt(reason: string): string {
return reason.replace(/'/g, "\\'");
}
Loading