Skip to content

Commit

Permalink
Implement support for @stream directive
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/execution/execute.ts
#	src/validation/index.d.ts
#	src/validation/index.ts
  • Loading branch information
robrichard committed Jun 21, 2022
1 parent 0c117c2 commit e046952
Show file tree
Hide file tree
Showing 13 changed files with 1,981 additions and 24 deletions.
1,192 changes: 1,192 additions & 0 deletions src/execution/__tests__/stream-test.ts

Large diffs are not rendered by default.

360 changes: 341 additions & 19 deletions src/execution/execute.ts

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions src/validation/__tests__/DeferStreamDirectiveLabelRule-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,63 @@ describe('Validate: Defer/Stream directive on root field', () => {
},
]);
});
it('Defer and stream with no label', () => {
expectValid(`
{
dog {
...dogFragment @defer
}
pets @stream(initialCount: 0) @stream {
name
}
}
fragment dogFragment on Dog {
name
}
`);
});
it('Stream with variable label', () => {
expectErrors(`
query ($label: String!) {
dog {
...dogFragment @defer
}
pets @stream(initialCount: 0) @stream(label: $label) {
name
}
}
fragment dogFragment on Dog {
name
}
`).toDeepEqual([
{
message:
'Directive "stream"\'s label argument must be a static string.',
locations: [{ line: 6, column: 39 }],
},
]);
});
it('Defer and stream with the same label', () => {
expectErrors(`
{
dog {
...dogFragment @defer(label: "MyLabel")
}
pets @stream(initialCount: 0) @stream(label: "MyLabel") {
name
}
}
fragment dogFragment on Dog {
name
}
`).toDeepEqual([
{
message: 'Defer/Stream directive label argument must be unique.',
locations: [
{ line: 4, column: 26 },
{ line: 6, column: 39 },
],
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const schema = buildSchema(`
type QueryRoot {
message: Message
messages: [Message]
}
schema {
Expand Down Expand Up @@ -167,4 +168,91 @@ describe('Validate: Defer/Stream directive on root field', () => {
}
`);
});
it('Stream field on root query field', () => {
expectValid(`
{
messages @stream {
name
}
}
`);
});
it('Stream field on fragment on root query field', () => {
expectValid(`
{
...rootFragment
}
fragment rootFragment on QueryType {
messages @stream {
name
}
}
`);
});
it('Stream field on root mutation field', () => {
expectErrors(`
mutation {
mutationListField @stream {
name
}
}
`).toDeepEqual([
{
message:
'Stream directive cannot be used on root mutation type "MutationRoot".',
locations: [{ line: 3, column: 27 }],
},
]);
});
it('Stream field on fragment on root mutation field', () => {
expectErrors(`
mutation {
...rootFragment
}
fragment rootFragment on MutationRoot {
mutationListField @stream {
name
}
}
`).toDeepEqual([
{
message:
'Stream directive cannot be used on root mutation type "MutationRoot".',
locations: [{ line: 6, column: 27 }],
},
]);
});
it('Stream field on root subscription field', () => {
expectErrors(`
subscription {
subscriptionListField @stream {
name
}
}
`).toDeepEqual([
{
message:
'Stream directive cannot be used on root subscription type "SubscriptionRoot".',
locations: [{ line: 3, column: 31 }],
},
]);
});
it('Stream field on fragment on root subscription field', () => {
expectErrors(`
subscription {
...rootFragment
}
fragment rootFragment on SubscriptionRoot {
subscriptionListField @stream {
name
}
}
`).toDeepEqual([
{
message:
'Stream directive cannot be used on root subscription type "SubscriptionRoot".',
locations: [{ line: 6, column: 31 }],
},
]);
});
});
108 changes: 108 additions & 0 deletions src/validation/__tests__/OverlappingFieldsCanBeMergedRule-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,114 @@ describe('Validate: Overlapping fields can be merged', () => {
`);
});

it('Same stream directives supported', () => {
expectValid(`
fragment differentDirectivesWithDifferentAliases on Dog {
name @stream(label: "streamLabel", initialCount: 1)
name @stream(label: "streamLabel", initialCount: 1)
}
`);
});

it('different stream directive label', () => {
expectErrors(`
fragment conflictingArgs on Dog {
name @stream(label: "streamLabel", initialCount: 1)
name @stream(label: "anotherLabel", initialCount: 1)
}
`).toDeepEqual([
{
message:
'Fields "name" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
locations: [
{ line: 3, column: 9 },
{ line: 4, column: 9 },
],
},
]);
});

it('different stream directive initialCount', () => {
expectErrors(`
fragment conflictingArgs on Dog {
name @stream(label: "streamLabel", initialCount: 1)
name @stream(label: "streamLabel", initialCount: 2)
}
`).toDeepEqual([
{
message:
'Fields "name" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
locations: [
{ line: 3, column: 9 },
{ line: 4, column: 9 },
],
},
]);
});

it('different stream directive first missing args', () => {
expectErrors(`
fragment conflictingArgs on Dog {
name @stream
name @stream(label: "streamLabel", initialCount: 1)
}
`).toDeepEqual([
{
message:
'Fields "name" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
locations: [
{ line: 3, column: 9 },
{ line: 4, column: 9 },
],
},
]);
});

it('different stream directive second missing args', () => {
expectErrors(`
fragment conflictingArgs on Dog {
name @stream(label: "streamLabel", initialCount: 1)
name @stream
}
`).toDeepEqual([
{
message:
'Fields "name" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
locations: [
{ line: 3, column: 9 },
{ line: 4, column: 9 },
],
},
]);
});

it('mix of stream and no stream', () => {
expectErrors(`
fragment conflictingArgs on Dog {
name @stream
name
}
`).toDeepEqual([
{
message:
'Fields "name" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
locations: [
{ line: 3, column: 9 },
{ line: 4, column: 9 },
],
},
]);
});

it('different stream directive both missing args', () => {
expectValid(`
fragment conflictingArgs on Dog {
name @stream
name @stream
}
`);
});

it('Same aliases with different field targets', () => {
expectErrors(`
fragment sameAliasesWithDifferentFieldTargets on Dog {
Expand Down
79 changes: 79 additions & 0 deletions src/validation/__tests__/StreamDirectiveOnListFieldRule-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { describe, it } from 'mocha';

import { StreamDirectiveOnListFieldRule } from '../rules/StreamDirectiveOnListFieldRule';

import { expectValidationErrors } from './harness';

function expectErrors(queryStr: string) {
return expectValidationErrors(StreamDirectiveOnListFieldRule, queryStr);
}

function expectValid(queryStr: string) {
expectErrors(queryStr).toDeepEqual([]);
}

describe('Validate: Stream directive on list field', () => {
it('Stream on list field', () => {
expectValid(`
fragment objectFieldSelection on Human {
pets @stream(initialCount: 0) {
name
}
}
`);
});

it('Stream on non-null list field', () => {
expectValid(`
fragment objectFieldSelection on Human {
relatives @stream(initialCount: 0) {
name
}
}
`);
});

it("Doesn't validate other directives on list fields", () => {
expectValid(`
fragment objectFieldSelection on Human {
pets @include(if: true) {
name
}
}
`);
});

it("Doesn't validate other directives on non-list fields", () => {
expectValid(`
fragment objectFieldSelection on Human {
pets {
name @include(if: true)
}
}
`);
});

it("Doesn't validate misplaced stream directives", () => {
expectValid(`
fragment objectFieldSelection on Human {
... @stream(initialCount: 0) {
name
}
}
`);
});

it('reports errors when stream is used on non-list field', () => {
expectErrors(`
fragment objectFieldSelection on Human {
name @stream(initialCount: 0)
}
`).toDeepEqual([
{
message:
'Stream directive cannot be used on non-list field "name" on type "Human".',
locations: [{ line: 3, column: 14 }],
},
]);
});
});
2 changes: 1 addition & 1 deletion src/validation/__tests__/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const testSchema: GraphQLSchema = buildSchema(`
type Human {
name(surname: Boolean): String
pets: [Pet]
relatives: [Human]
relatives: [Human]!
}
enum FurColor {
Expand Down
3 changes: 3 additions & 0 deletions src/validation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export { ScalarLeafsRule } from './rules/ScalarLeafsRule';
// Spec Section: "Subscriptions with Single Root Field"
export { SingleFieldSubscriptionsRule } from './rules/SingleFieldSubscriptionsRule';

// Spec Section: "Stream Directives Are Used On List Fields"
export { StreamDirectiveOnListFieldRule } from './rules/StreamDirectiveOnListFieldRule';

// Spec Section: "Argument Uniqueness"
export { UniqueArgumentNamesRule } from './rules/UniqueArgumentNamesRule';

Expand Down
Loading

0 comments on commit e046952

Please sign in to comment.