Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Relay ID handling for pure code-first. #2165

Merged
merged 3 commits into from
Jul 26, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 17 additions & 5 deletions src/Core/Core/Execution/Utilities/ArgumentValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ namespace HotChocolate.Execution
{
public readonly struct ArgumentValue
{
public ArgumentValue(IInputType type, ValueKind kind, object value)
public ArgumentValue(
IInputType type,
ValueKind kind,
object value,
IFieldValueSerializer serializer)
{
Type = type ?? throw new ArgumentNullException(nameof(type));
Value = value;
Kind = kind;
Value = value;
Serializer = serializer;
Error = null;
Literal = null;
}
Expand All @@ -22,14 +27,19 @@ public ArgumentValue(IInputType type, IError error)
Kind = null;
Value = null;
Literal = null;
Serializer = null;
}

public ArgumentValue(IInputType type, ValueKind kind, IValueNode literal)
public ArgumentValue(
IInputType type,
ValueKind kind,
IValueNode literal,
IFieldValueSerializer serializer)
{
Type = type ?? throw new ArgumentNullException(nameof(type));
Literal = literal
?? throw new ArgumentNullException(nameof(literal));
Literal = literal ?? throw new ArgumentNullException(nameof(literal));
Kind = kind;
Serializer = serializer;
Value = null;
Error = null;
}
Expand All @@ -43,5 +53,7 @@ public ArgumentValue(IInputType type, ValueKind kind, IValueNode literal)
public IValueNode Literal { get; }

public IError Error { get; }

public IFieldValueSerializer Serializer { get; }
}
}
6 changes: 5 additions & 1 deletion src/Core/Core/Execution/Utilities/ArgumentVariableValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ internal readonly struct ArgumentVariableValue
public ArgumentVariableValue(
IInputType type,
NameString variableName,
object defaultValue)
object defaultValue,
IFieldValueSerializer serializer)
{
Type = type;
VariableName = variableName;
DefaultValue = defaultValue;
Serializer = serializer;
}

public IInputType Type { get; }

public NameString VariableName { get; }

public object DefaultValue { get; }

