From bd11bbc78c147612a6dfa166f4c63496932abc89 Mon Sep 17 00:00:00 2001 From: BEN ABT Date: Sat, 27 Apr 2024 21:03:27 +0200 Subject: [PATCH 1/6] add code enhancement and tests, also some fixes --- .editorconfig | 11 +- Directory.Build.props | 2 +- Directory.Packages.props | 4 + global.json | 2 +- .../StrongOf.AspNetCore.csproj | 7 + .../StrongOf.FluentValidation.csproj | 9 +- .../StrongStringValidators.cs | 21 +- .../StrongDateTimeOffsetJsonConverter.cs | 5 +- src/StrongOf.Json/StrongInt32JsonConverter.cs | 5 +- src/StrongOf.Json/StrongInt64JsonConverter.cs | 5 +- src/StrongOf.Json/StrongOf.Json.csproj | 7 + ...verter.cs => StrongStringJsonConverter.cs} | 0 src/StrongOf/StrongChar.Operators.cs | 177 ++++++++++++ src/StrongOf/StrongChar.cs | 44 +-- src/StrongOf/StrongDateTime.Operators.cs | 8 +- src/StrongOf/StrongDateTime.cs | 20 +- .../StrongDateTimeOffset.Operators.cs | 8 +- src/StrongOf/StrongDateTimeOffset.cs | 15 +- src/StrongOf/StrongDecimal.Operators.cs | 217 ++++++++++++++ src/StrongOf/StrongDecimal.cs | 57 +--- src/StrongOf/StrongGuid.Operators.cs | 180 ++++++++++++ src/StrongOf/StrongGuid.cs | 45 +-- src/StrongOf/StrongInt32.cs | 18 +- src/StrongOf/StrongInt64.cs | 18 +- src/StrongOf/StrongOf.cs | 14 +- src/StrongOf/StrongOf.csproj | 11 +- src/StrongOf/StrongString.Methods.cs | 12 +- src/StrongOf/StrongString.Operators.cs | 43 +++ src/StrongOf/StrongString.Properties.cs | 2 + src/StrongOf/StrongString.cs | 46 +-- .../StrongDateTimeJsonConverterTests.cs | 2 +- .../StrongOf.UnitTests/StrongChar_As_Tests.cs | 80 ++++++ .../StrongChar_Operators_Tests.cs | 272 ++++++++++++++++++ .../StrongDateTime_As_Tests.cs | 104 ++++++- .../StrongDateTime_Tests.cs | 28 ++ .../StrongDecimal_Operators_Tests.cs | 231 +++++++++++++++ .../StrongString_Operators_Tests.cs | 78 +++++ 37 files changed, 1567 insertions(+), 241 deletions(-) rename src/StrongOf.Json/{StrongOfJsonConverter.cs => StrongStringJsonConverter.cs} (100%) create mode 100644 src/StrongOf/StrongChar.Operators.cs create mode 100644 src/StrongOf/StrongDecimal.Operators.cs create mode 100644 src/StrongOf/StrongGuid.Operators.cs create mode 100644 src/StrongOf/StrongString.Operators.cs create mode 100644 tests/StrongOf.UnitTests/StrongChar_Operators_Tests.cs create mode 100644 tests/StrongOf.UnitTests/StrongDecimal_Operators_Tests.cs create mode 100644 tests/StrongOf.UnitTests/StrongString_Operators_Tests.cs diff --git a/.editorconfig b/.editorconfig index 6c110f0..694a60f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -287,4 +287,13 @@ dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case # IDE0290: Use primary constructor -dotnet_diagnostic.IDE0290.severity = none \ No newline at end of file +dotnet_diagnostic.IDE0290.severity = none + +# MA0017: Abstract types should not have public or internal constructors +dotnet_diagnostic.MA0017.severity = none + +# MA0018: Do not declare static members on generic types (deprecated; use CA1000 instead) +dotnet_diagnostic.MA0018.severity = none + +# MA0154: Use langword in XML comment +dotnet_diagnostic.MA0154.severity = none diff --git a/Directory.Build.props b/Directory.Build.props index 1d5eead..cd1bbc4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,7 +27,7 @@ - net7.0;net8.0 + net7.0;net8.0;net9.0 diff --git a/Directory.Packages.props b/Directory.Packages.props index 9544ddf..a23f179 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,6 +16,10 @@ + + all + runtime; build; native; contentfiles; analyzers + diff --git a/global.json b/global.json index 9da8d87..be277f9 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100" + "version": "9.0.100-preview.3" } } diff --git a/src/StrongOf.AspNetCore/StrongOf.AspNetCore.csproj b/src/StrongOf.AspNetCore/StrongOf.AspNetCore.csproj index f9cf339..a90692f 100644 --- a/src/StrongOf.AspNetCore/StrongOf.AspNetCore.csproj +++ b/src/StrongOf.AspNetCore/StrongOf.AspNetCore.csproj @@ -1,5 +1,12 @@  + + + all + runtime; build; native; contentfiles; analyzers + + + true readme.md diff --git a/src/StrongOf.FluentValidation/StrongOf.FluentValidation.csproj b/src/StrongOf.FluentValidation/StrongOf.FluentValidation.csproj index fe168c4..4eb8ca7 100644 --- a/src/StrongOf.FluentValidation/StrongOf.FluentValidation.csproj +++ b/src/StrongOf.FluentValidation/StrongOf.FluentValidation.csproj @@ -1,5 +1,12 @@  + + + all + runtime; build; native; contentfiles; analyzers + + + true readme.md @@ -16,6 +23,6 @@ - + diff --git a/src/StrongOf.FluentValidation/StrongStringValidators.cs b/src/StrongOf.FluentValidation/StrongStringValidators.cs index 8b648df..b6dfdaf 100644 --- a/src/StrongOf.FluentValidation/StrongStringValidators.cs +++ b/src/StrongOf.FluentValidation/StrongStringValidators.cs @@ -46,7 +46,7 @@ public static class StrongStringValidators /// The rule builder options. public static IRuleBuilderOptions HasMaximumLength(this IRuleBuilder rule, int maxLength) where TStrong : StrongString - => rule.Must(content => content?.Value is null ? true : content.Value.Length <= maxLength); + => rule.Must(content => content?.Value is null || content.Value.Length <= maxLength); /// /// Validates that the strong string matches a regular expression. @@ -94,15 +94,20 @@ public static class StrongStringValidators } /// - /// Validates that the strong string only contains allowed characters. + /// Specifies that the value being validated must only contain characters from the specified collection. /// - /// The type of the object being validated. - /// The type of the strong string. + /// The type of the parent object being validated. + /// The type of the strong string being validated. /// The rule builder. - /// The set of allowed characters. - /// The message pattern to use if the validation fails. + /// The collection of allowed characters. + /// The pattern used to format the error message if validation fails. + /// An optional that supplies culture-specific formatting information. /// The rule builder options. - public static IRuleBuilderOptionsConditions AllowedChars(this IRuleBuilder rule, HashSet chars, string messagePattern) + /// + /// This method adds a custom validation rule to the rule builder that checks if the value being validated contains only characters from the specified collection. + /// If the validation fails, an error message is added to the validation context using the provided message pattern. + /// + public static IRuleBuilderOptionsConditions AllowedChars(this IRuleBuilder rule, ICollection chars, string messagePattern, IFormatProvider? formatProvider = null) where TStrong : StrongString { return rule.Custom((topic, context) => @@ -111,7 +116,7 @@ public static class StrongStringValidators { if (strong.ContainsInvalidChars(chars, out ICollection? invalidChars)) { - context.AddFailure(string.Format(messagePattern, string.Concat(invalidChars))); + context.AddFailure(string.Format(formatProvider, messagePattern, string.Concat(invalidChars))); } } }); diff --git a/src/StrongOf.Json/StrongDateTimeOffsetJsonConverter.cs b/src/StrongOf.Json/StrongDateTimeOffsetJsonConverter.cs index ab03cf0..11423be 100644 --- a/src/StrongOf.Json/StrongDateTimeOffsetJsonConverter.cs +++ b/src/StrongOf.Json/StrongDateTimeOffsetJsonConverter.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Globalization; +using System.Text.Json; using System.Text.Json.Serialization; namespace StrongOf.Json; @@ -35,5 +36,5 @@ public class StrongDateTimeOffsetJsonConverter : JsonConverter /// The value to write. /// Options to control the serializer behavior during writing. public override void Write(Utf8JsonWriter writer, TStrong strong, JsonSerializerOptions options) - => writer.WriteStringValue(strong.Value.ToString()); + => writer.WriteStringValue(strong.Value.ToString(CultureInfo.InvariantCulture)); } diff --git a/src/StrongOf.Json/StrongInt32JsonConverter.cs b/src/StrongOf.Json/StrongInt32JsonConverter.cs index 6715b42..257fdd8 100644 --- a/src/StrongOf.Json/StrongInt32JsonConverter.cs +++ b/src/StrongOf.Json/StrongInt32JsonConverter.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Globalization; +using System.Text.Json; using System.Text.Json.Serialization; namespace StrongOf.Json; @@ -35,5 +36,5 @@ public class StrongInt32JsonConverter : JsonConverter /// The value to write. /// Options to control the serializer behavior during writing. public override void Write(Utf8JsonWriter writer, TStrong strong, JsonSerializerOptions options) - => writer.WriteStringValue(strong.Value.ToString()); + => writer.WriteStringValue(strong.Value.ToString(CultureInfo.InvariantCulture)); } diff --git a/src/StrongOf.Json/StrongInt64JsonConverter.cs b/src/StrongOf.Json/StrongInt64JsonConverter.cs index 65d84b9..3870e3c 100644 --- a/src/StrongOf.Json/StrongInt64JsonConverter.cs +++ b/src/StrongOf.Json/StrongInt64JsonConverter.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Globalization; +using System.Text.Json; using System.Text.Json.Serialization; namespace StrongOf.Json; @@ -35,5 +36,5 @@ public class StrongInt64JsonConverter : JsonConverter /// The value to write. /// Options to control the serializer behavior during writing. public override void Write(Utf8JsonWriter writer, TStrong strong, JsonSerializerOptions options) - => writer.WriteStringValue(strong.Value.ToString()); + => writer.WriteStringValue(strong.Value.ToString(CultureInfo.InvariantCulture)); } diff --git a/src/StrongOf.Json/StrongOf.Json.csproj b/src/StrongOf.Json/StrongOf.Json.csproj index 7be2ecb..065027c 100644 --- a/src/StrongOf.Json/StrongOf.Json.csproj +++ b/src/StrongOf.Json/StrongOf.Json.csproj @@ -1,5 +1,12 @@  + + + all + runtime; build; native; contentfiles; analyzers + + + true readme.md diff --git a/src/StrongOf.Json/StrongOfJsonConverter.cs b/src/StrongOf.Json/StrongStringJsonConverter.cs similarity index 100% rename from src/StrongOf.Json/StrongOfJsonConverter.cs rename to src/StrongOf.Json/StrongStringJsonConverter.cs diff --git a/src/StrongOf/StrongChar.Operators.cs b/src/StrongOf/StrongChar.Operators.cs new file mode 100644 index 0000000..e172136 --- /dev/null +++ b/src/StrongOf/StrongChar.Operators.cs @@ -0,0 +1,177 @@ +namespace StrongOf; + +public abstract partial class StrongChar +{ + /// + /// Determines whether two specified instances of StrongChar are equal. + /// + /// The first instance to compare. + /// The object to compare. + /// True if strong and value represent the same char; otherwise, false. + public static bool operator ==(StrongChar? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is char charValue) + { + return strong.Value == charValue; + } + + if (other is StrongChar otherStrong) + { + return strong.Value == otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether two specified instances of StrongChar are not equal. + /// + /// The first instance to compare. + /// The object to compare. + /// True if strong and other do not represent the same char; otherwise, false. + public static bool operator !=(StrongChar? strong, object? other) + { + return (strong == other) is false; + } + + /// + /// Determines whether a object is less than another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is less than the object; + /// otherwise, . + /// + /// + /// If is , the method returns if is also , otherwise . + /// If is a char, the method compares the value of with the char value. + /// If is a object, the method compares the value of with the value of the other object. + /// + public static bool operator <(StrongChar? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is char charValue) + { + return strong.Value < charValue; + } + + if (other is StrongChar otherStrong) + { + return strong.Value < otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether a object is greater than another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is greater than the object; + /// otherwise, . + /// + /// + /// If is , the method returns if is also , otherwise . + /// If is a char, the method compares the value of with the char value. + /// If is a object, the method compares the value of with the value of the other object. + /// + public static bool operator >(StrongChar? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is char charValue) + { + return strong.Value > charValue; + } + + if (other is StrongChar otherStrong) + { + return strong.Value > otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether a object is less than or equal to another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is less than or equal to the object; + /// otherwise, . + /// + /// + /// If is , the method returns if is also , otherwise . + /// If is a char, the method compares the value of with the char value. + /// If is a object, the method compares the value of with the value of the other object. + /// + public static bool operator <=(StrongChar? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is char charValue) + { + return strong.Value <= charValue; + } + + if (other is StrongChar otherStrong) + { + return strong.Value <= otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether a object is greater than or equal to another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is greater than or equal to the object; + /// otherwise, . + /// + /// + /// If is , the method returns if is also , otherwise . + /// If is a char, the method compares the value of with the char value. + /// If is a object, the method compares the value of with the value of the other object. + /// + public static bool operator >=(StrongChar? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is char charValue) + { + return strong.Value >= charValue; + } + + if (other is StrongChar otherStrong) + { + return strong.Value >= otherStrong.Value; + } + + return false; + } +} diff --git a/src/StrongOf/StrongChar.cs b/src/StrongOf/StrongChar.cs index 6591f13..b8fb7ea 100644 --- a/src/StrongOf/StrongChar.cs +++ b/src/StrongOf/StrongChar.cs @@ -6,7 +6,8 @@ namespace StrongOf; /// Represents a strongly typed character. /// /// The type of the strong character. -public abstract class StrongChar(char Value) : StrongOf(Value), IComparable, IStrongChar +public abstract partial class StrongChar(char Value) + : StrongOf(Value), IComparable, IStrongChar where TStrong : StrongChar { /// @@ -48,7 +49,7 @@ public int CompareTo(object? other) return Value.CompareTo(otherStrong.Value); } - throw new ArgumentException($"Object is not a {typeof(TStrong)}"); + throw new ArgumentException($"Object is not a {typeof(TStrong)}", nameof(other)); } /// @@ -69,45 +70,6 @@ public static bool TryParse(string content, [NotNullWhen(true)] out TStrong? str return false; } - // Operators - - /// - /// Determines whether two specified instances of StrongChar are equal. - /// - /// The first instance to compare. - /// The object to compare. - /// True if strong and value represent the same char; otherwise, false. - public static bool operator ==(StrongChar? strong, object? other) - { - if (strong is null) - { - return other is null; - } - - if (other is char charValue) - { - return strong.Value == charValue; - } - - if (other is StrongChar otherStrong) - { - return strong.Value == otherStrong.Value; - } - - return false; - } - - /// - /// Determines whether two specified instances of StrongChar are not equal. - /// - /// The first instance to compare. - /// The object to compare. - /// True if strong and other do not represent the same char; otherwise, false. - public static bool operator !=(StrongChar? strong, object? other) - { - return (strong == other) is false; - } - // Equals /// diff --git a/src/StrongOf/StrongDateTime.Operators.cs b/src/StrongOf/StrongDateTime.Operators.cs index 5cc267a..8e6b143 100644 --- a/src/StrongOf/StrongDateTime.Operators.cs +++ b/src/StrongOf/StrongDateTime.Operators.cs @@ -86,7 +86,7 @@ public abstract partial class StrongDateTime if (other is DateTimeOffset dtoValue) { - return strong.Value < dtoValue; + return strong.Value < dtoValue.Date; } return false; @@ -112,7 +112,7 @@ public abstract partial class StrongDateTime if (other is DateTimeOffset dtoValue) { - return strong.Value > dtoValue; + return strong.Value > dtoValue.Date; } return false; @@ -138,7 +138,7 @@ public abstract partial class StrongDateTime if (other is DateTimeOffset dtoValue) { - return strong.Value <= dtoValue; + return strong.Value <= dtoValue.Date; } return false; @@ -164,7 +164,7 @@ public abstract partial class StrongDateTime if (other is DateTimeOffset dtoValue) { - return strong.Value >= dtoValue; + return strong.Value >= dtoValue.Date; } return false; diff --git a/src/StrongOf/StrongDateTime.cs b/src/StrongOf/StrongDateTime.cs index 0d74f40..d6a1a24 100644 --- a/src/StrongOf/StrongDateTime.cs +++ b/src/StrongOf/StrongDateTime.cs @@ -8,7 +8,8 @@ namespace StrongOf; /// /// The type of the strong DateTime. /// "The DateTime type has some design flaws. Please migrate to DateTimeOffset." -public abstract partial class StrongDateTime(DateTime Value) : StrongOf(Value), IComparable, IStrongDateTime +public abstract partial class StrongDateTime(DateTime Value) + : StrongOf(Value), IComparable, IStrongDateTime where TStrong : StrongDateTime { /// @@ -73,7 +74,7 @@ public int CompareTo(object? other) return Value.CompareTo(otherStrong.Value); } - throw new ArgumentException($"Object is not a {typeof(TStrong)}"); + throw new ArgumentException($"Object is not a {typeof(TStrong)}", nameof(other)); } /// @@ -84,7 +85,7 @@ public int CompareTo(object? other) /// True if content was converted successfully; otherwise, false. public static bool TryParseIso8601(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong) { - if (TryParseExact(content, "o", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out strong)) + if (TryParse(content, CultureInfo.InvariantCulture, out strong)) { return true; } @@ -115,14 +116,15 @@ public static bool TryParseExact(ReadOnlySpan content, string format, IFor } /// - /// Tries to parse an DateTime from a ReadOnlySpan of char and returns a value that indicates whether the operation succeeded. + /// Tries to parse the specified content into a object. /// - /// A ReadOnlySpan of char containing an DateTime to convert. - /// When this method returns, contains the DateTime value equivalent to the DateTime contained in content, if the conversion succeeded, or null if the conversion failed. - /// True if content was converted successfully; otherwise, false. - public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong) + /// The content to parse. + /// When this method returns, contains the parsed value if the parsing succeeded, or null if the parsing failed. The parsing is case-sensitive. + /// An optional that supplies culture-specific formatting information. + /// true if the parsing was successful; otherwise, false. + public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong, IFormatProvider? formatProvider = null) { - if (DateTime.TryParse(content, out DateTime value)) + if (DateTime.TryParse(content, formatProvider, out DateTime value)) { strong = From(value); return true; diff --git a/src/StrongOf/StrongDateTimeOffset.Operators.cs b/src/StrongOf/StrongDateTimeOffset.Operators.cs index 3027483..14a9358 100644 --- a/src/StrongOf/StrongDateTimeOffset.Operators.cs +++ b/src/StrongOf/StrongDateTimeOffset.Operators.cs @@ -59,7 +59,7 @@ public abstract partial class StrongDateTimeOffset if (other is DateTime dtValue) { - return strong.Value < dtValue; + return strong.Value < new DateTimeOffset(dtValue); } return false; @@ -85,7 +85,7 @@ public abstract partial class StrongDateTimeOffset if (other is DateTime dtValue) { - return strong.Value > dtValue; + return strong.Value > new DateTimeOffset(dtValue); } return false; @@ -111,7 +111,7 @@ public abstract partial class StrongDateTimeOffset if (other is DateTime dtValue) { - return strong.Value <= dtValue; + return strong.Value <= new DateTimeOffset(dtValue); } return false; @@ -137,7 +137,7 @@ public abstract partial class StrongDateTimeOffset if (other is DateTime dtValue) { - return strong.Value >= dtValue; + return strong.Value >= new DateTimeOffset(dtValue); } return false; diff --git a/src/StrongOf/StrongDateTimeOffset.cs b/src/StrongOf/StrongDateTimeOffset.cs index 080a3b2..99c7e99 100644 --- a/src/StrongOf/StrongDateTimeOffset.cs +++ b/src/StrongOf/StrongDateTimeOffset.cs @@ -77,7 +77,7 @@ public int CompareTo(object? other) return Value.CompareTo(otherStrong.Value); } - throw new ArgumentException($"Object is not a {typeof(TStrong)}"); + throw new ArgumentException($"Object is not a {typeof(TStrong)}", nameof(other)); } /// @@ -119,14 +119,15 @@ public static bool TryParseExact(ReadOnlySpan content, string format, IFor } /// - /// Tries to parse a DateTimeOffset from a ReadOnlySpan of char and returns a value that indicates whether the operation succeeded. + /// Tries to parse the specified content into a object. /// - /// A ReadOnlySpan of char containing a DateTimeOffset to convert. - /// When this method returns, contains the DateTimeOffset value equivalent to the DateTimeOffset contained in content, if the conversion succeeded, or null if the conversion failed. - /// True if content was converted successfully; otherwise, false. - public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong) + /// The content to parse. + /// When this method returns, contains the parsed value if the parsing succeeded, or null if the parsing failed. The parsing is case-sensitive. + /// An optional that supplies culture-specific formatting information. + /// true if the parsing was successful; otherwise, false. + public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong, IFormatProvider? formatProvider = null) { - if (DateTimeOffset.TryParse(content, out DateTimeOffset value)) + if (DateTimeOffset.TryParse(content, formatProvider, out DateTimeOffset value)) { strong = From(value); return true; diff --git a/src/StrongOf/StrongDecimal.Operators.cs b/src/StrongOf/StrongDecimal.Operators.cs new file mode 100644 index 0000000..337dd03 --- /dev/null +++ b/src/StrongOf/StrongDecimal.Operators.cs @@ -0,0 +1,217 @@ +namespace StrongOf; + +public abstract partial class StrongDecimal +{ + /// + /// Determines whether two specified instances of StrongDecimal are equal. + /// + /// The first instance to compare. + /// The object to compare. + /// True if strong and value represent the same decimal; otherwise, false. + public static bool operator ==(StrongDecimal? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is decimal decimalValue) + { + return strong.Value == decimalValue; + } + + if (other is StrongDecimal otherStrong) + { + return strong.Value == otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether two specified instances of StrongDecimal are not equal. + /// + /// The first instance to compare. + /// The object to compare. + /// True if strong and other do not represent the same decimal; otherwise, false. + public static bool operator !=(StrongDecimal? strong, object? other) + { + return (strong == other) is false; + } + + /// + /// Determines whether a object is less than another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is less than the object; + /// otherwise, . + /// + public static bool operator <(StrongDecimal? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is decimal decimalValue) + { + return strong.Value < decimalValue; + } + + if (other is StrongDecimal otherStrong) + { + return strong.Value < otherStrong.Value; + } + + if (other is int intValue) + { + return strong.Value < intValue; + } + + if (other is long longValue) + { + return strong.Value < longValue; + } + + if (other is uint uintValue) + { + return strong.Value < uintValue; + } + + return false; + } + + /// + /// Determines whether a object is greater than another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is greater than the object; + /// otherwise, . + /// + public static bool operator >(StrongDecimal? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is decimal decimalValue) + { + return strong.Value > decimalValue; + } + + if (other is StrongDecimal otherStrong) + { + return strong.Value > otherStrong.Value; + } + + if (other is int intValue) + { + return strong.Value > intValue; + } + + if (other is long longValue) + { + return strong.Value > longValue; + } + + if (other is uint uintValue) + { + return strong.Value > uintValue; + } + + return false; + } + + /// + /// Determines whether a object is less than or equal to another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is less than or equal to the object; + /// otherwise, . + /// + public static bool operator <=(StrongDecimal? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is decimal decimalValue) + { + return strong.Value <= decimalValue; + } + + if (other is StrongDecimal otherStrong) + { + return strong.Value <= otherStrong.Value; + } + + if (other is int intValue) + { + return strong.Value <= intValue; + } + + if (other is long longValue) + { + return strong.Value <= longValue; + } + + if (other is uint uintValue) + { + return strong.Value <= uintValue; + } + + return false; + } + + /// + /// Determines whether a object is greater than or equal to another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is greater than or equal to the object; + /// otherwise, . + /// + public static bool operator >=(StrongDecimal? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is decimal decimalValue) + { + return strong.Value >= decimalValue; + } + + if (other is StrongDecimal otherStrong) + { + return strong.Value >= otherStrong.Value; + } + + if (other is int intValue) + { + return strong.Value >= intValue; + } + + if (other is long longValue) + { + return strong.Value >= longValue; + } + + if (other is uint uintValue) + { + return strong.Value >= uintValue; + } + + return false; + } +} diff --git a/src/StrongOf/StrongDecimal.cs b/src/StrongOf/StrongDecimal.cs index e6cd707..06d2f1e 100644 --- a/src/StrongOf/StrongDecimal.cs +++ b/src/StrongOf/StrongDecimal.cs @@ -7,7 +7,8 @@ namespace StrongOf; /// Represents a strongly typed decimal. /// /// The type of the strong decimal. -public abstract class StrongDecimal(decimal Value) : StrongOf(Value), IComparable, IStrongDecimal +public abstract partial class StrongDecimal(decimal Value) + : StrongOf(Value), IComparable, IStrongDecimal where TStrong : StrongDecimal { /// @@ -49,18 +50,19 @@ public int CompareTo(object? other) return Value.CompareTo(otherStrong.Value); } - throw new ArgumentException($"Object is not a {typeof(TStrong)}"); + throw new ArgumentException($"Object is not a {typeof(TStrong)}", nameof(other)); } /// - /// Tries to parse a decimal from a ReadOnlySpan of char and returns a value that indicates whether the operation succeeded. + /// Tries to parse the specified content into a object. /// - /// A ReadOnlySpan of char containing a decimal to convert. - /// When this method returns, contains the decimal value equivalent to the decimal contained in content, if the conversion succeeded, or null if the conversion failed. - /// True if content was converted successfully; otherwise, false. - public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong) + /// The content to parse. + /// When this method returns, contains the parsed value if the parsing succeeded, or null if the parsing failed. The parsing is case-sensitive. + /// An optional that supplies culture-specific formatting information. + /// true if the parsing was successful; otherwise, false. + public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong, IFormatProvider? formatProvider = null) { - if (decimal.TryParse(content, out decimal value)) + if (decimal.TryParse(content, formatProvider, out decimal value)) { strong = From(value); return true; @@ -109,45 +111,6 @@ public static bool TryParse(ReadOnlySpan content, NumberStyles numberStyle return false; } - // Operators - - /// - /// Determines whether two specified instances of StrongDecimal are equal. - /// - /// The first instance to compare. - /// The object to compare. - /// True if strong and value represent the same decimal; otherwise, false. - public static bool operator ==(StrongDecimal? strong, object? other) - { - if (strong is null) - { - return other is null; - } - - if (other is decimal decimalValue) - { - return strong.Value == decimalValue; - } - - if (other is StrongDecimal otherStrong) - { - return strong.Value == otherStrong.Value; - } - - return false; - } - - /// - /// Determines whether two specified instances of StrongDecimal are not equal. - /// - /// The first instance to compare. - /// The object to compare. - /// True if strong and other do not represent the same decimal; otherwise, false. - public static bool operator !=(StrongDecimal? strong, object? other) - { - return (strong == other) is false; - } - // Equals /// diff --git a/src/StrongOf/StrongGuid.Operators.cs b/src/StrongOf/StrongGuid.Operators.cs new file mode 100644 index 0000000..f9af57b --- /dev/null +++ b/src/StrongOf/StrongGuid.Operators.cs @@ -0,0 +1,180 @@ +namespace StrongOf; + +public abstract partial class StrongGuid +{ + /// + /// Determines whether two specified instances of StrongGuid are equal. + /// + /// The first instance to compare. + /// The object to compare. + /// True if strong and value represent the same Guid; otherwise, false. + public static bool operator ==(StrongGuid? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is Guid guidValue) + { + return strong.Value == guidValue; + } + + if (other is StrongGuid otherStrong) + { + return strong.Value == otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether two specified instances of StrongGuid are not equal. + /// + /// The first instance to compare. + /// The object to compare. + /// True if strong and other do not represent the same Guid; otherwise, false. + public static bool operator !=(StrongGuid? strong, object? other) + { + return (strong == other) is false; + } + + /// + /// Determines whether a object is greater than another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is greater than the object; + /// otherwise, . + /// + /// + /// If is , the method returns if is also , otherwise . + /// If is a , the method compares the value of with the value. + /// If is a object, the method compares the value of with the value of the other object. + /// + public static bool operator >(StrongGuid? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is Guid guidValue) + { + return strong.Value > guidValue; + } + + if (other is StrongGuid otherStrong) + { + return strong.Value > otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether a object is less than another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is less than the object; + /// otherwise, . + /// + /// + /// If is , the method returns if is also , otherwise . + /// If is a , the method compares the value of with the value. + /// If is a object, the method compares the value of with the value of the other object. + /// + + public static bool operator <(StrongGuid? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is Guid guidValue) + { + return strong.Value < guidValue; + } + + if (other is StrongGuid otherStrong) + { + return strong.Value < otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether a object is greater than or equal to another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is greater than or equal to the object; + /// otherwise, . + /// + /// + /// If is , the method returns if is also , otherwise . + /// If is a , the method compares the value of with the value. + /// If is a object, the method compares the value of with the value of the other object. + /// + + public static bool operator >=(StrongGuid? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is Guid guidValue) + { + return strong.Value >= guidValue; + } + + if (other is StrongGuid otherStrong) + { + return strong.Value >= otherStrong.Value; + } + + return false; + } + + /// + /// Determines whether a object is less than or equal to another object. + /// + /// The object to compare. + /// The object to compare with the object. + /// + /// if the object is less than or equal to the object; + /// otherwise, . + /// + /// + /// If is , the method returns if is also , otherwise . + /// If is a , the method compares the value of with the value. + /// If is a object, the method compares the value of with the value of the other object. + /// + + public static bool operator <=(StrongGuid? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is Guid guidValue) + { + return strong.Value <= guidValue; + } + + if (other is StrongGuid otherStrong) + { + return strong.Value <= otherStrong.Value; + } + + return false; + } +} diff --git a/src/StrongOf/StrongGuid.cs b/src/StrongOf/StrongGuid.cs index 4f2e67f..a153d97 100644 --- a/src/StrongOf/StrongGuid.cs +++ b/src/StrongOf/StrongGuid.cs @@ -6,7 +6,8 @@ namespace StrongOf; /// Represents a strong type of Guid. /// /// The type of the strong Guid. -public abstract class StrongGuid(Guid Value) : StrongOf(Value), IComparable, IStrongGuid +public abstract partial class StrongGuid(Guid Value) + : StrongOf(Value), IComparable, IStrongGuid where TStrong : StrongGuid { /// @@ -72,7 +73,7 @@ public int CompareTo(object? other) return Value.CompareTo(otherStrong.Value); } - throw new ArgumentException($"Object is not a {typeof(TStrong)}"); + throw new ArgumentException($"Object is not a {typeof(TStrong)}", nameof(other)); } /// @@ -136,46 +137,6 @@ public string ToStringWithDashes() public string ToStringWithoutDashes() => Value.ToString("N"); - // Operators - - /// - /// Determines whether two specified instances of StrongGuid are equal. - /// - /// The first instance to compare. - /// The object to compare. - /// True if strong and value represent the same Guid; otherwise, false. - public static bool operator ==(StrongGuid? strong, object? other) - { - if (strong is null) - { - return other is null; - } - - if (other is Guid guidValue) - { - return strong.Value == guidValue; - } - - if (other is StrongGuid otherStrong) - { - return strong.Value == otherStrong.Value; - } - - return false; - } - - /// - /// Determines whether two specified instances of StrongGuid are not equal. - /// - /// The first instance to compare. - /// The object to compare. - /// True if strong and other do not represent the same Guid; otherwise, false. - public static bool operator !=(StrongGuid? strong, object? other) - { - return (strong == other) is false; - } - - // Equals /// diff --git a/src/StrongOf/StrongInt32.cs b/src/StrongOf/StrongInt32.cs index 30925ea..ed85f63 100644 --- a/src/StrongOf/StrongInt32.cs +++ b/src/StrongOf/StrongInt32.cs @@ -6,7 +6,8 @@ namespace StrongOf; /// Represents a strong type of Int32. /// /// The type of the strong Int32. -public abstract partial class StrongInt32(int Value) : StrongOf(Value), IComparable, IStrongInt32 +public abstract partial class StrongInt32(int Value) + : StrongOf(Value), IComparable, IStrongInt32 where TStrong : StrongInt32 { /// @@ -53,18 +54,19 @@ public int CompareTo(object? other) return Value.CompareTo(otherStrong.Value); } - throw new ArgumentException($"Object is not a {typeof(TStrong)}"); + throw new ArgumentException($"Object is not a {typeof(TStrong)}", nameof(other)); } /// - /// Tries to parse an Int32 from a ReadOnlySpan of char and returns a value that indicates whether the operation succeeded. + /// Tries to parse the specified content into a object. /// - /// A ReadOnlySpan of char containing an Int32 to convert. - /// When this method returns, contains the Int32 value equivalent to the Int32 contained in content, if the conversion succeeded, or null if the conversion failed. - /// True if content was converted successfully; otherwise, false. - public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong) + /// The content to parse. + /// When this method returns, contains the parsed value if the parsing succeeded, or null if the parsing failed. The parsing is case-sensitive. + /// An optional that supplies culture-specific formatting information. + /// true if the parsing was successful; otherwise, false. + public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong, IFormatProvider? formatProvider = null) { - if (int.TryParse(content, out int value)) + if (int.TryParse(content, formatProvider, out int value)) { strong = From(value); return true; diff --git a/src/StrongOf/StrongInt64.cs b/src/StrongOf/StrongInt64.cs index f441af7..126ac87 100644 --- a/src/StrongOf/StrongInt64.cs +++ b/src/StrongOf/StrongInt64.cs @@ -6,7 +6,8 @@ namespace StrongOf; /// Represents a strong type of Int64. /// /// The type of the strong Int64. -public abstract partial class StrongInt64(long Value) : StrongOf(Value), IComparable, IStrongInt64 +public abstract partial class StrongInt64(long Value) + : StrongOf(Value), IComparable, IStrongInt64 where TStrong : StrongInt64 { @@ -54,18 +55,19 @@ public int CompareTo(object? other) return Value.CompareTo(otherStrong.Value); } - throw new ArgumentException($"Object is not a {typeof(TStrong)}"); + throw new ArgumentException($"Object is not a {typeof(TStrong)}", nameof(other)); } /// - /// Tries to parse an Int64 from a ReadOnlySpan of char and returns a value that indicates whether the operation succeeded. + /// Tries to parse the specified content into a object. /// - /// A ReadOnlySpan of char containing an Int64 to convert. - /// When this method returns, contains the Int64 value equivalent to the Int64 contained in content, if the conversion succeeded, or null if the conversion failed. - /// True if content was converted successfully; otherwise, false. - public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong) + /// The content to parse. + /// When this method returns, contains the parsed value if the parsing succeeded, or null if the parsing failed. The parsing is case-sensitive. + /// An optional that supplies culture-specific formatting information. + /// true if the parsing was successful; otherwise, false. + public static bool TryParse(ReadOnlySpan content, [NotNullWhen(true)] out TStrong? strong, IFormatProvider? formatProvider = null) { - if (long.TryParse(content, out long value)) + if (long.TryParse(content, formatProvider, out long value)) { strong = From(value); return true; diff --git a/src/StrongOf/StrongOf.cs b/src/StrongOf/StrongOf.cs index 7409cd3..55d2df7 100644 --- a/src/StrongOf/StrongOf.cs +++ b/src/StrongOf/StrongOf.cs @@ -3,12 +3,15 @@ namespace StrongOf; +#pragma warning disable MA0049 // Type name should not match containing namespace + /// /// Represents a strong type of TTarget. /// /// The type of the target. /// The type of the strong. -public abstract class StrongOf : IStrongOf +public abstract class StrongOf + : IStrongOf, IEquatable> where TStrong : StrongOf { private static readonly Func s_factoryWithParameter; @@ -51,7 +54,7 @@ public static TStrong From(TTarget value) /// The source to create the list of strong types from. /// A list of strong types. [return: NotNullIfNotNull(nameof(source))] - public static List? From(IEnumerable? source) + public static ICollection? From(IEnumerable? source) => source?.Select(e => From(e)).ToList(); @@ -132,8 +135,13 @@ public override bool Equals(object? other) /// /// The StrongOf to compare with the current StrongOf. /// True if the specified StrongOf is equal to the current StrongOf; otherwise, false. - public virtual bool Equals(StrongOf other) + public virtual bool Equals(StrongOf? other) { + if (other is null) + { + return false; + } + return s_comparer.Equals(Value, other.Value); } diff --git a/src/StrongOf/StrongOf.csproj b/src/StrongOf/StrongOf.csproj index f13cfe7..be03d13 100644 --- a/src/StrongOf/StrongOf.csproj +++ b/src/StrongOf/StrongOf.csproj @@ -1,5 +1,12 @@  + + + all + runtime; build; native; contentfiles; analyzers + + + true readme.md @@ -7,8 +14,8 @@ - - + + diff --git a/src/StrongOf/StrongString.Methods.cs b/src/StrongOf/StrongString.Methods.cs index 8b90930..fc8d524 100644 --- a/src/StrongOf/StrongString.Methods.cs +++ b/src/StrongOf/StrongString.Methods.cs @@ -1,7 +1,9 @@ using System.Diagnostics.CodeAnalysis; +using System.Globalization; namespace StrongOf; +#pragma warning disable MA0097 // A class that implements IComparable or IComparable should override comparison operators public abstract partial class StrongString { /// @@ -47,8 +49,8 @@ public bool Equals(T other, StringComparison stringComparison) where T : TStr /// Returns a copy of this string converted to lowercase. /// /// A string in lowercase. - public TStrong ToLower() - => From(Value.ToLower()); + public TStrong ToLower(CultureInfo? cultureInfo = null) + => From(Value.ToLower(cultureInfo)); /// /// Returns a copy of this string converted to lowercase, using the casing rules of the invariant culture. @@ -61,8 +63,8 @@ public TStrong ToLowerInvariant() /// Returns a copy of this string converted to uppercase. /// /// A string in uppercase. - public TStrong ToUpper() - => From(Value.ToUpper()); + public TStrong ToUpper(CultureInfo? cultureInfo = null) + => From(Value.ToUpper(cultureInfo)); /// /// Returns a copy of this string converted to uppercase, using the casing rules of the invariant culture. @@ -91,7 +93,7 @@ public char FirstCharUpperInvariant() /// A set of characters that are allowed in the string. /// When this method returns, contains the collection of invalid characters, if any. This parameter is passed uninitialized. /// true if the current string contains any characters not present in the allowedChars parameter; otherwise, false. - public bool ContainsInvalidChars(HashSet allowedChars, [NotNullWhen(true)] out ICollection? invalidCharacters) + public bool ContainsInvalidChars(ICollection allowedChars, [NotNullWhen(true)] out ICollection? invalidCharacters) { HashSet? invalidChars = null; diff --git a/src/StrongOf/StrongString.Operators.cs b/src/StrongOf/StrongString.Operators.cs new file mode 100644 index 0000000..430af09 --- /dev/null +++ b/src/StrongOf/StrongString.Operators.cs @@ -0,0 +1,43 @@ +namespace StrongOf; + +#pragma warning disable MA0097 // A class that implements IComparable or IComparable should override comparison operators + +public abstract partial class StrongString +{ + /// + /// Determines whether the specified strong string and string are equal. + /// + /// The strong string to compare. + /// The object to compare. + /// True if the specified strong string and string are equal; otherwise, false. + public static bool operator ==(StrongString? strong, object? other) + { + if (strong is null) + { + return other is null; + } + + if (other is string stringValue) + { + return strong.Value.Equals(stringValue, StringComparison.Ordinal); + } + + if (other is StrongString otherStrong) + { + return string.Equals(strong.Value, otherStrong.Value, StringComparison.Ordinal); + } + + return false; + } + + /// + /// Determines whether the specified strong string and string are not equal. + /// + /// The strong string to compare. + /// The object to compare. + /// True if the specified strong string and string are not equal; otherwise, false. + public static bool operator !=(StrongString? strong, object? other) + { + return (strong == other) is false; + } +} diff --git a/src/StrongOf/StrongString.Properties.cs b/src/StrongOf/StrongString.Properties.cs index 7472064..732d805 100644 --- a/src/StrongOf/StrongString.Properties.cs +++ b/src/StrongOf/StrongString.Properties.cs @@ -1,5 +1,7 @@ namespace StrongOf; +#pragma warning disable MA0097 // A class that implements IComparable or IComparable should override comparison operators + public abstract partial class StrongString { /// diff --git a/src/StrongOf/StrongString.cs b/src/StrongOf/StrongString.cs index d8156be..3df3617 100644 --- a/src/StrongOf/StrongString.cs +++ b/src/StrongOf/StrongString.cs @@ -2,11 +2,14 @@ namespace StrongOf; +#pragma warning disable MA0097 // A class that implements IComparable or IComparable should override comparison operators + /// /// Represents a strong type of string. /// /// The type of the strong string. -public abstract partial class StrongString(string Value) : StrongOf(Value), IComparable, IStrongString +public abstract partial class StrongString(string Value) + : StrongOf(Value), IComparable, IStrongString where TStrong : StrongString { /// @@ -56,7 +59,7 @@ public int CompareTo(object? other) return Value.CompareTo(otherStrong.Value); } - throw new ArgumentException($"Object is not a {typeof(TStrong)}"); + throw new ArgumentException($"Object is not a {typeof(TStrong)}", nameof(other)); } /// @@ -73,45 +76,6 @@ public static TStrong Empty() public bool IsEmpty() => Value == ""; - // Operators - - /// - /// Determines whether the specified strong string and string are equal. - /// - /// The strong string to compare. - /// The object to compare. - /// True if the specified strong string and string are equal; otherwise, false. - public static bool operator ==(StrongString? strong, object? other) - { - if (strong is null) - { - return other is null; - } - - if (other is string stringValue) - { - return strong.Value == stringValue; - } - - if (other is StrongString otherStrong) - { - return strong.Value == otherStrong.Value; - } - - return false; - } - - /// - /// Determines whether the specified strong string and string are not equal. - /// - /// The strong string to compare. - /// The object to compare. - /// True if the specified strong string and string are not equal; otherwise, false. - public static bool operator !=(StrongString? strong, object? other) - { - return (strong == other) is false; - } - // Equals /// diff --git a/tests/StrongOf.Json.UnitTests/StrongDateTimeJsonConverterTests.cs b/tests/StrongOf.Json.UnitTests/StrongDateTimeJsonConverterTests.cs index 8379dea..fa25c40 100644 --- a/tests/StrongOf.Json.UnitTests/StrongDateTimeJsonConverterTests.cs +++ b/tests/StrongOf.Json.UnitTests/StrongDateTimeJsonConverterTests.cs @@ -25,7 +25,7 @@ public void Read_ValidJson_ReturnsStrongDateTime() // Act TestDateTimeOffsetOf? result = _converter.Read(ref reader, typeof(TestDateTimeOffsetOf), _options); - DateTime expected = DateTime.ParseExact("2023-12-17T14:24:22.6412808+00:00", "o", CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.AdjustToUniversal); + DateTime expected = DateTime.ParseExact("2023-12-17T14:24:22.6412808+00:00", "o", CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.None); // Assert Assert.NotNull(result); diff --git a/tests/StrongOf.UnitTests/StrongChar_As_Tests.cs b/tests/StrongOf.UnitTests/StrongChar_As_Tests.cs index c3427dc..4e92ab7 100644 --- a/tests/StrongOf.UnitTests/StrongChar_As_Tests.cs +++ b/tests/StrongOf.UnitTests/StrongChar_As_Tests.cs @@ -15,4 +15,84 @@ public void AsChar_ReturnsCorrectResult() // Assert Assert.Equal(strong.Value, strong.AsChar()); } + + + [Fact] + public void FromNullable_WithValue_ReturnsNonNull() + { + // Arrange + char value = 'A'; + + // Act + TestCharOf result = TestCharOf.FromNullable(value); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public void FromNullable_WithNull_ReturnsNull() + { + // Arrange + char? value = null; + + // Act + TestCharOf? result = TestCharOf.FromNullable(value); + + // Assert + Assert.Null(result); + } + + [Fact] + public void FromNullable_WithNotNull_ReturnsCorrectValue() + { + // Arrange + char value = 'A'; + + // Act + TestCharOf result = TestCharOf.FromNullable(value); + + // Assert + Assert.Equal(value, result.Value); + } + + [Fact] + public void CompareTo_WithSameType_ReturnsCorrectComparison() + { + // Arrange + TestCharOf instance1 = new('A'); + TestCharOf instance2 = new('A'); + + // Act + int result = instance1.CompareTo(instance2); + + // Assert + Assert.Equal(0, result); + } + + [Fact] + public void CompareTo_WithDifferentType_ThrowsArgumentException() + { + // Arrange + TestCharOf instance = new('A'); + object other = new (); + + // Act & Assert + Assert.Throws(() => instance.CompareTo(other)); + } + + [Fact] + public void GetHashCode_ReturnsConsistentHashCodeForEqualObjects() + { + // Arrange + TestCharOf instance1 = new('A'); + TestCharOf instance2 = new('A'); + + // Act + int hashCode1 = instance1.GetHashCode(); + int hashCode2 = instance2.GetHashCode(); + + // Assert + Assert.Equal(hashCode1, hashCode2); + } } diff --git a/tests/StrongOf.UnitTests/StrongChar_Operators_Tests.cs b/tests/StrongOf.UnitTests/StrongChar_Operators_Tests.cs new file mode 100644 index 0000000..a3c2106 --- /dev/null +++ b/tests/StrongOf.UnitTests/StrongChar_Operators_Tests.cs @@ -0,0 +1,272 @@ +using Xunit; + +namespace StrongOf.UnitTests; + +public class StrongChar_Operators_Tests +{ + private sealed class TestCharOf(char Value) : StrongChar(Value) { } + + [Fact] + public void EqualityOperator_WithNullStrongAndNullObject_ReturnsTrue() + { + // Arrange & Act + bool result = (TestCharOf?)null == null; + + // Assert + Assert.True(result); + } + + [Fact] + public void EqualityOperator_WithNonNullStrongAndNullObject_ReturnsFalse() + { + // Arrange + TestCharOf strong = new('A'); + + // Act + bool result = strong == null; + + // Assert + Assert.False(result); + } + + [Fact] + public void EqualityOperator_WithCharAndStrongChar_ReturnsCorrectResult() + { + // Arrange + TestCharOf strong = new('A'); + char charValue = 'A'; + char differentCharValue = 'B'; + + // Act + bool result1 = strong == charValue; + bool result2 = strong == new TestCharOf(differentCharValue); + + // Assert + Assert.True(result1); + Assert.False(result2); + } + + [Fact] + public void LessThanOperator_WithNonNullStrongCharAndNonNullOtherChar_ReturnsCorrectResult() + { + // Arrange + TestCharOf strong = new('a'); + object other = 'b'; + + // Act + bool result = strong < other; + + // Assert + Assert.True(result); + } + + [Fact] + public void LessThanOperator_WithNullStrongChar_ReturnsFalse() + { + // Arrange + TestCharOf? strong = null; + object? other = 'b'; + + // Act + bool result = strong < other; + + // Assert + Assert.False(result); + } + + [Fact] + public void LessThanOperator_WithNonNullStrongCharAndNullOther_ReturnsFalse() + { + // Arrange + TestCharOf strong = new('a'); + object? other = null; + + // Act + bool result = strong < other; + + // Assert + Assert.False(result); + } + + [Fact] + public void LessThanOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestCharOf? strong = null; + object? other = null; + + // Act + bool result = strong < other; + + // Assert + Assert.True(result); + } + + [Fact] + public void GreaterThanOperator_WithNonNullStrongCharAndNonNullOtherChar_ReturnsCorrectResult() + { + // Arrange + TestCharOf strong = new('b'); + object other = 'a'; + + // Act + bool result = strong > other; + + // Assert + Assert.True(result); + } + + [Fact] + public void GreaterThanOperator_WithNullStrongChar_ReturnsFalse() + { + // Arrange + TestCharOf? strong = null; + object other = 'b'; + + // Act + bool result = strong > other; + + // Assert + Assert.False(result); + } + + [Fact] + public void GreaterThanOperator_WithNonNullStrongCharAndNullOther_ReturnsFalse() + { + // Arrange + TestCharOf strong = new('b'); + object? other = null; + + // Act + bool result = strong > other; + + // Assert + Assert.False(result); + } + + [Fact] + public void GreaterThanOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestCharOf? strong = null; + object? other = null; + + // Act + bool result = strong > other; + + // Assert + Assert.True(result); + } + + [Fact] + public void LessThanOrEqualOperator_WithNonNullStrongCharAndNonNullOtherChar_ReturnsCorrectResult() + { + // Arrange + TestCharOf strong = new('a'); + object other = 'b'; + + // Act + bool result = strong <= other; + + // Assert + Assert.True(result); + } + + [Fact] + public void LessThanOrEqualOperator_WithNullStrongChar_ReturnsFalse() + { + // Arrange + TestCharOf? strong = null; + object other = 'b'; + + // Act + bool result = strong <= other; + + // Assert + Assert.False(result); + } + + [Fact] + public void LessThanOrEqualOperator_WithNonNullStrongCharAndNullOther_ReturnsFalse() + { + // Arrange + TestCharOf strong = new('a'); + object? other = null; + + // Act + bool result = strong <= other; + + // Assert + Assert.False(result); + } + + [Fact] + public void LessThanOrEqualOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestCharOf? strong = null; + object? other = null; + + // Act + bool result = strong <= other; + + // Assert + Assert.True(result); + } + + [Fact] + public void GreaterThanOrEqualOperator_WithNonNullStrongCharAndNonNullOtherChar_ReturnsCorrectResult() + { + // Arrange + TestCharOf? strong = new('b'); + object other = 'a'; + + // Act + bool result = strong >= other; + + // Assert + Assert.True(result); + } + + [Fact] + public void GreaterThanOrEqualOperator_WithNullStrongChar_ReturnsFalse() + { + // Arrange + TestCharOf? strong = null; + object other = 'b'; + + // Act + bool result = strong >= other; + + // Assert + Assert.False(result); + } + + [Fact] + public void GreaterThanOrEqualOperator_WithNonNullStrongCharAndNullOther_ReturnsFalse() + { + // Arrange + TestCharOf strong = new('b'); + object? other = null; + + // Act + bool result = strong >= other; + + // Assert + Assert.False(result); + } + + [Fact] + public void GreaterThanOrEqualOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestCharOf? strong = null; + object? other = null; + + // Act + bool result = strong >= other; + + // Assert + Assert.True(result); + } +} diff --git a/tests/StrongOf.UnitTests/StrongDateTime_As_Tests.cs b/tests/StrongOf.UnitTests/StrongDateTime_As_Tests.cs index fd72cef..7cafb73 100644 --- a/tests/StrongOf.UnitTests/StrongDateTime_As_Tests.cs +++ b/tests/StrongOf.UnitTests/StrongDateTime_As_Tests.cs @@ -1,4 +1,5 @@ -using Xunit; +using System.Globalization; +using Xunit; namespace StrongOf.Tests; @@ -6,6 +7,45 @@ public class StrongDateTime_As_Tests { private sealed class TestDateTimeOf(DateTime Value) : StrongDateTime(Value) { } + [Fact] + public void FromNullable_WithValue_ReturnsNonNull() + { + // Arrange + DateTime value = DateTime.UtcNow; + + // Act + TestDateTimeOf result = TestDateTimeOf.FromNullable(value); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public void FromNullable_WithNull_ReturnsNull() + { + // Arrange + DateTime? value = null; + + // Act + TestDateTimeOf? result = TestDateTimeOf.FromNullable(value); + + // Assert + Assert.Null(result); + } + + [Fact] + public void FromNullable_WithNotNull_ReturnsCorrectValue() + { + // Arrange + DateTime value = DateTime.UtcNow; + + // Act + TestDateTimeOf result = TestDateTimeOf.FromNullable(value); + + // Assert + Assert.Equal(value, result.Value); + } + [Fact] public void AsDateTime_ReturnsCorrectResult() { @@ -45,4 +85,66 @@ public void AsTime_ReturnsCorrectResult() // Assert Assert.Equal(TimeOnly.FromDateTime(strong.Value), strong.AsTime()); } + + [Fact] + public void TryParseIso8601_WithValidInput_ReturnsTrueAndNonNull() + { + // Arrange + ReadOnlySpan content = "2022-01-02T00:00:00+00:00".AsSpan(); + TestDateTimeOf? expected = TestDateTimeOf.From(DateTime.Parse("2022-01-02T00:00:00+00:00")); + + // Act + bool result = TestDateTimeOf.TryParseIso8601(content, out TestDateTimeOf? strong); + + // Assert + Assert.True(result); + Assert.NotNull(strong); + Assert.Equal(expected, strong); + } + + [Fact] + public void TryParseIso8601_WithInvalidInput_ReturnsFalseAndNull() + { + // Arrange + ReadOnlySpan content = "invalid-date".AsSpan(); + + // Act + bool result = TestDateTimeOf.TryParseIso8601(content, out TestDateTimeOf? strong); + + // Assert + Assert.False(result); + Assert.Null(strong); + } + + [Fact] + public void TryParseExact_WithValidInput_ReturnsTrueAndNonNull() + { + // Arrange + ReadOnlySpan content = "2024-04-26T12:00:00".AsSpan(); + string format = "yyyy-MM-ddTHH:mm:ss"; + TestDateTimeOf? expected = TestDateTimeOf.From(DateTime.ParseExact("2024-04-26T12:00:00", format, CultureInfo.InvariantCulture)); + + // Act + bool result = TestDateTimeOf.TryParseExact(content, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out TestDateTimeOf? strong); + + // Assert + Assert.True(result); + Assert.NotNull(strong); + Assert.Equal(expected, strong); + } + + [Fact] + public void TryParseExact_WithInvalidInput_ReturnsFalseAndNull() + { + // Arrange + ReadOnlySpan content = "invalid-date".AsSpan(); + string format = "yyyy-MM-ddTHH:mm:ss"; + + // Act + bool result = TestDateTimeOf.TryParseExact(content, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out TestDateTimeOf? strong); + + // Assert + Assert.False(result); + Assert.Null(strong); + } } diff --git a/tests/StrongOf.UnitTests/StrongDateTime_Tests.cs b/tests/StrongOf.UnitTests/StrongDateTime_Tests.cs index 24a69ee..9525436 100644 --- a/tests/StrongOf.UnitTests/StrongDateTime_Tests.cs +++ b/tests/StrongOf.UnitTests/StrongDateTime_Tests.cs @@ -42,6 +42,34 @@ public void TryParse_ShouldReturnFalseForInvalidDateTime() Assert.Null(strong); } + [Fact] + public void ToString_DelegatesCallToUnderlyingValue_WithDefaultProvider() + { + // Arrange + TestDateTimeOf strong = new(new DateTime(2000, 1, 1)); + string expected = strong.Value.ToString(); + + // Act + string result = strong.ToString(); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void ToString_DelegatesCallToUnderlyingValue_WithCustomProvider() + { + // Arrange + TestDateTimeOf strong = new(new DateTime(2000, 1, 1)); + string expected = strong.Value.ToString("o", CultureInfo.InvariantCulture); + + // Act + string result = strong.ToString("o", CultureInfo.InvariantCulture); + + // Assert + Assert.Equal(expected, result); + } + [Fact] public void ToString_Iso8601() { diff --git a/tests/StrongOf.UnitTests/StrongDecimal_Operators_Tests.cs b/tests/StrongOf.UnitTests/StrongDecimal_Operators_Tests.cs new file mode 100644 index 0000000..99b35c5 --- /dev/null +++ b/tests/StrongOf.UnitTests/StrongDecimal_Operators_Tests.cs @@ -0,0 +1,231 @@ +using Xunit; + +namespace StrongOf.UnitTests; + +public class StrongDecimal_Operators_Tests +{ + private sealed class TestDecimalOf(decimal Value) : StrongDecimal(Value) { } + + [Fact] + public void LessThanOperator_WithNonNullStrongDecimalAndNonNullDecimalOther_ReturnsCorrectResult() + { + // Arrange + TestDecimalOf strong = new(10.5m); + object other = 15.7m; + + // Act + bool result = strong < other; + + // Assert + Assert.True(result); + } + + [Fact] + public void LessThanOperator_WithNullStrongDecimal_ReturnsFalse() + { + // Arrange + TestDecimalOf? strong = null; + object other = 15.7m; + + // Act + bool result = strong < other; + + // Assert + Assert.False(result); + } + + [Fact] + public void LessThanOperator_WithNonNullStrongDecimalAndNullOther_ReturnsFalse() + { + // Arrange + TestDecimalOf strong = new(10.5m); + object? other = null; + + // Act + bool result = strong < other; + + // Assert + Assert.False(result); + } + + [Fact] + public void LessThanOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestDecimalOf? strong = null; + object? other = null; + + // Act + bool result = strong < other; + + // Assert + Assert.True(result); + } + + [Fact] + public void GreaterThanOperator_WithNonNullStrongDecimalAndNonNullDecimalOther_ReturnsCorrectResult() + { + // Arrange + TestDecimalOf strong = new(15.7m); + object other = 10.5m; + + // Act + bool result = strong > other; + + // Assert + Assert.True(result); + } + + [Fact] + public void GreaterThanOperator_WithNullStrongDecimal_ReturnsFalse() + { + // Arrange + TestDecimalOf? strong = null; + object other = 15.7m; + + // Act + bool result = strong > other; + + // Assert + Assert.False(result); + } + + [Fact] + public void GreaterThanOperator_WithNonNullStrongDecimalAndNullOther_ReturnsFalse() + { + // Arrange + TestDecimalOf strong = new(15.7m); + object? other = null; + + // Act + bool result = strong > other; + + // Assert + Assert.False(result); + } + + [Fact] + public void GreaterThanOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestDecimalOf? strong = null; + object? other = null; + + // Act + bool result = strong > other; + + // Assert + Assert.True(result); + } + + [Fact] + public void LessThanOrEqualOperator_WithNonNullStrongDecimalAndNonNullDecimalOther_ReturnsCorrectResult() + { + // Arrange + TestDecimalOf strong = new(10.5m); + object other = 15.7m; + + // Act + bool result = strong <= other; + + // Assert + Assert.True(result); + } + + [Fact] + public void LessThanOrEqualOperator_WithNullStrongDecimal_ReturnsFalse() + { + // Arrange + TestDecimalOf? strong = null; + object other = 15.7m; + + // Act + bool result = strong <= other; + + // Assert + Assert.False(result); + } + + [Fact] + public void LessThanOrEqualOperator_WithNonNullStrongDecimalAndNullOther_ReturnsFalse() + { + // Arrange + TestDecimalOf strong = new(10.5m); + object? other = null; + + // Act + bool result = strong <= other; + + // Assert + Assert.False(result); + } + + [Fact] + public void LessThanOrEqualOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestDecimalOf? strong = null; + object? other = null; + + // Act + bool result = strong <= other; + + // Assert + Assert.True(result); + } + [Fact] + public void GreaterThanOrEqualOperator_WithNonNullStrongDecimalAndNonNullDecimalOther_ReturnsCorrectResult() + { + // Arrange + TestDecimalOf strong = new(15.7m); + object other = 10.5m; + + // Act + bool result = strong >= other; + + // Assert + Assert.True(result); + } + + [Fact] + public void GreaterThanOrEqualOperator_WithNullStrongDecimal_ReturnsFalse() + { + // Arrange + TestDecimalOf? strong = null; + object other = 15.7m; + + // Act + bool result = strong >= other; + + // Assert + Assert.False(result); + } + + [Fact] + public void GreaterThanOrEqualOperator_WithNonNullStrongDecimalAndNullOther_ReturnsFalse() + { + // Arrange + TestDecimalOf strong = new(15.7m); + object? other = null; + + // Act + bool result = strong >= other; + + // Assert + Assert.False(result); + } + + [Fact] + public void GreaterThanOrEqualOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestDecimalOf? strong = null; + object? other = null; + + // Act + bool result = strong >= other; + + // Assert + Assert.True(result); + } +} diff --git a/tests/StrongOf.UnitTests/StrongString_Operators_Tests.cs b/tests/StrongOf.UnitTests/StrongString_Operators_Tests.cs new file mode 100644 index 0000000..ccce6b3 --- /dev/null +++ b/tests/StrongOf.UnitTests/StrongString_Operators_Tests.cs @@ -0,0 +1,78 @@ +using Xunit; + +namespace StrongOf.UnitTests; + +public class StrongString_Operators_Tests +{ + private sealed class TestStringOf(string Value) : StrongString(Value) { } + + [Fact] + public void EqualityOperator_WithNonNullStrongStringAndNonNullStringOther_ReturnsCorrectResult() + { + // Arrange + TestStringOf strong = new("hello"); + object other = "hello"; + + // Act + bool result = strong == other; + + // Assert + Assert.True(result); + } + + [Fact] + public void EqualityOperator_WithNonNullStrongStringAndNonNullStringOther_ReturnsIncorrectResult() + { + // Arrange + TestStringOf strong = new("hello"); + object other = "world"; + + // Act + bool result = strong == other; + + // Assert + Assert.False(result); + } + + [Fact] + public void EqualityOperator_WithNullStrongStringAndNonNullOther_ReturnsFalse() + { + // Arrange + TestStringOf? strong = null; + object other = "world"; + + // Act + bool result = strong == other; + + // Assert + Assert.False(result); + } + + [Fact] + public void EqualityOperator_WithNonNullStrongStringAndNullOther_ReturnsFalse() + { + // Arrange + TestStringOf strong = new("hello"); + object? other = null; + + // Act + bool result = strong == other; + + // Assert + Assert.False(result); + } + + [Fact] + public void EqualityOperator_WithBothNull_ReturnsTrue() + { + // Arrange + TestStringOf? strong = null; + object? other = null; + + // Act + bool result = strong == other; + + // Assert + Assert.True(result); + } +} From 6adcfd9d9ab565b9a27ce2d9a17126400975d37a Mon Sep 17 00:00:00 2001 From: BEN ABT Date: Sat, 27 Apr 2024 21:15:08 +0200 Subject: [PATCH 2/6] add .net 9 --- global.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/global.json b/global.json index be277f9..d1c03d7 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100-preview.3" + "version": "9.0.100-preview.3", + "allowPrerelease": true } } - From e3f466f0fdc740ed93039ea381f8d76b9eb086e7 Mon Sep 17 00:00:00 2001 From: BEN ABT Date: Sat, 27 Apr 2024 21:21:55 +0200 Subject: [PATCH 3/6] add preview flag --- .github/workflows/ci.yml | 1 + global.json | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7653d0c..9ca6f0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ jobs: uses: actions/setup-dotnet@v3 with: global-json-file: ./global.json + dotnet-quality: 'preview' - name: Versioning uses: dotnet/nbgv@master diff --git a/global.json b/global.json index d1c03d7..207cf6f 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,5 @@ { "sdk": { - "version": "9.0.100-preview.3", - "allowPrerelease": true + "version": "9.0.100-preview.3" } } From 64855fc729dc0de914a0f7bd1d4e6ff467a96210 Mon Sep 17 00:00:00 2001 From: BEN ABT Date: Sat, 27 Apr 2024 21:23:51 +0200 Subject: [PATCH 4/6] add yml fix --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ca6f0c..e7e0ca8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Cancel previous builds in PR - uses: styfle/cancel-workflow-action@0.9.1 + uses: styfle/cancel-workflow-action@0.12.1 - name: 'Checkoud Code' uses: actions/checkout@v4 @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - name: 'Install .NET SDK' - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: global-json-file: ./global.json dotnet-quality: 'preview' From 21ac3389b44f5955595b0acc391b5e70cec11076 Mon Sep 17 00:00:00 2001 From: BEN ABT Date: Sat, 27 Apr 2024 21:25:55 +0200 Subject: [PATCH 5/6] add fix --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7e0ca8..6b1f937 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,8 @@ jobs: - name: 'Install .NET SDK' uses: actions/setup-dotnet@v4 with: - global-json-file: ./global.json + #global-json-file: ./global.json + dotnet-version: '9.x' dotnet-quality: 'preview' - name: Versioning From ccda21bba7c1aa2fc411d0e1f84ff5c998049c68 Mon Sep 17 00:00:00 2001 From: BEN ABT Date: Sat, 27 Apr 2024 21:27:22 +0200 Subject: [PATCH 6/6] fix typo, fix v --- .github/workflows/ci.yml | 8 ++++---- global.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b1f937..7408378 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: Cancel previous builds in PR uses: styfle/cancel-workflow-action@0.12.1 - - name: 'Checkoud Code' + - name: 'Checkout Code' uses: actions/checkout@v4 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. @@ -28,9 +28,9 @@ jobs: - name: 'Install .NET SDK' uses: actions/setup-dotnet@v4 with: - #global-json-file: ./global.json - dotnet-version: '9.x' - dotnet-quality: 'preview' + global-json-file: ./global.json + #dotnet-version: '9.x' + #dotnet-quality: 'preview' - name: Versioning uses: dotnet/nbgv@master diff --git a/global.json b/global.json index 207cf6f..a28e9c5 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "9.0.100-preview.3" + "version": "9.0.100-preview.3.24204.13" } }