Skip to content

Commit eff0415

Browse files
authored
Scalar serialization and deserialization is not handled correctly. (#333)
1 parent e437eeb commit eff0415

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1460
-329
lines changed

src/Core.Tests/Execution/Utilities/VariableValueBuilderTests.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Globalization;
34
using System.Linq;
45
using System.Threading;
56
using HotChocolate.Language;
@@ -221,6 +222,85 @@ public void CoerceInputObjectWithEnumAsEnumValueNode()
221222
Assert.Equal(BarEnum.B, bar.F.B);
222223
}
223224

225+
[InlineData(int.MaxValue)]
226+
[InlineData(1)]
227+
[InlineData(0)]
228+
[InlineData(-1)]
229+
[InlineData(int.MinValue)]
230+
[Theory]
231+
public void CreateValues_IntValue_Int(int value)
232+
{
233+
// arrange
234+
Schema schema = CreateSchema();
235+
OperationDefinitionNode operation = CreateQuery(
236+
"query test($test: Int) { a }");
237+
238+
var variableValues = new Dictionary<string, object>();
239+
variableValues.Add("test", 1);
240+
241+
var resolver = new VariableValueBuilder(schema, operation);
242+
243+
// act
244+
VariableCollection coercedVariableValues =
245+
resolver.CreateValues(variableValues);
246+
247+
// assert
248+
int result = coercedVariableValues.GetVariable<int>("test");
249+
Assert.Equal(1, result);
250+
}
251+
252+
[Fact]
253+
public void CreateValues_ObjectAsDictionary_Object()
254+
{
255+
// arrange
256+
Schema schema = CreateSchema();
257+
OperationDefinitionNode operation = CreateQuery(
258+
"query test($test: BarInput!) { a }");
259+
260+
var fooInput = new Dictionary<string, object>();
261+
fooInput["b"] = "B";
262+
263+
var barInput = new Dictionary<string, object>();
264+
barInput["f"] = fooInput;
265+
266+
var variableValues = new Dictionary<string, object>();
267+
variableValues.Add("test", barInput);
268+
269+
var resolver = new VariableValueBuilder(schema, operation);
270+
271+
// act
272+
VariableCollection coercedVariableValues =
273+
resolver.CreateValues(variableValues);
274+
275+
// assert
276+
Bar bar = coercedVariableValues.GetVariable<Bar>("test");
277+
Assert.NotNull(bar.F);
278+
Assert.Equal(BarEnum.B, bar.F.B);
279+
}
280+
281+
[Fact]
282+
public void CreateValues_SerializedDecimal_Decimal()
283+
{
284+
// arrange
285+
Schema schema = CreateSchema();
286+
OperationDefinitionNode operation = CreateQuery(
287+
"query test($test: Decimal) { a }");
288+
string input = "1.000000E-004";
289+
290+
var variableValues = new Dictionary<string, object>();
291+
variableValues.Add("test", input);
292+
293+
var resolver = new VariableValueBuilder(schema, operation);
294+
295+
// act
296+
VariableCollection coercedVariableValues =
297+
resolver.CreateValues(variableValues);
298+
299+
// assert
300+
decimal result = coercedVariableValues.GetVariable<decimal>("test");
301+
Assert.Equal(0.0001m, result);
302+
}
303+
224304
private Schema CreateSchema()
225305
{
226306
return Schema.Create(

src/Core.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,35 @@ friends @skip(if: $withFriends) {
287287
result.Snapshot();
288288
}
289289

290+
[Fact]
291+
public void GraphQLOrgDirectiveSkipExample1WithPlainClrVarTypes()
292+
{
293+
// arrange
294+
Schema schema = CreateSchema();
295+
string query = @"
296+
query Hero($episode: Episode, $withFriends: Boolean!) {
297+
hero(episode: $episode) {
298+
name
299+
friends @skip(if: $withFriends) {
300+
name
301+
}
302+
}
303+
}";
304+
305+
var variables = new Dictionary<string, object>
306+
{
307+
{ "episode", "JEDI" },
308+
{ "withFriends", false }
309+
};
310+
311+
// act
312+
IExecutionResult result = schema.Execute(
313+
query, variableValues: variables);
314+
315+
// assert
316+
result.Snapshot();
317+
}
318+
290319
[Fact]
291320
public void GraphQLOrgDirectiveSkipExample2()
292321
{

src/Core.Tests/__snapshots__/ExecuteGraphiQLIntrospectionQuery.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
{
1111
"kind": "SCALAR",
1212
"name": "String",
13-
"description": null,
13+
"description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
1414
"fields": null,
1515
"inputFields": null,
1616
"interfaces": null,
@@ -20,7 +20,7 @@
2020
{
2121
"kind": "SCALAR",
2222
"name": "ID",
23-
"description": null,
23+
"description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.",
2424
"fields": null,
2525
"inputFields": null,
2626
"interfaces": null,
@@ -30,7 +30,7 @@
3030
{
3131
"kind": "SCALAR",
3232
"name": "Boolean",
33-
"description": null,
33+
"description": "The `Boolean` scalar type represents `true` or `false`.",
3434
"fields": null,
3535
"inputFields": null,
3636
"interfaces": null,
@@ -40,7 +40,7 @@
4040
{
4141
"kind": "SCALAR",
4242
"name": "Int",
43-
"description": null,
43+
"description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.",
4444
"fields": null,
4545
"inputFields": null,
4646
"interfaces": null,
@@ -50,7 +50,7 @@
5050
{
5151
"kind": "SCALAR",
5252
"name": "Float",
53-
"description": null,
53+
"description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).",
5454
"fields": null,
5555
"inputFields": null,
5656
"interfaces": null,
@@ -80,7 +80,7 @@
8080
{
8181
"kind": "SCALAR",
8282
"name": "DateTime",
83-
"description": "ISO-8601 compliant date time type.",
83+
"description": "The `DateTime` scalar represents an ISO-8601 compliant date time type.",
8484
"fields": null,
8585
"inputFields": null,
8686
"interfaces": null,
@@ -90,7 +90,7 @@
9090
{
9191
"kind": "SCALAR",
9292
"name": "Date",
93-
"description": "ISO-8601 compliant date type.",
93+
"description": "The `Date` scalar represents an ISO-8601 compliant date type.",
9494
"fields": null,
9595
"inputFields": null,
9696
"interfaces": null,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"Data": {
3+
"hero": {
4+
"name": "R2-D2",
5+
"friends": [
6+
{
7+
"name": "Luke Skywalker"
8+
},
9+
{
10+
"name": "Han Solo"
11+
},
12+
{
13+
"name": "Leia Organa"
14+
}
15+
]
16+
}
17+
},
18+
"Errors": null
19+
}

src/Core/Execution/IQueryExecutionResult.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,5 @@ public interface IQueryExecutionResult
1010
T ToObject<T>();
1111

1212
string ToJson();
13-
14-
IReadOnlyDictionary<string, object> ToDictionary();
1513
}
1614
}

src/Core/Execution/QueryResult.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,6 @@ public override string ToString()
125125
return ToJson(true);
126126
}
127127
}
128+
129+
128130
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
5+
namespace HotChocolate.Execution
6+
{
7+
internal class DeserializationContext
8+
{
9+
public object Object { get; set; }
10+
11+
public Type Type { get; set; }
12+
13+
public ILookup<string, PropertyInfo> Fields { get; set; }
14+
}
15+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using HotChocolate.Utilities;
7+
8+
namespace HotChocolate.Execution
9+
{
10+
internal class DictionaryToObjectConverter
11+
: QueryResultVisitor<DeserializationContext>
12+
{
13+
public override void VisitObject(
14+
ICollection<KeyValuePair<string, object>> dictionary,
15+
DeserializationContext context)
16+
{
17+
if (!context.Type.IsValueType && context.Type != typeof(string))
18+
{
19+
ILookup<string, PropertyInfo> properties =
20+
context.Type.GetProperties()
21+
.ToLookup(t => t.Name,
22+
StringComparer.OrdinalIgnoreCase);
23+
context.Fields = properties;
24+
context.Object = Activator.CreateInstance(context.Type);
25+
26+
foreach (KeyValuePair<string, object> field in dictionary)
27+
{
28+
VisitField(field, context);
29+
}
30+
}
31+
}
32+
33+
protected override void VisitField(
34+
KeyValuePair<string, object> field,
35+
DeserializationContext context)
36+
{
37+
PropertyInfo property = context.Fields[field.Key].FirstOrDefault();
38+
if (property != null)
39+
{
40+
var valueContext = new DeserializationContext();
41+
valueContext.Type = property.PropertyType;
42+
Visit(field.Value, valueContext);
43+
property.SetValue(context.Object, valueContext.Object);
44+
}
45+
}
46+
47+
protected override void VisitList(
48+
IList<object> list,
49+
DeserializationContext context)
50+
{
51+
if (context.Type.IsArray)
52+
{
53+
var array = Array.CreateInstance(
54+
context.Type.GetElementType(),
55+
list.Count);
56+
57+
for (int i = 0; i < list.Count; i++)
58+
{
59+
var valueContext = new DeserializationContext();
60+
valueContext.Type = context.Type.GetElementType();
61+
Visit(list[i], valueContext);
62+
63+
array.SetValue(valueContext.Object, i);
64+
}
65+
66+
context.Object = array;
67+
}
68+
else
69+
{
70+
Type elementType = DotNetTypeInfoFactory.GetInnerListType(context.Type);
71+
if (elementType != null)
72+
{
73+
Type listType = typeof(List<>).MakeGenericType(elementType);
74+
IList l = (IList)Activator.CreateInstance(listType);
75+
76+
for (int i = 0; i < list.Count; i++)
77+
{
78+
var valueContext = new DeserializationContext();
79+
valueContext.Type = context.Type.GetElementType();
80+
Visit(list[i], valueContext);
81+
82+
list.Add(valueContext.Object);
83+
}
84+
}
85+
}
86+
}
87+
88+
protected override void VisitValue(
89+
object value,
90+
DeserializationContext context)
91+
{
92+
if (value is string s && context.Type.IsEnum)
93+
{
94+
context.Object = Enum.Parse(context.Type, s);
95+
}
96+
else if (value != null && !context.Type.IsInstanceOfType(value))
97+
{
98+
context.Object = Convert.ChangeType(value, context.Type);
99+
}
100+
else
101+
{
102+
context.Object = value;
103+
}
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)