Skip to content

Commit bd3e1bf

Browse files
committed
update with spec changes
My previous proposal only addressed pure OneOf cycles, and failed to handle other invalid cycles, like mixed OneOf/non-Oneof cycles: ``` input A @OneOf { b:B } input B { a:A! } ``` This trues up the implementation with the proposed spec changes, which consolidates input cycle detection under one algorithm: InputObjectCanBeProvidedAFiniteValue
1 parent 60272fc commit bd3e1bf

File tree

2 files changed

+400
-60
lines changed

2 files changed

+400
-60
lines changed

src/type/__tests__/validation-test.ts

Lines changed: 264 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ describe('Type System: Input Objects must have fields', () => {
924924
expectJSON(validateSchema(schema)).toDeepEqual([
925925
{
926926
message:
927-
'Invalid circular reference. The Input Object SomeInputObject references itself in the non-null field SomeInputObject.nonNullSelf.',
927+
'Input Object SomeInputObject references itself via the required fields: SomeInputObject.nonNullSelf.',
928928
locations: [{ line: 7, column: 9 }],
929929
},
930930
]);
@@ -952,13 +952,31 @@ describe('Type System: Input Objects must have fields', () => {
952952
expectJSON(validateSchema(schema)).toDeepEqual([
953953
{
954954
message:
955-
'Invalid circular reference. The Input Object SomeInputObject references itself via the non-null fields: SomeInputObject.startLoop, AnotherInputObject.nextInLoop, YetAnotherInputObject.closeLoop.',
955+
'Input Object SomeInputObject references itself via the required fields: SomeInputObject.startLoop, AnotherInputObject.nextInLoop, YetAnotherInputObject.closeLoop.',
956956
locations: [
957957
{ line: 7, column: 9 },
958958
{ line: 11, column: 9 },
959959
{ line: 15, column: 9 },
960960
],
961961
},
962+
{
963+
message:
964+
'Input Object AnotherInputObject references itself via the required fields: AnotherInputObject.nextInLoop, YetAnotherInputObject.closeLoop, SomeInputObject.startLoop.',
965+
locations: [
966+
{ line: 11, column: 9 },
967+
{ line: 15, column: 9 },
968+
{ line: 7, column: 9 },
969+
],
970+
},
971+
{
972+
message:
973+
'Input Object YetAnotherInputObject references itself via the required fields: YetAnotherInputObject.closeLoop, SomeInputObject.startLoop, AnotherInputObject.nextInLoop.',
974+
locations: [
975+
{ line: 15, column: 9 },
976+
{ line: 7, column: 9 },
977+
{ line: 11, column: 9 },
978+
],
979+
},
962980
]);
963981
});
964982

@@ -986,24 +1004,28 @@ describe('Type System: Input Objects must have fields', () => {
9861004
expectJSON(validateSchema(schema)).toDeepEqual([
9871005
{
9881006
message:
989-
'Invalid circular reference. The Input Object SomeInputObject references itself via the non-null fields: SomeInputObject.startLoop, AnotherInputObject.closeLoop.',
1007+
'Input Object SomeInputObject references itself via the required fields: SomeInputObject.startLoop, AnotherInputObject.closeLoop.',
9901008
locations: [
9911009
{ line: 7, column: 9 },
9921010
{ line: 11, column: 9 },
9931011
],
9941012
},
9951013
{
9961014
message:
997-
'Invalid circular reference. The Input Object AnotherInputObject references itself via the non-null fields: AnotherInputObject.startSecondLoop, YetAnotherInputObject.closeSecondLoop.',
1015+
'Input Object AnotherInputObject references itself via the required fields: AnotherInputObject.closeLoop, SomeInputObject.startLoop.',
9981016
locations: [
999-
{ line: 12, column: 9 },
1000-
{ line: 16, column: 9 },
1017+
{ line: 11, column: 9 },
1018+
{ line: 7, column: 9 },
10011019
],
10021020
},
10031021
{
10041022
message:
1005-
'Invalid circular reference. The Input Object YetAnotherInputObject references itself in the non-null field YetAnotherInputObject.nonNullSelf.',
1006-
locations: [{ line: 17, column: 9 }],
1023+
'Input Object YetAnotherInputObject references itself via the required fields: YetAnotherInputObject.closeSecondLoop, AnotherInputObject.closeLoop, SomeInputObject.startLoop.',
1024+
locations: [
1025+
{ line: 16, column: 9 },
1026+
{ line: 11, column: 9 },
1027+
{ line: 7, column: 9 },
1028+
],
10071029
},
10081030
]);
10091031
});
@@ -2409,6 +2431,240 @@ describe('Type System: OneOf Input Object fields must be nullable', () => {
24092431
});
24102432
});
24112433

