Skip to content

Commit 285e1c6

Browse files
robrichardlilianammmatosglasser
authored andcommitted
Refactoring and test changes
Extracted from initial defer/stream PR graphql#3659 Co-authored-by: Liliana Matos <[email protected]> Co-authored-by: David Glasser <[email protected]>
1 parent 20c49b9 commit 285e1c6

File tree

4 files changed

+95
-20
lines changed

4 files changed

+95
-20
lines changed

src/execution/__tests__/nonnull-test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { describe, it } from 'mocha';
33

44
import { expectJSON } from '../../__testUtils__/expectJSON';
55

6+
import type { PromiseOrValue } from '../../jsutils/PromiseOrValue';
7+
68
import { parse } from '../../language/parser';
79

810
import { GraphQLNonNull, GraphQLObjectType } from '../../type/definition';
@@ -109,7 +111,7 @@ const schema = buildSchema(`
109111
function executeQuery(
110112
query: string,
111113
rootValue: unknown,
112-
): ExecutionResult | Promise<ExecutionResult> {
114+
): PromiseOrValue<ExecutionResult> {
113115
return execute({ schema, document: parse(query), rootValue });
114116
}
115117

src/execution/__tests__/subscribe-test.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ const EmailType = new GraphQLObjectType({
3333
fields: {
3434
from: { type: GraphQLString },
3535
subject: { type: GraphQLString },
36+
asyncSubject: {
37+
type: GraphQLString,
38+
resolve: (email) => Promise.resolve(email.subject),
39+
},
3640
message: { type: GraphQLString },
3741
unread: { type: GraphQLBoolean },
3842
},
@@ -84,13 +88,19 @@ const emailSchema = new GraphQLSchema({
8488
}),
8589
});
8690

87-
function createSubscription(pubsub: SimplePubSub<Email>) {
91+
function createSubscription(
92+
pubsub: SimplePubSub<Email>,
93+
variableValues?: { readonly [variable: string]: unknown },
94+
) {
8895
const document = parse(`
89-
subscription ($priority: Int = 0) {
96+
subscription ($priority: Int = 0, $shouldDefer: Boolean = false, $asyncResolver: Boolean = false) {
9097
importantEmail(priority: $priority) {
9198
email {
9299
from
93100
subject
101+
... @include(if: $asyncResolver) {
102+
asyncSubject
103+
}
94104
}
95105
inbox {
96106
unread
@@ -124,7 +134,12 @@ function createSubscription(pubsub: SimplePubSub<Email>) {
124134
}),
125135
};
126136

127-
return subscribe({ schema: emailSchema, document, rootValue: data });
137+
return subscribe({
138+
schema: emailSchema,
139+
document,
140+
rootValue: data,
141+
variableValues,
142+
});
128143
}
129144

130145
const DummyQueryType = new GraphQLObjectType({
@@ -549,6 +564,45 @@ describe('Subscription Publish Phase', () => {
549564
expect(await payload2).to.deep.equal(expectedPayload);
550565
});
551566

567+
it('produces a payload when queried fields are async', async () => {
568+
const pubsub = new SimplePubSub<Email>();
569+
570+
const subscription = createSubscription(pubsub, { asyncResolver: true });
571+
assert(isAsyncIterable(subscription));
572+
573+
expect(
574+
pubsub.emit({
575+
576+
subject: 'Alright',
577+
message: 'Tests are good',
578+
unread: true,
579+
}),
580+
).to.equal(true);
581+
582+
expect(await subscription.next()).to.deep.equal({
583+
done: false,
584+
value: {
585+
data: {
586+
importantEmail: {
587+
email: {
588+
589+
subject: 'Alright',
590+
asyncSubject: 'Alright',
591+
},
592+
inbox: {
593+
unread: 1,
594+
total: 2,
595+
},
596+
},
597+
},
598+
},
599+
});
600+
expect(await subscription.return()).to.deep.equal({
601+
done: true,
602+
value: undefined,
603+
});
604+
});
605+
552606
it('produces a payload per subscription event', async () => {
553607
const pubsub = new SimplePubSub<Email>();
554608
const subscription = createSubscription(pubsub);

src/execution/execute.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@ function executeField(
489489
fieldNodes: ReadonlyArray<FieldNode>,
490490
path: Path,
491491
): PromiseOrValue<unknown> {
492+
const errors = exeContext.errors;
492493
const fieldName = fieldNodes[0].name.value;
493494
const fieldDef = exeContext.schema.getField(parentType, fieldName);
494495
if (!fieldDef) {
@@ -545,13 +546,13 @@ function executeField(
545546
// to take a second callback for the error case.
546547
return completed.then(undefined, (rawError) => {
547548
const error = locatedError(rawError, fieldNodes, pathToArray(path));
548-
return handleFieldError(error, returnType, exeContext);
549+
return handleFieldError(error, returnType, errors);
549550
});
550551
}
551552
return completed;
552553
} catch (rawError) {
553554
const error = locatedError(rawError, fieldNodes, pathToArray(path));
554-
return handleFieldError(error, returnType, exeContext);
555+
return handleFieldError(error, returnType, errors);
555556
}
556557
}
557558

@@ -585,7 +586,7 @@ export function buildResolveInfo(
585586
function handleFieldError(
586587
error: GraphQLError,
587588
returnType: GraphQLOutputType,
588-
exeContext: ExecutionContext,
589+
errors: Array<GraphQLError>,
589590
): null {
590591
// If the field type is non-nullable, then it is resolved without any
591592
// protection from errors, however it still properly locates the error.
@@ -595,7 +596,7 @@ function handleFieldError(
595596

596597
// Otherwise, error protection is applied, logging the error and resolving
597598
// a null value for this field if one is encountered.
598-
exeContext.errors.push(error);
599+
errors.push(error);
599600
return null;
600601
}
601602

@@ -718,6 +719,7 @@ async function completeAsyncIteratorValue(
718719
path: Path,
719720
iterator: AsyncIterator<unknown>,
720721
): Promise<ReadonlyArray<unknown>> {
722+
const errors = exeContext.errors;
721723
let containsPromise = false;
722724
const completedResults = [];
723725
let index = 0;
@@ -752,12 +754,12 @@ async function completeAsyncIteratorValue(
752754
fieldNodes,
753755
pathToArray(fieldPath),
754756
);
755-
handleFieldError(error, itemType, exeContext);
757+
handleFieldError(error, itemType, errors);
756758
}
757759
} catch (rawError) {
758760
completedResults.push(null);
759761
const error = locatedError(rawError, fieldNodes, pathToArray(fieldPath));
760-
handleFieldError(error, itemType, exeContext);
762+
handleFieldError(error, itemType, errors);
761763
break;
762764
}
763765
index += 1;
@@ -778,6 +780,7 @@ function completeListValue(
778780
result: unknown,
779781
): PromiseOrValue<ReadonlyArray<unknown>> {
780782
const itemType = returnType.ofType;
783+
const errors = exeContext.errors;
781784

782785
if (isAsyncIterable(result)) {
783786
const iterator = result[Symbol.asyncIterator]();
@@ -839,13 +842,13 @@ function completeListValue(
839842
fieldNodes,
840843
pathToArray(itemPath),
841844
);
842-
return handleFieldError(error, itemType, exeContext);
845+
return handleFieldError(error, itemType, errors);
843846
});
844847
}
845848
return completedItem;
846849
} catch (rawError) {
847850
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
848-
return handleFieldError(error, itemType, exeContext);
851+
return handleFieldError(error, itemType, errors);
849852
}
850853
});
851854

@@ -989,9 +992,6 @@ function completeObjectValue(
989992
path: Path,
990993
result: unknown,
991994
): PromiseOrValue<ObjMap<unknown>> {
992-
// Collect sub-fields to execute to complete this value.
993-
const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes);
994-
995995
// If there is an isTypeOf predicate function, call it with the
996996
// current result. If isTypeOf returns false, then raise an error rather
997997
// than continuing execution.
@@ -1003,12 +1003,12 @@ function completeObjectValue(
10031003
if (!resolvedIsTypeOf) {
10041004
throw invalidReturnTypeError(returnType, result, fieldNodes);
10051005
}
1006-
return executeFields(
1006+
return collectAndExecuteSubfields(
10071007
exeContext,
10081008
returnType,
1009-
result,
1009+
fieldNodes,
10101010
path,
1011-
subFieldNodes,
1011+
result,
10121012
);
10131013
});
10141014
}
@@ -1018,7 +1018,13 @@ function completeObjectValue(
10181018
}
10191019
}
10201020

1021-
return executeFields(exeContext, returnType, result, path, subFieldNodes);
1021+
return collectAndExecuteSubfields(
1022+
exeContext,
1023+
returnType,
1024+
fieldNodes,
1025+
path,
1026+
result,
1027+
);
10221028
}
10231029

10241030
function invalidReturnTypeError(
@@ -1032,6 +1038,19 @@ function invalidReturnTypeError(
10321038
);
10331039
}
10341040

1041+
function collectAndExecuteSubfields(
1042+
exeContext: ExecutionContext,
1043+
returnType: GraphQLObjectType,
1044+
fieldNodes: ReadonlyArray<FieldNode>,
1045+
path: Path,
1046+
result: unknown,
1047+
): PromiseOrValue<ObjMap<unknown>> {
1048+
// Collect sub-fields to execute to complete this value.
1049+
const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes);
1050+
1051+
return executeFields(exeContext, returnType, result, path, subFieldNodes);
1052+
}
1053+
10351054
/**
10361055
* If a resolveType function is not given, then a default resolve behavior is
10371056
* used which attempts two strategies:

src/validation/__tests__/harness.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const testSchema: GraphQLSchema = buildSchema(`
5858
type Human {
5959
name(surname: Boolean): String
6060
pets: [Pet]
61-
relatives: [Human]
61+
relatives: [Human]!
6262
}
6363
6464
enum FurColor {

0 commit comments

Comments
 (0)