Skip to content
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

"GraphQL.ExecutionError: Null value provided for non-nullable argument 'field1'" - Even though it is actually not null. #268

Open
floge07 opened this issue Dec 13, 2024 · 6 comments

Comments

@floge07
Copy link

floge07 commented Dec 13, 2024

Hello again,

after I worked around #267 and then rolled out the new version, I discovered more obscure problems.
Some operations suddenly fail with the error mentioned in the title. No changes to the actual query or backend types were made.

The "ensure non null" validation seems to throw some new false positives.
If I remove the NonNull<> wrapper from that field it executes again, and there actually is a value!

This only happens on some operations. The thing that stands out about them is their complexity.
Looking at the request body, it's an operation with variables, which calls a query with one of the variables, and includes a fragment, which also uses a variable on one of it's methods.

An example:

query getStuffAndMore($var1: [String!]!, $var2: [String!]!, $var3: String) {
    getStuff(path: $var2, filter: $var3) {
        ...Fragment1
        ...on ErrorInfo {
            errorMessage
            errorCode
            __typename
        }
        __typename
    }
}

fragment Fragment1 on UnionType1 {
    more(field1: $var1)
}

I worked around it for now by removing the NonNull<> wrapper on the effected methods on the server side and instead added ArgumentNullException.ThrowIfNull() to the first line.

GraphQL: 8.2.1
GraphQL.Server.Transports.AspNetCore: 8.2.0
GraphQL.Convetions: 8.0.0

@Shane32
Copy link
Member

Shane32 commented Dec 13, 2024

This one is probably a bug in GraphQL.NET’s new argument processing and not due to the use of the conventions project. Probably has to do with the use of the argument within the fragment. I’d be happy to fix it if we can reproduce the problem (with or without the conventions project).

@Shane32
Copy link
Member

Shane32 commented Dec 13, 2024

Attempting to replicate the test, can you see if this schema would somewhat match what you're attempting?

# The Query type
type Query {
    getStuff(path: [String!]!, filter: String): GetStuffResponse!
}

# Union type definition used in the fragment
union GetStuffResponse = UnionType1 | ErrorInfo

# Union member type with the fragment field
type UnionType1 {
    more(field1: [String!]!): String!
}

# Error information type
type ErrorInfo {
    errorMessage: String!
    errorCode: String!
}

If getStuff returns a union, then it can only ever return either the UnionType1 object graph type, or the ErrorInfo type, correct?

@Shane32
Copy link
Member

Shane32 commented Dec 13, 2024

So far I cannot reproduce the problem within GraphQL.NET alone that fails using the above sample query. My code so far:

using GraphQL.Types;
using Microsoft.Extensions.DependencyInjection;

namespace GraphQL.Tests.Bugs;

public class Bug268
{
    [Theory]
    [InlineData(
        """{ "var1": ["test"], "var2": ["test"], "var3": "test" }""",
        """{ "data": { "getStuff": { "more": "test", "__typename": "UnionType1" } } }""")]
    [InlineData(
        """{ "var1": ["test432"], "var2": ["test"], "var3": "test" }""",
        """{ "data": { "getStuff": { "more": "test432", "__typename": "UnionType1" } } }""")]
    [InlineData(
        """{ "var1": [], "var2": [], "var3": null }""",
        """{ "data": { "getStuff": { "more": null, "__typename": "UnionType1" } } }""")]
    [InlineData(
        """{ "var1": ["test"], "var2": ["test"], "var3": "error" }""",
        """{ "data": { "getStuff": { "errorMessage": "Test1", "errorCode": "TestCode", "__typename": "ErrorInfo" }}}""")]
    public async Task Sample_Matrix(string variablesJson, string expectedResponseJson)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddGraphQL(b => b
            .AddSchema<MySchema>()
            .AddGraphTypes()
        );
        using var service = serviceCollection.BuildServiceProvider();
        var schema = service.GetRequiredService<MySchema>();
        var actualResponseJson = await schema.ExecuteAsync(_ =>
        {
            _.Query = """
                query getStuffAndMore($var1: [String!]!, $var2: [String!]!, $var3: String) {
                    getStuff(path: $var2, filter: $var3) {
                        ...Fragment1
                        ... on ErrorInfo {
                            errorMessage
                            errorCode
                            __typename
                        }
                        __typename
                    }
                }

                fragment Fragment1 on UnionType1 {
                    more(field1: $var1)
                }
                """;
            _.Variables = variablesJson.ToInputs();
        });
        actualResponseJson.ShouldBeCrossPlatJson(expectedResponseJson);
    }

    public class MySchema : Schema
    {
        public MySchema(IServiceProvider provider) : base(provider)
        {
            Query = provider.GetRequiredService<MyQuery>();
        }
    }

    public class MyQuery : ObjectGraphType
    {
        public MyQuery()
        {
            Name = "Query";
            Field<GetStuffResponseType>("getStuff")
                .Argument<NonNullGraphType<ListGraphType<NonNullGraphType<StringGraphType>>>>("path")
                .Argument<StringGraphType>("filter")
                .Resolve(context => context.GetArgument<string>("filter") == "error"
                    ? new ErrorInfo { ErrorMessage = "Test1", ErrorCode = "TestCode" }
                    : new UnionType1());
        }
    }

    public class GetStuffResponseType : UnionGraphType
    {
        public GetStuffResponseType()
        {
            Name = "GetStuffResponse";
            Type<UnionType1Type>();
            Type<ErrorInfoType>();
        }
    }

    public class UnionType1Type : ObjectGraphType<UnionType1>
    {
        public UnionType1Type()
        {
            Field<StringGraphType>("more")
                .Argument<NonNullGraphType<ListGraphType<NonNullGraphType<StringGraphType>>>>("field1")
                .Resolve(context => context.GetArgument<List<string>>("field1").FirstOrDefault());
        }
    }

    public class UnionType1
    {
    }

    public class ErrorInfoType : ObjectGraphType<ErrorInfo>
    {
        public ErrorInfoType()
        {
            Field(x => x.ErrorMessage);
            Field(x => x.ErrorCode);
        }
    }

    public class ErrorInfo
    {
        public string ErrorMessage { get; set; }
        public string ErrorCode { get; set; }
    }
}

@Shane32
Copy link
Member

Shane32 commented Dec 13, 2024

I'm looking at the Conventions project and I see ResolutionContext.cs:41 throws an error message of the exact type you described. So this problem may be specific to the Conventions project.

@Shane32
Copy link
Member

Shane32 commented Dec 13, 2024

If you can write a sample that reproduces the bug using the Conventions project, I can look further at this for you.

@floge07
Copy link
Author

floge07 commented Dec 13, 2024

Thanks for the support, and yeah, I will for sure do that.
But sadly I'm on vacation starting next week, so it will probably take a few weeks to get back to this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants