From 757e730515643882413bb0f61db9cc1082d7e9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eafak=20G=C3=BCr?= Date: Sun, 1 Mar 2020 16:02:10 +0000 Subject: [PATCH 1/6] Add KindSpecified/Unspecified guards for DateTime --- docs/standard-validations.md | 18 +++-- snippets/guard-cs.vs.snippet | 80 +++++++++++++++++++++ src/Guard.DateTime.cs | 134 +++++++++++++++++++++++++++++++++++ src/Guard.Messages.cs | 8 ++- tests/DateTimeTests.cs | 65 +++++++++++++++++ 5 files changed, 297 insertions(+), 8 deletions(-) create mode 100644 src/Guard.DateTime.cs create mode 100644 tests/DateTimeTests.cs diff --git a/docs/standard-validations.md b/docs/standard-validations.md index 8c0106a..ab7413f 100644 --- a/docs/standard-validations.md +++ b/docs/standard-validations.md @@ -50,6 +50,12 @@ For `ArgumentInfo where T : struct, IComparable` * `Negative()` * `NotNegative()` +### Boolean Guards + +For `ArgumentInfo` +* `True()` +* `False()` + ### Collection Guards For `ArgumentInfo where T : IEnumerable` @@ -104,6 +110,12 @@ For `ArgumentInfo` * `DoesNotMatch(string, TimeSpan)` * `DoesNotMatch(Regex)` +### Time Guards + +For `ArgumentInfo` + +* `KindSpecified()` + ### Floating-Point Number Guards For `ArgumentInfo` @@ -118,12 +130,6 @@ For `ArgumentInfo` * `Equal(T, T)` - Approx. equality. * `NotEqual(T, T)` - Approx. unequality. -### Boolean Guards - -For `ArgumentInfo` -* `True()` -* `False()` - ### URI Guards For `ArgumentInfo` diff --git a/snippets/guard-cs.vs.snippet b/snippets/guard-cs.vs.snippet index 94e720d..5cc074e 100644 --- a/snippets/guard-cs.vs.snippet +++ b/snippets/guard-cs.vs.snippet @@ -1432,6 +1432,86 @@ + +
+ Guard.KindSpecified() + gtks +
+ + + + Dawn + + + + + arg + arg + + + + +
+ +
+ Guard.NotNull().KindSpecified() + gxtks +
+ + + + Dawn + + + + + arg + arg + + + + +
+ +
+ Guard.KindUnspecified() + gtku +
+ + + + Dawn + + + + + arg + arg + + + + +
+ +
+ Guard.NotNull().KindUnspecified() + gxtku +
+ + + + Dawn + + + + + arg + arg + + + + +
Guard.Equal(other, delta) diff --git a/src/Guard.DateTime.cs b/src/Guard.DateTime.cs new file mode 100644 index 0000000..123d99e --- /dev/null +++ b/src/Guard.DateTime.cs @@ -0,0 +1,134 @@ +#nullable enable + +using System; +using System.Diagnostics; +using JetBrains.Annotations; + +namespace Dawn +{ + /// Provides preconditions for arguments. + public static partial class Guard + { + /// + /// Requires the date-time argument to have its specified, + /// i.e. not to have it as . + /// + /// The argument. + /// + /// The message of the exception that will be thrown if the precondition is not satisfied. + /// + /// . + /// + /// 's property is + /// . + /// + [AssertionMethod] + [DebuggerStepThrough] + [GuardFunction("DateTime", "gtks")] + public static ref readonly ArgumentInfo KindSpecified( + in this ArgumentInfo argument, Func? message = null) + { + if (argument.Value.Kind == DateTimeKind.Unspecified) + { + var m = message?.Invoke(argument.Value) ?? Messages.KindSpecified(argument); + throw Fail(new ArgumentException(m, argument.Name)); + } + + return ref argument; + } + + /// + /// Requires the date-time argument to either be null or have its + /// specified, i.e. not to have it as + /// . + /// + /// The argument. + /// + /// The message of the exception that will be thrown if the precondition is not satisfied. + /// + /// . + /// + /// 's property is + /// . + /// + [AssertionMethod] + [DebuggerStepThrough] + [GuardFunction("DateTime", "gtks")] + public static ref readonly ArgumentInfo KindSpecified( + in this ArgumentInfo argument, Func? message = null) + { + if (argument.HasValue()) + { + var value = argument.GetValueOrDefault(); + if (value.Kind == DateTimeKind.Unspecified) + { + var m = message?.Invoke(value) ?? Messages.KindSpecified(argument); + throw Fail(new ArgumentException(m, argument.Name)); + } + } + + return ref argument; + } + + /// + /// Requires the date-time argument not to have its + /// specified, i.e. to have it as . + /// + /// The argument. + /// + /// The message of the exception that will be thrown if the precondition is not satisfied. + /// + /// . + /// + /// 's property is not + /// . + /// + [AssertionMethod] + [DebuggerStepThrough] + [GuardFunction("DateTime", "gtku")] + public static ref readonly ArgumentInfo KindUnspecified( + in this ArgumentInfo argument, Func? message = null) + { + if (argument.Value.Kind != DateTimeKind.Unspecified) + { + var m = message?.Invoke(argument.Value) ?? Messages.KindUnspecified(argument); + throw Fail(new ArgumentException(m, argument.Name)); + } + + return ref argument; + } + + /// + /// Requires the date-time argument either to be null or not to have its + /// specified, i.e. to have it as + /// . + /// + /// The argument. + /// + /// The message of the exception that will be thrown if the precondition is not satisfied. + /// + /// . + /// + /// 's property is not + /// . + /// + [AssertionMethod] + [DebuggerStepThrough] + [GuardFunction("DateTime", "gtku")] + public static ref readonly ArgumentInfo KindUnspecified( + in this ArgumentInfo argument, Func? message = null) + { + if (argument.HasValue()) + { + var value = argument.GetValueOrDefault(); + if (value.Kind != DateTimeKind.Unspecified) + { + var m = message?.Invoke(value) ?? Messages.KindUnspecified(argument); + throw Fail(new ArgumentException(m, argument.Name)); + } + } + + return ref argument; + } + } +} diff --git a/src/Guard.Messages.cs b/src/Guard.Messages.cs index 7ff40d8..e9c9532 100644 --- a/src/Guard.Messages.cs +++ b/src/Guard.Messages.cs @@ -264,7 +264,6 @@ public static string UriHttps(in ArgumentInfo argument) => $"{argument.Name} must be an absolute URI with the HTTPS scheme."; #if !NETSTANDARD1_0 - public static string EmailHasHost(in ArgumentInfo argument, string host) => argument.Secure ? Require(argument) : $"{argument.Name} must have the host '{host}'."; @@ -282,9 +281,14 @@ public static string EmailHasDisplayName(in ArgumentInfo argument) public static string EmailDoesNotHaveDisplayName(in ArgumentInfo argument) => $"{argument.Name} cannot have a display name specified."; - #endif + public static string KindSpecified(in ArgumentInfo argument) + => $"{argument.Name} must have its kind specified."; + + public static string KindUnspecified(in ArgumentInfo argument) + => $"{argument.Name} cannot have its kind specified."; + private static string ToString(object? obj) => obj?.ToString() ?? "null"; private static string Join(IEnumerable collection) diff --git a/tests/DateTimeTests.cs b/tests/DateTimeTests.cs new file mode 100644 index 0000000..b0fa8e3 --- /dev/null +++ b/tests/DateTimeTests.cs @@ -0,0 +1,65 @@ +using System; +using Xunit; + +namespace Dawn.Tests +{ + public sealed class DateTimeTests : BaseTests + { + [Theory(DisplayName = "DateTime: KindSpecified/KindUnspecified")] + [InlineData(true)] + [InlineData(false)] + public void KindSpecified(bool testNull) + { + var specified = testNull ? default(DateTime?) : DateTime.UtcNow; + var nullableSpecifiedArg = Guard.Argument(() => specified).KindSpecified(); + + var unspecified = testNull ? default(DateTime?) : default(DateTime); + var nullableUnspecifiedArg = Guard.Argument(() => unspecified).KindUnspecified(); + + if (testNull) + { + nullableSpecifiedArg.KindUnspecified(); + nullableUnspecifiedArg.KindSpecified(); + return; + } + + ThrowsArgumentException( + nullableSpecifiedArg, + arg => arg.KindUnspecified(), + (arg, message) => arg.KindUnspecified(d => + { + Assert.Equal(specified, d); + return message; + })); + + ThrowsArgumentException( + nullableUnspecifiedArg, + arg => arg.KindSpecified(), + (arg, message) => arg.KindSpecified(d => + { + Assert.Equal(unspecified, d); + return message; + })); + + var specifiedArg = Guard.Argument(specified.Value, nameof(specified)).KindSpecified(); + var unspecifiedArg = Guard.Argument(unspecified.Value, nameof(unspecified)).KindUnspecified(); + ThrowsArgumentException( + specifiedArg, + arg => arg.KindUnspecified(), + (arg, message) => arg.KindUnspecified(d => + { + Assert.Equal(specified, d); + return message; + })); + + ThrowsArgumentException( + unspecifiedArg, + arg => arg.KindSpecified(), + (arg, message) => arg.KindSpecified(d => + { + Assert.Equal(unspecified, d); + return message; + })); + } + } +} From 2cfefa0d3762495b0070344aa9a5ddadd64e7bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eafak=20G=C3=BCr?= Date: Sun, 1 Mar 2020 17:20:10 +0000 Subject: [PATCH 2/6] Add KindUnspecified to standard validations doc --- docs/standard-validations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/standard-validations.md b/docs/standard-validations.md index ab7413f..2a24836 100644 --- a/docs/standard-validations.md +++ b/docs/standard-validations.md @@ -115,6 +115,7 @@ For `ArgumentInfo` For `ArgumentInfo` * `KindSpecified()` +* `KindUnspecified()` ### Floating-Point Number Guards From 05b5fa221528e8f9f1300a4a369ae59c6fa88440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eafak=20G=C3=BCr?= Date: Tue, 31 Mar 2020 23:09:56 +0100 Subject: [PATCH 3/6] Fix #48 --- src/Guard.IEnumerable.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Guard.IEnumerable.cs b/src/Guard.IEnumerable.cs index c39fb1d..bf4e8f3 100644 --- a/src/Guard.IEnumerable.cs +++ b/src/Guard.IEnumerable.cs @@ -1047,11 +1047,16 @@ NewExpression GetSetNew(ParameterExpression? comparerParam) var countGetter = GetCountGetter(); if (countGetter != null) - { - var setCtor = setType.GetConstructor(new[] { typeof(int) }); - var countCall = Expression.Call(collectionParam, countGetter); - return Expression.New(setCtor, countCall); - } + try + { + var setCtor = setType.GetConstructor(new[] { typeof(int) }); + var countCall = Expression.Call(collectionParam, countGetter); + return Expression.New(setCtor, countCall); + } + catch (Exception) + { + // Swallow and use default + } return Expression.New(setType); } From 53a885062808ac571aa8a336b558fde87e3606a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eafak=20G=C3=BCr?= Date: Tue, 31 Mar 2020 23:17:33 +0100 Subject: [PATCH 4/6] Bump version to 1.12.0 --- src/Guard.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Guard.csproj b/src/Guard.csproj index 826384d..3062e52 100644 --- a/src/Guard.csproj +++ b/src/Guard.csproj @@ -5,7 +5,7 @@ netstandard2.0;netstandard1.0 Dawn.Guard Dawn Utils - 1.11.0 + 1.12.0 1.0.0 Dawn true From f05e22317d77b56bacbcc246a92d589e073f05d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eafak=20G=C3=BCr?= Date: Tue, 31 Mar 2020 23:27:32 +0100 Subject: [PATCH 5/6] Use latest images for builds --- .azure-pipelines.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 084dbaa..8ac406d 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -2,22 +2,22 @@ jobs: - template: .azure-pipelines-build.yml parameters: name: Windows - image: windows-2019 + image: windows-latest artifacts: true - template: .azure-pipelines-build.yml parameters: name: macOS - image: macOS-10.13 + image: macOS-latest - template: .azure-pipelines-build.yml parameters: name: Linux - image: Ubuntu-16.04 + image: ubuntu-latest - job: Deploy pool: - vmImage: macOS-10.13 + vmImage: macOS-latest dependsOn: - Windows From 2a79a903895ded2c4dab7b29e7f830ba9f0a331e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eafak=20G=C3=BCr?= Date: Tue, 31 Mar 2020 23:29:37 +0100 Subject: [PATCH 6/6] Use ubuntu-latest for deployments --- .azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 8ac406d..9b537fc 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -17,7 +17,7 @@ jobs: - job: Deploy pool: - vmImage: macOS-latest + vmImage: ubuntu-latest dependsOn: - Windows