diff --git a/.editorconfig b/.editorconfig index 7a7d7e5..43dae9b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -464,11 +464,13 @@ dotnet_diagnostic.MA0048.severity = error # https://github.com/atc-net dotnet_diagnostic.CA1014.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1014.md dotnet_diagnostic.CA1068.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1068.md dotnet_diagnostic.CA1305.severity = error +dotnet_diagnostic.CA1308.severity = suggestion # Normalize strings to uppercase dotnet_diagnostic.CA1510.severity = suggestion # Use ArgumentNullException throw helper dotnet_diagnostic.CA1511.severity = suggestion # Use ArgumentException throw helper dotnet_diagnostic.CA1512.severity = suggestion # Use ArgumentOutOfRangeException throw helper dotnet_diagnostic.CA1513.severity = suggestion # Use ObjectDisposedException throw helper dotnet_diagnostic.CA1514.severity = error # Avoid redundant length argument +dotnet_diagnostic.CA1515.severity = suggestion # Because an application's API isn't typically referenced from outside the assembly, types can be made internal (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515) dotnet_diagnostic.CA1707.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1707.md dotnet_diagnostic.CA1812.severity = none dotnet_diagnostic.CA1822.severity = suggestion @@ -504,7 +506,16 @@ dotnet_diagnostic.CA2259.severity = error # Ensure ThreadStatic is onl dotnet_diagnostic.CA2260.severity = error # Implement generic math interfaces correctly dotnet_diagnostic.CA2261.severity = error # Do not use ConfigureAwaitOptions.SuppressThrowing with Task dotnet_diagnostic.IDE0005.severity = warning # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/IDE0005.md +dotnet_diagnostic.IDE0010.severity = suggestion # Populate switch +dotnet_diagnostic.IDE0028.severity = suggestion # Collection initialization can be simplified +dotnet_diagnostic.IDE0021.severity = suggestion # Use expression body for constructor +dotnet_diagnostic.IDE0055.severity = none # Fix formatting dotnet_diagnostic.IDE0058.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/IDE0058.md +dotnet_diagnostic.IDE0061.severity = suggestion # Use expression body for local function +dotnet_diagnostic.IDE0130.severity = suggestion # Namespace does not match folder structure +dotnet_diagnostic.IDE0290.severity = none # Use primary constructor +dotnet_diagnostic.IDE0301.severity = suggestion # Use collection expression for empty +dotnet_diagnostic.IDE0305.severity = suggestion # Collection initialization can be simplified # Microsoft - Compiler Errors @@ -541,6 +552,7 @@ dotnet_diagnostic.SA1649.severity = error # https://github.com/atc-net # https://rules.sonarsource.com/csharp dotnet_diagnostic.S1135.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/SonarAnalyzerCSharp/S1135.md dotnet_diagnostic.S2629.severity = none # Don't use string interpolation in logging message templates. +dotnet_diagnostic.S3358.severity = none # Extract this nested ternary operation into an independent statement. dotnet_diagnostic.S6602.severity = none # "Find" method should be used instead of the "FirstOrDefault" dotnet_diagnostic.S6603.severity = none # The collection-specific "TrueForAll" method should be used instead of the "All" dotnet_diagnostic.S6605.severity = none # Collection-specific "Exists" method should be used instead of the "Any" diff --git a/Directory.Build.props b/Directory.Build.props index 7b1fdac..b70a3eb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -43,7 +43,7 @@ - + diff --git a/src/Atc.Rest.Client/Atc.Rest.Client.csproj b/src/Atc.Rest.Client/Atc.Rest.Client.csproj index 840aa20..991faac 100644 --- a/src/Atc.Rest.Client/Atc.Rest.Client.csproj +++ b/src/Atc.Rest.Client/Atc.Rest.Client.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Atc.Rest.Client/Builder/MessageRequestBuilder.cs b/src/Atc.Rest.Client/Builder/MessageRequestBuilder.cs index 725d8a1..3e0012a 100644 --- a/src/Atc.Rest.Client/Builder/MessageRequestBuilder.cs +++ b/src/Atc.Rest.Client/Builder/MessageRequestBuilder.cs @@ -148,7 +148,7 @@ public IMessageRequestBuilder WithQueryParameter( { var objects = ((IEnumerable)value).Cast().ToArray(); var sb = new StringBuilder(); - for (int i = 0; i < objects.Length; i++) + for (var i = 0; i < objects.Length; i++) { sb.Append(i == 0 ? Uri.EscapeDataString(objects[i].ToString()) @@ -157,6 +157,23 @@ public IMessageRequestBuilder WithQueryParameter( queryMapper["#" + name] = sb.ToString(); } + else if (valueType.IsEnum) + { + queryMapper[name] = valueType + .GetTypeInfo() + .DeclaredMembers + .FirstOrDefault(x => x.Name == value.ToString()) + ?.GetCustomAttribute(inherit: false) + ?.Value ?? value.ToString(); + } + else if (value is DateTime dt) + { + queryMapper[name] = dt.ToString("o"); + } + else if (value is DateTimeOffset dto) + { + queryMapper[name] = dto.ToString("o"); + } else { queryMapper[name] = value.ToString(); @@ -188,9 +205,7 @@ private Uri BuildRequestUri() private static string BuildQueryKeyEqualValue( KeyValuePair pair) - { - return pair.Key.StartsWith("#", StringComparison.Ordinal) + => pair.Key.StartsWith("#", StringComparison.Ordinal) ? $"{pair.Key.Replace("#", string.Empty)}={pair.Value}" : $"{pair.Key}={Uri.EscapeDataString(pair.Value)}"; - } } \ No newline at end of file diff --git a/src/Atc.Rest.Client/GlobalUsings.cs b/src/Atc.Rest.Client/GlobalUsings.cs index 1be85be..9965ae2 100644 --- a/src/Atc.Rest.Client/GlobalUsings.cs +++ b/src/Atc.Rest.Client/GlobalUsings.cs @@ -3,6 +3,8 @@ global using System.Diagnostics.CodeAnalysis; global using System.Net; global using System.Net.Http.Headers; +global using System.Reflection; +global using System.Runtime.Serialization; global using System.Text; global using System.Text.Json; global using System.Text.Json.Serialization; diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bce62e1..db76856 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -54,7 +54,7 @@ - + diff --git a/test/.editorconfig b/test/.editorconfig index 92667f9..793a84e 100644 --- a/test/.editorconfig +++ b/test/.editorconfig @@ -22,7 +22,7 @@ # https://www.meziantou.net/enforcing-asynchronous-code-good-practices-using-a-roslyn-analyzer.htm dotnet_diagnostic.MA0004.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0004.md dotnet_diagnostic.MA0016.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0016.md - +dotnet_diagnostic.MA0051.severity = none # Method Length # Microsoft - Code Analysis # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ diff --git a/test/Atc.Rest.Client.Tests/Atc.Rest.Client.Tests.csproj b/test/Atc.Rest.Client.Tests/Atc.Rest.Client.Tests.csproj index ecc6cdf..b597631 100644 --- a/test/Atc.Rest.Client.Tests/Atc.Rest.Client.Tests.csproj +++ b/test/Atc.Rest.Client.Tests/Atc.Rest.Client.Tests.csproj @@ -7,9 +7,10 @@ - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Atc.Rest.Client.Tests/Builder/MessageRequestBuilderTests.cs b/test/Atc.Rest.Client.Tests/Builder/MessageRequestBuilderTests.cs index 53cad1c..c85206d 100644 --- a/test/Atc.Rest.Client.Tests/Builder/MessageRequestBuilderTests.cs +++ b/test/Atc.Rest.Client.Tests/Builder/MessageRequestBuilderTests.cs @@ -132,6 +132,59 @@ public void Should_Replace_Query_Parameters( .Be($"/api?foo={fooValue}&bar={barValue}"); } + [Theory] + [InlineAutoNSubstituteData("/api")] + public void Should_Replace_Query_Parameters_With_Enum_Member_Value(string template) + { + const OperatorRole operatorRole = OperatorRole.Owner; + var sut = CreateSut(template); + + sut.WithQueryParameter("operatorRole", operatorRole); + var message = sut.Build(HttpMethod.Post); + + message! + .RequestUri! + .ToString() + .Should() + .Be("/api?operatorRole=owner"); + } + + [Theory] + [InlineAutoNSubstituteData("/api")] + public void Should_Replace_Query_Parameters_With_DateTime(string template) + { + var from = DateTime.UtcNow; + + var sut = CreateSut(template); + + sut.WithQueryParameter("from", from); + var message = sut.Build(HttpMethod.Post); + + message! + .RequestUri! + .ToString() + .Should() + .Be($"/api?from={Uri.EscapeDataString(from.ToString("o"))}"); + } + + [Theory] + [InlineAutoNSubstituteData("/api")] + public void Should_Replace_Query_Parameters_With_DateTimeOffset(string template) + { + var from = DateTimeOffset.UtcNow; + + var sut = CreateSut(template); + + sut.WithQueryParameter("from", from); + var message = sut.Build(HttpMethod.Post); + + message! + .RequestUri! + .ToString() + .Should() + .Be($"/api?from={Uri.EscapeDataString(from.ToString("o"))}"); + } + [Theory] [InlineAutoNSubstituteData("/api")] public void Should_Replace_Query_Parameters_WithNull( diff --git a/test/Atc.Rest.Client.Tests/Builder/OperatorRole.cs b/test/Atc.Rest.Client.Tests/Builder/OperatorRole.cs new file mode 100644 index 0000000..85a6989 --- /dev/null +++ b/test/Atc.Rest.Client.Tests/Builder/OperatorRole.cs @@ -0,0 +1,12 @@ +namespace Atc.Rest.Client.Tests.Builder; + +public enum OperatorRole +{ + None = 0, + + [EnumMember(Value = "owner")] + Owner = 1, + + [EnumMember(Value = "admin")] + Admin = 2, +} \ No newline at end of file diff --git a/test/Atc.Rest.Client.Tests/GlobalUsings.cs b/test/Atc.Rest.Client.Tests/GlobalUsings.cs index a28f48c..29d079f 100644 --- a/test/Atc.Rest.Client.Tests/GlobalUsings.cs +++ b/test/Atc.Rest.Client.Tests/GlobalUsings.cs @@ -1,5 +1,6 @@ global using System.Net; global using System.Net.Http.Headers; +global using System.Runtime.Serialization; global using System.Text.Json; global using Atc.Rest.Client.Builder; global using Atc.Rest.Client.Serialization; \ No newline at end of file diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 020fd5c..3190960 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -11,7 +11,7 @@ - +