Support for partial update mutations
Closed this issue · 11 comments
Partial updates are useful for allowing a single mutation function update only the fields provided in the input payload instead of arbitrarily setting the missing fields to null. But in the current GraphQL Conventions framework there is no way to detect whether a given field was provided by the client.
The proposal is to add an Optional wrapper that can be applied to the input fields of partial update mutations that exposes an IsSpecified property for checking whether the client included that field.
Below is an example of an input type using the proposed wrapper:
[InputType]
public class ProfileInput
{
public Optional<string> FirstName { get; set; }
public Optional<string> LastName { get; set; }
}
And this code snippet shows how the IsSpecified property might be used for processing the update:
if (input.FirstName.IsSpecified)
{
dto.FirstName = input.FirstName.Value;
}
if (input.LastName.IsSpecified)
{
dto.LastName = input.LastName.Value;
}
I have already implemented this in a fork and will be submitting a PR shortly.
Just for history: this looks like #115
If I understand #115 correctly, this would require the client to send a Maybe structure for every field. My solution would definitely be more compact and have less coding burden on the client.
It seems that GraphQL has no notion of optional input type fields without connection to a default value.
See the grammar of InputValueDefinition: https://facebook.github.io/graphql/June2018/#InputValueDefinition
In order to make input value pass validation it must be defined as optional by adding a default value.
Having said this, if you want to treat Optional<T>
as its underlying type T
in the schema, you can't give any default value outside of a const value of type T
. But this is essentially what you want, I guess.
A different behavior based on the fact that whether a value was given by default or explicitly default value, will rather confusing.
input ProfileInput {
FirstName: String = null
}
and then making query
mutation {
update1: updateProfile({ FirstName: null })
update2: updateProfile
}
why I would expect different behavior if the value is the same as default?
The partial update requests are able to pass validation because of the input coercion rules defined for input objects here:
https://facebook.github.io/graphql/June2018/#sec-Input-Objects
In this case, the fields have no default value and are not non-null. They are therefor left out of the result map. The Optional wrapper just provides a way to determine if a field is in the result map and doesn't introduce any changes to validation.
This input would set FirstName to 'Foo' and LastName to 'Bar'
{
FirstName: "Foo",
LastName: "Bar"
}
This input would set LastName to 'Baz', leaving FirstName unchanged.
{
LastName: "Baz"
}
So what if we did pass null for FirstName as in the input below, FirstName would be changed to null, and LastName would be unchanged.
{
FirstName: null
}
Ah, correct, I missed this part:
If the value null was provided for an input object field, and the field’s type is not a non‐null type, an entry in the coerced unordered map is given the value null. In other words, there is a semantic difference between the explicitly provided value null versus having not provided a value.
My bad.
So then I would suggest to make sure Optional
is only applied to nullable fields as it is an error on non-null fields to be not specified.
Good point. Valid examples would include:
- Optional<string>
- Optional<int?>
Invalid examples would include:
- Optional<NonNull<string>>
- Optional<int>
Do you have any suggestions on how best to implement this? Unfortunately there's not a good way to produce a compile time error. I was thinking of using the where restriction on the Optional class, but it is not flexible enough to handle both reference types (e.g. string) and nullable types (e.g. int?). This leaves producing a run-time error, but I'm not sure exactly when/where this should happen. Optional could self validate as it is being applied by throwing an exception in the Construct method. Could also try to report an error when schema metadata is being generated. Thoughts?
Adding this static constructor to Optional provides a decent run-time check for usage of nullable types:
static Optional()
{
var typeInfo = typeof(T).GetTypeInfo();
if ((typeInfo.IsValueType && !typeInfo.IsGenericType(typeof(Nullable<>))) || typeInfo.IsGenericType(typeof(NonNull<>)))
{
throw new InvalidOperationException(
string.Format("Cannot instantiate with non-nullable type: {0}",
typeof(T)));
}
}
If all looks good I'll update my PR with the requested changes.
+1
Hello
I need some help. I am trying to use this feature. But I find it difficult to implement.
I have a model
[InputType]
public class Person
{
public Optional<string> Name{ get; set; }
public Optional<string> LastName{ get; set; }
public Optional<string> Id { get; set; }
}
I have an InputGraphType
public class PersonType : InputObjectGraphType<Person>
{
public PersonType ()
{
this.Name = "person";
this.Field(person=> person.Name);
this.Field(person=> person.LastName);
}
}
But I got this error:
System.Argument.Exception: The graphQL type for field: 'Name' on parent type: Optional could not derived implicitly.
ArgumentOutOfRangeException: The type: Optional'1 cannot be coerced effectively to a GraphQL type (Parameter type)
I also try to make something like this, but dont work.
Field(person => person.Name.Value);
Field<string>(person => person.Name.Value.ToString());
this.Field(Name= "name", person => person.Name.Value.ToString());
Like a read in this post I am just trying to make a partial update mutation ignoring the field that are not sent
Hello
I need some help. I am trying to use this feature. But I find it difficult to implement.
I have a model[InputType] public class Person { public Optional<string> Name{ get; set; } public Optional<string> LastName{ get; set; } public Optional<string> Id { get; set; } }
I have an InputGraphType
public class PersonType : InputObjectGraphType<Person> { public PersonType () { this.Name = "person"; this.Field(person=> person.Name); this.Field(person=> person.LastName); } }
But I got this error:
System.Argument.Exception: The graphQL type for field: 'Name' on parent type: Optional could not derived implicitly.ArgumentOutOfRangeException: The type: Optional'1 cannot be coerced effectively to a GraphQL type (Parameter type)
I also try to make something like this, but dont work.
Field(person => person.Name.Value); Field<string>(person => person.Name.Value.ToString()); this.Field(Name= "name", person => person.Name.Value.ToString());
Like a read in this post I am just trying to make a partial update mutation ignoring the field that are not sent
I fixed this by not using conventions as i felt i was adding too much to a simple API. Check my solution i made here:
graphql-dotnet/graphql-dotnet#541 (comment)
Feel free to ask questions.