Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions src/HotChocolate/Core/src/Types/Types/InputParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -614,14 +614,6 @@ private object DeserializeObject(object resultValue, InputObjectType type, Path
}

object? value = null;

// if the type is nullable but the runtime type is a non-nullable value
// we will create a default instance and assign that instead.
if (field.RuntimeType.IsValueType)
{
value = Activator.CreateInstance(field.RuntimeType);
}

return field.IsOptional
? new Optional(value, false)
: value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ private static void CompileSetProperties<T>(
{
value = CreateOptional(value, field.RuntimeType);
}
else if (field.Property.PropertyType.IsValueType
&& System.Nullable.GetUnderlyingType(field.Property.PropertyType) == null)
{
value = Expression.Coalesce(value, Expression.Default(field.Property.PropertyType));
}

value = Expression.Convert(value, field.Property.PropertyType);
Expression setPropertyValue = Expression.Call(instance, setter, value);
Expand Down
60 changes: 60 additions & 0 deletions src/HotChocolate/Core/test/Types.Tests/Types/InputParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,55 @@ public void Force_NonNull_Struct_To_Be_Optional()
Assert.IsType<Test4Input>(runtimeValue).MatchSnapshot();
}

[Fact]
public async Task Integration_CodeFirst_InputObjectNoDefaultValue_NoRuntimeTypeDefaultValueIsInitialized()
{
// arrange
var resolverArgumentsAccessor = new ResolverArgumentsAccessor();
var executor = await new ServiceCollection()
.AddSingleton(resolverArgumentsAccessor)
.AddGraphQL()
.AddQueryType(x => x.Field("foo")
.Argument("args", a => a.Type<NonNullType<MyInputType>>())
.Type<StringType>()
.ResolveWith<ResolverArgumentsAccessor>(r => r.ResolveWith(default!)))
.BuildRequestExecutorAsync();

// act
var query =
OperationRequest.FromSourceText(
"""
{
a: foo(args: { string: "allSet" int: 1 bool: true })
b: foo(args: { string: "noneSet" })
c: foo(args: { string: "intExplicitlyNull" int: null })
d: foo(args: { string: "boolExplicitlyNull" bool: null })
e: foo(args: { string: "intSetBoolNull" int: 1 bool: null })
f: foo(args: { string: "boolSetIntNull" int: null bool: true })
}
""");
await executor.ExecuteAsync(query, CancellationToken.None);

// assert
resolverArgumentsAccessor.Arguments.MatchSnapshot();
}

private class ResolverArgumentsAccessor
{
private readonly object _lock = new();
internal SortedDictionary<string, IDictionary<string, object?>?> Arguments { get; } = new();

internal string? ResolveWith(IDictionary<string, object?> args)
{
lock (_lock)
{
Arguments[args["string"]!.ToString()!] = args;
}

return "OK";
}
}

public class TestInput
{
public string? Field1 { get; set; }
Expand Down Expand Up @@ -564,4 +613,15 @@ public class Test4Input

public int Field2 { get; set; }
}

public class MyInputType : InputObjectType
{
protected override void Configure(IInputObjectTypeDescriptor descriptor)
{
descriptor.Name("MyInput");
descriptor.Field("string").Type<StringType>();
descriptor.Field("int").Type<IntType>();
descriptor.Field("bool").Type<BooleanType>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"allSet": {
"string": "allSet",
"int": 1,
"bool": true
},
"boolExplicitlyNull": {
"string": "boolExplicitlyNull",
"int": null,
"bool": null
},
"boolSetIntNull": {
"string": "boolSetIntNull",
"int": null,
"bool": true
},
"intExplicitlyNull": {
"string": "intExplicitlyNull",
"int": null,
"bool": null
},
"intSetBoolNull": {
"string": "intSetBoolNull",
"int": 1,
"bool": null
},
"noneSet": {
"string": "noneSet",
"int": null,
"bool": null
}
}