From 5b8e4a7bad6ca0d243fe47e8e9060f4f2595ba33 Mon Sep 17 00:00:00 2001 From: Glen Date: Thu, 21 Nov 2024 12:30:12 +0200 Subject: [PATCH 1/2] Moved LocalDate/LocalTime to HotChocolate.Types and added LocalDateTime --- .../Types.Scalars/ScalarResources.Designer.cs | 55 -- .../src/Types.Scalars/ScalarResources.resx | 18 - .../Core/src/Types.Scalars/ThrowHelper.cs | 46 +- .../src/Types.Scalars/WellKnownScalarTypes.cs | 2 - .../Properties/TypeResources.Designer.cs | 27 + .../src/Types/Properties/TypeResources.resx | 9 + .../Types/Types/Scalars/LocalDateTimeType.cs | 136 +++++ .../Types/Scalars}/LocalDateType.cs | 18 +- .../Types/Scalars}/LocalTimeType.cs | 20 +- .../src/Types/Types/Scalars/ScalarNames.cs | 3 + .../Core/src/Types/Types/Scalars/Scalars.cs | 7 +- .../Types.Scalars.Tests/LocalDateTypeTests.cs | 450 -------------- .../Types.Scalars.Tests/LocalTimeTypeTests.cs | 399 ------------- .../Types/Scalars/LocalDateTimeTypeTests.cs | 457 ++++++++++++++ .../Types/Scalars/LocalDateTypeTests.cs | 563 ++++++++++++++++++ .../Types/Scalars/LocalTimeTypeTests.cs | 502 ++++++++++++++++ ...meTypeTests.LocalDateTime_As_Argument.snap | 7 + ...ests.LocalDateTime_As_Argument_Schema.snap | 14 + ...ypeTests.LocalDateTime_As_ReturnValue.snap | 7 + ...s.LocalDateTime_As_ReturnValue_Schema.snap | 14 + ...calDateTypeTests.DateOnly_As_Argument.snap | 7 + ...TypeTests.DateOnly_As_Argument_Schema.snap | 14 + ...DateTypeTests.DateOnly_As_ReturnValue.snap | 7 + ...Tests.DateOnly_As_ReturnValue_Schema.snap} | 12 +- ...calTimeTypeTests.TimeOnly_As_Argument.snap | 7 + ...TypeTests.TimeOnly_As_Argument_Schema.snap | 14 + ...TimeTypeTests.TimeOnly_As_ReturnValue.snap | 7 + ...Tests.TimeOnly_As_ReturnValue_Schema.snap} | 12 +- .../FilterConventionDescriptorExtensions.cs | 8 +- ...s => LocalDateOperationFilterInputType.cs} | 6 +- .../LocalTimeOperationFilterInputType.cs | 14 + ...putTests.Create_Implicit_Operation.graphql | 108 ++-- ...eate_Implicit_Operation_Normalized.graphql | 80 +-- ...oDbFilterConventionDescriptorExtensions.cs | 8 +- 34 files changed, 1963 insertions(+), 1095 deletions(-) create mode 100644 src/HotChocolate/Core/src/Types/Types/Scalars/LocalDateTimeType.cs rename src/HotChocolate/Core/src/{Types.Scalars => Types/Types/Scalars}/LocalDateType.cs (86%) rename src/HotChocolate/Core/src/{Types.Scalars => Types/Types/Scalars}/LocalTimeType.cs (84%) delete mode 100644 src/HotChocolate/Core/test/Types.Scalars.Tests/LocalDateTypeTests.cs delete mode 100644 src/HotChocolate/Core/test/Types.Scalars.Tests/LocalTimeTypeTests.cs create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTimeTypeTests.cs create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTypeTests.cs create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalTimeTypeTests.cs create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_Argument.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_Argument_Schema.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_ReturnValue.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_ReturnValue_Schema.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_Argument.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_Argument_Schema.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_ReturnValue.snap rename src/HotChocolate/Core/test/{Types.Scalars.Tests/__snapshots__/LocalDateTypeTests.Schema_WithScalar_IsMatch.snap => Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_ReturnValue_Schema.snap} (65%) create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_Argument.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_Argument_Schema.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_ReturnValue.snap rename src/HotChocolate/Core/test/{Types.Scalars.Tests/__snapshots__/LocalTimeTypeTests.Schema_WithScalar_IsMatch.snap => Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_ReturnValue_Schema.snap} (56%) rename src/HotChocolate/Data/src/Data/Filters/Types/{DateOperationFilterInputType.cs => LocalDateOperationFilterInputType.cs} (57%) create mode 100644 src/HotChocolate/Data/src/Data/Filters/Types/LocalTimeOperationFilterInputType.cs diff --git a/src/HotChocolate/Core/src/Types.Scalars/ScalarResources.Designer.cs b/src/HotChocolate/Core/src/Types.Scalars/ScalarResources.Designer.cs index bb3473c2139..ef9f24d9a95 100644 --- a/src/HotChocolate/Core/src/Types.Scalars/ScalarResources.Designer.cs +++ b/src/HotChocolate/Core/src/Types.Scalars/ScalarResources.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -303,60 +302,6 @@ internal static string LocalCurrencyType_IsInvalid_ParseValue { } } - /// - /// Looks up a localized string similar to The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences YYYY-MM-DD. The scalar follows the specification defined in RFC3339. - /// - internal static string LocalDateType_Description { - get { - return ResourceManager.GetString("LocalDateType_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LocalDateType cannot parse the provided literal. The provided value is not a valid Local Date.. - /// - internal static string LocalDateType_IsInvalid_ParseLiteral { - get { - return ResourceManager.GetString("LocalDateType_IsInvalid_ParseLiteral", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LocalDateType cannot parse the provided value. The provided value is not a valid Local Date.. - /// - internal static string LocalDateType_IsInvalid_ParseValue { - get { - return ResourceManager.GetString("LocalDateType_IsInvalid_ParseValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The LocalTime scalar type is a local time string (i.e., with no associated timezone) in 24-hr HH:mm:ss.. - /// - internal static string LocalTimeType_Description { - get { - return ResourceManager.GetString("LocalTimeType_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LocalTimeType cannot parse the provided literal. The provided value is not a valid Local Time.. - /// - internal static string LocalTimeType_IsInvalid_ParseLiteral { - get { - return ResourceManager.GetString("LocalTimeType_IsInvalid_ParseLiteral", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LocalTimeType cannot parse the provided value. The provided value is not a valid Local Time.. - /// - internal static string LocalTimeType_IsInvalid_ParseValue { - get { - return ResourceManager.GetString("LocalTimeType_IsInvalid_ParseValue", resourceCulture); - } - } - /// /// Looks up a localized string similar to The Longitude scalar type is a valid decimal degrees longitude number.. /// diff --git a/src/HotChocolate/Core/src/Types.Scalars/ScalarResources.resx b/src/HotChocolate/Core/src/Types.Scalars/ScalarResources.resx index dd01a34376e..a627266d183 100644 --- a/src/HotChocolate/Core/src/Types.Scalars/ScalarResources.resx +++ b/src/HotChocolate/Core/src/Types.Scalars/ScalarResources.resx @@ -189,15 +189,6 @@ IsbnType cannot parse the provided value. The provided value is not a valid ISBN number. - - The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences YYYY-MM-DD. The scalar follows the specification defined in RFC3339 - - - LocalDateType cannot parse the provided literal. The provided value is not a valid Local Date. - - - LocalDateType cannot parse the provided value. The provided value is not a valid Local Date. - The Latitude scalar type represents represents a valid decimal degrees latitude number. @@ -207,15 +198,6 @@ LatitudeType cannot parse the provided value. The provided value was not a valid decimal degrees latitude number. - - The LocalTime scalar type is a local time string (i.e., with no associated timezone) in 24-hr HH:mm:ss. - - - LocalTimeType cannot parse the provided literal. The provided value is not a valid Local Time. - - - LocalTimeType cannot parse the provided value. The provided value is not a valid Local Time. - The LocalCurrency scalar type is a currency string. diff --git a/src/HotChocolate/Core/src/Types.Scalars/ThrowHelper.cs b/src/HotChocolate/Core/src/Types.Scalars/ThrowHelper.cs index ec451e29b89..620bf41dc56 100644 --- a/src/HotChocolate/Core/src/Types.Scalars/ThrowHelper.cs +++ b/src/HotChocolate/Core/src/Types.Scalars/ThrowHelper.cs @@ -178,28 +178,6 @@ public static SerializationException LatitudeType_ParseLiteral_IsInvalid(IType t type); } - public static SerializationException LocalDateType_ParseValue_IsInvalid(IType type) - { - return new SerializationException( - ErrorBuilder.New() - .SetMessage(ScalarResources.LocalDateType_IsInvalid_ParseValue) - .SetCode(ErrorCodes.Scalars.InvalidRuntimeType) - .SetExtension("actualType", WellKnownScalarTypes.LocalDate) - .Build(), - type); - } - - public static SerializationException LocalDateType_ParseLiteral_IsInvalid(IType type) - { - return new SerializationException( - ErrorBuilder.New() - .SetMessage(ScalarResources.LocalDateType_IsInvalid_ParseLiteral) - .SetCode(ErrorCodes.Scalars.InvalidSyntaxFormat) - .SetExtension("actualType", WellKnownScalarTypes.LocalDate) - .Build(), - type); - } - public static SerializationException LocalCurrencyType_ParseValue_IsInvalid(IType type) { return new SerializationException( @@ -215,35 +193,13 @@ public static SerializationException LocalCurrencyType_ParseLiteral_IsInvalid(IT { return new SerializationException( ErrorBuilder.New() - .SetMessage(ScalarResources.LocalDateType_IsInvalid_ParseLiteral) + .SetMessage(ScalarResources.LocalCurrencyType_IsInvalid_ParseLiteral) .SetCode(ErrorCodes.Scalars.InvalidSyntaxFormat) .SetExtension("actualType", WellKnownScalarTypes.LocalCurrency) .Build(), type); } - public static SerializationException LocalTimeType_ParseValue_IsInvalid(IType type) - { - return new SerializationException( - ErrorBuilder.New() - .SetMessage(ScalarResources.LocalTimeType_IsInvalid_ParseValue) - .SetCode(ErrorCodes.Scalars.InvalidRuntimeType) - .SetExtension("actualType", WellKnownScalarTypes.LocalTime) - .Build(), - type); - } - - public static SerializationException LocalTimeType_ParseLiteral_IsInvalid(IType type) - { - return new SerializationException( - ErrorBuilder.New() - .SetMessage(ScalarResources.LocalTimeType_IsInvalid_ParseLiteral) - .SetCode(ErrorCodes.Scalars.InvalidSyntaxFormat) - .SetExtension("actualType", WellKnownScalarTypes.LocalTime) - .Build(), - type); - } - public static SerializationException LongitudeType_ParseValue_IsInvalid(IType type) { return new SerializationException( diff --git a/src/HotChocolate/Core/src/Types.Scalars/WellKnownScalarTypes.cs b/src/HotChocolate/Core/src/Types.Scalars/WellKnownScalarTypes.cs index e2653c8cafd..1f9fdbecfc6 100644 --- a/src/HotChocolate/Core/src/Types.Scalars/WellKnownScalarTypes.cs +++ b/src/HotChocolate/Core/src/Types.Scalars/WellKnownScalarTypes.cs @@ -10,9 +10,7 @@ internal static class WellKnownScalarTypes public const string IPv6 = nameof(IPv6); public const string Isbn = nameof(Isbn); public const string Latitude = nameof(Latitude); - public const string LocalDate = nameof(LocalDate); public const string LocalCurrency = nameof(LocalCurrency); - public const string LocalTime = nameof(LocalTime); public const string Longitude = nameof(Longitude); public const string MacAddress = nameof(MacAddress); public const string NegativeFloat = nameof(NegativeFloat); diff --git a/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs b/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs index 6d9cddc987c..964e9d8fad3 100644 --- a/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs +++ b/src/HotChocolate/Core/src/Types/Properties/TypeResources.Designer.cs @@ -1339,6 +1339,33 @@ internal static string IntType_Description { } } + /// + /// Looks up a localized string similar to The `LocalDateTime` scalar type is a local date/time string (i.e., with no associated timezone) with the format `YYYY-MM-DDThh:mm:ss`.. + /// + internal static string LocalDateTimeType_Description { + get { + return ResourceManager.GetString("LocalDateTimeType_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences YYYY-MM-DD. The scalar follows the specification defined in RFC3339. + /// + internal static string LocalDateType_Description { + get { + return ResourceManager.GetString("LocalDateType_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The LocalTime scalar type is a local time string (i.e., with no associated timezone) in 24-hr HH:mm:ss.. + /// + internal static string LocalTimeType_Description { + get { + return ResourceManager.GetString("LocalTimeType_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1.. /// diff --git a/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx b/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx index 00b1dc464ba..e8edaecc1f1 100644 --- a/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx +++ b/src/HotChocolate/Core/src/Types/Properties/TypeResources.resx @@ -1003,4 +1003,13 @@ Type: `{0}` Cycle in object graph detected. + + The `LocalDateTime` scalar type is a local date/time string (i.e., with no associated timezone) with the format `YYYY-MM-DDThh:mm:ss`. + + + The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences YYYY-MM-DD. The scalar follows the specification defined in RFC3339 + + + The LocalTime scalar type is a local time string (i.e., with no associated timezone) in 24-hr HH:mm:ss. + diff --git a/src/HotChocolate/Core/src/Types/Types/Scalars/LocalDateTimeType.cs b/src/HotChocolate/Core/src/Types/Types/Scalars/LocalDateTimeType.cs new file mode 100644 index 00000000000..b531816d3a6 --- /dev/null +++ b/src/HotChocolate/Core/src/Types/Types/Scalars/LocalDateTimeType.cs @@ -0,0 +1,136 @@ +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using HotChocolate.Language; +using HotChocolate.Properties; + +#nullable enable + +namespace HotChocolate.Types; + +/// +/// The `LocalDateTime` scalar type is a local date/time string (i.e., with no associated timezone) +/// with the format `YYYY-MM-DDThh:mm:ss`. +/// +public class LocalDateTimeType : ScalarType +{ + private const string _localFormat = "yyyy-MM-ddTHH\\:mm\\:ss"; + + /// + /// Initializes a new instance of the class. + /// + public LocalDateTimeType( + string name, + string? description = null, + BindingBehavior bind = BindingBehavior.Explicit) + : base(name, bind) + { + Description = description; + } + + /// + /// Initializes a new instance of the class. + /// + [ActivatorUtilitiesConstructor] + public LocalDateTimeType() + : this( + ScalarNames.LocalDateTime, + TypeResources.LocalDateTimeType_Description) + { + } + + public override IValueNode ParseResult(object? resultValue) + { + return resultValue switch + { + null => NullValueNode.Default, + string s => new StringValueNode(s), + DateTimeOffset o => ParseValue(o.DateTime), + DateTime dt => ParseValue(dt), + _ => throw new SerializationException( + TypeResourceHelper.Scalar_Cannot_ParseResult(Name, resultValue.GetType()), this) + }; + } + + protected override DateTime ParseLiteral(StringValueNode valueSyntax) + { + if (TryDeserializeFromString(valueSyntax.Value, out var value)) + { + return value.Value; + } + + throw new SerializationException( + TypeResourceHelper.Scalar_Cannot_ParseLiteral(Name, valueSyntax.GetType()), + this); + } + + protected override StringValueNode ParseValue(DateTime runtimeValue) + { + return new(Serialize(runtimeValue)); + } + + public override bool TrySerialize(object? runtimeValue, out object? resultValue) + { + switch (runtimeValue) + { + case null: + resultValue = null; + return true; + case DateTimeOffset o: + resultValue = Serialize(o); + return true; + case DateTime dt: + resultValue = Serialize(dt); + return true; + default: + resultValue = null; + return false; + } + } + + public override bool TryDeserialize(object? resultValue, out object? runtimeValue) + { + switch (resultValue) + { + case null: + runtimeValue = null; + return true; + case string s when TryDeserializeFromString(s, out var d): + runtimeValue = d; + return true; + case DateTimeOffset o: + runtimeValue = o.DateTime; + return true; + case DateTime dt: + runtimeValue = dt; + return true; + default: + runtimeValue = null; + return false; + } + } + + private static string Serialize(IFormattable value) + { + return value.ToString(_localFormat, CultureInfo.InvariantCulture); + } + + private static bool TryDeserializeFromString( + string? serialized, + [NotNullWhen(true)] out DateTime? value) + { + if (serialized is not null + && DateTime.TryParseExact( + serialized, + _localFormat, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var dateTime)) + { + value = dateTime; + return true; + } + + value = null; + return false; + } +} diff --git a/src/HotChocolate/Core/src/Types.Scalars/LocalDateType.cs b/src/HotChocolate/Core/src/Types/Types/Scalars/LocalDateType.cs similarity index 86% rename from src/HotChocolate/Core/src/Types.Scalars/LocalDateType.cs rename to src/HotChocolate/Core/src/Types/Types/Scalars/LocalDateType.cs index 06a3b412a79..792fd944312 100644 --- a/src/HotChocolate/Core/src/Types.Scalars/LocalDateType.cs +++ b/src/HotChocolate/Core/src/Types/Types/Scalars/LocalDateType.cs @@ -1,11 +1,14 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using HotChocolate.Language; +using HotChocolate.Properties; + +#nullable enable namespace HotChocolate.Types; /// -/// The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 +/// The `LocalDate` scalar type represents an ISO date string, represented as UTF-8 /// character sequences YYYY-MM-DD. The scalar follows the specification defined in /// RFC3339 /// @@ -31,8 +34,8 @@ public LocalDateType( [ActivatorUtilitiesConstructor] public LocalDateType() : this( - WellKnownScalarTypes.LocalDate, - ScalarResources.LocalDateType_Description) + ScalarNames.LocalDate, + TypeResources.LocalDateType_Description) { } @@ -45,7 +48,8 @@ public override IValueNode ParseResult(object? resultValue) DateOnly d => ParseValue(d), DateTimeOffset o => ParseValue(DateOnly.FromDateTime(o.DateTime)), DateTime dt => ParseValue(DateOnly.FromDateTime(dt)), - _ => throw ThrowHelper.LocalDateType_ParseValue_IsInvalid(this), + _ => throw new SerializationException( + TypeResourceHelper.Scalar_Cannot_ParseResult(Name, resultValue.GetType()), this) }; } @@ -56,7 +60,9 @@ protected override DateOnly ParseLiteral(StringValueNode valueSyntax) return value.Value; } - throw ThrowHelper.LocalDateType_ParseLiteral_IsInvalid(this); + throw new SerializationException( + TypeResourceHelper.Scalar_Cannot_ParseLiteral(Name, valueSyntax.GetType()), + this); } protected override StringValueNode ParseValue(DateOnly runtimeValue) @@ -124,6 +130,8 @@ private static bool TryDeserializeFromString( && DateOnly.TryParseExact( serialized, _localFormat, + CultureInfo.InvariantCulture, + DateTimeStyles.None, out var date)) { value = date; diff --git a/src/HotChocolate/Core/src/Types.Scalars/LocalTimeType.cs b/src/HotChocolate/Core/src/Types/Types/Scalars/LocalTimeType.cs similarity index 84% rename from src/HotChocolate/Core/src/Types.Scalars/LocalTimeType.cs rename to src/HotChocolate/Core/src/Types/Types/Scalars/LocalTimeType.cs index e1c95d93afd..65f9bdcd522 100644 --- a/src/HotChocolate/Core/src/Types.Scalars/LocalTimeType.cs +++ b/src/HotChocolate/Core/src/Types/Types/Scalars/LocalTimeType.cs @@ -1,6 +1,9 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using HotChocolate.Language; +using HotChocolate.Properties; + +#nullable enable namespace HotChocolate.Types; @@ -30,8 +33,8 @@ public LocalTimeType( [ActivatorUtilitiesConstructor] public LocalTimeType() : this( - WellKnownScalarTypes.LocalTime, - ScalarResources.LocalTimeType_Description) + ScalarNames.LocalTime, + TypeResources.LocalTimeType_Description) { } @@ -42,9 +45,10 @@ public override IValueNode ParseResult(object? resultValue) null => NullValueNode.Default, string s => new StringValueNode(s), TimeOnly t => ParseValue(t), - DateTimeOffset d => ParseValue(d), - DateTime dt => ParseValue(dt), - _ => throw ThrowHelper.LocalTimeType_ParseValue_IsInvalid(this), + DateTimeOffset d => ParseValue(TimeOnly.FromDateTime(d.DateTime)), + DateTime dt => ParseValue(TimeOnly.FromDateTime(dt)), + _ => throw new SerializationException( + TypeResourceHelper.Scalar_Cannot_ParseResult(Name, resultValue.GetType()), this) }; } @@ -55,7 +59,9 @@ protected override TimeOnly ParseLiteral(StringValueNode valueSyntax) return value.Value; } - throw ThrowHelper.LocalTimeType_ParseLiteral_IsInvalid(this); + throw new SerializationException( + TypeResourceHelper.Scalar_Cannot_ParseLiteral(Name, valueSyntax.GetType()), + this); } protected override StringValueNode ParseValue(TimeOnly runtimeValue) @@ -123,6 +129,8 @@ private static bool TryDeserializeFromString( && TimeOnly.TryParseExact( serialized, _localFormat, + CultureInfo.InvariantCulture, + DateTimeStyles.None, out var time)) { value = time; diff --git a/src/HotChocolate/Core/src/Types/Types/Scalars/ScalarNames.cs b/src/HotChocolate/Core/src/Types/Types/Scalars/ScalarNames.cs index 5c6ff35dca6..46ecd86099d 100644 --- a/src/HotChocolate/Core/src/Types/Types/Scalars/ScalarNames.cs +++ b/src/HotChocolate/Core/src/Types/Types/Scalars/ScalarNames.cs @@ -20,4 +20,7 @@ public static class ScalarNames public const string TimeSpan = nameof(TimeSpan); public const string Name = nameof(Name); public const string JSON = nameof(JSON); + public const string LocalDate = nameof(LocalDate); + public const string LocalDateTime = nameof(LocalDateTime); + public const string LocalTime = nameof(LocalTime); } diff --git a/src/HotChocolate/Core/src/Types/Types/Scalars/Scalars.cs b/src/HotChocolate/Core/src/Types/Types/Scalars/Scalars.cs index 7dea9963827..0f4b5261f7c 100644 --- a/src/HotChocolate/Core/src/Types/Types/Scalars/Scalars.cs +++ b/src/HotChocolate/Core/src/Types/Types/Scalars/Scalars.cs @@ -31,8 +31,8 @@ public static class Scalars { typeof(byte[]), typeof(ByteArrayType) }, { typeof(TimeSpan), typeof(TimeSpanType) }, - { typeof(DateOnly), typeof(DateType) }, - { typeof(TimeOnly), typeof(TimeSpanType) }, + { typeof(DateOnly), typeof(LocalDateType) }, + { typeof(TimeOnly), typeof(LocalTimeType) }, { typeof(JsonElement), typeof(JsonType) }, }; @@ -55,6 +55,9 @@ public static class Scalars { ScalarNames.Date, typeof(DateType) }, { ScalarNames.TimeSpan, typeof(TimeSpanType) }, { ScalarNames.Any, typeof(AnyType) }, + { ScalarNames.LocalDate, typeof(LocalDateType) }, + { ScalarNames.LocalDateTime, typeof(LocalDateTimeType) }, + { ScalarNames.LocalTime, typeof(LocalTimeType) }, { ScalarNames.ByteArray, typeof(ByteArrayType) }, { ScalarNames.JSON, typeof(JsonType) } diff --git a/src/HotChocolate/Core/test/Types.Scalars.Tests/LocalDateTypeTests.cs b/src/HotChocolate/Core/test/Types.Scalars.Tests/LocalDateTypeTests.cs deleted file mode 100644 index d681e0a3c24..00000000000 --- a/src/HotChocolate/Core/test/Types.Scalars.Tests/LocalDateTypeTests.cs +++ /dev/null @@ -1,450 +0,0 @@ -using System.Globalization; -using CookieCrumble; -using HotChocolate.Language; - -namespace HotChocolate.Types; - -public class LocalDateTypeTests : ScalarTypeTestBase -{ - [Fact] - protected void Schema_WithScalar_IsMatch() - { - // arrange - var schema = BuildSchema(); - - // act - // assert - schema.ToString().MatchSnapshot(); - } - - [Fact] - public void LocalDate_EnsureLocalDateTypeKindIsCorrect() - { - // arrange - var type = new LocalDateType(); - - // act - var kind = type.Kind; - - // assert - Assert.Equal(TypeKind.Scalar, type.Kind); - } - - [Fact] - protected void LocalDate_ExpectIsStringValueToMatch() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new StringValueNode("2018-06-29"); - - // act - var result = scalar.IsInstanceOfType(valueSyntax); - - // assert - Assert.True(result); - } - - [Fact] - protected void LocalDate_ExpectIsDateOnlyToMatch() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new DateOnly(2018, 6, 29); - - // act - var result = scalar.IsInstanceOfType(valueSyntax); - - // assert - Assert.True(result); - } - - [Fact] - protected void LocalDate_ExpectParseLiteralToMatch() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new StringValueNode("2018-06-29"); - var expectedResult = new DateOnly(2018, 6, 29); - - // act - object result = (DateOnly)scalar.ParseLiteral(valueSyntax)!; - - // assert - Assert.Equal(expectedResult, result); - } - - [InlineData("en-US")] - [InlineData("en-AU")] - [InlineData("en-GB")] - [InlineData("de-CH")] - [InlineData("de-de")] - [Theory] - public void LocalDate_ParseLiteralStringValueDifferentCulture( - string cultureName) - { - // arrange - Thread.CurrentThread.CurrentCulture = - CultureInfo.GetCultureInfo(cultureName); - - ScalarType scalar = new LocalDateType(); - var valueSyntax = new StringValueNode("2018-06-29"); - var expectedDateOnly = new DateOnly(2018, 6, 29); - - // act - var dateTime = (DateOnly)scalar.ParseLiteral(valueSyntax)!; - - // assert - Assert.Equal(expectedDateOnly, dateTime); - } - - [Fact] - protected void LocalDate_ExpectParseLiteralToThrowSerializationException() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new StringValueNode("foo"); - - // act - var result = Record.Exception(() => scalar.ParseLiteral(valueSyntax)); - - // assert - Assert.IsType(result); - } - - [Fact] - protected void LocalDate_ExpectParseValueToMatchDateOnly() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new DateOnly(2018, 6, 29); - - // act - var result = scalar.ParseValue(valueSyntax); - - // assert - Assert.Equal(typeof(StringValueNode), result.GetType()); - } - - [Fact] - protected void LocalDate_ExpectParseValueToThrowSerializationException() - { - // arrange - var scalar = CreateType(); - var runtimeValue = new StringValueNode("foo"); - - // act - var result = Record.Exception(() => scalar.ParseValue(runtimeValue)); - - // assert - Assert.IsType(result); - } - - [Fact] - protected void LocalDate_ExpectSerializeUtcToMatch() - { - // arrange - ScalarType scalar = new LocalDateType(); - DateTimeOffset dateTime = new DateTime( - 2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); - - var expectedValue = "2018-06-11"; - - // act - var serializedValue = (string)scalar.Serialize(dateTime)!; - - // assert - Assert.Equal(expectedValue, serializedValue); - } - - [Fact] - protected void LocalDate_ExpectSerializeDateOnlyToMatch() - { - // arrange - ScalarType scalar = new LocalDateType(); - var dateOnly = new DateOnly(2018, 6, 11); - var expectedValue = "2018-06-11"; - - // act - var serializedValue = (string)scalar.Serialize(dateOnly)!; - - // assert - Assert.Equal(expectedValue, serializedValue); - } - - [Fact] - protected void LocalDate_ExpectSerializeDateTimeToMatch() - { - // arrange - ScalarType scalar = new LocalDateType(); - var dateTime = new DateTime(2018, 6, 11, 8, 46, 14); - var expectedValue = "2018-06-11"; - - // act - var serializedValue = (string)scalar.Serialize(dateTime)!; - - // assert - Assert.Equal(expectedValue, serializedValue); - } - - [Fact] - protected void LocalDate_ExpectSerializeDateTimeOffsetToMatch() - { - // arrange - ScalarType scalar = new LocalDateType(); - var dateTime = new DateTimeOffset( - new DateTime(2018, 6, 11, 8, 46, 14), - new TimeSpan(4, 0, 0)); - var expectedValue = "2018-06-11"; - - // act - var serializedValue = (string)scalar.Serialize(dateTime)!; - - // assert - Assert.Equal(expectedValue, serializedValue); - } - - [Fact] - protected void LocalDate_ExpectDeserializeNullToMatch() - { - // arrange - ScalarType scalar = new LocalDateType(); - - // act - var success = scalar.TryDeserialize(null, out var deserialized); - - // assert - Assert.True(success); - Assert.Null(deserialized); - } - - [Fact] - public void LocalDate_ExpectDeserializeNullableDateOnlyToDateOnly() - { - // arrange - ScalarType scalar = new LocalDateType(); - DateOnly? date = null; - - // act - var success = scalar.TryDeserialize(date, out var deserialized); - - // assert - Assert.True(success); - Assert.Null(deserialized); - } - - [Fact] - protected void LocalDate_ExpectDeserializeStringToMatch() - { - // arrange - var scalar = CreateType(); - var runtimeValue = new DateOnly(2018, 6, 11); - - // act - var deserializedValue = (DateOnly)scalar.Deserialize("2018-06-11")!; - - // assert - Assert.Equal(runtimeValue, deserializedValue); - } - - [Fact] - protected void LocalDate_ExpectDeserializeDateOnlyToMatch() - { - // arrange - var scalar = CreateType(); - var resultValue = new DateOnly(2018, 6, 11); - - // act - var result = scalar.Deserialize(resultValue); - - // assert - Assert.Equal(resultValue, result); - } - - [Fact] - protected void LocalDate_ExpectDeserializeDateTimeToMatch() - { - // arrange - var scalar = CreateType(); - var input = new DateTime(2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); - var expected = new DateOnly(2018, 6, 11); - - // act - var result = scalar.Deserialize(input); - - // assert - Assert.Equal(expected, result); - } - - [Fact] - protected void LocalDate_ExpectDeserializeDateTimeOffsetToMatch() - { - // arrange - var scalar = CreateType(); - var input = new DateTimeOffset( - new DateTime(2018, 6, 11, 8, 46, 14), - new TimeSpan(4, 0, 0)); - var expected = new DateOnly(2018, 6, 11); - - // act - var result = scalar.Deserialize(input); - - // assert - Assert.Equal(expected, result); - } - - [Fact] - public void LocalDate_ExpectDeserializeInvalidFormatToDateOnly() - { - // arrange - ScalarType scalar = new LocalDateType(); - - // act - var success = scalar.TryDeserialize("2018/06/11", out var _); - - // assert - Assert.False(success); - } - - [Fact] - public void LocalDate_ExpectDeserializeInvalidStringToDateOnly() - { - // arrange - ScalarType scalar = new LocalDateType(); - - // act - var success = scalar.TryDeserialize("abc", out var _); - - // assert - Assert.False(success); - } - - [Fact] - public void LocalDate_ExpectDeserializeNullToNull() - { - // arrange - ScalarType scalar = new LocalDateType(); - - // act - var success = scalar.TryDeserialize(null, out var deserialized); - - // assert - Assert.True(success); - Assert.Null(deserialized); - } - - [Fact] - protected void LocalDate_ExpectSerializeToThrowSerializationException() - { - // arrange - var scalar = CreateType(); - - // act - var result = Record.Exception(() => scalar.Serialize("foo")); - - // assert - Assert.IsType(result); - } - - [Fact] - protected void LocalDate_ExpectDeserializeToThrowSerializationException() - { - // arrange - var scalar = CreateType(); - object? runtimeValue = new IntValueNode(1); - - // act - var result = Record.Exception(() => scalar.Deserialize(runtimeValue)); - - // assert - Assert.IsType(result); - } - - [Fact] - protected void ParseResult_Null() - { - // arrange - ScalarType scalar = new LocalDateType(); - - // act - var result = scalar.ParseResult(null); - - // assert - Assert.Equal(typeof(NullValueNode), result.GetType()); - } - - [Fact] - protected void ParseResult_String() - { - // arrange - ScalarType scalar = new LocalDateType(); - const string valueSyntax = "2018-06-29"; - - // act - var result = scalar.ParseResult(valueSyntax); - - // assert - Assert.Equal(typeof(StringValueNode), result.GetType()); - } - - [Fact] - protected void ParseResult_SerializationException() - { - // arrange - ScalarType scalar = new LocalDateType(); - IValueNode runtimeValue = new IntValueNode(1); - - // act - var result = Record.Exception(() => scalar.ParseResult(runtimeValue)); - - // assert - Assert.IsType(result); - } - - [Fact] - public void ParseResult_DateOnly() - { - // arrange - var scalar = new LocalDateType(); - var resultValue = new DateOnly(2023, 6, 19); - var expectedLiteralValue = "2023-06-19"; - - // act - var literal = scalar.ParseResult(resultValue); - - // assert - Assert.Equal(typeof(StringValueNode), literal.GetType()); - Assert.Equal(expectedLiteralValue, literal.Value); - } - - [Fact] - public void ParseResult_DateTime() - { - // arrange - var scalar = new LocalDateType(); - var resultValue = new DateTime(2023, 6, 19, 11, 24, 0, DateTimeKind.Utc); - var expectedLiteralValue = "2023-06-19"; - - // act - var literal = scalar.ParseResult(resultValue); - - // assert - Assert.Equal(typeof(StringValueNode), literal.GetType()); - Assert.Equal(expectedLiteralValue, literal.Value); - } - - [Fact] - public void ParseResult_DateTimeOffset() - { - // arrange - var scalar = new LocalDateType(); - var resultValue = new DateTimeOffset(2023, 6, 19, 11, 24, 0, new TimeSpan(6, 0, 0)); - var expectedLiteralValue = "2023-06-19"; - - // act - var literal = scalar.ParseResult(resultValue); - - // assert - Assert.Equal(typeof(StringValueNode), literal.GetType()); - Assert.Equal(expectedLiteralValue, literal.Value); - } -} diff --git a/src/HotChocolate/Core/test/Types.Scalars.Tests/LocalTimeTypeTests.cs b/src/HotChocolate/Core/test/Types.Scalars.Tests/LocalTimeTypeTests.cs deleted file mode 100644 index c51e27cf8e7..00000000000 --- a/src/HotChocolate/Core/test/Types.Scalars.Tests/LocalTimeTypeTests.cs +++ /dev/null @@ -1,399 +0,0 @@ -using System.Globalization; -using CookieCrumble; -using HotChocolate.Language; - -namespace HotChocolate.Types; - -public class LocalTimeTypeTests : ScalarTypeTestBase -{ - [Fact] - protected void Schema_WithScalar_IsMatch() - { - // arrange - var schema = BuildSchema(); - - // act - - // assert - schema.ToString().MatchSnapshot(); - } - - [Fact] - public void LocalTime_EnsureLocalTimeTypeKindIsCorrect() - { - // arrange - var type = new LocalTimeType(); - - // act - var kind = type.Kind; - - // assert - Assert.Equal(TypeKind.Scalar, kind); - } - - [Fact] - protected void LocalTime_ExpectIsStringValueToMatch() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new StringValueNode("08:46:14"); - - // act - var result = scalar.IsInstanceOfType(valueSyntax); - - // assert - Assert.True(result); - } - - [Fact] - protected void LocalTime_ExpectIsTimeOnlyToMatch() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new TimeOnly(8, 46, 14); - - // act - var result = scalar.IsInstanceOfType(valueSyntax); - - // assert - Assert.True(result); - } - - [Fact] - protected void LocalTime_ExpectParseLiteralToMatch() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new StringValueNode("14:46:14"); - var expectedResult = new TimeOnly(14, 46, 14); - - // act - object result = (TimeOnly)scalar.ParseLiteral(valueSyntax)!; - - // assert - Assert.Equal(expectedResult, result); - } - - [InlineData("en-US")] - [InlineData("en-AU")] - [InlineData("en-GB")] - [InlineData("de-CH")] - [InlineData("de-de")] - [Theory] - public void LocalTime_ParseLiteralStringValueDifferentCulture(string cultureName) - { - // arrange - Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(cultureName); - - ScalarType scalar = new LocalTimeType(); - var valueSyntax = new StringValueNode("08:46:14"); - var expectedTimeOnly = new TimeOnly(8, 46, 14); - - // act - var dateTime = (TimeOnly)scalar.ParseLiteral(valueSyntax)!; - - // assert - Assert.Equal(expectedTimeOnly, dateTime); - } - - [Fact] - protected void LocalTime_ExpectParseLiteralToThrowSerializationException() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new StringValueNode("foo"); - - // act - var result = Record.Exception(() => scalar.ParseLiteral(valueSyntax)); - - // assert - Assert.IsType(result); - } - - [Fact] - protected void LocalTime_ExpectParseValueToMatchTimeOnly() - { - // arrange - var scalar = CreateType(); - var valueSyntax = new TimeOnly(8, 46, 14); - - // act - var result = scalar.ParseValue(valueSyntax); - - // assert - Assert.Equal(typeof(StringValueNode), result.GetType()); - } - - [Fact] - protected void LocalTime_ExpectParseValueToThrowSerializationException() - { - // arrange - var scalar = CreateType(); - var runtimeValue = new StringValueNode("foo"); - - // act - var result = Record.Exception(() => scalar.ParseValue(runtimeValue)); - - // assert - Assert.IsType(result); - } - - [Fact] - protected void LocalTime_ExpectSerializeUtcToMatch() - { - // arrange - ScalarType scalar = new LocalTimeType(); - DateTimeOffset dateTime = new DateTime(2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); - const string expectedValue = "08:46:14"; - - // act - var serializedValue = (string)scalar.Serialize(dateTime)!; - - // assert - Assert.Equal(expectedValue, serializedValue); - } - - [Fact] - protected void LocalTime_ExpectSerializeTimeOnlyToMatch() - { - // arrange - ScalarType scalar = new LocalTimeType(); - var timeOnly = new TimeOnly(8, 46, 14); - var expectedValue = "08:46:14"; - - // act - var serializedValue = (string)scalar.Serialize(timeOnly)!; - - // assert - Assert.Equal(expectedValue, serializedValue); - } - - [Fact] - protected void LocalTime_ExpectSerializeDateTimeToMatch() - { - // arrange - ScalarType scalar = new LocalTimeType(); - var dateTime = new DateTime(2018, 6, 11, 8, 46, 14); - var expectedValue = "08:46:14"; - - // act - var serializedValue = (string)scalar.Serialize(dateTime)!; - - // assert - Assert.Equal(expectedValue, serializedValue); - } - - [Fact] - protected void LocalTime_ExpectSerializeDateTimeOffsetToMatch() - { - // arrange - ScalarType scalar = new LocalTimeType(); - var dateTime = new DateTimeOffset( - new DateTime(2018, 6, 11, 8, 46, 14), - new TimeSpan(4, 0, 0)); - var expectedValue = "08:46:14"; - - // act - var serializedValue = (string)scalar.Serialize(dateTime)!; - - // assert - Assert.Equal(expectedValue, serializedValue); - } - - [Fact] - protected void LocalTime_ExpectDeserializeNullToMatch() - { - // arrange - ScalarType scalar = new LocalTimeType(); - - // act - var success = scalar.TryDeserialize(null, out var deserialized); - - // assert - Assert.True(success); - Assert.Null(deserialized); - } - - [Fact] - public void LocalTime_ExpectDeserializeNullableTimeOnlyToTimeOnly() - { - // arrange - ScalarType scalar = new LocalTimeType(); - TimeOnly? time = null; - - // act - var success = scalar.TryDeserialize(time, out var deserialized); - - // assert - Assert.True(success); - Assert.Null(deserialized); - } - - [Fact] - protected void LocalTime_ExpectDeserializeStringToMatch() - { - // arrange - var scalar = CreateType(); - var runtimeValue = new TimeOnly(8, 46, 14); - - // act - var deserializedValue = (TimeOnly)scalar.Deserialize("08:46:14")!; - - // assert - Assert.Equal(runtimeValue, deserializedValue); - } - - [Fact] - protected void LocalTime_ExpectDeserializeTimeOnlyToMatch() - { - // arrange - var scalar = CreateType(); - object resultValue = new TimeOnly(8, 46, 14); - - // act - var result = scalar.Deserialize(resultValue); - - // assert - Assert.Equal(resultValue, result); - } - - [Fact] - protected void LocalTime_ExpectDeserializeDateTimeToMatch() - { - // arrange - var scalar = CreateType(); - object resultValue = new DateTime(2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); - var expected = new TimeOnly(8, 46, 14); - - // act - var result = scalar.Deserialize(resultValue); - - // assert - Assert.Equal(expected, result); - } - - [Fact] - protected void LocalTime_ExpectDeserializeDateTimeOffsetToMatch() - { - // arrange - var scalar = CreateType(); - var input = new DateTimeOffset( - new DateTime(2018, 6, 11, 8, 46, 14), - new TimeSpan(4, 0, 0)); - var expected = new TimeOnly(8, 46, 14); - - // act - var result = scalar.Deserialize(input); - - // assert - Assert.Equal(expected, result); - } - - [Fact] - public void LocalTime_ExpectDeserializeInvalidFormatToTimeOnly() - { - // arrange - ScalarType scalar = new LocalTimeType(); - - // act - var success = scalar.TryDeserialize("08:46:14 pm", out var _); - - // assert - Assert.False(success); - } - - [Fact] - public void LocalTime_ExpectDeserializeInvalidStringToTimeOnly() - { - // arrange - ScalarType scalar = new LocalTimeType(); - - // act - var success = scalar.TryDeserialize("abc", out var _); - - // assert - Assert.False(success); - } - - [Fact] - public void LocalTime_ExpectDeserializeNullToNull() - { - // arrange - ScalarType scalar = new LocalTimeType(); - - // act - var success = scalar.TryDeserialize(null, out var deserialized); - - // assert - Assert.True(success); - Assert.Null(deserialized); - } - - [Fact] - protected void LocalTime_ExpectSerializeToThrowSerializationException() - { - // arrange - var scalar = CreateType(); - - // act - var result = Record.Exception(() => scalar.Serialize("foo")); - - // assert - Assert.IsType(result); - } - - [Fact] - protected void LocalTime_ExpectDeserializeToThrowSerializationException() - { - // arrange - var scalar = CreateType(); - object runtimeValue = new IntValueNode(1); - - // act - var result = Record.Exception(() => scalar.Deserialize(runtimeValue)); - - // assert - Assert.IsType(result); - } - - [Fact] - protected void LocalTime_ExpectParseResultToMatchNull() - { - // arrange - ScalarType scalar = new LocalTimeType(); - - // act - var result = scalar.ParseResult(null); - - // assert - Assert.Equal(typeof(NullValueNode), result.GetType()); - } - - [Fact] - protected void LocalTime_ExpectParseResultToMatchStringValue() - { - // arrange - ScalarType scalar = new LocalTimeType(); - const string valueSyntax = "08:46:14"; - - // act - var result = scalar.ParseResult(valueSyntax); - - // assert - Assert.Equal(typeof(StringValueNode), result.GetType()); - } - - [Fact] - protected void LocalTime_ExpectParseResultToThrowSerializationException() - { - // arrange - ScalarType scalar = new LocalTimeType(); - IValueNode runtimeValue = new IntValueNode(1); - - // act - var result = Record.Exception(() => scalar.ParseResult(runtimeValue)); - - // assert - Assert.IsType(result); - } -} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTimeTypeTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTimeTypeTests.cs new file mode 100644 index 00000000000..2ea3412b4f1 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTimeTypeTests.cs @@ -0,0 +1,457 @@ +using System.Globalization; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Tests; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Types; + +public class LocalDateTimeTypeTests +{ + [Fact] + public void Serialize_DateTime() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var dateTime = new DateTime(2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); + var expectedValue = "2018-06-11T08:46:14"; + + // act + var serializedValue = (string)localDateTimeType.Serialize(dateTime); + + // assert + Assert.Equal(expectedValue, serializedValue); + } + + [Fact] + public void Serialize_DateTimeOffset() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var dateTime = new DateTimeOffset( + new DateTime(2018, 6, 11, 2, 46, 14), + new TimeSpan(4, 0, 0)); + var expectedValue = "2018-06-11T02:46:14"; + + // act + var serializedValue = (string)localDateTimeType.Serialize(dateTime); + + // assert + Assert.Equal(expectedValue, serializedValue); + } + + [Fact] + public void Serialize_Null() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + + // act + var serializedValue = localDateTimeType.Serialize(null); + + // assert + Assert.Null(serializedValue); + } + + [Fact] + public void Serialize_String_Exception() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + + // act + void Action() => localDateTimeType.Serialize("foo"); + + // assert + Assert.Throws(Action); + } + + [Fact] + public void Deserialize_IsoString_DateTime() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var dateTime = new DateTime(2018, 6, 11, 8, 46, 14); + + // act + var result = (DateTime)localDateTimeType.Deserialize("2018-06-11T08:46:14")!; + + // assert + Assert.Equal(dateTime, result); + } + + [Fact] + public void Deserialize_InvalidFormat_To_DateTime() + { + // arrange + var type = new LocalDateTimeType(); + + // act + var success = type.TryDeserialize("2018/06/11T08:46:14 pm", out _); + + // assert + Assert.False(success); + } + + [Fact] + public void Deserialize_InvalidString_To_DateTime() + { + // arrange + var type = new LocalDateTimeType(); + + // act + var success = type.TryDeserialize("abc", out _); + + // assert + Assert.False(success); + } + + [Fact] + public void Deserialize_DateTime_To_DateTime() + { + // arrange + var type = new LocalDateTimeType(); + var dateTime = new DateTime(2018, 6, 11, 8, 46, 14); + + // act + var success = type.TryDeserialize(dateTime, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(dateTime, deserialized); + } + + [Fact] + public void Deserialize_DateTimeOffset_To_DateTime() + { + // arrange + var type = new LocalDateTimeType(); + var dateTime = new DateTimeOffset( + new DateTime(2018, 6, 11, 2, 46, 14), + new TimeSpan(4, 0, 0)); + + // act + var success = type.TryDeserialize(dateTime, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(dateTime.DateTime, Assert.IsType(deserialized)); + } + + [Fact] + public void Deserialize_NullableDateTime_To_DateTime() + { + // arrange + var type = new LocalDateTimeType(); + DateTime? dateTime = new DateTime(2018, 6, 11, 8, 46, 14); + + // act + var success = type.TryDeserialize(dateTime, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(dateTime, Assert.IsType(deserialized)); + } + + [Fact] + public void Deserialize_NullableDateTime_To_DateTime_2() + { + // arrange + var type = new LocalDateTimeType(); + DateTime? dateTime = null; + + // act + var success = type.TryDeserialize(dateTime, out var deserialized); + + // assert + Assert.True(success); + Assert.Null(deserialized); + } + + [Fact] + public void Deserialize_Null_To_Null() + { + // arrange + var type = new LocalDateTimeType(); + + // act + var success = type.TryDeserialize(null, out var deserialized); + + // assert + Assert.True(success); + Assert.Null(deserialized); + } + + [Fact] + public void ParseLiteral_StringValueNode() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var literal = new StringValueNode("2018-06-29T08:46:14"); + var expectedDateTime = new DateTime(2018, 6, 29, 8, 46, 14); + + // act + var dateTime = (DateTime)localDateTimeType.ParseLiteral(literal)!; + + // assert + Assert.Equal(expectedDateTime, dateTime); + } + + [InlineData("en-US")] + [InlineData("en-AU")] + [InlineData("en-GB")] + [InlineData("de-CH")] + [InlineData("de-de")] + [Theory] + public void ParseLiteral_StringValueNode_DifferentCulture( + string cultureName) + { + // arrange + Thread.CurrentThread.CurrentCulture = + CultureInfo.GetCultureInfo(cultureName); + + var localDateTimeType = new LocalDateTimeType(); + var literal = new StringValueNode("2018-06-29T08:46:14"); + var expectedDateTime = new DateTime(2018, 6, 29, 8, 46, 14); + + // act + var dateTime = (DateTime)localDateTimeType.ParseLiteral(literal)!; + + // assert + Assert.Equal(expectedDateTime, dateTime); + } + + [Fact] + public void ParseLiteral_NullValueNode() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var literal = NullValueNode.Default; + + // act + var value = localDateTimeType.ParseLiteral(literal); + + // assert + Assert.Null(value); + } + + [Fact] + public void ParseValue_DateTime() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var dateTime = new DateTime(2018, 6, 11, 8, 46, 14); + var expectedLiteralValue = "2018-06-11T08:46:14"; + + // act + var stringLiteral = + (StringValueNode)localDateTimeType.ParseValue(dateTime); + + // assert + Assert.Equal(expectedLiteralValue, stringLiteral.Value); + } + + [Fact] + public void ParseValue_Null() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + + // act + var literal = localDateTimeType.ParseValue(null); + + // assert + Assert.Equal(NullValueNode.Default, literal); + } + + [Fact] + public void ParseResult_DateTime() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var resultValue = new DateTime(2023, 6, 19, 11, 24, 0, DateTimeKind.Utc); + var expectedLiteralValue = "2023-06-19T11:24:00"; + + // act + var literal = localDateTimeType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_DateTimeOffset() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var resultValue = new DateTimeOffset(2023, 6, 19, 11, 24, 0, new TimeSpan(6, 0, 0)); + var expectedLiteralValue = "2023-06-19T11:24:00"; + + // act + var literal = localDateTimeType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_String() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var resultValue = "2023-06-19T11:24:00"; + var expectedLiteralValue = "2023-06-19T11:24:00"; + + // act + var literal = localDateTimeType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_Null() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + + // act + var literal = localDateTimeType.ParseResult(null); + + // assert + Assert.Equal(NullValueNode.Default, literal); + } + + [Fact] + public void ParseResult_SerializationException() + { + // arrange + var localDateTimeType = new LocalDateTimeType(); + var resultValue = 1; + + // act + var exception = Record.Exception(() => localDateTimeType.ParseResult(resultValue)); + + // assert + Assert.IsType(exception); + } + + [Fact] + public void EnsureLocalDateTimeTypeKindIsCorrect() + { + // arrange + var type = new LocalDateTimeType(); + + // act + var kind = type.Kind; + + // assert + Assert.Equal(TypeKind.Scalar, kind); + } + + [Fact] + public void LocalDateTimeType_Binds_Only_Explicitly() + { + // arrange + // act + var schema = SchemaBuilder.New() + .AddQueryType() + .AddType(new LocalDateTimeType()) + .Create(); + + // assert + IType localDateTimeType = schema.QueryType.Fields["localDateTimeField"].Type; + IType dateTimeType = schema.QueryType.Fields["dateTimeField"].Type; + + Assert.IsType(localDateTimeType); + Assert.IsType(dateTimeType); + } + + [Fact] + public async Task LocalDateTime_As_Argument_Schema() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task LocalDateTime_As_Argument() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .ExecuteRequestAsync( + """ + { + foo { + localDateTime(localDateTime: "2017-12-30T11:24:00") + } + } + """) + .MatchSnapshotAsync(); + } + + [Fact] + public async Task LocalDateTime_As_ReturnValue_Schema() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task LocalDateTime_As_ReturnValue() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .ExecuteRequestAsync( + """ + { + bar { + localDateTime + } + } + """) + .MatchSnapshotAsync(); + } + + public class Query + { + [GraphQLType] + public DateTime? LocalDateTimeField => new(); + + public DateTime? DateTimeField => new(); + } + + public class QueryDateTime1 + { + public Foo Foo => new(); + } + + public class Foo + { + [GraphQLType] + public DateTime GetLocalDateTime([GraphQLType] DateTime localDateTime) + => localDateTime; + } + + public class QueryDateTime2 + { + public Bar Bar => new(); + } + + public class Bar + { + [GraphQLType] + public DateTime GetLocalDateTime() => DateTime.MaxValue; + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTypeTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTypeTests.cs new file mode 100644 index 00000000000..20c9fe8e452 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalDateTypeTests.cs @@ -0,0 +1,563 @@ +using System.Globalization; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Tests; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Types; + +public class LocalDateTypeTests +{ + [Fact] + public void Serialize_DateOnly() + { + // arrange + var localDateType = new LocalDateType(); + var dateOnly = new DateOnly(2018, 6, 11); + var expectedValue = "2018-06-11"; + + // act + var serializedValue = (string)localDateType.Serialize(dateOnly); + + // assert + Assert.Equal(expectedValue, serializedValue); + } + + [Fact] + public void Serialize_DateTime() + { + // arrange + var localDateType = new LocalDateType(); + var dateTime = new DateTime(2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); + var expectedValue = "2018-06-11"; + + // act + var serializedValue = (string)localDateType.Serialize(dateTime); + + // assert + Assert.Equal(expectedValue, serializedValue); + } + + [Fact] + public void Serialize_DateTimeOffset() + { + // arrange + var localDateType = new LocalDateType(); + var dateTime = new DateTimeOffset( + new DateTime(2018, 6, 11, 2, 46, 14), + new TimeSpan(4, 0, 0)); + var expectedValue = "2018-06-11"; + + // act + var serializedValue = (string)localDateType.Serialize(dateTime); + + // assert + Assert.Equal(expectedValue, serializedValue); + } + + [Fact] + public void Serialize_Null() + { + // arrange + var localDateType = new LocalDateType(); + + // act + var serializedValue = localDateType.Serialize(null); + + // assert + Assert.Null(serializedValue); + } + + [Fact] + public void Serialize_String_Exception() + { + // arrange + var localDateType = new LocalDateType(); + + // act + void Action() => localDateType.Serialize("foo"); + + // assert + Assert.Throws(Action); + } + + [Fact] + public void Deserialize_IsoString_DateOnly() + { + // arrange + var localDateType = new LocalDateType(); + var date = new DateOnly(2018, 6, 11); + + // act + var result = (DateOnly)localDateType.Deserialize("2018-06-11")!; + + // assert + Assert.Equal(date, result); + } + + [Fact] + public void Deserialize_InvalidFormat_To_DateOnly() + { + // arrange + var type = new LocalDateType(); + + // act + var success = type.TryDeserialize("2018/06/11", out _); + + // assert + Assert.False(success); + } + + [Fact] + public void Deserialize_InvalidString_To_DateOnly() + { + // arrange + var type = new LocalDateType(); + + // act + var success = type.TryDeserialize("abc", out _); + + // assert + Assert.False(success); + } + + [Fact] + public void Deserialize_DateOnly_To_DateOnly() + { + // arrange + var type = new LocalDateType(); + var date = new DateOnly(2018, 6, 11); + + // act + var success = type.TryDeserialize(date, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(date, deserialized); + } + + [Fact] + public void Deserialize_DateTime_To_DateOnly() + { + // arrange + var type = new LocalDateType(); + var date = new DateTime(2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); + + // act + var success = type.TryDeserialize(date, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(DateOnly.FromDateTime(date), + Assert.IsType(deserialized)); + } + + [Fact] + public void Deserialize_DateTimeOffset_To_DateOnly() + { + // arrange + var type = new LocalDateType(); + var date = new DateTimeOffset( + new DateTime(2018, 6, 11, 2, 46, 14), + new TimeSpan(4, 0, 0)); + + // act + var success = type.TryDeserialize(date, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(DateOnly.FromDateTime(date.DateTime), + Assert.IsType(deserialized)); + } + + [Fact] + public void Deserialize_NullableDateOnly_To_DateOnly() + { + // arrange + var type = new LocalDateType(); + DateOnly? date = new DateOnly(2018, 6, 11); + + // act + var success = type.TryDeserialize(date, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(date, Assert.IsType(deserialized)); + } + + [Fact] + public void Deserialize_NullableDateOnly_To_DateOnly_2() + { + // arrange + var type = new LocalDateType(); + DateOnly? date = null; + + // act + var success = type.TryDeserialize(date, out var deserialized); + + // assert + Assert.True(success); + Assert.Null(deserialized); + } + + [Fact] + public void Deserialize_Null_To_Null() + { + // arrange + var type = new LocalDateType(); + + // act + var success = type.TryDeserialize(null, out var deserialized); + + // assert + Assert.True(success); + Assert.Null(deserialized); + } + + [Fact] + public void ParseLiteral_StringValueNode() + { + // arrange + var localDateType = new LocalDateType(); + var literal = new StringValueNode("2018-06-29"); + var expectedDateOnly = new DateOnly(2018, 6, 29); + + // act + var dateOnly = (DateOnly)localDateType.ParseLiteral(literal)!; + + // assert + Assert.Equal(expectedDateOnly, dateOnly); + } + + [Theory] + [MemberData(nameof(ValidLocalDateScalarStrings))] + public void ParseLiteral_StringValueNode_Valid(string dateTime, DateOnly result) + { + // arrange + var localDateType = new LocalDateType(); + var literal = new StringValueNode(dateTime); + + // act + var dateTimeOffset = (DateOnly?)localDateType.ParseLiteral(literal); + + // assert + Assert.Equal(result, dateTimeOffset); + } + + [Theory] + [MemberData(nameof(InvalidLocalDateScalarStrings))] + public void ParseLiteral_StringValueNode_Invalid(string dateTime) + { + // arrange + var localDateType = new LocalDateType(); + var literal = new StringValueNode(dateTime); + + // act + void Act() + { + localDateType.ParseLiteral(literal); + } + + // assert + Assert.Equal( + "LocalDate cannot parse the given literal of type `StringValueNode`.", + Assert.Throws(Act).Message); + } + + [InlineData("en-US")] + [InlineData("en-AU")] + [InlineData("en-GB")] + [InlineData("de-CH")] + [InlineData("de-de")] + [Theory] + public void ParseLiteral_StringValueNode_DifferentCulture( + string cultureName) + { + // arrange + Thread.CurrentThread.CurrentCulture = + CultureInfo.GetCultureInfo(cultureName); + + var localDateType = new LocalDateType(); + var literal = new StringValueNode("2018-06-29"); + var expectedDateOnly = new DateOnly(2018, 6, 29); + + // act + var dateOnly = (DateOnly)localDateType.ParseLiteral(literal)!; + + // assert + Assert.Equal(expectedDateOnly, dateOnly); + } + + [Fact] + public void ParseLiteral_NullValueNode() + { + // arrange + var localDateType = new LocalDateType(); + var literal = NullValueNode.Default; + + // act + var value = localDateType.ParseLiteral(literal); + + // assert + Assert.Null(value); + } + + [Fact] + public void ParseValue_DateOnly() + { + // arrange + var localDateType = new LocalDateType(); + var dateOnly = new DateOnly(2018, 6, 11); + var expectedLiteralValue = "2018-06-11"; + + // act + var stringLiteral = + (StringValueNode)localDateType.ParseValue(dateOnly); + + // assert + Assert.Equal(expectedLiteralValue, stringLiteral.Value); + } + + [Fact] + public void ParseValue_Null() + { + // arrange + var localDateType = new LocalDateType(); + + // act + var literal = localDateType.ParseValue(null); + + // assert + Assert.Equal(NullValueNode.Default, literal); + } + + [Fact] + public void ParseResult_DateOnly() + { + // arrange + var localDateType = new LocalDateType(); + var resultValue = new DateOnly(2023, 6, 19); + var expectedLiteralValue = "2023-06-19"; + + // act + var literal = localDateType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_DateTime() + { + // arrange + var localDateType = new LocalDateType(); + var resultValue = new DateTime(2023, 6, 19, 11, 24, 0, DateTimeKind.Utc); + var expectedLiteralValue = "2023-06-19"; + + // act + var literal = localDateType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_DateTimeOffset() + { + // arrange + var localDateType = new LocalDateType(); + var resultValue = new DateTimeOffset(2023, 6, 19, 11, 24, 0, new TimeSpan(6, 0, 0)); + var expectedLiteralValue = "2023-06-19"; + + // act + var literal = localDateType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_String() + { + // arrange + var localDateType = new LocalDateType(); + var resultValue = "2023-06-19"; + var expectedLiteralValue = "2023-06-19"; + + // act + var literal = localDateType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_Null() + { + // arrange + var localDateType = new LocalDateType(); + + // act + var literal = localDateType.ParseResult(null); + + // assert + Assert.Equal(NullValueNode.Default, literal); + } + + [Fact] + public void ParseResult_SerializationException() + { + // arrange + var localDateType = new LocalDateType(); + var resultValue = 1; + + // act + var exception = Record.Exception(() => localDateType.ParseResult(resultValue)); + + // assert + Assert.IsType(exception); + } + + [Fact] + public void EnsureLocalDateTypeKindIsCorrect() + { + // arrange + var type = new LocalDateType(); + + // act + var kind = type.Kind; + + // assert + Assert.Equal(TypeKind.Scalar, kind); + } + + [Fact] + public void LocalDateType_Binds_Only_Explicitly() + { + // arrange + // act + var schema = SchemaBuilder.New() + .AddQueryType() + .AddType(new LocalDateType()) + .Create(); + + // assert + IType localDateType = schema.QueryType.Fields["dateField"].Type; + IType dateTimeType = schema.QueryType.Fields["dateTimeField"].Type; + + Assert.IsType(localDateType); + Assert.IsType(dateTimeType); + } + + [Fact] + public async Task DateOnly_As_Argument_Schema() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task DateOnly_As_Argument() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .ExecuteRequestAsync( + """ + { + foo { + date(date: "2017-12-30") + } + } + """) + .MatchSnapshotAsync(); + } + + [Fact] + public async Task DateOnly_As_ReturnValue_Schema() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task DateOnly_As_ReturnValue() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .ExecuteRequestAsync( + """ + { + bar { + date + } + } + """) + .MatchSnapshotAsync(); + } + + public class Query + { + [GraphQLType(typeof(LocalDateType))] + public DateOnly? DateField => new(); + + public DateTime? DateTimeField => new(); + } + + public class QueryDateTime1 + { + public Foo Foo => new(); + } + + public class Foo + { + public DateOnly GetDate(DateOnly date) => date; + } + + public class QueryDateTime2 + { + public Bar Bar => new(); + } + + public class Bar + { + public DateOnly GetDate() => DateOnly.MaxValue; + } + + public static TheoryData ValidLocalDateScalarStrings() + { + return new TheoryData + { + // https://scalars.graphql.org/andimarek/local-date.html#sec-Overview + { + "1983-10-20", + new(1983, 10, 20) + }, + { + "2023-04-01", + new(2023, 4, 1) + } + }; + } + + public static TheoryData InvalidLocalDateScalarStrings() + { + return new TheoryData + { + // https://scalars.graphql.org/andimarek/local-date.html#sec-Overview + // There isn't a 13th month in a year. + "2011-13-10" + }; + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalTimeTypeTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalTimeTypeTests.cs new file mode 100644 index 00000000000..544d0d3ddde --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/LocalTimeTypeTests.cs @@ -0,0 +1,502 @@ +using System.Globalization; +using HotChocolate.Execution; +using HotChocolate.Language; +using HotChocolate.Tests; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Types; + +public class LocalTimeTypeTests +{ + [Fact] + public void Serialize_TimeOnly() + { + // arrange + var localTimeType = new LocalTimeType(); + var timeOnly = new TimeOnly(8, 46, 14); + var expectedValue = "08:46:14"; + + // act + var serializedValue = (string)localTimeType.Serialize(timeOnly); + + // assert + Assert.Equal(expectedValue, serializedValue); + } + + [Fact] + public void Serialize_DateTime() + { + // arrange + var localTimeType = new LocalTimeType(); + var dateTime = new DateTime(2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); + var expectedValue = "08:46:14"; + + // act + var serializedValue = (string)localTimeType.Serialize(dateTime); + + // assert + Assert.Equal(expectedValue, serializedValue); + } + + [Fact] + public void Serialize_DateTimeOffset() + { + // arrange + var localTimeType = new LocalTimeType(); + var dateTime = new DateTimeOffset( + new DateTime(2018, 6, 11, 2, 46, 14), + new TimeSpan(4, 0, 0)); + var expectedValue = "02:46:14"; + + // act + var serializedValue = (string)localTimeType.Serialize(dateTime); + + // assert + Assert.Equal(expectedValue, serializedValue); + } + + [Fact] + public void Serialize_Null() + { + // arrange + var localTimeType = new LocalTimeType(); + + // act + var serializedValue = localTimeType.Serialize(null); + + // assert + Assert.Null(serializedValue); + } + + [Fact] + public void Serialize_String_Exception() + { + // arrange + var localTimeType = new LocalTimeType(); + + // act + void Action() => localTimeType.Serialize("foo"); + + // assert + Assert.Throws(Action); + } + + [Fact] + public void Deserialize_IsoString_TimeOnly() + { + // arrange + var localTimeType = new LocalTimeType(); + var time = new TimeOnly(8, 46, 14); + + // act + var result = (TimeOnly)localTimeType.Deserialize("08:46:14")!; + + // assert + Assert.Equal(time, result); + } + + [Fact] + public void Deserialize_InvalidFormat_To_TimeOnly() + { + // arrange + var type = new LocalTimeType(); + + // act + var success = type.TryDeserialize("08:46:14 pm", out _); + + // assert + Assert.False(success); + } + + [Fact] + public void Deserialize_InvalidString_To_TimeOnly() + { + // arrange + var type = new LocalTimeType(); + + // act + var success = type.TryDeserialize("abc", out _); + + // assert + Assert.False(success); + } + + [Fact] + public void Deserialize_TimeOnly_To_TimeOnly() + { + // arrange + var type = new LocalTimeType(); + var time = new TimeOnly(8, 46, 14); + + // act + var success = type.TryDeserialize(time, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(time, deserialized); + } + + [Fact] + public void Deserialize_DateTime_To_TimeOnly() + { + // arrange + var type = new LocalTimeType(); + var date = new DateTime(2018, 6, 11, 8, 46, 14, DateTimeKind.Utc); + + // act + var success = type.TryDeserialize(date, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(TimeOnly.FromDateTime(date), + Assert.IsType(deserialized)); + } + + [Fact] + public void Deserialize_DateTimeOffset_To_TimeOnly() + { + // arrange + var type = new LocalTimeType(); + var date = new DateTimeOffset( + new DateTime(2018, 6, 11, 2, 46, 14), + new TimeSpan(4, 0, 0)); + + // act + var success = type.TryDeserialize(date, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(TimeOnly.FromDateTime(date.DateTime), + Assert.IsType(deserialized)); + } + + [Fact] + public void Deserialize_NullableTimeOnly_To_TimeOnly() + { + // arrange + var type = new LocalTimeType(); + TimeOnly? time = new TimeOnly(8, 46, 14); + + // act + var success = type.TryDeserialize(time, out var deserialized); + + // assert + Assert.True(success); + Assert.Equal(time, Assert.IsType(deserialized)); + } + + [Fact] + public void Deserialize_NullableTimeOnly_To_TimeOnly_2() + { + // arrange + var type = new LocalTimeType(); + TimeOnly? time = null; + + // act + var success = type.TryDeserialize(time, out var deserialized); + + // assert + Assert.True(success); + Assert.Null(deserialized); + } + + [Fact] + public void Deserialize_Null_To_Null() + { + // arrange + var type = new LocalTimeType(); + + // act + var success = type.TryDeserialize(null, out var deserialized); + + // assert + Assert.True(success); + Assert.Null(deserialized); + } + + [Fact] + public void ParseLiteral_StringValueNode() + { + // arrange + var localTimeType = new LocalTimeType(); + var literal = new StringValueNode("08:46:14"); + var expectedTimeOnly = new TimeOnly(8, 46, 14); + + // act + var timeOnly = (TimeOnly)localTimeType.ParseLiteral(literal)!; + + // assert + Assert.Equal(expectedTimeOnly, timeOnly); + } + + [InlineData("en-US")] + [InlineData("en-AU")] + [InlineData("en-GB")] + [InlineData("de-CH")] + [InlineData("de-de")] + [Theory] + public void ParseLiteral_StringValueNode_DifferentCulture( + string cultureName) + { + // arrange + Thread.CurrentThread.CurrentCulture = + CultureInfo.GetCultureInfo(cultureName); + + var localTimeType = new LocalTimeType(); + var literal = new StringValueNode("08:46:14"); + var expectedTimeOnly = new TimeOnly(8, 46, 14); + + // act + var timeOnly = (TimeOnly)localTimeType.ParseLiteral(literal)!; + + // assert + Assert.Equal(expectedTimeOnly, timeOnly); + } + + [Fact] + public void ParseLiteral_NullValueNode() + { + // arrange + var localTimeType = new LocalTimeType(); + var literal = NullValueNode.Default; + + // act + var value = localTimeType.ParseLiteral(literal); + + // assert + Assert.Null(value); + } + + [Fact] + public void ParseValue_TimeOnly() + { + // arrange + var localTimeType = new LocalTimeType(); + var timeOnly = new TimeOnly(8, 46, 14); + var expectedLiteralValue = "08:46:14"; + + // act + var stringLiteral = + (StringValueNode)localTimeType.ParseValue(timeOnly); + + // assert + Assert.Equal(expectedLiteralValue, stringLiteral.Value); + } + + [Fact] + public void ParseValue_Null() + { + // arrange + var localTimeType = new LocalTimeType(); + + // act + var literal = localTimeType.ParseValue(null); + + // assert + Assert.Equal(NullValueNode.Default, literal); + } + + [Fact] + public void ParseResult_TimeOnly() + { + // arrange + var localTimeType = new LocalTimeType(); + var resultValue = new TimeOnly(8, 46, 14); + var expectedLiteralValue = "08:46:14"; + + // act + var literal = localTimeType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_DateTime() + { + // arrange + var localTimeType = new LocalTimeType(); + var resultValue = new DateTime(2023, 6, 19, 11, 24, 0, DateTimeKind.Utc); + var expectedLiteralValue = "11:24:00"; + + // act + var literal = localTimeType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_DateTimeOffset() + { + // arrange + var localTimeType = new LocalTimeType(); + var resultValue = new DateTimeOffset(2023, 6, 19, 11, 24, 0, new TimeSpan(6, 0, 0)); + var expectedLiteralValue = "11:24:00"; + + // act + var literal = localTimeType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_String() + { + // arrange + var localTimeType = new LocalTimeType(); + var resultValue = "11:24:00"; + var expectedLiteralValue = "11:24:00"; + + // act + var literal = localTimeType.ParseResult(resultValue); + + // assert + Assert.Equal(typeof(StringValueNode), literal.GetType()); + Assert.Equal(expectedLiteralValue, literal.Value); + } + + [Fact] + public void ParseResult_Null() + { + // arrange + var localTimeType = new LocalTimeType(); + + // act + var literal = localTimeType.ParseResult(null); + + // assert + Assert.Equal(NullValueNode.Default, literal); + } + + [Fact] + public void ParseResult_SerializationException() + { + // arrange + var localTimeType = new LocalTimeType(); + var resultValue = 1; + + // act + var exception = Record.Exception(() => localTimeType.ParseResult(resultValue)); + + // assert + Assert.IsType(exception); + } + + [Fact] + public void EnsureLocalTimeTypeKindIsCorrect() + { + // arrange + var type = new LocalTimeType(); + + // act + var kind = type.Kind; + + // assert + Assert.Equal(TypeKind.Scalar, kind); + } + + [Fact] + public void LocalTimeType_Binds_Only_Explicitly() + { + // arrange + // act + var schema = SchemaBuilder.New() + .AddQueryType() + .AddType(new LocalTimeType()) + .Create(); + + // assert + IType localTimeType = schema.QueryType.Fields["timeField"].Type; + IType dateTimeType = schema.QueryType.Fields["dateTimeField"].Type; + + Assert.IsType(localTimeType); + Assert.IsType(dateTimeType); + } + + [Fact] + public async Task TimeOnly_As_Argument_Schema() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task TimeOnly_As_Argument() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .ExecuteRequestAsync( + """ + { + foo { + time(time: "11:22:00") + } + } + """) + .MatchSnapshotAsync(); + } + + [Fact] + public async Task TimeOnly_As_ReturnValue_Schema() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task TimeOnly_As_ReturnValue() + { + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .ExecuteRequestAsync( + """ + { + bar { + time + } + } + """) + .MatchSnapshotAsync(); + } + + public class Query + { + [GraphQLType(typeof(LocalTimeType))] + public TimeOnly? TimeField => new(); + + public DateTime? DateTimeField => new(); + } + + public class QueryDateTime1 + { + public Foo Foo => new(); + } + + public class Foo + { + public TimeOnly GetTime(TimeOnly time) => time; + } + + public class QueryDateTime2 + { + public Bar Bar => new(); + } + + public class Bar + { + public TimeOnly GetTime() => TimeOnly.MaxValue; + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_Argument.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_Argument.snap new file mode 100644 index 00000000000..4b2041ed180 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_Argument.snap @@ -0,0 +1,7 @@ +{ + "data": { + "foo": { + "localDateTime": "2017-12-30T11:24:00" + } + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_Argument_Schema.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_Argument_Schema.snap new file mode 100644 index 00000000000..b9856c5d06e --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_Argument_Schema.snap @@ -0,0 +1,14 @@ +schema { + query: QueryDateTime1 +} + +type Foo { + localDateTime(localDateTime: LocalDateTime): LocalDateTime +} + +type QueryDateTime1 { + foo: Foo +} + +"The `LocalDateTime` scalar type is a local date\/time string (i.e., with no associated timezone) with the format `YYYY-MM-DDThh:mm:ss`." +scalar LocalDateTime diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_ReturnValue.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_ReturnValue.snap new file mode 100644 index 00000000000..9bee239ca23 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_ReturnValue.snap @@ -0,0 +1,7 @@ +{ + "data": { + "bar": { + "localDateTime": "9999-12-31T23:59:59" + } + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_ReturnValue_Schema.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_ReturnValue_Schema.snap new file mode 100644 index 00000000000..3c344f56aeb --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTimeTypeTests.LocalDateTime_As_ReturnValue_Schema.snap @@ -0,0 +1,14 @@ +schema { + query: QueryDateTime2 +} + +type Bar { + localDateTime: LocalDateTime +} + +type QueryDateTime2 { + bar: Bar +} + +"The `LocalDateTime` scalar type is a local date\/time string (i.e., with no associated timezone) with the format `YYYY-MM-DDThh:mm:ss`." +scalar LocalDateTime diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_Argument.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_Argument.snap new file mode 100644 index 00000000000..dcf9d52b072 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_Argument.snap @@ -0,0 +1,7 @@ +{ + "data": { + "foo": { + "date": "2017-12-30" + } + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_Argument_Schema.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_Argument_Schema.snap new file mode 100644 index 00000000000..a8f804da2f8 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_Argument_Schema.snap @@ -0,0 +1,14 @@ +schema { + query: QueryDateTime1 +} + +type Foo { + date(date: LocalDate!): LocalDate! +} + +type QueryDateTime1 { + foo: Foo +} + +"The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences YYYY-MM-DD. The scalar follows the specification defined in RFC3339" +scalar LocalDate diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_ReturnValue.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_ReturnValue.snap new file mode 100644 index 00000000000..bb38224a34a --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_ReturnValue.snap @@ -0,0 +1,7 @@ +{ + "data": { + "bar": { + "date": "9999-12-31" + } + } +} diff --git a/src/HotChocolate/Core/test/Types.Scalars.Tests/__snapshots__/LocalDateTypeTests.Schema_WithScalar_IsMatch.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_ReturnValue_Schema.snap similarity index 65% rename from src/HotChocolate/Core/test/Types.Scalars.Tests/__snapshots__/LocalDateTypeTests.Schema_WithScalar_IsMatch.snap rename to src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_ReturnValue_Schema.snap index 0f3f696b761..3f3ced67393 100644 --- a/src/HotChocolate/Core/test/Types.Scalars.Tests/__snapshots__/LocalDateTypeTests.Schema_WithScalar_IsMatch.snap +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalDateTypeTests.DateOnly_As_ReturnValue_Schema.snap @@ -1,9 +1,13 @@ -schema { - query: Query +schema { + query: QueryDateTime2 } -type Query { - scalar: LocalDate +type Bar { + date: LocalDate! +} + +type QueryDateTime2 { + bar: Bar } "The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences YYYY-MM-DD. The scalar follows the specification defined in RFC3339" diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_Argument.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_Argument.snap new file mode 100644 index 00000000000..b707f41601e --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_Argument.snap @@ -0,0 +1,7 @@ +{ + "data": { + "foo": { + "time": "11:22:00" + } + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_Argument_Schema.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_Argument_Schema.snap new file mode 100644 index 00000000000..4f129ea9a53 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_Argument_Schema.snap @@ -0,0 +1,14 @@ +schema { + query: QueryDateTime1 +} + +type Foo { + time(time: LocalTime!): LocalTime! +} + +type QueryDateTime1 { + foo: Foo +} + +"The LocalTime scalar type is a local time string (i.e., with no associated timezone) in 24-hr HH:mm:ss." +scalar LocalTime diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_ReturnValue.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_ReturnValue.snap new file mode 100644 index 00000000000..b5a3efa6587 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_ReturnValue.snap @@ -0,0 +1,7 @@ +{ + "data": { + "bar": { + "time": "23:59:59" + } + } +} diff --git a/src/HotChocolate/Core/test/Types.Scalars.Tests/__snapshots__/LocalTimeTypeTests.Schema_WithScalar_IsMatch.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_ReturnValue_Schema.snap similarity index 56% rename from src/HotChocolate/Core/test/Types.Scalars.Tests/__snapshots__/LocalTimeTypeTests.Schema_WithScalar_IsMatch.snap rename to src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_ReturnValue_Schema.snap index 938ffe5d9b6..4c144c2c72d 100644 --- a/src/HotChocolate/Core/test/Types.Scalars.Tests/__snapshots__/LocalTimeTypeTests.Schema_WithScalar_IsMatch.snap +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/LocalTimeTypeTests.TimeOnly_As_ReturnValue_Schema.snap @@ -1,9 +1,13 @@ -schema { - query: Query +schema { + query: QueryDateTime2 } -type Query { - scalar: LocalTime +type Bar { + time: LocalTime! +} + +type QueryDateTime2 { + bar: Bar } "The LocalTime scalar type is a local time string (i.e., with no associated timezone) in 24-hr HH:mm:ss." diff --git a/src/HotChocolate/Data/src/Data/Filters/Convention/Extensions/FilterConventionDescriptorExtensions.cs b/src/HotChocolate/Data/src/Data/Filters/Convention/Extensions/FilterConventionDescriptorExtensions.cs index 81d99c2b2cb..11e2d40c763 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Convention/Extensions/FilterConventionDescriptorExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Convention/Extensions/FilterConventionDescriptorExtensions.cs @@ -146,10 +146,10 @@ public static IFilterConventionDescriptor BindDefaultTypes( .BindRuntimeType() .BindRuntimeType() .BindRuntimeType() - .BindRuntimeType() - .BindRuntimeType() - .BindRuntimeType() - .BindRuntimeType() + .BindRuntimeType() + .BindRuntimeType() + .BindRuntimeType() + .BindRuntimeType() .BindRuntimeType() .BindRuntimeType() .BindRuntimeType() diff --git a/src/HotChocolate/Data/src/Data/Filters/Types/DateOperationFilterInputType.cs b/src/HotChocolate/Data/src/Data/Filters/Types/LocalDateOperationFilterInputType.cs similarity index 57% rename from src/HotChocolate/Data/src/Data/Filters/Types/DateOperationFilterInputType.cs rename to src/HotChocolate/Data/src/Data/Filters/Types/LocalDateOperationFilterInputType.cs index c19079876a5..66b0c2f5370 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Types/DateOperationFilterInputType.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Types/LocalDateOperationFilterInputType.cs @@ -3,12 +3,12 @@ namespace HotChocolate.Data; -public class DateOperationFilterInputType - : ComparableOperationFilterInputType +public class LocalDateOperationFilterInputType + : ComparableOperationFilterInputType { protected override void Configure(IFilterInputTypeDescriptor descriptor) { - descriptor.Name("DateOperationFilterInput"); + descriptor.Name("LocalDateOperationFilterInput"); base.Configure(descriptor); } } diff --git a/src/HotChocolate/Data/src/Data/Filters/Types/LocalTimeOperationFilterInputType.cs b/src/HotChocolate/Data/src/Data/Filters/Types/LocalTimeOperationFilterInputType.cs new file mode 100644 index 00000000000..99da22f4764 --- /dev/null +++ b/src/HotChocolate/Data/src/Data/Filters/Types/LocalTimeOperationFilterInputType.cs @@ -0,0 +1,14 @@ +using HotChocolate.Data.Filters; +using HotChocolate.Types; + +namespace HotChocolate.Data; + +public class LocalTimeOperationFilterInputType + : ComparableOperationFilterInputType +{ + protected override void Configure(IFilterInputTypeDescriptor descriptor) + { + descriptor.Name("LocalTimeOperationFilterInput"); + base.Configure(descriptor); + } +} diff --git a/src/HotChocolate/Data/test/Data.Filters.Tests/Types/__snapshots__/ComparableOperationInputTests.Create_Implicit_Operation.graphql b/src/HotChocolate/Data/test/Data.Filters.Tests/Types/__snapshots__/ComparableOperationInputTests.Create_Implicit_Operation.graphql index 1f6bd03119b..0cbc5a3790a 100644 --- a/src/HotChocolate/Data/test/Data.Filters.Tests/Types/__snapshots__/ComparableOperationInputTests.Create_Implicit_Operation.graphql +++ b/src/HotChocolate/Data/test/Data.Filters.Tests/Types/__snapshots__/ComparableOperationInputTests.Create_Implicit_Operation.graphql @@ -22,18 +22,18 @@ input ComparableByteOperationFilterInput { } input ComparableDateOnlyOperationFilterInput { - eq: Date - neq: Date - in: [Date!] - nin: [Date!] - gt: Date - ngt: Date - gte: Date - ngte: Date - lt: Date - nlt: Date - lte: Date - nlte: Date + eq: LocalDate + neq: LocalDate + in: [LocalDate!] + nin: [LocalDate!] + gt: LocalDate + ngt: LocalDate + gte: LocalDate + ngte: LocalDate + lt: LocalDate + nlt: LocalDate + lte: LocalDate + nlte: LocalDate } input ComparableDecimalOperationFilterInput { @@ -127,18 +127,18 @@ input ComparableNullableOfByteOperationFilterInput { } input ComparableNullableOfDateOnlyOperationFilterInput { - eq: Date - neq: Date - in: [Date] - nin: [Date] - gt: Date - ngt: Date - gte: Date - ngte: Date - lt: Date - nlt: Date - lte: Date - nlte: Date + eq: LocalDate + neq: LocalDate + in: [LocalDate] + nin: [LocalDate] + gt: LocalDate + ngt: LocalDate + gte: LocalDate + ngte: LocalDate + lt: LocalDate + nlt: LocalDate + lte: LocalDate + nlte: LocalDate } input ComparableNullableOfDecimalOperationFilterInput { @@ -232,18 +232,18 @@ input ComparableNullableOfSingleOperationFilterInput { } input ComparableNullableOfTimeOnlyOperationFilterInput { - eq: TimeSpan - neq: TimeSpan - in: [TimeSpan] - nin: [TimeSpan] - gt: TimeSpan - ngt: TimeSpan - gte: TimeSpan - ngte: TimeSpan - lt: TimeSpan - nlt: TimeSpan - lte: TimeSpan - nlte: TimeSpan + eq: LocalTime + neq: LocalTime + in: [LocalTime] + nin: [LocalTime] + gt: LocalTime + ngt: LocalTime + gte: LocalTime + ngte: LocalTime + lt: LocalTime + nlt: LocalTime + lte: LocalTime + nlte: LocalTime } input ComparableSingleOperationFilterInput { @@ -262,18 +262,18 @@ input ComparableSingleOperationFilterInput { } input ComparableTimeOnlyOperationFilterInput { - eq: TimeSpan - neq: TimeSpan - in: [TimeSpan!] - nin: [TimeSpan!] - gt: TimeSpan - ngt: TimeSpan - gte: TimeSpan - ngte: TimeSpan - lt: TimeSpan - nlt: TimeSpan - lte: TimeSpan - nlte: TimeSpan + eq: LocalTime + neq: LocalTime + in: [LocalTime!] + nin: [LocalTime!] + gt: LocalTime + ngt: LocalTime + gte: LocalTime + ngte: LocalTime + lt: LocalTime + nlt: LocalTime + lte: LocalTime + nlte: LocalTime } input ComparableUriOperationFilterInput { @@ -335,19 +335,19 @@ directive @specifiedBy("The specifiedBy URL points to a human-readable specifica "The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." scalar Byte -"The `Date` scalar represents an ISO-8601 compliant date type." -scalar Date - "The `Decimal` scalar type represents a decimal floating-point number." scalar Decimal +"The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences YYYY-MM-DD. The scalar follows the specification defined in RFC3339" +scalar LocalDate + +"The LocalTime scalar type is a local time string (i.e., with no associated timezone) in 24-hr HH:mm:ss." +scalar LocalTime + "The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." scalar Long "The `Short` scalar type represents non-fractional signed whole 16-bit numeric values. Short can represent values between -(2^15) and 2^15 - 1." scalar Short -"The `TimeSpan` scalar represents an ISO-8601 compliant duration type." -scalar TimeSpan - scalar URL @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc3986") diff --git a/src/HotChocolate/Data/test/Data.Filters.Tests/Types/__snapshots__/ComparableOperationInputTests.Create_Implicit_Operation_Normalized.graphql b/src/HotChocolate/Data/test/Data.Filters.Tests/Types/__snapshots__/ComparableOperationInputTests.Create_Implicit_Operation_Normalized.graphql index ae45027b640..1833cb85c15 100644 --- a/src/HotChocolate/Data/test/Data.Filters.Tests/Types/__snapshots__/ComparableOperationInputTests.Create_Implicit_Operation_Normalized.graphql +++ b/src/HotChocolate/Data/test/Data.Filters.Tests/Types/__snapshots__/ComparableOperationInputTests.Create_Implicit_Operation_Normalized.graphql @@ -21,21 +21,6 @@ input ByteOperationFilterInput { nlte: Byte } -input DateOperationFilterInput { - eq: Date - neq: Date - in: [Date] - nin: [Date] - gt: Date - ngt: Date - gte: Date - ngte: Date - lt: Date - nlt: Date - lte: Date - nlte: Date -} - input DecimalOperationFilterInput { eq: Decimal neq: Decimal @@ -93,10 +78,10 @@ input FooFilterInput { barDecimalNullable: DecimalOperationFilterInput barByteNullable: ByteOperationFilterInput fooBar: FooBarOperationFilterInput - dateOnly: DateOperationFilterInput - dateOnlyNullable: DateOperationFilterInput - timeOnly: TimeSpanOperationFilterInput - timeOnlyNullable: TimeSpanOperationFilterInput + dateOnly: LocalDateOperationFilterInput + dateOnlyNullable: LocalDateOperationFilterInput + timeOnly: LocalTimeOperationFilterInput + timeOnlyNullable: LocalTimeOperationFilterInput } input IntOperationFilterInput { @@ -114,6 +99,36 @@ input IntOperationFilterInput { nlte: Int } +input LocalDateOperationFilterInput { + eq: LocalDate + neq: LocalDate + in: [LocalDate] + nin: [LocalDate] + gt: LocalDate + ngt: LocalDate + gte: LocalDate + ngte: LocalDate + lt: LocalDate + nlt: LocalDate + lte: LocalDate + nlte: LocalDate +} + +input LocalTimeOperationFilterInput { + eq: LocalTime + neq: LocalTime + in: [LocalTime] + nin: [LocalTime] + gt: LocalTime + ngt: LocalTime + gte: LocalTime + ngte: LocalTime + lt: LocalTime + nlt: LocalTime + lte: LocalTime + nlte: LocalTime +} + input LongOperationFilterInput { eq: Long neq: Long @@ -144,21 +159,6 @@ input ShortOperationFilterInput { nlte: Short } -input TimeSpanOperationFilterInput { - eq: TimeSpan - neq: TimeSpan - in: [TimeSpan] - nin: [TimeSpan] - gt: TimeSpan - ngt: TimeSpan - gte: TimeSpan - ngte: TimeSpan - lt: TimeSpan - nlt: TimeSpan - lte: TimeSpan - nlte: TimeSpan -} - input UrlOperationFilterInput { eq: URL neq: URL @@ -185,19 +185,19 @@ directive @specifiedBy("The specifiedBy URL points to a human-readable specifica "The `Byte` scalar type represents non-fractional whole numeric values. Byte can represent values between 0 and 255." scalar Byte -"The `Date` scalar represents an ISO-8601 compliant date type." -scalar Date - "The `Decimal` scalar type represents a decimal floating-point number." scalar Decimal +"The `LocalDate` scalar type represents a ISO date string, represented as UTF-8 character sequences YYYY-MM-DD. The scalar follows the specification defined in RFC3339" +scalar LocalDate + +"The LocalTime scalar type is a local time string (i.e., with no associated timezone) in 24-hr HH:mm:ss." +scalar LocalTime + "The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1." scalar Long "The `Short` scalar type represents non-fractional signed whole 16-bit numeric values. Short can represent values between -(2^15) and 2^15 - 1." scalar Short -"The `TimeSpan` scalar represents an ISO-8601 compliant duration type." -scalar TimeSpan - scalar URL @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc3986") diff --git a/src/HotChocolate/MongoDb/src/Data/Filters/Convention/Extensions/MongoDbFilterConventionDescriptorExtensions.cs b/src/HotChocolate/MongoDb/src/Data/Filters/Convention/Extensions/MongoDbFilterConventionDescriptorExtensions.cs index ff512b7502c..d1411c6e0eb 100644 --- a/src/HotChocolate/MongoDb/src/Data/Filters/Convention/Extensions/MongoDbFilterConventionDescriptorExtensions.cs +++ b/src/HotChocolate/MongoDb/src/Data/Filters/Convention/Extensions/MongoDbFilterConventionDescriptorExtensions.cs @@ -150,10 +150,10 @@ public static IFilterConventionDescriptor BindDefaultMongoDbTypes( .BindRuntimeType() .BindRuntimeType() .BindRuntimeType() - .BindRuntimeType() - .BindRuntimeType() - .BindRuntimeType() - .BindRuntimeType() + .BindRuntimeType() + .BindRuntimeType() + .BindRuntimeType() + .BindRuntimeType() .BindRuntimeType() .BindRuntimeType() .BindRuntimeType() From cf4ee8fd25f8544789495c5a27c7e766a639295b Mon Sep 17 00:00:00 2001 From: Glen Date: Thu, 21 Nov 2024 13:01:01 +0200 Subject: [PATCH 2/2] Fixed tests --- .../Types/Scalars/DateTypeTests.cs | 57 +++++++++---------- ...sts.DateOnly_And_TimeOnly_As_Argument.snap | 8 --- ...eOnly_And_TimeOnly_As_Argument_Schema.snap | 24 -------- ...ly_And_TimeOnly_As_ReturnValue_Schema.snap | 18 ------ .../DateTypeTests.DateOnly_As_Argument.snap | 7 +++ ...TypeTests.DateOnly_As_Argument_Schema.snap | 14 +++++ ...ateTypeTests.DateOnly_As_ReturnValue.snap} | 3 +- ...eTests.DateOnly_As_ReturnValue_Schema.snap | 14 +++++ 8 files changed, 64 insertions(+), 81 deletions(-) delete mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_Argument.snap delete mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_Argument_Schema.snap delete mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_ReturnValue_Schema.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_Argument.snap create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_Argument_Schema.snap rename src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/{DateTypeTests.DateOnly_And_TimeOnly_As_ReturnValue.snap => DateTypeTests.DateOnly_As_ReturnValue.snap} (62%) create mode 100644 src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_ReturnValue_Schema.snap diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/DateTypeTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/DateTypeTests.cs index 79b536f331e..2c706b70b5f 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/DateTypeTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/DateTypeTests.cs @@ -419,89 +419,88 @@ public void DateType_Binds_Only_Explicitly() } [Fact] - public async Task DateOnly_And_TimeOnly_As_Argument_Schema() + public async Task DateOnly_As_Argument_Schema() { await new ServiceCollection() .AddGraphQL() - .AddQueryType() + .AddQueryType() .BuildSchemaAsync() .MatchSnapshotAsync(); } [Fact] - public async Task DateOnly_And_TimeOnly_As_Argument() + public async Task DateOnly_As_Argument() { await new ServiceCollection() .AddGraphQL() - .AddQueryType() + .AddQueryType() .AddType(() => new TimeSpanType(TimeSpanFormat.DotNet)) .ExecuteRequestAsync( - @"{ - foo { - time(time: ""11:22"") - date(date: ""2017-12-30"") - } - }") + """ + { + foo { + date(date: "2017-12-30") + } + } + """) .MatchSnapshotAsync(); } [Fact] - public async Task DateOnly_And_TimeOnly_As_ReturnValue_Schema() + public async Task DateOnly_As_ReturnValue_Schema() { await new ServiceCollection() .AddGraphQL() - .AddQueryType() + .AddQueryType() .BuildSchemaAsync() .MatchSnapshotAsync(); } [Fact] - public async Task DateOnly_And_TimeOnly_As_ReturnValue() + public async Task DateOnly_As_ReturnValue() { await new ServiceCollection() .AddGraphQL() - .AddQueryType() + .AddQueryType() .AddType(() => new TimeSpanType(TimeSpanFormat.DotNet)) .ExecuteRequestAsync( - @"{ - bar { - time - date - } - }") + """ + { + bar { + date + } + } + """) .MatchSnapshotAsync(); } public class Query { [GraphQLType(typeof(DateType))] - public DateTime? DateField => DateTime.UtcNow; + public DateOnly? DateField => new(); public DateTime? DateTimeField => DateTime.UtcNow; } - public class QueryDateTime1 + public class QueryDate1 { public Foo Foo => new(); } public class Foo { - public TimeSpan GetTime(TimeOnly time) => time.ToTimeSpan(); - - public DateTime GetDate(DateOnly date) - => date.ToDateTime(new TimeOnly(15, 0), DateTimeKind.Utc); + [GraphQLType(typeof(DateType))] + public DateOnly GetDate([GraphQLType(typeof(DateType))] DateOnly date) => date; } - public class QueryDateTime2 + public class QueryDate2 { public Bar Bar => new(); } public class Bar { - public TimeOnly GetTime() => TimeOnly.MaxValue; - + [GraphQLType(typeof(DateType))] public DateOnly GetDate() => DateOnly.MaxValue; } } diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_Argument.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_Argument.snap deleted file mode 100644 index d26189528e4..00000000000 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_Argument.snap +++ /dev/null @@ -1,8 +0,0 @@ -{ - "data": { - "foo": { - "time": "11:22:00", - "date": "2017-12-30T15:00:00.000Z" - } - } -} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_Argument_Schema.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_Argument_Schema.snap deleted file mode 100644 index c068274c261..00000000000 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_Argument_Schema.snap +++ /dev/null @@ -1,24 +0,0 @@ -schema { - query: QueryDateTime1 -} - -type Foo { - time(time: TimeSpan!): TimeSpan! - date(date: Date!): DateTime! -} - -type QueryDateTime1 { - foo: Foo -} - -"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." -directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR - -"The `Date` scalar represents an ISO-8601 compliant date type." -scalar Date - -"The `DateTime` scalar represents an ISO-8601 compliant date time type." -scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") - -"The `TimeSpan` scalar represents an ISO-8601 compliant duration type." -scalar TimeSpan diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_ReturnValue_Schema.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_ReturnValue_Schema.snap deleted file mode 100644 index 647f834d606..00000000000 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_ReturnValue_Schema.snap +++ /dev/null @@ -1,18 +0,0 @@ -schema { - query: QueryDateTime2 -} - -type Bar { - time: TimeSpan! - date: Date! -} - -type QueryDateTime2 { - bar: Bar -} - -"The `Date` scalar represents an ISO-8601 compliant date type." -scalar Date - -"The `TimeSpan` scalar represents an ISO-8601 compliant duration type." -scalar TimeSpan diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_Argument.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_Argument.snap new file mode 100644 index 00000000000..dcf9d52b072 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_Argument.snap @@ -0,0 +1,7 @@ +{ + "data": { + "foo": { + "date": "2017-12-30" + } + } +} diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_Argument_Schema.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_Argument_Schema.snap new file mode 100644 index 00000000000..80703573b1c --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_Argument_Schema.snap @@ -0,0 +1,14 @@ +schema { + query: QueryDate1 +} + +type Foo { + date(date: Date): Date +} + +type QueryDate1 { + foo: Foo +} + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_ReturnValue.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_ReturnValue.snap similarity index 62% rename from src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_ReturnValue.snap rename to src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_ReturnValue.snap index 2acfbe89525..bb38224a34a 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_And_TimeOnly_As_ReturnValue.snap +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_ReturnValue.snap @@ -1,7 +1,6 @@ -{ +{ "data": { "bar": { - "time": "23:59:59.9999999", "date": "9999-12-31" } } diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_ReturnValue_Schema.snap b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_ReturnValue_Schema.snap new file mode 100644 index 00000000000..0ea6741c291 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/Scalars/__snapshots__/DateTypeTests.DateOnly_As_ReturnValue_Schema.snap @@ -0,0 +1,14 @@ +schema { + query: QueryDate2 +} + +type Bar { + date: Date +} + +type QueryDate2 { + bar: Bar +} + +"The `Date` scalar represents an ISO-8601 compliant date type." +scalar Date