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

Projection on UnionTypes leads to an Unable to cast Exception

Open phebing opened this issue 11 months ago • 1 comments

Product

Hot Chocolate

Version

13.8.1

Link to minimal reproduction

https://github.com/phebing/HC-UnionType-Projection-Bug

Steps to reproduce

Create a backend Query like

[ExtendObjectType(OperationTypeNames.Query)]
public class Queries
{
    public class Base
    {
        public string C { get; set; }
    }

    public class ChildA : Base
    {
        public string A { get; set; }
    }

    public class ChildB : Base
    {
        public string B { get; set; }
    }

    public class ChildAType : ObjectType<ChildA>
    {
        protected override void Configure(IObjectTypeDescriptor<ChildA> descriptor)
        {
            descriptor.Field(_ => _.A).Type<NonNullType<StringType>>();
        }
    }

    public class ChildBType : ObjectType<ChildB>
    {
        protected override void Configure(IObjectTypeDescriptor<ChildB> descriptor)
        {
            descriptor.Field(_ => _.B).Type<NonNullType<StringType>>();
        }
    }

    public class UnionTestType : UnionType
    {
        protected override void Configure(IUnionTypeDescriptor descriptor)
        {
            base.Configure(descriptor);
            descriptor.Name("UnionTestType");
            descriptor.Type<ChildAType>();
            descriptor.Type<ChildBType>();
        }
    }

    [UseProjection]
    [GraphQLType(typeof(ListType<UnionTestType>))]
    public IQueryable<Base> GetUnionTest()
    {
        var types = new List<Base>();
        return types.AsQueryable();
    }
}

And run a query on it like

query {
  unionTest {
    __typename
    ...on ChildA{
      a
    }
    ... on ChildB {
      b
    }
  }
}

What is expected?

The Query returns the specific Elements and uses a projected DB call to do so.

What is actually happening?

If you have a Query like GetUnionTest that returns an UnionType and you add [UseProjection] then it throws an "Unexpected Execution Error" with the message

"message":  "Unable to cast object of type 'System.Linq.Expressions.Expression1`1[System.Func`2[System.Object,System.Object]]' to type 'System.Linq.Expressions.Expression`1[System.Func`2[omittedNamespace...Queries+Base,omittedNamespace...Queries+Base]]'.",

"stackTrace": "   at HotChocolate.Data.Projections.Expressions.QueryableProjectionScopeExtensions.Project[T](QueryableProjectionScope scope)\n   at HotChocolate.Data.Projections.Expressions.QueryableProjectionContextExtensions.Project[T](QueryableProjectionContext context)\n   at HotChocolate.Data.Projections.Expressions.QueryableProjectionProvider.<CreateApplicator>b__7_0[TEntityType](IResolverContext context, Object input)\n   at HotChocolate.Data.Projections.Expressions.QueryableProjectionProvider.<>c__DisplayClass4_0`1.<<CreateExecutor>g__ExecuteAsync|1>d.MoveNext()\n--- End of stack trace from previous location ---\n   at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)\n   at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)"

Even a query just for the types throws this error

query {
  unionTest {
    __typename
  }
}

In the minimal example I skipped the DB, since the error is also happening on a List.AsQueryable().

The Documentation of Projections is not mentioning if UnionTypes are not supported. So if that is the case, then maybe this should be mentioned.

Relevant log output

[2024-03-01 12:39:34 ERR] An error occurred when resolving /unionTest
System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.Expression1`1[System.Func`2[System.Object,System.Object]]' to type 'System.Linq.Expressions.Expression`1[System.Func`2[omittedNamespace...Queries+Base]]'.
    at HotChocolate.Data.Projections.Expressions.QueryableProjectionScopeExtensions.Project[T](QueryableProjectionScope scope)
    at HotChocolate.Data.Projections.Expressions.QueryableProjectionContextExtensions.Project[T](QueryableProjectionContext context)
    at HotChocolate.Data.Projections.Expressions.QueryableProjectionProvider.<CreateApplicator>b__7_0[TEntityType](IResolverContext context, Object input)
    at HotChocolate.Data.Projections.Expressions.QueryableProjectionProvider.<>c__DisplayClass4_0`1.<<CreateExecutor>g__ExecuteAsync|1>d.MoveNext()
 --- End of stack trace from previous location ---
    at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)
    at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)


### Additional context

_No response_

phebing avatar Mar 01 '24 13:03 phebing

There seem to be more Issues going on with UnionTypes and Projections (maybe related): If I have the UnionType as a property of a ResponseType and the query uses projection, then the __typename is always only one of the types, even if the actual object uses the other type. Also in such a setup the query has to ask for the same fields in both types or it will result in an error. So with an object ChildA and ChildB a query like

__typename
... on ChildA { c }, 
... on ChildB { c }

would work, but you would always have __typename == "ChildA" (or B) but not one of each.

And

__typename
... on ChildA { a },
... on ChildB { b }

would throw an error.

And if the property is a List of the UnionType, then querying that also results in errors.

If this should be supported, then I can create an Issue with minimal examples as well, but feels like this is not supported at the moment.

phebing avatar Mar 07 '24 16:03 phebing