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

Added support for immutable classes and optionals to input types. #1317

Merged
merged 8 commits into from
Dec 23, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Added new serialization base
  • Loading branch information
michaelstaib committed Dec 23, 2019
commit bc253e2c922c0d5a667917828b719e878891df42
143 changes: 143 additions & 0 deletions src/Core/Abstractions/Optional.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;

#nullable enable

namespace HotChocolate
{
/// <summary>
/// The optional type is used to differentiate between not set and set input values.
/// </summary>
public readonly struct Optional<T>
: IEquatable<Optional<T>>
{

/// <summary>
/// Initializes a new instance of the <see cref="Optional{T}"/> struct.
/// </summary>
/// <param name="value">The actual value.</param>
public Optional(T value)
{
Value = value;
HasValue = true;
}

/// <summary>
/// The name value.
/// </summary>
public T Value { get; }

/// <summary>
/// <c>true</c> if the optional has a value.
/// </summary>
public bool HasValue { get; }

/// <summary>
/// <c>true</c> if the optional has no value.
/// </summary>
public bool IsEmpty => !HasValue;

/// <summary>
/// Provides the name string.
/// </summary>
/// <returns>The name string value</returns>
public override string? ToString()
{
return Value?.ToString();
}

/// <summary>
/// Compares this <see cref="Optional{T}"/> value to another value.
/// </summary>
/// <param name="other">
/// The second <see cref="Optional{T}"/> for comparison.
/// </param>
/// <returns>
/// <c>true</c> if both <see cref="Optional{T}"/> values are equal.
/// </returns>
public bool Equals(Optional<T> other)
{
if (!HasValue && !other.HasValue)
{
return true;
}

if (HasValue != other.HasValue)
{
return false;
}

return object.Equals(Value, other.Value);
}

/// <summary>
/// Compares this <see cref="Optional{T}"/> value to another value.
/// </summary>
/// <param name="obj">
/// The second <see cref="Optional{T}"/> for comparison.
/// </param>
/// <returns>
/// <c>true</c> if both <see cref="Optional{T}"/> values are equal.
/// </returns>
public override bool Equals(object? obj)
{
if (obj is null)
{
return IsEmpty;
}
return obj is Optional<T> n && Equals(n);
}

/// <summary>
/// Serves as a hash function for a <see cref="Optional"/> object.
/// </summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing
/// algorithms and data structures such as a hash table.
/// </returns>
public override int GetHashCode()
{
return (HasValue ? Value?.GetHashCode() ?? 0 : 0);
}

/// <summary>
/// Operator call through to Equals
/// </summary>
/// <param name="left">The left parameter</param>
/// <param name="right">The right parameter</param>
/// <returns>
/// <c>true</c> if both <see cref="Optional"/> values are equal.
/// </returns>
public static bool operator ==(Optional<T> left, Optional<T> right)
{
return left.Equals(right);
}

/// <summary>
/// Operator call through to Equals
/// </summary>
/// <param name="left">The left parameter</param>
/// <param name="right">The right parameter</param>
/// <returns>
/// <c>true</c> if both <see cref="Optional"/> values are not equal.
/// </returns>
public static bool operator !=(Optional<T> left, Optional<T> right)
{
return !left.Equals(right);
}

/// <summary>
/// Implicitly creates a new <see cref="Optional"/> from
/// the given value.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Optional<T>(T value)
=> new Optional<T>(value);

/// <summary>
/// Implicitly gets the optional value.
/// </summary>
/// <param name="name"></param>
public static implicit operator T(Optional<T> optional)
=> optional.Value;
}
}
23 changes: 23 additions & 0 deletions src/Core/Core/Execution/Middleware/ExceptionMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using HotChocolate.Language;
using HotChocolate.Types;

