Skip to content

Commit 665dbd9

Browse files
authored
Merge pull request #690 from Code-Hex/fix/687
fixes 687
2 parents dd89905 + 62b7738 commit 665dbd9

File tree

8 files changed

+92
-4
lines changed

8 files changed

+92
-4
lines changed

src/graphql.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,20 @@ export function isGeneratedByIntrospection(schema: GraphQLSchema): boolean {
185185
.filter(([name, type]) => !name.startsWith('__') && !isSpecifiedScalarType(type))
186186
.every(([, type]) => type.astNode === undefined)
187187
}
188+
189+
// https://spec.graphql.org/October2021/#EscapedCharacter
190+
const escapeMap: { [key: string]: string } = {
191+
'\"': '\\\"',
192+
'\\': '\\\\',
193+
'\/': '\\/',
194+
'\b': '\\b',
195+
'\f': '\\f',
196+
'\n': '\\n',
197+
'\r': '\\r',
198+
'\t': '\\t',
199+
};
200+
201+
export function escapeGraphQLCharacters(input: string): string {
202+
// eslint-disable-next-line regexp/no-escape-backspace
203+
return input.replace(/["\\/\f\n\r\t\b]/g, match => escapeMap[match]);
204+
}

src/myzod/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { Visitor } from '../visitor';
2222
import {
2323
InterfaceTypeDefinitionBuilder,
2424
ObjectTypeDefinitionBuilder,
25+
escapeGraphQLCharacters,
2526
isInput,
2627
isListType,
2728
isNamedType,
@@ -282,7 +283,7 @@ function generateFieldTypeMyZodSchema(config: ValidationSchemaPluginConfig, visi
282283
appliedDirectivesGen = `${appliedDirectivesGen}.default(${defaultValue.value})`;
283284

284285
if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM)
285-
appliedDirectivesGen = `${appliedDirectivesGen}.default("${defaultValue.value}")`;
286+
appliedDirectivesGen = `${appliedDirectivesGen}.default("${escapeGraphQLCharacters(defaultValue.value)}")`;
286287
}
287288

288289
if (isNonNullType(parentType)) {

src/yup/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { Visitor } from '../visitor';
2222
import {
2323
InterfaceTypeDefinitionBuilder,
2424
ObjectTypeDefinitionBuilder,
25+
escapeGraphQLCharacters,
2526
isInput,
2627
isListType,
2728
isNamedType,
@@ -284,7 +285,7 @@ function shapeFields(fields: readonly (FieldDefinitionNode | InputValueDefinitio
284285
}
285286

286287
if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM)
287-
fieldSchema = `${fieldSchema}.default("${defaultValue.value}")`;
288+
fieldSchema = `${fieldSchema}.default("${escapeGraphQLCharacters(defaultValue.value)}")`;
288289
}
289290

290291
if (isNonNullType(field.type))

src/zod/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { Visitor } from '../visitor';
2222
import {
2323
InterfaceTypeDefinitionBuilder,
2424
ObjectTypeDefinitionBuilder,
25+
escapeGraphQLCharacters,
2526
isInput,
2627
isListType,
2728
isNamedType,
@@ -295,7 +296,7 @@ function generateFieldTypeZodSchema(config: ValidationSchemaPluginConfig, visito
295296
appliedDirectivesGen = `${appliedDirectivesGen}.default(${defaultValue.value})`;
296297

297298
if (defaultValue?.kind === Kind.STRING || defaultValue?.kind === Kind.ENUM)
298-
appliedDirectivesGen = `${appliedDirectivesGen}.default("${defaultValue.value}")`;
299+
appliedDirectivesGen = `${appliedDirectivesGen}.default("${escapeGraphQLCharacters(defaultValue.value)}")`;
299300
}
300301

301302
if (isNonNullType(parentType)) {

tests/graphql.spec.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from 'graphql';
1313
import dedent from 'ts-dedent';
1414

15-
import { ObjectTypeDefinitionBuilder, isGeneratedByIntrospection, topologicalSortAST, topsort } from '../src/graphql';
15+
import { ObjectTypeDefinitionBuilder, escapeGraphQLCharacters, isGeneratedByIntrospection, topologicalSortAST, topsort } from '../src/graphql';
1616

1717
describe('graphql', () => {
1818
describe('objectTypeDefinitionBuilder', () => {
@@ -297,3 +297,65 @@ describe('isGeneratedByIntrospection function', () => {
297297
expect(isGeneratedByIntrospection(clientSchema)).toBe(true);
298298
});
299299
});
300+
301+
describe('escapeGraphQLCharacters', () => {
302+
it('should escape double quotes', () => {
303+
const input = 'This is a "test" string.';
304+
const expected = 'This is a \\\"test\\\" string.';
305+
expect(escapeGraphQLCharacters(input)).toBe(expected);
306+
});
307+
308+
it('should escape backslashes', () => {
309+
const input = 'This is a backslash: \\';
310+
const expected = 'This is a backslash: \\\\';
311+
expect(escapeGraphQLCharacters(input)).toBe(expected);
312+
});
313+
314+
it('should escape forward slashes', () => {
315+
const input = 'This is a forward slash: /';
316+
const expected = 'This is a forward slash: \\/';
317+
expect(escapeGraphQLCharacters(input)).toBe(expected);
318+
});
319+
320+
it('should escape backspaces', () => {
321+
const input = 'This is a backspace: \b';
322+
const expected = 'This is a backspace: \\b';
323+
expect(escapeGraphQLCharacters(input)).toBe(expected);
324+
});
325+
326+
it('should escape form feeds', () => {
327+
const input = 'This is a form feed: \f';
328+
const expected = 'This is a form feed: \\f';
329+
expect(escapeGraphQLCharacters(input)).toBe(expected);
330+
});
331+
332+
it('should escape new lines', () => {
333+
const input = 'This is a new line: \n';
334+
const expected = 'This is a new line: \\n';
335+
expect(escapeGraphQLCharacters(input)).toBe(expected);
336+
});
337+
338+
it('should escape carriage returns', () => {
339+
const input = 'This is a carriage return: \r';
340+
const expected = 'This is a carriage return: \\r';
341+
expect(escapeGraphQLCharacters(input)).toBe(expected);
342+
});
343+
344+
it('should escape horizontal tabs', () => {
345+
const input = 'This is a tab: \t';
346+
const expected = 'This is a tab: \\t';
347+
expect(escapeGraphQLCharacters(input)).toBe(expected);
348+
});
349+
350+
it('should escape multiple special characters', () => {
351+
const input = 'This is a "test" string with \n new line and \t tab.';
352+
const expected = 'This is a \\\"test\\\" string with \\n new line and \\t tab.';
353+
expect(escapeGraphQLCharacters(input)).toBe(expected);
354+
});
355+
356+
it('should not escape non-special characters', () => {
357+
const input = 'Normal string with no special characters.';
358+
const expected = 'Normal string with no special characters.';
359+
expect(escapeGraphQLCharacters(input)).toBe(expected);
360+
});
361+
});

tests/myzod.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,7 @@ describe('myzod', () => {
13761376
input PageInput {
13771377
pageType: PageType! = PUBLIC
13781378
greeting: String = "Hello"
1379+
newline: String = "Hello\\nWorld"
13791380
score: Int = 100
13801381
ratio: Float = 0.5
13811382
isMember: Boolean = true
@@ -1399,6 +1400,7 @@ describe('myzod', () => {
13991400
return myzod.object({
14001401
pageType: PageTypeSchema.default("PUBLIC"),
14011402
greeting: myzod.string().default("Hello").optional().nullable(),
1403+
newline: myzod.string().default("Hello\\nWorld").optional().nullable(),
14021404
score: myzod.number().default(100).optional().nullable(),
14031405
ratio: myzod.number().default(0.5).optional().nullable(),
14041406
isMember: myzod.boolean().default(true).optional().nullable()

tests/yup.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,7 @@ describe('yup', () => {
14001400
input PageInput {
14011401
pageType: PageType! = PUBLIC
14021402
greeting: String = "Hello"
1403+
newline: String = "Hello\\nWorld"
14031404
score: Int = 100
14041405
ratio: Float = 0.5
14051406
isMember: Boolean = true
@@ -1423,6 +1424,7 @@ describe('yup', () => {
14231424
return yup.object({
14241425
pageType: PageTypeSchema.nonNullable().default("PUBLIC"),
14251426
greeting: yup.string().defined().nullable().default("Hello").optional(),
1427+
newline: yup.string().defined().nullable().default("Hello\\nWorld").optional(),
14261428
score: yup.number().defined().nullable().default(100).optional(),
14271429
ratio: yup.number().defined().nullable().default(0.5).optional(),
14281430
isMember: yup.boolean().defined().nullable().default(true).optional()

tests/zod.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ describe('zod', () => {
539539
input PageInput {
540540
pageType: PageType! = PUBLIC
541541
greeting: String = "Hello"
542+
newline: String = "Hello\\nWorld"
542543
score: Int = 100
543544
ratio: Float = 0.5
544545
isMember: Boolean = true
@@ -562,6 +563,7 @@ describe('zod', () => {
562563
return z.object({
563564
pageType: PageTypeSchema.default("PUBLIC"),
564565
greeting: z.string().default("Hello").nullish(),
566+
newline: z.string().default("Hello\\nWorld").nullish(),
565567
score: z.number().default(100).nullish(),
566568
ratio: z.number().default(0.5).nullish(),
567569
isMember: z.boolean().default(true).nullish()

0 commit comments

Comments
 (0)