-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
#172 Allow TypeReference as InputType #204
Conversation
Some notes from #34
|
This means that in the current code, TypeReferences are only searched for and replaced in the output object fields, so simply adding the interface like in #34 isn't enough, as it would still leave TypeReferences used as arguments or in input object definitions unreplaced. I.e. it doesn't always replaces them, but only in some expected locations.
|
Yeah, sorry that was confusing. Yes, the existing tests are passing. I meant tests that have not yet been written :). I'll spend time in the next few days (most likely this weekend) writing tests for this PR if you don't get a chance first. |
I was having trouble understanding how the current tests are organized and how they actually work... didn't have much success to be honest. I have a vague idea where to put tests for this, but could use some pointers. |
Oh cool. I just spent some time getting familiar with them too. I have среда, 21 сентября 2016 г. пользователь Bojan Tomic написал:
|
Based on this graphql/graphql-spec#189 it seems circular references are allowed just as long as the circularity can be broken. For e.g. (stealing code from the issue)
Perhaps even this is fine
Just not this
Or this
Also I think you'll need to add a check to ensure that the type reference is to an instance of GraphQLInputType. |
Right now, no implementation checks for non-broken recursion. I was asking in the chat about how to implement this and gave some ideas. I was thinking about adding a check if the reference points to a |
@@ -136,11 +134,12 @@ bintray { | |||
publications = ['graphqlJava'] | |||
publish = true | |||
pkg { | |||
repo = 'graphql-java' |
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.
These changes should probably be reverted from the pull request.
public interface TypeResolver { | ||
|
||
|
||
default GraphQLObjectType getType(TypeResolutionEnvironment env) { |
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.
Personally, I'm using this library with Java 1.8, but I'm not sure that requiring Java 1.8 will be acceptable to the maintainer of this project.
@@ -1,9 +1,15 @@ | |||
package graphql.schema; | |||
|
|||
|
|||
import graphql.TypeResolutionEnvironment; |
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.
Can we separate out this change regarding extension of the TypeResolver interface? That conflates the original goal: resolving TypeReferences.... I'm also curious about why you need all this extra context information when you implement your TypeResolver.
I'm willing to help with the change related to supporting TypeReferences as input types, but it appears that this PR also contains changes to add extra context information to the TypeResolver interface (which requires Java 1.8). ...I think @dminkovsky was hoping that the TypeReference as input change would include unit tests, and I think @IamCornholio was hoping that a schema validation check would be added to detect when a circular input type is declared as "non null" (and thus it would be impossible to provide such an input that would be valid). @kaqqao, if you don't think you address the above two items, I can extract out the TypeReference parts of this pull request and address the above two items as a separate pull request from me, but I don't want to rudely undermine your pull request. Please let me know your plan by Monday, otherwise I'll just open a separate PR that addresses these issues. Thanks, |
@brimworks Wooow, the past commits were completely unrelated and I did not intend to add them to this PR! Will fix immediately! Only the first commit was intentional. Sorry for the confusion! Regarding the tests, I can add them, but I need some help as to where to put the check for non-null circular references. I was thinking it should be somewhere at the end, once the schema has been already built. What do you think? And, as to why I need extra context for |
Ok, I reverted to the initial state, with just the first commit. Not sure if I should also re-apply the commits that were adding support for |
Yes, I think we need to do that. For implementation, perhaps a minor refactor can be performed, although I'd love to get @dminkovsky's opinion. Specifically, if we introduce this interface: public interface ResolveTypeReferences {
public void resolveTypeReferences(SchemaUtil util, Map<String, GraphQLType> typeMap);
} ...then void replaceTypeReferences(GraphQLSchema schema) {
Map<String, GraphQLType> typeMap = allTypes(schema, schema.getDictionary());
for (GraphQLType type : typeMap.values()) {
if (type instanceof ResolveTypeReferences) {
((ResolveTypeReferences)type).resolveTypeReferences(this, typeMap);
}
}
} ...and then modify the existing (and new places) where ...this way the logic for resolving the type references can be pushed down into the individual types. For "bonus points", the <T extends GraphQLType> T resolveTypeReference(T type, Map<String, GraphQLType> typeMap) {
if (type instanceof GraphQLTypeReference || typeMap.containsKey(type.getName())) {
GraphQLType resolvedType = typeMap.get(type.getName());
if (resolvedType == null) {
throw new GraphQLException("type " + type.getName() + " not found in schema");
}
return (T)resolvedType;
}
if (type instanceof ResolveTypeReferences) {
((ResolveTypeReferences) type).replaceTypeReferences(this, typeMap);
}
return type;
}
<T extends GraphQLType> List<T> resolveTypeReferences(List<T> types, Map<String, GraphQLType> typeMap) {
List<T> resolvedTypes = new ArrayList<T>();
for (T type : types) {
resolvedTypes.add((T)resolveTypeReference(type, typeMap));
}
return resolvedTypes;
} |
It seems the |
I definitely agree on the approaches you suggested. Just to clarify why I said I didn't like my own impl for
At some point I was thinking of making |
Yes, I see exactly what you mean @kaqqao ! Perhaps changing the ...similar thing with the |
@brimworks Wouldn't it make GraphQLObjectType type = newObject()
.withInterface(anInterface)
.withInterface(aReference)
.build();
type.getInterfaces(); would actually break with a |
...if it has not been resolved, then you are correct. We could implement |
Added a simple schema validation mechanism and a rule for recursive input types Added a test to ensure schema validation detects invalid recursive input types Added a test to ensure no dangling references exist Added a test to ensure input type references can not be used in place of output types (cherry picked from commit 7f86751)
Great work @kaqqao ! I'd love to see this change get merged in. Thanks! |
@brimworks I've now pushed the changes I had on my branch, including ubiquitous support for references. The important detail I found out is that order in which types are discovered is important for correct replacement of references, leading to rather surprising behaviors e.g. if the query/mutation type only uses references, and the real types are provided in the dictionary, they'll never get correctly replaced. For this reason, I changed Regarding the talk above, I went with the approach of creating dedicated private types for references to Object types, Interface types etc. Seems to maintain type-safety nicely without requiring special case handling, but still feels somewhat iffy. On the topic of schema validation, I added something more-or-less similar to query validation, alas simpler. It still feels like an overkill, to be honest 😯 It currently only checks for infinitely recursive input types, as expected. I also added some tests, but I feel I am misunderstanding a lot when it comes to test organization in the project (and it sure does not help that I'm not exactly good with Groovy or know anything about Spock). All in all, I'm not particularly happy with my code, especially the tests, so please feel 100% welcome to change anything and everything you don't like. I'll be OK with you even completely throwing it away in lieu of your own implementation. Just do pay attention to the changes in |
@kaqqao I don't know: I think your code might be fine and this just might be a particularly rough corner of the implementation, with these mutable fields that need to be updated with types that might be recursive and have certain restrictions... If you and @brimworks feel good enough about this in the sense that it adds spec functionality and doesn't break anything, then we should merge it. |
I don't think the changes should break anything, and they fix much needed functionality, so I'd love to see the changes get merged in. With that said, if it is important to have "pure" code, then I think all the schema types should essentially hold "type references". To get a concrete type you would pass in a map of type names into concrete types. However, the above suggestion would break the API, so it would require us to wait for a major version update. |
Cool, let's merge this then. Yes, going forward I think we'll want something like that, that also addresses the other issues with the definition types (hashCode/equals, circularity, validation). That can be for a breaking revision. |
As described in #172 this allows
TypeReference
s to be used as input types, making sure they're properly replaced, in both type and argument definitions.Worth to note is that it does not add validations that would disallow creating infinitely recursive types, as described in graphql/graphql-spec#189
I need some help/discussion for that. I see a few possible approaches here: add checks GraphQLInputObjectType.Builder#field, so they're ran as the fields are added (not really sure this is possible), add it to GraphQLFieldDefinition.Builder#argument (sounds like it's enough to just run this check for query & mutation roots?), or to GraphQLSchema.Builder#build so they're ran for the entire schema at the end.