juancastillo0 / leto

Dart GraphQL server libraries. Utilities, code generator, examples and reference implementation.
https://juancastillo0.github.io/leto/
MIT License
38 stars 6 forks source link

Nested variable inputs fail validation when they are formatted correctly #29

Open warrenisarobot opened 1 month ago

warrenisarobot commented 1 month ago

When GraphQLInputs are used with nested inputs using variables they fail validation. Here is an example using Let annotations:

import 'package:json_annotation/json_annotation.dart';
import 'package:leto_schema/leto_schema.dart';

part 'test_handler.g.dart';

@GraphQLInput()
@JsonSerializable()
class TestValue {
  final String value;

  TestValue(this.value);

  factory TestValue.fromJson(Map<String, dynamic> json) => _$TestValueFromJson(json);

  Map<String, dynamic> toJson() => _$TestValueToJson(this);
}

@GraphQLInput()
@JsonSerializable()
class TestInput {
  String words;
  TestValue? value;
  TestValue? value2;

  TestInput(this.words, this.value, this.value2);

  factory TestInput.fromJson(Map<String, dynamic> json) => _$TestInputFromJson(json);

  Map<String, dynamic> toJson() => _$TestInputToJson(this);
}

@Mutation()
bool testMutation(Ctx ctx, TestInput input, int num, bool isATest) {
  print("TestMutation: ${input.words}, ${input.value?.value}, ${input.value2?.value}");
  return true;
}

Queries used for testing:

mutation test ($words: String!, $val1: TestValue, $val2: TestValue, $num: Int!, $test: Boolean!) {
  testMutation(input: {words: $words, value: $val1, value2: $val2}, num: $num, isATest: $test) 
}

mutation test2 ($input: TestInput!) {
  testMutation(input: $input, num: 1, isATest: true) 
}

Variables used for testing:

{
  "words": "test words",
  "val1": null,
  "val2": {"value": "value 2"},
  "num": 10,
  "test": true,
  "input": {"words": "test words", "value": {"value": "value 2"}, "val2": null}
  }

Errors from tests

First query errors:

{
  "errors": [
    {
      "message": "Type coercion error for argument \"input\" (TestInput!) of field \"testMutation\". Got value {words: test words, value: null, value2: Instance of 'TestValue'}.",
      "path": [
        "testMutation"
      ],
      "locations": [
        {
          "line": 84,
          "column": 15
        }
      ]
    },
    {
      "message": "input: Expected \"value2\" to be a Map of type TestValue. Got invalid value Instance of 'TestValue'.",
      "path": [
        "testMutation"
      ],
      "locations": [
        {
          "line": 84,
          "column": 15
        }
      ]
    }
  ],
  "data": null
}

Second query works correctly since it is the top-level object variable.

Potential Solution

I believe this is happening because the variable values are coerced first, then the input is parsed. When the input is parsed it validates against the values. This is skipped when the top-level is an InputNode, but if it is below the first level it will be validated.

https://github.com/juancastillo0/leto/pull/28

This PR removes the variable coercion and lets the input validation happen in the same way.

I've gone though a portion of the code, but I don't have the full picture as of now. There may be a better solution than this.