2434+
describe('Type System: Input Objects must be provided a finite value', () => {
2435+
it('accepts a OneOf Input Object with a scalar field', () => {
2436+
const schema = buildSchema(`
2437+
type Query {
2438+
test(arg: A): Int
2439+
}
2440+
2441+
input A @oneOf {
2442+
a: Int
2443+
}
2444+
`);
2445+
expectJSON(validateSchema(schema)).toDeepEqual([]);
2446+
});
2447+
2448+
it('accepts a OneOf Input Object with a recursive list field', () => {
2449+
const schema = buildSchema(`
2450+
type Query {
2451+
test(arg: A): Int
2452+
}
2453+
2454+
input A @oneOf {
2455+
a: [A!]
2456+
}
2457+
`);
2458+
expectJSON(validateSchema(schema)).toDeepEqual([]);
2459+
});
2460+
2461+
it('accepts a OneOf Input Object referencing a non-OneOf input object', () => {
2462+
const schema = buildSchema(`
2463+
type Query {
2464+
test(arg: A): Int
2465+
}
2466+
2467+
input A @oneOf {
2468+
b: B
2469+
}
2470+
2471+
input B {
2472+
x: Int
2473+
}
2474+
`);
2475+
expectJSON(validateSchema(schema)).toDeepEqual([]);
2476+
});
2477+
2478+
it('accepts a OneOf/OneOf cycle with a scalar escape', () => {
2479+
const schema = buildSchema(`
2480+
type Query {
2481+
test(arg: A): Int
2482+
}
2483+
2484+
input A @oneOf {
2485+
b: B
2486+
escape: Int
2487+
}
2488+
2489+
input B @oneOf {
2490+
a: A
2491+
}
2492+
`);
2493+
expectJSON(validateSchema(schema)).toDeepEqual([]);
2494+
});
2495+
2496+
it('accepts a OneOf/non-OneOf cycle with a nullable escape', () => {
2497+
const schema = buildSchema(`
2498+
type Query {
2499+
test(arg: A): Int
2500+
}
2501+
2502+
input A @oneOf {
2503+
b: B
2504+
}
2505+
2506+
input B {
2507+
a: A
2508+
}
2509+
`);
2510+
expectJSON(validateSchema(schema)).toDeepEqual([]);
2511+
});
2512+
2513+
it('rejects a self-referencing OneOf type with no escapes', () => {
2514+
const schema = buildSchema(`
2515+
type Query {
2516+
test(arg: A): Int
2517+
}
2518+
2519+
input A @oneOf {
2520+
self: A
2521+
}
2522+
`);
2523+
expectJSON(validateSchema(schema)).toDeepEqual([
2524+
{
2525+
message:
2526+
'Input Object A references itself via the required fields: A.self.',
2527+
locations: [{ line: 7, column: 9 }],
2528+
},
2529+
]);
2530+
});
2531+
2532+
it('rejects a mixed OneOf/non-OneOf cycle with no escapes', () => {
2533+
const schema = buildSchema(`
2534+
type Query {
2535+
test(arg: A): Int
2536+
}
2537+
2538+
input A @oneOf {
2539+
b: B
2540+
}
2541+
2542+
input B {
2543+
a: A!
2544+
}
2545+
`);
2546+
expectJSON(validateSchema(schema)).toDeepEqual([
2547+
{
2548+
message:
2549+
'Input Object A references itself via the required fields: A.b, B.a.',
2550+
locations: [
2551+
{ line: 7, column: 9 },
2552+
{ line: 11, column: 9 },
2553+
],
2554+
},
2555+
{
2556+
message:
2557+
'Input Object B references itself via the required fields: B.a, A.b.',
2558+
locations: [
2559+
{ line: 11, column: 9 },
2560+
{ line: 7, column: 9 },
2561+
],
2562+
},
2563+
]);
2564+
});
2565+
2566+
it('accepts a OneOf/non-OneOf with scalar escape', () => {
2567+
const schema = buildSchema(`
2568+
type Query {
2569+
test(arg: A): Int
2570+
}
2571+
2572+
input A @oneOf {
2573+
b: B
2574+
escape: Int
2575+
}
2576+
2577+
input B {
2578+
a: A!
2579+
}
2580+
`);
2581+
expectJSON(validateSchema(schema)).toDeepEqual([]);
2582+
});
2583+
2584+
it('accepts a non-OneOf/non-OneOf cycle with a nullable escape', () => {
2585+
const schema = buildSchema(`
2586+
type Query {
2587+
test(arg: A): Int
2588+
}
2589+
2590+
input A {
2591+
b: B!
2592+
}
2593+
2594+
input B {
2595+
a: A
2596+
}
2597+
`);
2598+
expectJSON(validateSchema(schema)).toDeepEqual([]);
2599+
});
2600+
2601+
it('accepts a non-OneOf/non-OneOf cycle with a list escape', () => {
2602+
const schema = buildSchema(`
2603+
type Query {
2604+
test(arg: A): Int
2605+
}
2606+
2607+
input A {
2608+
b: [B!]!
2609+
}
2610+
2611+
input B {
2612+
a: A!
2613+
}
2614+
`);
2615+
expectJSON(validateSchema(schema)).toDeepEqual([]);
2616+
});
2617+
2618+
it('rejects a larger mixed OneOf/non-OneOf cycle with no escapes', () => {
2619+
const schema = buildSchema(`
2620+
type Query {
2621+
test(arg: A): Int
2622+
}
2623+
2624+
input A @oneOf {
2625+
b: B
2626+
}
2627+
2628+
input B {
2629+
c: C!
2630+
}
2631+
2632+
input C @oneOf {
2633+
a: A
2634+
}
2635+
`);
2636+
expectJSON(validateSchema(schema)).toDeepEqual([
2637+
{
2638+
message:
2639+
'Input Object A references itself via the required fields: A.b, B.c, C.a.',
2640+
locations: [
2641+
{ line: 7, column: 9 },
2642+
{ line: 11, column: 9 },
2643+
{ line: 15, column: 9 },
2644+
],
2645+
},
2646+
{
2647+
message:
2648+
'Input Object B references itself via the required fields: B.c, C.a, A.b.',
2649+
locations: [
2650+
{ line: 11, column: 9 },
2651+
{ line: 15, column: 9 },
2652+
{ line: 7, column: 9 },
2653+
],
2654+
},
2655+
{
2656+
message:
2657+
'Input Object C references itself via the required fields: C.a, A.b, B.c.',
2658+
locations: [
2659+
{ line: 15, column: 9 },
2660+
{ line: 7, column: 9 },
2661+
{ line: 11, column: 9 },
2662+
],
2663+
},
2664+
]);
2665+
});
2666+
});
2667+
24122668
describe('Objects must adhere to Interface they implement', () => {
24132669
it('accepts an Object which implements an Interface', () => {
24142670
const schema = buildSchema(`

0 commit comments

Comments
 (0)