From 48f2e54d84dbcbc740f9e200796d5e1d209b5fe7 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Thu, 15 Aug 2024 02:52:43 +0200 Subject: [PATCH 1/4] chore: nuget updates --- .editorconfig | 2 ++ Directory.Build.props | 4 ++-- src/Atc.OpenApi/Atc.OpenApi.csproj | 2 +- src/Atc.Rest.Extended/Atc.Rest.Extended.csproj | 4 ++-- src/Atc.Rest.HealthChecks/Atc.Rest.HealthChecks.csproj | 2 +- src/Directory.Build.props | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1e273a01..9ac83152 100644 --- a/.editorconfig +++ b/.editorconfig @@ -543,12 +543,14 @@ dotnet_diagnostic.SA1615.severity = suggestion # Category: 'Documentation' - El dotnet_diagnostic.S1168.severity = suggestion # Return an empty collection instead of null dotnet_diagnostic.S1172.severity = none # False positive: Unused method parameters should be removed dotnet_diagnostic.S2094.severity = none # Remove this empty class, write its code or make it an "interface". +dotnet_diagnostic.S2325.severity = none # Make xxx to a static method dotnet_diagnostic.S3267.severity = suggestion # Simplified with LINQ expressions dotnet_diagnostic.S4456.severity = none # Split method into two - one validating the parameters and one handling the iterator dotnet_diagnostic.S4457.severity = none # Split this method into two, one handling parameters check and the other handling the asynchronous code dotnet_diagnostic.S4487.severity = none # Remove this unread private field 'testOutputHelper' or refactor the code to use its value. dotnet_diagnostic.S6562.severity = suggestion # Provide the "DateTimeKind" when creating this object dotnet_diagnostic.S6588.severity = suggestion # Use "DateTime.UnixEpoch" instead of creating DateTime instances that point to the unix epoch time +dotnet_diagnostic.S6608.severity = none # Indexing at 0 should be used instead of the "Enumerable" extension method "First" dotnet_diagnostic.IDE0079.severity = suggestion # Remove unnecessary suppression (not working with all SonarSource) - https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0079 diff --git a/Directory.Build.props b/Directory.Build.props index 34a8c95f..5818e300 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -43,10 +43,10 @@ - + - + \ No newline at end of file diff --git a/src/Atc.OpenApi/Atc.OpenApi.csproj b/src/Atc.OpenApi/Atc.OpenApi.csproj index a9e3d513..2b2b5074 100644 --- a/src/Atc.OpenApi/Atc.OpenApi.csproj +++ b/src/Atc.OpenApi/Atc.OpenApi.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Atc.Rest.Extended/Atc.Rest.Extended.csproj b/src/Atc.Rest.Extended/Atc.Rest.Extended.csproj index cedcb728..4ff2943e 100644 --- a/src/Atc.Rest.Extended/Atc.Rest.Extended.csproj +++ b/src/Atc.Rest.Extended/Atc.Rest.Extended.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/src/Atc.Rest.HealthChecks/Atc.Rest.HealthChecks.csproj b/src/Atc.Rest.HealthChecks/Atc.Rest.HealthChecks.csproj index 22416a8b..34c2e828 100644 --- a/src/Atc.Rest.HealthChecks/Atc.Rest.HealthChecks.csproj +++ b/src/Atc.Rest.HealthChecks/Atc.Rest.HealthChecks.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 798213e9..96a2d6a7 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -54,7 +54,7 @@ - + \ No newline at end of file From 6cf33c8e20f42257404e370aa4acf39128f14163 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Thu, 15 Aug 2024 02:53:03 +0200 Subject: [PATCH 2/4] fix: add missing AttributeUsage --- src/Atc.Rest/Filters/ErrorHandlingExceptionFilterAttribute.cs | 1 + .../DataAnnotations/ValidationAttributes/KeyStringAttribute.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Atc.Rest/Filters/ErrorHandlingExceptionFilterAttribute.cs b/src/Atc.Rest/Filters/ErrorHandlingExceptionFilterAttribute.cs index 170b7183..2cc74a25 100644 --- a/src/Atc.Rest/Filters/ErrorHandlingExceptionFilterAttribute.cs +++ b/src/Atc.Rest/Filters/ErrorHandlingExceptionFilterAttribute.cs @@ -2,6 +2,7 @@ // ReSharper disable once CheckNamespace namespace Microsoft.AspNetCore.Mvc.Filters; +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public sealed class ErrorHandlingExceptionFilterAttribute : ExceptionFilterAttribute { private readonly TelemetryClient telemetryClient; diff --git a/src/Atc/Attributes/DataAnnotations/ValidationAttributes/KeyStringAttribute.cs b/src/Atc/Attributes/DataAnnotations/ValidationAttributes/KeyStringAttribute.cs index 2e72a7af..058b58f9 100644 --- a/src/Atc/Attributes/DataAnnotations/ValidationAttributes/KeyStringAttribute.cs +++ b/src/Atc/Attributes/DataAnnotations/ValidationAttributes/KeyStringAttribute.cs @@ -1,6 +1,7 @@ // ReSharper disable once CheckNamespace namespace System.ComponentModel.DataAnnotations; +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public sealed class KeyStringAttribute : StringAttribute { public KeyStringAttribute() From 06f05d09953849339fada7fb377aab72285dc1c3 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Thu, 15 Aug 2024 02:53:47 +0200 Subject: [PATCH 3/4] refactor: improve CountAsync and ToListAsync --- src/Atc/Extensions/EnumerableExtensions.cs | 25 ++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Atc/Extensions/EnumerableExtensions.cs b/src/Atc/Extensions/EnumerableExtensions.cs index 2e52c7ca..07821093 100644 --- a/src/Atc/Extensions/EnumerableExtensions.cs +++ b/src/Atc/Extensions/EnumerableExtensions.cs @@ -38,7 +38,8 @@ public static async IAsyncEnumerable ToAsyncEnumerable( /// A to observe while waiting for the asynchronous operation to complete. /// A task that represents the asynchronous operation. The task result contains the number of elements in the sequence. /// Thrown when the source sequence is null. - public static Task CountAsync( + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312:Variable names should begin with lower-case letter", Justification = "False/Positive")] + public static async Task CountAsync( this IEnumerable source, CancellationToken cancellationToken = default) { @@ -47,7 +48,15 @@ public static Task CountAsync( throw new ArgumentNullException(nameof(source)); } - return Task.Run(source.Count, cancellationToken); + var count = 0; + foreach (var _ in source) + { + cancellationToken.ThrowIfCancellationRequested(); + count++; + await Task.Yield(); + } + + return count; } /// @@ -58,7 +67,7 @@ public static Task CountAsync( /// A to observe while waiting for the asynchronous operation to complete. /// A task that represents the asynchronous operation. The task result contains a list with the elements from the input sequence. /// Thrown when the source sequence is null. - public static Task> ToListAsync( + public static async Task> ToListAsync( this IEnumerable source, CancellationToken cancellationToken = default) { @@ -67,7 +76,15 @@ public static Task> ToListAsync( throw new ArgumentNullException(nameof(source)); } - return Task.Run(source.ToList, cancellationToken); + var list = new List(); + foreach (var item in source) + { + cancellationToken.ThrowIfCancellationRequested(); + list.Add(item); + await Task.Yield(); + } + + return list; } /// From 616fdce1d6f8b303438a76c3c66f431a012b33e7 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Thu, 15 Aug 2024 02:54:21 +0200 Subject: [PATCH 4/4] feat: add AsyncEnumerableFactory.FromSingleItem --- src/Atc/Factories/AsyncEnumerableFactory.cs | 12 ++++ .../Factories/AsyncEnumerableFactoryTests.cs | 63 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/Atc/Factories/AsyncEnumerableFactory.cs b/src/Atc/Factories/AsyncEnumerableFactory.cs index 04026a1c..9b11e788 100644 --- a/src/Atc/Factories/AsyncEnumerableFactory.cs +++ b/src/Atc/Factories/AsyncEnumerableFactory.cs @@ -15,4 +15,16 @@ public static async IAsyncEnumerable Empty() await Task.CompletedTask; yield break; } + + /// + /// Converts a single item into an . + /// + /// The type of the item. + /// The item to convert. + /// An containing the single item. + public static async IAsyncEnumerable FromSingleItem(T item) + { + yield return item; + await Task.CompletedTask; + } } \ No newline at end of file diff --git a/test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs b/test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs index 6897abda..c10d08e3 100644 --- a/test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs +++ b/test/Atc.Tests/Factories/AsyncEnumerableFactoryTests.cs @@ -86,4 +86,67 @@ public async Task Empty_ReturnsEmptySequence_WithDifferentTypes() Assert.Empty(stringResult); Assert.Empty(customTypeResult); } + + [Fact] + public async Task FromSingleItem_ReturnsAsyncEnumerableWithSingleItem() + { + // Arrange + const int item = 42; + + // Act + var result = new List(); + await foreach (var value in AsyncEnumerableFactory.FromSingleItem(item)) + { + result.Add(value); + } + + // Assert + Assert.Single(result); + Assert.Equal(item, result.First()); + } + + [Fact] + public async Task FromSingleItem_ReturnsAsyncEnumerable_WithReferenceType() + { + // Arrange + const string item = "TestString"; + + // Act + var result = new List(); + await foreach (var value in AsyncEnumerableFactory.FromSingleItem(item)) + { + result.Add(value); + } + + // Assert + Assert.Single(result); + Assert.Equal(item, result.First()); + } + + [Fact] + public async Task FromSingleItem_CanBeEnumeratedMultipleTimes() + { + // Arrange + var item = 42; + var asyncEnumerable = AsyncEnumerableFactory.FromSingleItem(item); + + // Act + var firstEnumeration = new List(); + await foreach (var value in asyncEnumerable) + { + firstEnumeration.Add(value); + } + + var secondEnumeration = new List(); + await foreach (var value in asyncEnumerable) + { + secondEnumeration.Add(value); + } + + // Assert + Assert.Single(firstEnumeration); + Assert.Single(secondEnumeration); + Assert.Equal(item, firstEnumeration.First()); + Assert.Equal(item, secondEnumeration.First()); + } } \ No newline at end of file