Skip to content

RFC: Allow interfaces to implement other interfaces #1218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
64 changes: 54 additions & 10 deletions src/execution/__tests__/union-interface-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,46 @@ const NamedType = new GraphQLInterfaceType({
},
});

const LifeType = new GraphQLInterfaceType({
name: 'Life',
fields: () => ({
progeny: { type: GraphQLList(LifeType) },
}),
});

const MammalType = new GraphQLInterfaceType({
name: 'Mammal',
interfaces: [LifeType],
fields: () => ({
progeny: { type: GraphQLList(MammalType) },
mother: { type: MammalType },
father: { type: MammalType },
}),
});

const DogType = new GraphQLObjectType({
name: 'Dog',
interfaces: [NamedType],
fields: {
interfaces: [MammalType, LifeType, NamedType],
fields: () => ({
name: { type: GraphQLString },
barks: { type: GraphQLBoolean },
},
progeny: { type: GraphQLList(DogType) },
mother: { type: DogType },
father: { type: DogType },
}),
isTypeOf: value => value instanceof Dog,
});

const CatType = new GraphQLObjectType({
name: 'Cat',
interfaces: [NamedType],
fields: {
interfaces: [MammalType, LifeType, NamedType],
fields: () => ({
name: { type: GraphQLString },
meows: { type: GraphQLBoolean },
},
progeny: { type: GraphQLList(CatType) },
mother: { type: CatType },
father: { type: CatType },
}),
isTypeOf: value => value instanceof Cat,
});