public IFieldValueSerializer Serializer { get; }
}
}
9 changes: 6 additions & 3 deletions src/Core/Core/Execution/Utilities/FieldCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,8 @@ private void CoerceArgumentValue(
new ArgumentVariableValue(
argument.Type,
variable.Name.Value,
defaultValue);
defaultValue,
argument.Serializer);
}
else
{
Expand Down Expand Up @@ -340,15 +341,17 @@ private void CreateArgumentValue(
new ArgumentValue(
argument.Type,
literal.GetValueKind(),
ParseLiteral(argument.Type, literal));
ParseLiteral(argument.Type, literal),
argument.Serializer);
}
else
{
fieldInfo.Arguments[argument.Name] =
new ArgumentValue(
argument.Type,
literal.GetValueKind(),
literal);
literal,
argument.Serializer);
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/Core/Core/Execution/Utilities/FieldSelection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,14 @@ public IReadOnlyDictionary<NameString, ArgumentValue> CoerceArguments(
if (value is IValueNode literal)
{
kind = literal.GetValueKind();
args[var.Key] = new ArgumentValue(var.Value.Type, kind, literal);
args[var.Key] = new ArgumentValue(
var.Value.Type, kind, literal, var.Value.Serializer);
}
else
{
Scalars.TryGetKind(value, out kind);
args[var.Key] = new ArgumentValue(var.Value.Type, kind, value);
args[var.Key] = new ArgumentValue(
var.Value.Type, kind, value, var.Value.Serializer);
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ private T CoerceArgumentValue<T>(
return default;
}

value = argumentValue.Serializer is null
? value
: argumentValue.Serializer.Deserialize(value);

if (value is T resolved)
{
return resolved;
Expand Down
24 changes: 19 additions & 5 deletions src/Core/Types.Tests/Types/Descriptors/DescriptorContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ public void Create_ServicesNull_ArgumentException()
var options = new SchemaOptions();

// act
Action action = () => DescriptorContext.Create(options, null);
Action action = () =>
DescriptorContext.Create(
options,
null,
() => throw new NotSupportedException());

// assert
Assert.Throws<ArgumentNullException>(action);
Expand All @@ -24,10 +28,14 @@ public void Create_ServicesNull_ArgumentException()
public void Create_OptionsNull_ArgumentException()
{
// arrange
var service = new EmptyServiceProvider();
var services = new EmptyServiceProvider();

// act
Action action = () => DescriptorContext.Create(null, service);
Action action = () =>
DescriptorContext.Create(
null,
services,
() => throw new NotSupportedException());

// assert
Assert.Throws<ArgumentNullException>(action);
Expand All @@ -45,7 +53,10 @@ public void Create_With_Custom_NamingConventions()

// act
DescriptorContext context =
DescriptorContext.Create(options, services);
DescriptorContext.Create(
options,
services,
() => throw new NotSupportedException());

// assert
Assert.Equal(naming, context.Naming);
Expand All @@ -65,7 +76,10 @@ public void Create_With_Custom_TypeInspector()

// act
DescriptorContext context =
DescriptorContext.Create(options, services);
DescriptorContext.Create(
options,
services,
() => throw new NotSupportedException());

// assert
Assert.Equal(inspector, context.Inspector);
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Types.Tests/Types/InterfaceTypeExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void InterfaceTypeExtension_AddField()

[Obsolete]
[Fact]
public void InterfaceTypeExtension_DepricateField()
public void InterfaceTypeExtension_DeprecateField()
{
// arrange
FieldResolverDelegate resolver =
Expand Down
20 changes: 20 additions & 0 deletions src/Core/Types.Tests/Types/ObjectTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,26 @@ public void Required_Attribute_Is_Recognized()
.MatchSnapshot();
}

[Fact]
public void Infer_NonNull_Int_Array_Correctly()
{
SchemaBuilder.New()
.AddQueryType<Query>()
.Create()
.ToString()
.MatchSnapshot();
}

#nullable enable
public class Query
{
public byte[] Array()
{
throw new NotImplementedException();
}
}
#nullable disable

public class GenericFoo<T>
{
public T Value { get; }
Expand Down
110 changes: 110 additions & 0 deletions src/Core/Types.Tests/Types/Relay/IdAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Threading.Tasks;
using HotChocolate.Execution;
using Snapshooter.Xunit;
using Xunit;

namespace HotChocolate.Types.Relay
{
public class IdAttributeTests
{
[Fact]
public async Task Id_On_Arguments()
{
// arrange
var idSerializer = new IdSerializer();
string intId = idSerializer.Serialize("Query", 1);
string stringId = idSerializer.Serialize("Query", "abc");
string guidId = idSerializer.Serialize("Query", Guid.Empty);

// act
IExecutionResult result =
await SchemaBuilder.New()
.AddQueryType<Query>()
.AddType<FooPayload>()
.Create()
.MakeExecutable()
.ExecuteAsync(
QueryRequestBuilder.New()
.SetQuery(
@"query foo ($intId: ID! $stringId: ID! $guidId: ID!) {
intId(id: $intId)
stringId(id: $stringId)
guidId(id: $guidId)
}")
.SetVariableValue("intId", intId)
.SetVariableValue("stringId", stringId)
.SetVariableValue("guidId", guidId)
.Create());

// assert
result.ToJson().MatchSnapshot();
}

[Fact]
public async Task Id_On_Objects()
{
// arrange
var idSerializer = new IdSerializer();
string someId = idSerializer.Serialize("Some", 1);

// act
IExecutionResult result =
await SchemaBuilder.New()
.AddQueryType<Query>()
.AddType<FooPayload>()
.Create()
.MakeExecutable()
.ExecuteAsync(
QueryRequestBuilder.New()
.SetQuery(
@"query foo ($someId: ID!) {
foo(input: { someId: $someId }) {
someId
}
}")
.SetVariableValue("someId", someId)
.Create());

// assert
new {
result = result.ToJson(),
someId
}.MatchSnapshot();
}

[Fact]
public void Id_Type_Is_Correctly_Inferred()
{
SchemaBuilder.New()
.AddQueryType<Query>()
.AddType<FooPayload>()
.Create()
.ToString()
.MatchSnapshot();
}

public class Query
{
public string IntId([ID] int id) => id.ToString();
public string StringId([ID] string id) => id.ToString();
public string GuidId([ID] Guid id) => id.ToString();
public IFooPayload Foo(FooInput input) => new FooPayload { SomeId = input.SomeId };
}

public class FooInput
{
[ID("Some")] public string SomeId { get; set; }
}

public class FooPayload : IFooPayload
{
[ID("Bar")] public string SomeId { get; set; }
}

public interface IFooPayload
{
[ID] string SomeId { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"data":{"intId":"1","stringId":"abc","guidId":"00000000-0000-0000-0000-000000000000"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"result": "{\"data\":{\"foo\":{\"someId\":\"QmFyCmQx\"}}}",
"someId": "U29tZQppMQ=="
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
schema {
query: Query
}

interface IFooPayload {
someId: ID
}

type FooPayload implements IFooPayload {
someId: ID
}

type Query {
foo(input: FooInput): IFooPayload
guidId(id: ID!): String
intId(id: ID!): String
stringId(id: ID): String
}

input FooInput {
someId: ID
}

"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."
scalar ID

"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."
scalar String
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
schema {
query: Query
}

type Query {
array: [Byte!]!
}

"The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255."
scalar Byte
Loading