-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Allow interface type extensions #1222
Conversation
@lillyfwang Don't worry about it. My PR has a more long-term goal to allow simplify Schema transformation in general. Your PR is very helpful since it adds more test-cases and challenges |
src/utilities/extendSchema.js
Outdated
[def], | ||
); | ||
} | ||
// Extend all of the object types that implement this interface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lillyfwang You shouldn't do that, if you want to extend interface you shouldn't also extend all objects that implement this interface with missing fields:
extend interface SomeInterface {
newInterface: NewInterface
}
extend type Foo {
newInterface: NewObject
}
extend type Bar {
newInterface: NewOtherObject
}
Note: similar to how you normally define a type that implements an interface you can use compatible type. That's why you can't auto-implement interfaces 😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@IvanGoncharov I'm not sure if I'm understanding your comment properly, but is your concern here that "NewOtherObject" could possibly not implement "NewInterface"? Is a solution here to validate the schema after extending it to make sure the new extended schema is valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lillyfwang No I mean you can't do:
Extend all of the object types that implement this interface
You shouldn't add fields to GraphQLObjectType
since extend interface
should affect only interface not object types implementing this interface.
For example, if you use bellow schema:
interface SomeInterface {
foo: String
}
type SomeObject {
foo: String
}
Incorrect extension (correct in your PR):
extend interface SomeInterface {
bar: String
}
Correct extension:
extend interface SomeInterface {
bar: String
}
extend type SomeObject {
bar: String
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh okay I understand - thanks for explaining!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @IvanGoncharov here - we want to preserve the use case for extending interface fields when types already have the fields defined, and we also want to ensure we can extend objects with a more specific variant of that field, example:
extend interface SomeInterface {
newField: SomeInterface
}
extend type SomeObject { # Assuming it already implements SomeInterface
newField: SomeObject # This is okay now, since SomeObject is more specific than SomeInterface
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great, @lillyfwang - thanks for taking this on.
I think with a couple more tests and addressing the feedback this should be ready to merge
src/utilities/extendSchema.js
Outdated
[def], | ||
); | ||
} | ||
// Extend all of the object types that implement this interface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @IvanGoncharov here - we want to preserve the use case for extending interface fields when types already have the fields defined, and we also want to ensure we can extend objects with a more specific variant of that field, example:
extend interface SomeInterface {
newField: SomeInterface
}
extend type SomeObject { # Assuming it already implements SomeInterface
newField: SomeObject # This is okay now, since SomeObject is more specific than SomeInterface
}
src/utilities/extendSchema.js
Outdated
@@ -232,6 +272,17 @@ export function extendSchema( | |||
astNode: schema.astNode, | |||
}); | |||
|
|||
function appendExtensionToTypeExtensions( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
Just saw @leebyron's comment...updating with more tests now |
src/utilities/extendSchema.js
Outdated
case Kind.INTERFACE_TYPE_EXTENSION: | ||
const extendedInterfaceTypeName = def.name.value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can you please move this case before Kind.DIRECTIVE_DEFINITION
`Cannot extend interface "${extendedInterfaceTypeName}" because ` + | ||
'it does not exist in the existing schema.', | ||
[def], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to test it similar to how it's down for object type:
it('does not allow extending an unknown type', () => { |
throw new GraphQLError( | ||
`Cannot extend non-interface type "${extendedInterfaceTypeName}".`, | ||
[def], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here:
describe('does not allow extending a non-object type', () => { |
] = appendExtensionToTypeExtensions( | ||
def, | ||
typeExtensionsMap[extendedInterfaceTypeName], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to test multiple extension of the same interface:
it('extends objects multiple times', () => { |
]); | ||
}); | ||
|
||
it('accepts Objects implementing the extended interface by extending Objects with fields', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense to test failing cases because error messages should have correct locations (extend AST nodes) inside error objects.
But I'm not sure if it worth to test passing test here since they already covered in extendSchema
.
]); | ||
}); | ||
|
||
it('accepts an Object implementing the extended interface with existing field', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's more appropriate to have this test as part of extendSchema
test suite.
message: | ||
'Interface field argument AnotherInterface.newField(test:) expected but AnotherObject.newField does not provide it.', | ||
locations: [{ line: 3, column: 20 }, { line: 7, column: 11 }], | ||
path: undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove path
here since it's compared with containSubset
type Bar implements SomeInterface { | ||
name: String | ||
some: SomeInterface | ||
foo: Foo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably bug here, should fail because both Foo
and Bar
are missing additional fields:
newObject: NewObject
newInterface: NewInterface
newUnion: NewUnion
newScalar: NewScalar
newTree: [Foo]!
newField(arg1: String, arg2: NewEnum!): String
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well printSchema
should not fail here: we should be able to parse, print and extend invalid schemas. Only validateSchema(extendedSchema)
should fail.
But if that's what you're testing, you should be explicit "can parse and print schema extension with missing Object fields", then prove that it's invalid by testing validateSchema fails (don't test the actual error message though, as we don't wan to force people to update this test when they update that error message).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mjmahone You are right, I completely forget about that.
But I still think this particular test should contain valid SDL examples to not confuse readers.
But if that's what you're testing, you should be explicit "can parse and print schema extension with missing Object fields", then prove that it's invalid by testing validateSchema fails (don't test the actual error message though, as we don't wan to force people to update this test when they update that error message).
Totally make sense to add one such test for buildShema
and extendSchema
functions 👍
@lillyfwang Looks great now 👍 |
Excellent work, @lillyfwang! |
The GraphQL SDL was recently updated to allow interface type extensions (along with other GraphQL type extensions), but
extendSchema()
hasn't been updated to actually support these type extensions (as noted in #1095). This PR adds support for extending interface types by extending every GraphQLObject type that implement the interface with the same new fields and directives. It also extends the interface type itself by updating theextensionASTNodes
property for the GraphQLInterface Type.Edit: Just saw #1199 - are there major conflicts with those changes/future work? It didn't seem like it at first glance.