Expand All @@ -84,12 +107,15 @@ const PetType = new GraphQLUnionType({

const PersonType = new GraphQLObjectType({
name: 'Person',
interfaces: [NamedType],
fields: {
interfaces: [NamedType, MammalType, LifeType],
fields: () => ({
name: { type: GraphQLString },
pets: { type: GraphQLList(PetType) },
friends: { type: GraphQLList(NamedType) },
},
progeny: { type: GraphQLList(PersonType) },
mother: { type: PersonType },
father: { type: PersonType },
}),
isTypeOf: value => value instanceof Person,
});

Expand All @@ -116,6 +142,15 @@ describe('Execute: Union and intersection types', () => {
enumValues { name }
inputFields { name }
}
Mammal: __type(name: "Mammal") {
kind
name
fields { name }
interfaces { name }
possibleTypes { name }
enumValues { name }
inputFields { name }
}
Pet: __type(name: "Pet") {
kind
name
Expand All @@ -134,7 +169,16 @@ describe('Execute: Union and intersection types', () => {
kind: 'INTERFACE',
name: 'Named',
fields: [{ name: 'name' }],
interfaces: null,
interfaces: [],
possibleTypes: [{ name: 'Person' }, { name: 'Dog' }, { name: 'Cat' }],
enumValues: null,
inputFields: null,
},
Mammal: {
kind: 'INTERFACE',
name: 'Mammal',
fields: [{ name: 'progeny' }, { name: 'mother' }, { name: 'father' }],
interfaces: [{ name: 'Life' }],
possibleTypes: [{ name: 'Person' }, { name: 'Dog' }, { name: 'Cat' }],
enumValues: null,
inputFields: null,
Expand Down
6 changes: 6 additions & 0 deletions src/language/__tests__/schema-kitchen-sink.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ extend interface Bar {

extend interface Bar @onInterface

interface Baz implements Bar {
one: Type
two(argument: InputType!): Type
four(argument: String = "string"): String
}

union Feed = Story | Article | Advert

union AnnotatedUnion @onUnion = A | B
Expand Down
173 changes: 169 additions & 4 deletions src/language/__tests__/schema-parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ extend type Hello {
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Extension without fields', () => {
it('Object extension without fields', () => {
const body = 'extend type Hello implements Greeting';
const doc = parse(body);
const expected = {
Expand All @@ -203,7 +203,27 @@ extend type Hello {
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Extension without fields followed by extension', () => {
it('Interface extension without fields', () => {
const body = 'extend interface Hello implements Greeting';
const doc = parse(body);
const expected = {
kind: 'Document',
definitions: [
{
kind: 'InterfaceTypeExtension',
name: nameNode('Hello', { start: 17, end: 22 }),
interfaces: [typeNode('Greeting', { start: 34, end: 42 })],
directives: [],
fields: [],
loc: { start: 0, end: 42 },
},
],
loc: { start: 0, end: 42 },
};
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Object extension without fields followed by extension', () => {
const body = `
extend type Hello implements Greeting

Expand Down Expand Up @@ -235,14 +255,53 @@ extend type Hello {
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Extension without anything throws', () => {
it('Interface extension without fields followed by extension', () => {
const body = `
extend interface Hello implements Greeting

extend interface Hello implements SecondGreeting
`;
const doc = parse(body);
const expected = {
kind: 'Document',
definitions: [
{
kind: 'InterfaceTypeExtension',
name: nameNode('Hello', { start: 24, end: 29 }),
interfaces: [typeNode('Greeting', { start: 41, end: 49 })],
directives: [],
fields: [],
loc: { start: 7, end: 49 },
},
{
kind: 'InterfaceTypeExtension',
name: nameNode('Hello', { start: 74, end: 79 }),
interfaces: [typeNode('SecondGreeting', { start: 91, end: 105 })],
directives: [],
fields: [],
loc: { start: 57, end: 105 },
},
],
loc: { start: 0, end: 110 },
};
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Object extension without anything throws', () => {
expectSyntaxError('extend type Hello', 'Unexpected <EOF>', {
line: 1,
column: 18,
});
});

it('Extension do not include descriptions', () => {
it('Interface extension without anything throws', () => {
expectSyntaxError('extend interface Hello', 'Unexpected <EOF>', {
line: 1,
column: 23,
});
});

it('Object extension do not include descriptions', () => {
expectSyntaxError(
`
"Description"
Expand All @@ -263,6 +322,27 @@ extend type Hello {
);
});

it('Interface extension do not include descriptions', () => {
expectSyntaxError(
`
"Description"
extend interface Hello {
world: String
}`,
'Unexpected Name "extend"',
{ line: 3, column: 7 },
);

expectSyntaxError(
`
extend "Description" interface Hello {
world: String
}`,
'Unexpected String "Description"',
{ line: 2, column: 14 },
);
});

it('Simple non-null type', () => {
const body = `
type Hello {
Expand Down Expand Up @@ -322,6 +402,32 @@ type Hello {
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Simple interface inheriting interface', () => {
const body = 'interface Hello implements World { field: String }';
const doc = parse(body);
const expected = {
kind: 'Document',
definitions: [
{
kind: 'InterfaceTypeDefinition',
name: nameNode('Hello', { start: 10, end: 15 }),
interfaces: [typeNode('World', { start: 27, end: 32 })],
directives: [],
fields: [
fieldNode(
nameNode('field', { start: 35, end: 40 }),
typeNode('String', { start: 42, end: 48 }),
{ start: 35, end: 48 },
),
],
loc: { start: 0, end: 50 },
},
],
loc: { start: 0, end: 50 },
};
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Simple type inheriting multiple interfaces', () => {
const body = 'type Hello implements Wo & rld { field: String }';
const doc = parse(body);
Expand Down Expand Up @@ -351,6 +457,35 @@ type Hello {
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Simple interface inheriting multiple interfaces', () => {
const body = 'interface Hello implements Wo & rld { field: String }';
const doc = parse(body);
const expected = {
kind: 'Document',
definitions: [
{
kind: 'InterfaceTypeDefinition',
name: nameNode('Hello', { start: 10, end: 15 }),
interfaces: [
typeNode('Wo', { start: 27, end: 29 }),
typeNode('rld', { start: 32, end: 35 }),
],
directives: [],
fields: [
fieldNode(
nameNode('field', { start: 38, end: 43 }),
typeNode('String', { start: 45, end: 51 }),
{ start: 38, end: 51 },
),
],
loc: { start: 0, end: 53 },
},
],
loc: { start: 0, end: 53 },
};
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Simple type inheriting multiple interfaces with leading ampersand', () => {
const body = 'type Hello implements & Wo & rld { field: String }';
const doc = parse(body);
Expand Down Expand Up @@ -380,6 +515,35 @@ type Hello {
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Simple interface inheriting multiple interfaces with leading ampersand', () => {
const body = 'interface Hello implements & Wo & rld { field: String }';
const doc = parse(body);
const expected = {
kind: 'Document',
definitions: [
{
kind: 'InterfaceTypeDefinition',
name: nameNode('Hello', { start: 10, end: 15 }),
interfaces: [
typeNode('Wo', { start: 29, end: 31 }),
typeNode('rld', { start: 34, end: 37 }),
],
directives: [],
fields: [
fieldNode(
nameNode('field', { start: 40, end: 45 }),
typeNode('String', { start: 47, end: 53 }),
{ start: 40, end: 53 },
),
],
loc: { start: 0, end: 55 },
},
],
loc: { start: 0, end: 55 },
};
expect(printJson(doc)).to.equal(printJson(expected));
});

it('Single value enum', () => {
const body = 'enum Hello { WORLD }';
const doc = parse(body);
Expand Down Expand Up @@ -433,6 +597,7 @@ interface Hello {
{
kind: 'InterfaceTypeDefinition',
name: nameNode('Hello', { start: 11, end: 16 }),
interfaces: [],
directives: [],
fields: [
fieldNode(
Expand Down
6 changes: 6 additions & 0 deletions src/language/__tests__/schema-printer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ describe('Printer: SDL document', () => {

extend interface Bar @onInterface

interface Baz implements Bar {
one: Type
two(argument: InputType!): Type
four(argument: String = "string"): String
}

union Feed = Story | Article | Advert

union AnnotatedUnion @onUnion = A | B
Expand Down
2 changes: 2 additions & 0 deletions src/language/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ export type InterfaceTypeDefinitionNode = {
+loc?: Location,
+description?: StringValueNode,
+name: NameNode,
+interfaces?: $ReadOnlyArray<NamedTypeNode>,
+directives?: $ReadOnlyArray<DirectiveNode>,
+fields?: $ReadOnlyArray<FieldDefinitionNode>,
};
Expand Down Expand Up @@ -527,6 +528,7 @@ export type InterfaceTypeExtensionNode = {
+kind: 'InterfaceTypeExtension',
+loc?: Location,
+name: NameNode,
+interfaces?: $ReadOnlyArray<NamedTypeNode>,
+directives?: $ReadOnlyArray<DirectiveNode>,
+fields?: $ReadOnlyArray<FieldDefinitionNode>,
};
Expand Down
Loading