[WIP] Extend the extend functionality#1322
[WIP] Extend the extend functionality#1322grenierdev wants to merge 12 commits intographql:masterfrom grenierdev:extend-enum-input
Conversation
| Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed. If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks! |
| isEnumType, | ||
| GraphQLEnumType, | ||
| isInputObjectType, | ||
| GraphQLInputObjectType, |
There was a problem hiding this comment.
Please put function imports together with other function imports.
| name: type.name, | ||
| description: type.description, | ||
| fields: () => extendInputFieldMap(type), | ||
| astNode: type.astNode, |
There was a problem hiding this comment.
You need to add extensionASTNodes here. But first, you need to add it to GraphQLInputObjectType class.
| return new GraphQLEnumType({ | ||
| name: type.name, | ||
| values: extendValueMap(type), | ||
| astNode: type.astNode, |
There was a problem hiding this comment.
Also missing extensionASTNodes.
| types: type.getTypes().map(getExtendedType), | ||
| types: types, | ||
| astNode: type.astNode, | ||
| resolveType: type.resolveType, |
There was a problem hiding this comment.
Also missing extensionASTNodes.
src/utilities/extendSchema.js Outdated
| } | ||
| | ||
| function extendArgsMap( | ||
| args: GraphQLArgument[], |
There was a problem hiding this comment.
It accepts and returns an array so it probably shouldn't have Map in its name.
src/utilities/extendSchema.js Outdated
| function extendArgsMap( | ||
| args: GraphQLArgument[], | ||
| ): GraphQLArgument[] { | ||
| return args.map(arg => ({ |
There was a problem hiding this comment.
I think it's better to use keyValMap here so the result of this function can be assigned to GraphQLFieldConfig::args without additional manipulations.
src/utilities/extendSchema.js Outdated
| const oldValueMap = Object.create(null); | ||
| const newValueMap = Object.create(null); | ||
| type.getValues().forEach(value => { | ||
| newValueMap[value.name] = oldValueMap[value.name] = { |
There was a problem hiding this comment.
I think code here should match extendFieldMap:
graphql-js/src/utilities/extendSchema.js
Lines 335 to 336 in 3cb4500
So you either change
extendFieldMap to do the same code or remove assigment chain here. src/utilities/extendSchema.js Outdated
| [field], | ||
| ); | ||
| } | ||
| newFieldMap[fieldName] = astBuilder.buildField(field); |
There was a problem hiding this comment.
You can't use buildField here since it return type/interface field. You should extract new function from _makeInputValues.
| newValueMap[value.name] = oldValueMap[value.name] = { | ||
| name: value.name, | ||
| description: value.description, | ||
| value: value.value, |
There was a problem hiding this comment.
You shouldn't do that, because of this check:
graphql-js/src/type/definition.js
Line 1164 in 3cb4500
In the current version, it will convert all enum values without explicit
value to undefined.This is the very important case so please add a separate test for it.
| @mgrenier Look nice. I left some review comments also, please fix Travis errors. One important feature thing missing in this PR: Since you extending types that can be used in directive arguments that mean you need to recreate directives with updated arguments. |
| Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
| @IvanGoncharov I still have to wrap my head around the directive argument case. Most tests cases I tried gave me errors.
function getMergedDirectives(): Array<GraphQLDirective> { const existingDirectives = schema.getDirectives(); invariant(existingDirectives, 'schema must have default directives'); return existingDirectives.concat( directiveDefinitions.map(node => astBuilder.buildDirective(node)), ).map(directive => new GraphQLDirective({ name: directive.name, description: directive.description, locations: directive.locations, args: extendArgs(directive.args), astNode: directive.astNode, })); }
it('extend input', () => { const extendedSchema = extendTestSchema(` extend type Query { newField(testArg: TestInput): String } input TestInput { testInputField: String } directive @test(arg: TestInput) on FIELD `);Seems like just passing the directives args trough the newly I need some help on this one. |
src/utilities/extendSchema.js Outdated
| Object.keys(oldFieldMap).forEach(fieldName => { | ||
| const field = oldFieldMap[fieldName]; | ||
| newFieldMap[fieldName] = { | ||
| // name: fieldName, |
src/utilities/extendSchema.js Outdated
| description: arg.description, | ||
| astNode: arg.astNode, | ||
| })); | ||
| function extendArgs(args: GraphQLArgument[]): GraphQLFieldConfigArgumentMap { |
There was a problem hiding this comment.
Please use Array similar to other places in this file
| arg => arg.name, | ||
| arg => ({ | ||
| name: arg.name, | ||
| type: extendFieldType(arg.type), |
There was a problem hiding this comment.
Not a critical issue but you could think of a better name for this function since now it also used for arguments. How about extendType?
There was a problem hiding this comment.
extendType is already used in this scope. Should I move the logic of extendFieldType into this function or used something like extendValueType ?
src/utilities/extendSchema.js Outdated
| args, | ||
| arg => arg.name, | ||
| arg => ({ | ||
| name: arg.name, |
There was a problem hiding this comment.
You don't need name, see:
graphql-js/src/type/definition.js
Lines 842 to 847 in ae5b163
| const newEnum = extendedSchema.getType('SomeEnum'); | ||
| const testDirective = extendedSchema.getDirective('test'); | ||
| | ||
| expect(oldEnum._values.length).to.equal(2); |
There was a problem hiding this comment.
nit: expect(oldEnum._values).to.have.lengthOf(2)
| expect(oldEnum._values.length).to.equal(2); | ||
| expect(newEnum._values.length).to.equal(3); | ||
| expect(newEnum._values[2].name).to.equal('NEW_ENUM'); | ||
| expect(newEnum).to.equal(testDirective.args[0].type); |
There was a problem hiding this comment.
nit: expect(testDirective).to.have.nested.property('args[0].type', newEnum)
@mgrenier You code is correct, except you shouldn't recreate new directives, only old ones: function getMergedDirectives(): Array<GraphQLDirective> { const existingDirectives = schema.getDirectives() ); invariant(existingDirectives, 'schema must have default directives'); return existingDirectives.concat( existingDirectives.map( directive => new GraphQLDirective({ name: directive.name, description: directive.description, locations: directive.locations, args: extendArgs(directive.args), astNode: directive.astNode, }), ), directiveDefinitions.map(node => astBuilder.buildDirective(node)), ); }If you move out directive recreation into separate function it will make this code more readable. Also don't forget that all code should be tested e.g.: values of |
| @mgrenier Also please rebase your PR on top of master, since we updated Flow & Prettier. |
src/utilities/extendSchema.js Outdated
| newValueMap[valueName] = { | ||
| name: value.name, | ||
| description: value.description, | ||
| value: value.hasOwnProperty('value') ? value.value : valueName, |
There was a problem hiding this comment.
My bad, I got confused with something else 😞
You don't need this code, it should be value: value.value.
| @mgrenier I review you PR one more time and found that you also need to extend this function: graphql-js/src/utilities/extendSchema.js Line 409 in ae5b163 |
| All review and rebase have been done |
| @mgrenier it still has conflicts with |
@jgcmarins @mgrenier It's minor conflict because of #1323 |
| | ||
| extend enum SomeEnum { | ||
| DEPRECATED @deprecated(reason: "do not use") | ||
| } |
There was a problem hiding this comment.
Test name is "extends objects with deprecated fields"
So I think it's better to create a separate one for enum values.
| someInput: String | ||
| } | ||
| | ||
| union UnusedUnion = Unused |
There was a problem hiding this comment.
I think extends schema by adding new unused types would be a better name for this test.
union UnusedUnion = Unused
Unused became used 😄 It's better to create separate DummyUnionMember type.
| interface NewInterface { | ||
| buzz: String | ||
| } | ||
| |
There was a problem hiding this comment.
test description: extends objects multiple times => extends types multiple times
| @mgrenier I notice decreased coverage: |
| ); | ||
| } | ||
| | ||
| function extendDirectiveType(type: GraphQLDirective): GraphQLDirective { |
There was a problem hiding this comment.
function name is confusing it should be either extendDirective or extendDirectiveTypes (I like it more).
There was a problem hiding this comment.
I'm going with extendDirective
| | ||
| function getMergedDirectives(): Array<GraphQLDirective> { | ||
| const existingDirectives = schema.getDirectives(); | ||
| const existingDirectives = schema.getDirectives().map(extendDirectiveType); |
There was a problem hiding this comment.
Maybe we shouldn't recreate standard directives like @skip, @include and @deprecate.
@leebyron What do you think?
There was a problem hiding this comment.
This is really elegant, so we can start with this. It would be a breaking change to disallow extending @skip/etc. but I like being able to extend them just like any other directive.
| const valueName = value.name.value; | ||
| if (oldValueMap[valueName]) { | ||
| throw new GraphQLError( | ||
| `Enum "${type.name}.${valueName}" already exists in the ` + |
There was a problem hiding this comment.
Enum => Enum value or something similar
| @mgrenier Do you need any help with this PR? |
| @IvanGoncharov sure do. I dont have much time these days. Sorry for the delays. If someone can dig in, feel free. |
* Extend the extend functionality * Fix flowtype * Extract logic from _makeInputValues to its own function * Fix lint and minor refactors * Fix type error due to copy & paste * Fix array syntax * Fix test more verbose * Removed unnecessary check * Extend directive * Moved new tests into existing tests * Fix lint errors * Added missing description * fix enum value error test
| This was merged in #1373 |
This is a WIP implementation for extending enum, input and union. See discussion in issue #1095