namespace HotChocolate.Execution
{
Expand Down Expand Up @@ -43,6 +44,28 @@ public async Task InvokeAsync(IQueryContext context)
context.Exception = ex;
context.Result = QueryResult.CreateError(error);
}
catch (ScalarSerializationException ex)
{
IError error = _errorHandler.CreateUnexpectedError(ex)
.SetMessage(ex.Message)
.Build();

error = _errorHandler.Handle(error);

context.Exception = ex;
context.Result = QueryResult.CreateError(error);
}
catch (InputObjectSerializationException ex)
{
IError error = _errorHandler.CreateUnexpectedError(ex)
.SetMessage(ex.Message)
.Build();

error = _errorHandler.Handle(error);

context.Exception = ex;
context.Result = QueryResult.CreateError(error);
}
catch (Exception ex)
{
IError error = _errorHandler.CreateUnexpectedError(ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void Nullable_Dictionary_Is_Correctly_Detected()
.MatchSnapshot();
}

[Fact(Skip = "Fix deserialization")]
[Fact]
public async Task Dictionary_Is_Correctly_Deserialized()
{
// arrange
Expand Down
12 changes: 11 additions & 1 deletion src/Core/Types.Tests/Types/InputObjectTypeExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,18 @@ protected override void Configure(

public class Foo
{
public string Description { get; } = "hello";
public Foo()
{
}

public Foo(string name, string description)
{
Name = name;
Description = description;
}

public string Name { get; } = "hello";
public string Description { get; } = "hello";
}

public class DummyDirective
Expand Down
42 changes: 20 additions & 22 deletions src/Core/Types.Tests/Types/InputObjectTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ public void ParseLiteral()
{
// arrange
Schema schema = Create();
InputObjectType inputObjectType =
schema.GetType<InputObjectType>("Object1");
InputObjectType inputObjectType = schema.GetType<InputObjectType>("Object1");
ObjectValueNode literal = CreateObjectLiteral();

// act
Expand Down Expand Up @@ -332,7 +331,10 @@ private static ObjectValueNode CreateObjectLiteral()
return new ObjectValueNode(new List<ObjectFieldNode>
{
new ObjectFieldNode("foo",
new ObjectValueNode(new List<ObjectFieldNode>())),
new ObjectValueNode(new List<ObjectFieldNode>
{
new ObjectFieldNode("fooList", new ListValueNode(Array.Empty<IValueNode>()))
})),
new ObjectFieldNode("bar",
new StringValueNode("123"))
});
Expand All @@ -344,23 +346,19 @@ public Schema Create()
{
c.Options.StrictValidation = false;

c.RegisterType(
new InputObjectType<SerializationInputObject1>(d =>
{
d.Name("Object1");
d.Field(t => t.Foo)
.Type<InputObjectType<SerializationInputObject2>>();
d.Field(t => t.Bar).Type<StringType>();
}));

c.RegisterType(new InputObjectType<SerializationInputObject2>(
d =>
{
d.Name("Object2");
d.Field(t => t.FooList)
.Type<NonNullType<ListType<InputObjectType<
SerializationInputObject1>>>>();
}));
c.RegisterType(new InputObjectType<SerializationInputObject1>(d =>
{
d.Name("Object1");
d.Field(t => t.Foo).Type<InputObjectType<SerializationInputObject2>>();
d.Field(t => t.Bar).Type<StringType>();
}));

c.RegisterType(new InputObjectType<SerializationInputObject2>(d =>
{
d.Name("Object2");
d.Field(t => t.FooList)
.Type<NonNullType<ListType<InputObjectType<SerializationInputObject1>>>>();
}));
});
}

Expand Down Expand Up @@ -792,7 +790,7 @@ public void ParseLiteral_ValueIsStringValueNode_ArgumentException()
Action action = () => type.ParseLiteral(new StringValueNode("foo"));

// assert
Assert.Throws<ArgumentException>(action);
Assert.Throws<InputObjectSerializationException>(action);
}

[Fact]
Expand Down Expand Up @@ -995,7 +993,7 @@ public class SerializationInputObject2
public List<SerializationInputObject1> FooList { get; set; } =
new List<SerializationInputObject1>
{
new SerializationInputObject1()
new SerializationInputObject1()
};
}

Expand Down
7 changes: 7 additions & 0 deletions src/Core/Types.Tests/Types/ObjectTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,13 @@ public class GenericFoo<T>
public class Foo
: IFoo
{
public Foo() { }

public Foo(string description)
{
Description = description;
}

public string Description { get; } = "hello";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Data": {
"foo": "abc"
},
"Extensions": {},
"Errors": [],
"ContextData": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
schema {
query: Query
}

type Query {
foo(input: FooInput!): String!
}

input FooInput {
contextData: [KeyValuePairOfStringAndStringInput!]
}

input KeyValuePairOfStringAndStringInput {
key: String
value: String
}

"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
@@ -1,11 +1,6 @@
{
{
"Foo": {
"FooList": [
{
"Foo": null,
"Bar": "Bar"
}
]
"FooList": []
},
"Bar": "123"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
schema {
query: Foo
}

type Foo {
description: String @deprecated(reason: "Foo")
name(a: String): String
}

"The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema,such as deprecated fields on a type or deprecated enum values."
directive @deprecated("Deprecations include a reason for why it is deprecated, which is formatted using Markdown syntax (as specified by CommonMark)." reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

"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,14 @@
schema {
query: Foo
}

type Foo {
description: String @deprecated
name(a: String): String
}

"The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema,such as deprecated fields on a type or deprecated enum values."
directive @deprecated("Deprecations include a reason for why it is deprecated, which is formatted using Markdown syntax (as specified by CommonMark)." reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

"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
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using ChilliCream.Testing;
using HotChocolate.Types;
using Snapshooter.Xunit;
using Xunit;
Expand Down Expand Up @@ -34,8 +33,7 @@ public void Convert_Dictionary_FooInputObject()
foo["bar"] = bar;

// assert
var converter = new DictionaryToInputObjectConverter(
TypeConversion.Default);
var converter = new DictionaryToInputObjectConverter(TypeConversion.Default);
object converted = converter.Convert(foo, type);

// assert
Expand Down
Loading