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

[Question] Inspect given properties while working with complex variables for a mutation

Open Sascha-Lindtner opened this issue 3 years ago • 10 comments

I'm currently struggling while working with variabels for mutations, when arguments are a complex type. My problem is, that I get a deserialized type of Note in the example below but not a list of properties which are defined in the argument value (example call 1). I really think this is possible in any way and I have just a "simple" missunderstanding.

When not using a variable and passing the struct directly into the argument, it is very easy for me to read which properties are given (example call 2), just by inspecting context.Arguments.

I created this simple example to demonstrate my problem.

Classes

    public class Note
    {
        public string Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
    }

    public class NoteGraphType: ObjectGraphType<Note>
    {
        public NoteGraphType()
        {
            Name = $"Note";
            Field(typeof(StringGraphType), "Id");
            Field(typeof(StringGraphType), "Title");
            Field(typeof(StringGraphType), "Body");
        }
    }

    public class InputNoteGraphType : InputObjectGraphType<Note>
    {
        public InputNoteGraphType()
        {
            Name = $"InputNote";
            Field(typeof(StringGraphType), "Id");
            Field(typeof(StringGraphType), "Title");
            Field(typeof(StringGraphType), "Body");
        }
    }

Mutation

    Field<NoteGraphType>("upsertNote",
        arguments: new QueryArguments(
            new QueryArgument<NonNullGraphType<InputNoteGraphType>> { Name = "input" }

        ),
        resolve: context => {
            var argumentVar = context.Variables.FirstOrDefault(x => x.Name == "input").Value;
            // How to resolve which properties of the variable are given? Here: ["title", "body"]
            // store.UpsertNote(argumentVar, propertyList);
            return argumentVar;
        });

    // example call 1
   mutation upsertNote($input: InputNote!) {
      upsertNote(input: $input) {
        id
      }
    }

Example call 1

mutation upsertNote($input: InputNote!) {
  upsertNote(input: $input) {
    id
  }
}

# Variables
#  {
#    "input": { 
#      "title": "Test note",
#      "body": "Hello World"
#    }
#  }

Example call 2

mutation upsertNote {
  upsertNote(input: { title: "Test note", body: "Hello World" })  {
    id
  }
}

Sascha-Lindtner avatar Feb 01 '22 12:02 Sascha-Lindtner

Try look into context.Arguments dictionary first.

sungam3r avatar Feb 01 '22 12:02 sungam3r

In context.Arguments is also "just" the deserialized Note instance

Sascha-Lindtner avatar Feb 01 '22 13:02 Sascha-Lindtner

Then context.Inputs.

sungam3r avatar Feb 01 '22 13:02 sungam3r

Then context.Inputs.

context is IResolveFieldContext and has no property Inputs

Sascha-Lindtner avatar Feb 01 '22 13:02 Sascha-Lindtner

What version do you use?

sungam3r avatar Feb 01 '22 13:02 sungam3r

What version do you use?

5.2.0

Sascha-Lindtner avatar Feb 01 '22 13:02 Sascha-Lindtner

@Shane32 We have ExecutionOptions.Inputs and use it in ValidationContext to fill variables (parse/map discrete inputs into variables) but then no initial inputs available from resolver.

sungam3r avatar Feb 01 '22 14:02 sungam3r

Trying to read the "initial inputs" would be a pain anyway. The input might be a literal and not contained within the variables, or it might be nested within another object. Then answer is easy with ParseDictionary, as this is exactly what the method was designed for:

Option 1

Add this code to your input graph type:

public override object ParseDictionary(IDictionary<string, object?> value)
    => value;

Then within your resolver, pull the raw dictionary like this:

var args = context.GetArgument<IDictionary<string, object>>("argument");

Option 2

    public class Note
    {
        public string Id { get; set; }
        public bool HasId { get; set; }
        public string Title { get; set; }
        public bool HasTitle { get; set; }
        public string Body { get; set; }
        public bool HasBody { get; set; }
    }

//within input graph type
public override object ParseDictionary(IDictionary<string, object?> value)
{
    var note = new Note();
    note.HasId = value.TryGetValue("id", out var obj);
    if (note.HasId) note.Id = (string)obj;
    //repeat for title and body
    return note;
}

//within resolver
var arg = context.GetArgument<Note>("arg");
if (arg.HasId) ...

Note

If you nest complex types, you might need to use the second option. Well, not necessarily. I guess it would depend on your needs.

Shane32 avatar Feb 01 '22 14:02 Shane32

@Sascha-Lindtner Were you able to solve your issue?

Shane32 avatar Feb 03 '22 05:02 Shane32

Sorry for my late answer. I created a fix. But all the other solutions did not work. As long as I created my InputTypes via InputObjectGraphType, I'm not able to see which fields are given. Now all my InputTypes a inherited by InputObjectGraphType instead and its fine.

Sascha-Lindtner avatar Mar 31 '22 11:03 Sascha-Lindtner