graphql-java icon indicating copy to clipboard operation
graphql-java copied to clipboard

TypeResolutionEnvironment should have more information available about fragments

Open optionsome opened this issue 2 years ago • 5 comments

Currently TypeResolutionEnvironment contains only name of a FragmentDefinition. We have a use case where we would want to return different schema object type in a TypeResolver implementation based on which object type is used in a fragment because we are trying to rename a object type in a backwards compatible manner. DataFetchingEnvironment seems to contain this information but TypeResolutionEnvironment does not.

optionsome avatar Oct 21 '21 08:10 optionsome

Can you please give as example of this say in a pseudo graphql query so can better understand it

What is it in DFE that needs to be made available?

bbakerman avatar Oct 22 '21 06:10 bbakerman

Here are pseudo query examples:

{
  foo() {
    __typename
    ...OldBarFragment
  }
}

fragment OldBarFragment on OldBar {
  name
}


{
  foo() {
    __typename
    ...NewBarFragment
  }
}

fragment NewBarFragment on NewBar {
  newName
}

Right now the only information I'm able to get about the fragment in TypeResolutionEnvironment is the name of the fragment (NewBarFragment for example in this case). I'm not able to see if the fragment is on OldBar or in NewBar which would make it possible to return the right schema type.

I think with the DataFetchingEnvironment it would be possible to write in code env.getSelectionSet().getFields().stream().map(SelectedField::getObjectTypeNames).anyMatch(strings -> strings.contains("OldBar"))

optionsome avatar Oct 22 '21 11:10 optionsome

Let me look more into this.

Typically a type resolver is concerned with a in memory runtime object and a non concrete type eg Interface or Union.

So the normal question is - this non concrete foo field fetch returned a JVM object and we need to resolve it back to a concrete object type so that execution can proceed. Typically its the JVM object itself that tells you its a Foo or a Bar.

So to get this right - you wanting it to say it could be either a OldBar or a NewBar depending on what query asked for rather than what you know it to be say. Do I have that right?

And the plan is to migrate all clients to NewBar over time and then change the type resolver to say "NewBar" all the time after a suitable change period. Do I have that right?

bbakerman avatar Oct 23 '21 00:10 bbakerman

I put up a PR but I am still a little confused as to how field sub selection helps in type resolution

It feels like it SHOULD but the example you give above does not stack up

env.getSelectionSet().getFields().stream().map(SelectedField::getObjectTypeNames).anyMatch(strings -> strings.contains("OldBar"))

This wont work because OldBar and NewBar will always be present on a field selection of type BarInterface. At least as that code is written

Can you expand out on your motivations and how you would distinguish inside the type resolver?

I have an example in my test in the PR - does that stack up?

bbakerman avatar Oct 23 '21 04:10 bbakerman

I can give a more concrete example on how we would use this. We have a nodes in a graph that represent bike rental stations. However, nowadays there are all sorts of rental places for e-scooters and cars etc. so we decided to refactor the underlying "bike rental station" data model to a more generic "vehicle rental station". Now I would want to update the API so that it would also get rid of the bike rental specific terminology while maintaining backwards compatibility. We have a query type that finds nearest nodes from some point. This new generic "VehicleRentalStation" JVM object should return either schema type with the old terminology (and fields) or the new schema type depending on queried fragment.

My bad, I think the code example I gave doesn't work as you pointed out. It seems like it's possible to get the Fragment details from the document instead so the following code should roughly work if the document is available from the TypeResolutionEnvironment.

SelectionSet set = environment.getField().getFields().get(0).getSelectionSet();
boolean hasOldBarFragment = set != null ? set.getSelections().stream().map(selection -> 
  selection instanceof InlineFragment 
  ? ((InlineFragment) selection).getTypeCondition().getName().equals("OldBar") 
  : selection instanceof FragmentSpread 
    ? env.getDocument().getDefinitionsOfType(FragmentDefinition.class).stream()
      .filter(definition -> definition.getName().equals(((FragmentSpread) selection).getName()))
      .anyMatch(fragmentDefinition -> fragmentDefinition.getTypeCondition().getName().equals("OldBar")) 
    : false 
).anyMatch(t => t == true) : false;

optionsome avatar Oct 25 '21 11:10 optionsome

Hello, this issue has been inactive for 60 days, so we're marking it as stale. If you would like to continue this discussion, please comment within the next 30 days or we'll close the issue.

github-actions[bot] avatar Nov 30 '23 00:11 github-actions[bot]

Hello, as this issue has been inactive for 90 days, we're closing the issue. If you would like to resume the discussion, please create a new issue.

github-actions[bot] avatar Dec 31 '23 00:12 github-actions[bot]