Skip to content

Navigation Menu

Sign in
Sign up

Union response type not working #850

Answered by murtukov
WouterCypers asked this question in Q&A
Discussion options

Q A
Bug report? yes
Feature request? no
BC Break report? no
RFC? no
Version/Branch 1.0.0

I have a mutation ResetPassword which should return some date OR an error response. The response is defined as a Union. I get an error when executing the mutation:

Abstract type ResetPasswordResponse must resolve to an Object type at runtime for field publicMutation.ResetPassword with value "instance of App\GraphQL\PublicApi\Mutation\ResetPassword\ResetPasswordMutationFailedResponse", received "null". Either the ResetPasswordResponse type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.

In the documentation, I only see resolveType or isTypeOf being used for interfaces. I'm not sure how to make this work.

The GraphQL request:

mutation resetPassword {
 ResetPassword (
 input: {
 token: "PasswordResetRequest-011eaf33-a93a-4f72-9509-e2b739741535"
 newPassword: "MyPassword01!+qw121124"
 repeatNewPassword: "MyPassword01!+qw12112"
 }
 ) {
 ... on ResetPasswordSuccessfulResponse {
 token
 newPassword
 }
 ... on ResetPasswordFailedResponse {
 errors
 }
 }
}

The mutation:

ResetPassword:
 type: ResetPasswordResponse
 resolve: '@=mutation("reset_password", args["input"]["token"], args["input"]["newPassword"], args["input"]["repeatNewPassword"])'
 args:
 input:
 type: ResetPasswordInput!

The resolver:

class ResetPasswordMutation implements MutationInterface, AliasedInterface
{
 public function __construct(
 private MessageBusInterface $messageBus,
 ) {}
 public function resetPassword(
 string $token,
 string $newPassword,
 string $repeatNewPassword
 ): ResetPasswordMutationResponse {
 try {
 $this->messageBus->dispatch(
 new ResetPassword(
 $token,
 $newPassword,
 $repeatNewPassword
 )
 );
 return new ResetPasswordMutationSuccessfulResponse($token, $newPassword);
 } catch (ValidationFailedException $exception) {
 $violations = [];
 /** @var ConstraintViolationInterface $violation */
 foreach ($exception->getViolations() as $violation) {
 $violation->getMessage();
 }
 return new ResetPasswordMutationFailedResponse($violations);
 }
 }
 public static function getAliases(): array
 {
 return [
 'resetPassword' => 'reset_password',
 ];
 }
}

YAML types config:

ResetPasswordResponse:
 type: union
 config:
 types: [ResetPasswordSuccessfulResponse, ResetPasswordFailedResponse]
 description: 'Reset password succeeded or failed.'
ResetPasswordSuccessfulResponse:
 type: object
 config:
 fields:
 token:
 type: 'String!'
 newPassword:
 type: 'String!'
ResetPasswordFailedResponse:
 type: object
 config:
 fields:
 errors:
 type: '[String]'
You must be logged in to vote

@WouterCypers you should use union types exactly like interfaces. You can check the documentation for interfaces for more details.

In your case you should define for your abstract type (ResetPasswordResponse) a type resolver (resolveType):

ResetPasswordResponse:
 type: union
 config:
 types: [ResetPasswordSuccessfulResponse, ResetPasswordFailedResponse]
 description: 'Reset password succeeded or failed.'
 resolveType: "@=mutation('map_response_type', value, typeResolver)"

And modify your ResetPasswordMutation class:

use GraphQL\Type\Definition\ObjectType;
use Overblog\GraphQLBundle\Resolver\TypeResolver;
use Overblog\GraphQLBundle\Resolver\UnresolvableException;
class ResetPas...

Replies: 1 comment 2 replies

Comment options

@WouterCypers you should use union types exactly like interfaces. You can check the documentation for interfaces for more details.

In your case you should define for your abstract type (ResetPasswordResponse) a type resolver (resolveType):

ResetPasswordResponse:
 type: union
 config:
 types: [ResetPasswordSuccessfulResponse, ResetPasswordFailedResponse]
 description: 'Reset password succeeded or failed.'
 resolveType: "@=mutation('map_response_type', value, typeResolver)"

And modify your ResetPasswordMutation class:

use GraphQL\Type\Definition\ObjectType;
use Overblog\GraphQLBundle\Resolver\TypeResolver;
use Overblog\GraphQLBundle\Resolver\UnresolvableException;
class ResetPasswordMutation implements MutationInterface, AliasedInterface
{
 public function __construct(
 private MessageBusInterface $messageBus,
 ) {}
 public function resetPassword(
 string $token,
 string $newPassword,
 string $repeatNewPassword
 ): ResetPasswordMutationResponse {
 try {
 $this->messageBus->dispatch(
 new ResetPassword(
 $token,
 $newPassword,
 $repeatNewPassword
 )
 );
 return new ResetPasswordMutationSuccessfulResponse($token, $newPassword);
 } catch (ValidationFailedException $exception) {
 $violations = [];
 /** @var ConstraintViolationInterface $violation */
 foreach ($exception->getViolations() as $violation) {
 $violation->getMessage();
 }
 return new ResetPasswordMutationFailedResponse($violations);
 }
 }
 public function mapResponseType($value, TypeResolver $typeResolver): ObjectType
 {
 if ($value instanceof ResetPasswordMutationSuccessfulResponse) {
 return $typeResolver->resolve('ResetPasswordSuccessfulResponse');
 } 
 
 if ($value instanceof ResetPasswordMutationFailedResponse) {
 return $typeResolver->resolve('ResetPasswordFailedResponse');
 }
 throw new UnresolvableException("Couldn't resolve type for union 'ResetPasswordResponse'");
 }
 public static function getAliases(): array
 {
 return [
 'resetPassword' => 'reset_password',
 'mapResponseType' => 'map_response_type'
 ];
 }
}
You must be logged in to vote
2 replies
Comment options

@murtukov Thank you for your speedy reply!

I have two remarks and one question.

Remarks:

  • In the YAML config you provided, it should say @=mutation instead of @=query (I had a Unknown resolver with alias [..] error)
  • In my case, the TypeResolver instance returns a GraphQL\Type\Definition\Type instead of a GraphQL\Type\Definition\ObjectType

Question:

Would it also be possible to use isTypeOf on ResetPasswordMutationFailedResponse and ResetPasswordMutationSuccesfulResponse instead of the mapResponseType on the mutation? If yes, do you have a reference on how to implement this?

Comment options

Would it also be possible to use isTypeOf

Yes, it is possible, but not recommended for performance reasons. For more info: https://webonyx.github.io/graphql-php/type-definitions/interfaces/#interface-role-in-data-fetching


In the YAML config you provided, it should say @=mutation instead of @=query (I had a Unknown resolver with alias [..] error)

Yes, good catch, updated the answer. It should be @=mutation because your resolver is located in a class implementing MutationInterface.


In my case, the TypeResolver instance returns a GraphQL\Type\Definition\Type instead of a GraphQL\Type\Definition\ObjectType.

This is not possible, because your GraphQL types ResetPasswordSuccessfulResponse and ResetPasswordFailedResponse are of type object and thus it generates 2 PHP classes that extend ObjectType. And ObjectType extends Type.

So even though the return type-hint of the method TypeResolver::resolve is Type, in your case it actually returns an ObjectType instance.


P. S. The qualifiers mutation and query are likely to be changed in the future. You are currently using the 1.0 version, which is in development and there are many architectural changes planned. While it's ok to use 1.0 for learning I strongly discourage you to use it in production.

Answer selected by murtukov
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
Converted from issue

This discussion was converted from issue #849 on May 14, 2021 09:32.

AltStyle によって変換されたページ (->オリジナル) /