From 118bcf035f50e8bbcadfcbf57095eedea2c9b37c Mon Sep 17 00:00:00 2001 From: Radi Atanassov Date: Sun, 2 Mar 2025 15:42:56 +0200 Subject: [PATCH 1/7] initial commit --- .../OneBitSoftware.Utilities.OperationResult.csproj | 6 +++--- ...BitSoftware.Utilities.OperationResultTests.csproj | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OneBitSoftware.Utilities.OperationResult/OneBitSoftware.Utilities.OperationResult.csproj b/src/OneBitSoftware.Utilities.OperationResult/OneBitSoftware.Utilities.OperationResult.csproj index 96edbb8..6881c37 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/OneBitSoftware.Utilities.OperationResult.csproj +++ b/src/OneBitSoftware.Utilities.OperationResult/OneBitSoftware.Utilities.OperationResult.csproj @@ -1,7 +1,7 @@  - net6.0;net5.0 + net9.0 disable enable @@ -14,7 +14,7 @@ - + @@ -32,7 +32,7 @@ False README.md OneBitSoftware; OperationResult; - 1.4.6 + 2.0.0 diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/OneBitSoftware.Utilities.OperationResultTests.csproj b/tests/OneBitSoftware.Utilities.OperationResultTests/OneBitSoftware.Utilities.OperationResultTests.csproj index e593810..8826bd1 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/OneBitSoftware.Utilities.OperationResultTests.csproj +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/OneBitSoftware.Utilities.OperationResultTests.csproj @@ -1,17 +1,17 @@ - net6.0 + net9.0 enable enable - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 6620663e82f4dbd6ad3101d63a959747eb0f04ab Mon Sep 17 00:00:00 2001 From: Radi Atanassov Date: Tue, 4 Mar 2025 09:42:30 +0200 Subject: [PATCH 2/7] Edge case fix - when we merge operationresults and one class doesn't have a logger - we now log --- .../OperationResult.cs | 14 +++++- .../OperationResultAppendErrorsTests.cs | 50 +++++++++++++++++++ .../TestLogger.cs | 28 +++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 tests/OneBitSoftware.Utilities.OperationResultTests/TestLogger.cs diff --git a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs index 10de94e..1bfc8ae 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs @@ -93,8 +93,18 @@ public OperationResult AppendErrors(OperationResult otherOperationResult) { if (otherOperationResult is null) return this; - // Append the error message without logging (presuming that there is already a log message). - foreach (var error in otherOperationResult.Errors) this.AppendErrorInternal(error); + foreach (var error in otherOperationResult.Errors) + { + this.AppendErrorInternal(error); + + // Logs messages if the other operation result does not have a logger + // BUG: We don't know the severity of the message and need to assume a level. + // The task is to make IOperationError have info about the severity, so it can be used here + if (this._logger is not null && otherOperationResult._logger is null) + { + this._logger.Log(LogLevel.Error, error.Message); + } + } return this; } diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs index cd8ef05..dfb0aa8 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs @@ -111,4 +111,54 @@ public void AppendErrorsStringInt_ShouldListAllErrors() Assert.NotNull(operationResultBase.Errors.Single(r => r.Message.Equals(message2))); Assert.NotNull(operationResultBase.Errors.Single(r => r.Details is not null && r.Details.Equals(detail2))); } + + [Fact] + public void AppendErrors_ShouldLogWhenCreatedWithALogger() + { + // Arrange + var testLogger = new TestLogger(); + var operationResultNoLogger = new OperationResult(); + var operationResultWithLogger = new OperationResult(testLogger); + + // Act + operationResultNoLogger.AppendError("test"); + operationResultWithLogger.AppendErrors(operationResultNoLogger); + + // Assert + Assert.Equal(1, testLogger.LogMessages.Count); + } + + [Fact] + public void AppendErrors_ShouldLogOnceWhenCreatedWithALogger() + { + // Arrange + var testLogger = new TestLogger(); + var operationResultWithLogger = new OperationResult(testLogger); + var operationResultWithLogger2 = new OperationResult(testLogger); + + // Act + operationResultWithLogger2.AppendError("test"); + operationResultWithLogger.AppendErrors(operationResultWithLogger2); + + // Assert + Assert.Equal(1, testLogger.LogMessages.Count); + } + + + + [Fact] + public void AppendErrors_ShouldLogWhenCreatedWithNoLogger() + { + // Arrange + var testLogger = new TestLogger(); + var operationResultNoLogger = new OperationResult(); + var operationResultWithLogger = new OperationResult(testLogger); + + // Act + operationResultWithLogger.AppendError("test"); + operationResultNoLogger.AppendErrors(operationResultNoLogger); + + // Assert + Assert.Equal(1, testLogger.LogMessages.Count); + } } diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/TestLogger.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/TestLogger.cs new file mode 100644 index 0000000..1a744e3 --- /dev/null +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/TestLogger.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace OneBitSoftware.Utilities.OperationResultTests +{ + public class TestLogger : ILogger + { + private readonly List _logMessages = new List(); + + public IReadOnlyList LogMessages => _logMessages.AsReadOnly(); + + public IDisposable BeginScope(TState state) => null; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (formatter != null) + { + _logMessages.Add(formatter(state, exception)); + } + } + } +} From 77845acfa200b76a8b3983acdffc1816cdeb253d Mon Sep 17 00:00:00 2001 From: Radi Atanassov Date: Wed, 5 Mar 2025 09:55:28 +0200 Subject: [PATCH 3/7] fixing log counts --- .../Errors/IOperationError.cs | 12 +++++++ .../Errors/OperationError.cs | 9 ++++++ .../OperationResult.cs | 31 +++---------------- .../PolymorphicOperationErrorSerializer.cs | 2 +- .../OperationResultAppendErrorsTests.cs | 16 ++++++++++ 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs b/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs index bec1ac6..4d7c41d 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs @@ -1,5 +1,7 @@ namespace OneBitSoftware.Utilities.Errors { + using Microsoft.Extensions.Logging; + public interface IOperationError { int? Code { get; set; } @@ -7,5 +9,15 @@ public interface IOperationError string? Message { get; set; } string? Details { get; set; } + + /// + /// Defines if the error is logged or not. Used when merging instances and one of them does not have an ILogger. + /// + bool Logged { get; internal set; } + + /// + /// Defines the log level for the error. + /// + LogLevel? LogLevel { get; set; } } } diff --git a/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs b/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs index 9a50063..7d46c43 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs @@ -1,6 +1,7 @@ namespace OneBitSoftware.Utilities.Errors { using System.Text; + using Microsoft.Extensions.Logging; public class OperationError : IOperationError { @@ -17,12 +18,20 @@ public OperationError(string? message = null, int? code = null, string? details public string? Details { get; set; } + /// + public LogLevel? LogLevel { get; set; } + + /// + bool IOperationError.Logged { get; set; } + public override string ToString() { var result = new StringBuilder(); if (this.Code != null) result.AppendLine($"Code: {this.Code}"); + if (this.LogLevel is not null) result.AppendLine($"Severity: {this.LogLevel}"); // TODO: maybe convert to string? + if (!string.IsNullOrWhiteSpace(this.Message)) result.AppendLine($"Message: {this.Message}"); if (!string.IsNullOrWhiteSpace(this.Details)) result.AppendLine($"Trace: {this.Details}"); diff --git a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs index 1bfc8ae..c761c60 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs @@ -98,11 +98,10 @@ public OperationResult AppendErrors(OperationResult otherOperationResult) this.AppendErrorInternal(error); // Logs messages if the other operation result does not have a logger - // BUG: We don't know the severity of the message and need to assume a level. - // The task is to make IOperationError have info about the severity, so it can be used here - if (this._logger is not null && otherOperationResult._logger is null) + if (this._logger is not null && otherOperationResult._logger is null && !error.Logged) { - this._logger.Log(LogLevel.Error, error.Message); + this._logger.Log(GetLogLevel(error.LogLevel), error.Message); + error.Logged = true; } } @@ -159,9 +158,8 @@ public OperationResult AppendError(IOperationError error, LogLevel? logLevel = L if (this._logger != null) { -#pragma warning disable CA2254 // Template should be a static expression this._logger.Log(GetLogLevel(logLevel), error.Message); -#pragma warning restore CA2254 // Template should be a static expression + error.Logged = true; } return this; @@ -220,7 +218,6 @@ public static OperationResult FromError(string message, int? code = null, LogLev return result.AppendError(message, code, logLevel, details); } - // TODO: this method needs completing. protected static LogLevel GetLogLevel(LogLevel? optionalLevel) => optionalLevel ?? LogLevel.Error; /// @@ -294,26 +291,6 @@ public OperationResult(TResult resultObject) : base() return this; } - /// - /// Appends an to the internal errors collection. - /// - /// An instance of to add to the internal errors collection. - /// The logging level. - /// The current instance of the . - public new OperationResult AppendError(IOperationError error, LogLevel? logLevel = LogLevel.Error) - { - base.AppendErrorInternal(error); - - if (this._logger != null) - { -#pragma warning disable CA2254 // Template should be a static expression - this._logger.Log(GetLogLevel(logLevel), error.Message); -#pragma warning restore CA2254 // Template should be a static expression - } - - return this; - } - /// /// Appends error messages from to the current instance. /// diff --git a/src/OneBitSoftware.Utilities.OperationResult/PolymorphicOperationErrorSerializer.cs b/src/OneBitSoftware.Utilities.OperationResult/PolymorphicOperationErrorSerializer.cs index 9045bdb..65a8cfa 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/PolymorphicOperationErrorSerializer.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/PolymorphicOperationErrorSerializer.cs @@ -54,7 +54,7 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions if (this._typeMappings.TryGetValue(value.GetType(), out var typeValue) == false) throw new InvalidOperationException($"Model of type {value.GetType()} cannot be successfully serialized."); var tempBufferWriter = new ArrayBufferWriter(); - var tempWriter = new Utf8JsonWriter(tempBufferWriter); + var tempWriter = new Utf8JsonWriter(tempBufferWriter); // TODO: dispose with using var var fallbackDeserializationOptions = this.ConstructSafeFallbackOptions(options); JsonSerializer.Serialize(tempWriter, value, value.GetType(), fallbackDeserializationOptions); diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs index dfb0aa8..808fa9e 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs @@ -144,7 +144,23 @@ public void AppendErrors_ShouldLogOnceWhenCreatedWithALogger() Assert.Equal(1, testLogger.LogMessages.Count); } + [Fact] + public void AppendErrors_ShouldLogOnceWhenNestingWithALogger() + { + // Arrange + var testLogger = new TestLogger(); + var operationResultWithLogger = new OperationResult(testLogger); + var operationResultWithLogger2 = new OperationResult(testLogger); + var operationResultWithLogger3 = new OperationResult(testLogger); + // Act + operationResultWithLogger3.AppendError("test1"); + operationResultWithLogger2.AppendError("test2"); + operationResultWithLogger.AppendErrors(operationResultWithLogger2); + + // Assert + Assert.Equal(2, testLogger.LogMessages.Count); + } [Fact] public void AppendErrors_ShouldLogWhenCreatedWithNoLogger() From dd9b3855bb12076a508e4e5a3773067c436329c1 Mon Sep 17 00:00:00 2001 From: Radi Atanassov Date: Wed, 5 Mar 2025 10:01:16 +0200 Subject: [PATCH 4/7] pipeline updates --- .github/workflows/main.yml | 6 +++--- .github/workflows/pull-request-validation.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index befaeda..7abd306 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,12 +15,12 @@ jobs: NUGET_SOURCE: https://api.nuget.org/v3/index.json steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore ./src/OneBitSoftware.Utilities.OperationResult.sln diff --git a/.github/workflows/pull-request-validation.yml b/.github/workflows/pull-request-validation.yml index 8fc26d2..2a09b58 100644 --- a/.github/workflows/pull-request-validation.yml +++ b/.github/workflows/pull-request-validation.yml @@ -15,12 +15,12 @@ jobs: NUGET_SOURCE: https://api.nuget.org/v3/index.json steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore ./src/OneBitSoftware.Utilities.OperationResult.sln From 7c173f411db600ed400a8e97c6cd0a44ba5d8f8e Mon Sep 17 00:00:00 2001 From: Radi Atanassov Date: Wed, 5 Mar 2025 10:09:48 +0200 Subject: [PATCH 5/7] fixed failing tests --- src/OneBitSoftware.Utilities.OperationResult.sln | 11 +++++++++++ .../Errors/OperationError.cs | 3 ++- .../OperationResult.cs | 6 +++--- ...OperationResultSerializationSystemTextJsonTests.cs | 6 +++--- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/OneBitSoftware.Utilities.OperationResult.sln b/src/OneBitSoftware.Utilities.OperationResult.sln index 692c185..69984dd 100644 --- a/src/OneBitSoftware.Utilities.OperationResult.sln +++ b/src/OneBitSoftware.Utilities.OperationResult.sln @@ -7,6 +7,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneBitSoftware.Utilities.Op EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneBitSoftware.Utilities.OperationResultTests", "..\tests\OneBitSoftware.Utilities.OperationResultTests\OneBitSoftware.Utilities.OperationResultTests.csproj", "{142313C6-5DC0-4428-AE63-487B8D41552E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{6F29D051-AD77-482A-99A7-4E5ED288AB22}" + ProjectSection(SolutionItems) = preProject + ..\.github\workflows\main.yml = ..\.github\workflows\main.yml + ..\.github\workflows\pull-request-validation.yml = ..\.github\workflows\pull-request-validation.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -25,6 +33,9 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {6F29D051-AD77-482A-99A7-4E5ED288AB22} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3A384A11-1CD9-4A02-A6BB-3EC782DBF254} EndGlobalSection diff --git a/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs b/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs index 7d46c43..65b02ac 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs @@ -5,11 +5,12 @@ public class OperationError : IOperationError { - public OperationError(string? message = null, int? code = null, string? details = null) + public OperationError(string? message = null, int? code = null, string? details = null, LogLevel? logLevel = null) { this.Message = message; this.Code = code; this.Details = details; + this.LogLevel = logLevel; } public int? Code { get; set; } diff --git a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs index c761c60..f9e8c54 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs @@ -120,7 +120,7 @@ public OperationResult AppendError(string message, int? code = null, LogLevel? l { if (string.IsNullOrWhiteSpace(message)) throw new ArgumentNullException(nameof(message)); - var error = new OperationError(message, code) { Details = details }; + var error = new OperationError(message, code) { Details = details, LogLevel = logLevel }; this.AppendError(error, logLevel); return this; @@ -140,7 +140,7 @@ public OperationResult AppendError(string message, int? code = null, LogLevel { if (string.IsNullOrWhiteSpace(message)) throw new ArgumentNullException(nameof(message)); - var error = new T() { Message = message, Code = code, Details = details }; + var error = new T() { Message = message, Code = code, Details = details, LogLevel = logLevel }; this.AppendError(error, logLevel); return this; @@ -179,7 +179,7 @@ public OperationResult AppendException(Exception exception, int? errorCode = nul // Append the exception as a first if it is not yet set. this.InitialException ??= exception; - var error = new OperationError(exception.ToString(), errorCode); + var error = new OperationError(exception.ToString(), errorCode, null, LogLevel.Error); this.AppendError(error, logLevel); return this; diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/Serialization/OperationResultSerializationSystemTextJsonTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/Serialization/OperationResultSerializationSystemTextJsonTests.cs index 6e66093..4e347cb 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/Serialization/OperationResultSerializationSystemTextJsonTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/Serialization/OperationResultSerializationSystemTextJsonTests.cs @@ -56,7 +56,7 @@ public async Task SerializeWithSystemTextJsonIncludesTypeDiscriminator() // Assert Assert.Contains(testText, text); Assert.Contains(operationErrorText, text); - var expectText = @"{""Success"":false,""Errors"":[{""type"":""operation_error"",""Code"":123,""Message"":""Test"",""Details"":""\u0022type\u0022""}]}"; + var expectText = @"{""Success"":false,""Errors"":[{""type"":""operation_error"",""Code"":123,""Message"":""Test"",""Details"":""\u0022type\u0022"",""LogLevel"":null}]}"; Assert.Equal(expectText, text); } @@ -67,7 +67,7 @@ public async Task SerializeWithSystemTextJsonSetsExpectedJson() var testText = "\"type\""; var outputStream = new MemoryStream(); var operationResult = new OperationResult(); - operationResult.AppendError(new OperationError(message: "Test") { Code = 123, Details = testText }); + operationResult.AppendError(new OperationError(message: "Test") { Code = 123, Details = testText, LogLevel = Microsoft.Extensions.Logging.LogLevel.Warning }); // Act await JsonSerializer.SerializeAsync(outputStream, operationResult, GetSerializationOptions()); @@ -75,7 +75,7 @@ public async Task SerializeWithSystemTextJsonSetsExpectedJson() string text = new StreamReader(outputStream).ReadToEnd(); // Assert - var expectText = @"{""Success"":false,""Errors"":[{""type"":""operation_error"",""Code"":123,""Message"":""Test"",""Details"":""\u0022type\u0022""}]}"; + var expectText = @"{""Success"":false,""Errors"":[{""type"":""operation_error"",""Code"":123,""Message"":""Test"",""Details"":""\u0022type\u0022"",""LogLevel"":3}]}"; Assert.Equal(expectText, text); } From e542e9f4a6d6e2ba65c5e3163415d8a99288f349 Mon Sep 17 00:00:00 2001 From: Radi Atanassov Date: Wed, 5 Mar 2025 13:45:46 +0200 Subject: [PATCH 6/7] Changes after Tonys suggestion --- .../Errors/IOperationError.cs | 10 -- .../Errors/OperationError.cs | 11 +- .../OperationResult.cs | 100 ++++++++---------- .../OperationResultAppendErrorsTests.cs | 2 +- ...nResultSerializationSystemTextJsonTests.cs | 6 +- 5 files changed, 51 insertions(+), 78 deletions(-) diff --git a/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs b/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs index 4d7c41d..d9abc5e 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs @@ -9,15 +9,5 @@ public interface IOperationError string? Message { get; set; } string? Details { get; set; } - - /// - /// Defines if the error is logged or not. Used when merging instances and one of them does not have an ILogger. - /// - bool Logged { get; internal set; } - - /// - /// Defines the log level for the error. - /// - LogLevel? LogLevel { get; set; } } } diff --git a/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs b/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs index 65b02ac..0b4d0d5 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs @@ -5,12 +5,11 @@ public class OperationError : IOperationError { - public OperationError(string? message = null, int? code = null, string? details = null, LogLevel? logLevel = null) + public OperationError(string? message = null, int? code = null, string? details = null) { this.Message = message; this.Code = code; this.Details = details; - this.LogLevel = logLevel; } public int? Code { get; set; } @@ -19,20 +18,12 @@ public OperationError(string? message = null, int? code = null, string? details public string? Details { get; set; } - /// - public LogLevel? LogLevel { get; set; } - - /// - bool IOperationError.Logged { get; set; } - public override string ToString() { var result = new StringBuilder(); if (this.Code != null) result.AppendLine($"Code: {this.Code}"); - if (this.LogLevel is not null) result.AppendLine($"Severity: {this.LogLevel}"); // TODO: maybe convert to string? - if (!string.IsNullOrWhiteSpace(this.Message)) result.AppendLine($"Message: {this.Message}"); if (!string.IsNullOrWhiteSpace(this.Details)) result.AppendLine($"Trace: {this.Details}"); diff --git a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs index f9e8c54..bc060b8 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs @@ -13,9 +13,14 @@ /// public class OperationResult { - private readonly List _successMessages = new List(); + /// + /// Contains instances that have not been logged. + /// + private readonly List<(IOperationError Error, LogLevel? LogLevel)> _errorsNotLogged = new(); + + private readonly List _successMessages = new(); - protected readonly ILogger? _logger; + private readonly ILogger? _logger; /// /// Gets or sets a value indicating whether the operation is successful or not. @@ -44,7 +49,7 @@ public IEnumerable? SuccessMessages /// Gets an containing the error codes and messages of the . /// public List Errors { get; internal set; } = new List(); - + /// /// Gets or sets the first exception that resulted from the operation. /// @@ -93,18 +98,22 @@ public OperationResult AppendErrors(OperationResult otherOperationResult) { if (otherOperationResult is null) return this; - foreach (var error in otherOperationResult.Errors) + // store any messages for logging at a later stage, when merged to an OperationResult with a logger. + if (this._logger is null) + { + this._errorsNotLogged.AddRange(otherOperationResult._errorsNotLogged); + } + else { - this.AppendErrorInternal(error); - - // Logs messages if the other operation result does not have a logger - if (this._logger is not null && otherOperationResult._logger is null && !error.Logged) - { - this._logger.Log(GetLogLevel(error.LogLevel), error.Message); - error.Logged = true; - } + foreach (var (error, logLevel) in otherOperationResult._errorsNotLogged) + this.LogInternal(error, logLevel); + + otherOperationResult._errorsNotLogged.Clear(); } + // Append the error message without logging (presuming that there is already a log message). + foreach (var error in otherOperationResult.Errors) this.AppendErrorInternal(error); + return this; } @@ -120,7 +129,7 @@ public OperationResult AppendError(string message, int? code = null, LogLevel? l { if (string.IsNullOrWhiteSpace(message)) throw new ArgumentNullException(nameof(message)); - var error = new OperationError(message, code) { Details = details, LogLevel = logLevel }; + var error = new OperationError(message, code) { Details = details }; this.AppendError(error, logLevel); return this; @@ -140,7 +149,7 @@ public OperationResult AppendError(string message, int? code = null, LogLevel { if (string.IsNullOrWhiteSpace(message)) throw new ArgumentNullException(nameof(message)); - var error = new T() { Message = message, Code = code, Details = details, LogLevel = logLevel }; + var error = new T() { Message = message, Code = code, Details = details }; this.AppendError(error, logLevel); return this; @@ -155,31 +164,26 @@ public OperationResult AppendError(string message, int? code = null, LogLevel public OperationResult AppendError(IOperationError error, LogLevel? logLevel = LogLevel.Error) { this.AppendErrorInternal(error); - - if (this._logger != null) - { - this._logger.Log(GetLogLevel(logLevel), error.Message); - error.Logged = true; - } + this.LogInternal(error, logLevel); return this; } /// - /// Appends an exception to the error message collection and logs the full exception as an Error level. A call to this method will set the Success property to false. + /// Appends an exception to the error message collection and logs the full exception as an Error level. A call to this method will set the Success property to false. /// /// The exception to log. /// The error code. - /// The logging severity. + /// The logging severity. /// The current instance of the . - public OperationResult AppendException(Exception exception, int? errorCode = null, LogLevel? logLevel = null) + public OperationResult AppendException(Exception exception, int errorCode = 0, LogLevel? logLevel = null) { if (exception is null) throw new ArgumentNullException(nameof(exception)); // Append the exception as a first if it is not yet set. this.InitialException ??= exception; - var error = new OperationError(exception.ToString(), errorCode, null, LogLevel.Error); + var error = new OperationError(exception.ToString(), errorCode); this.AppendError(error, logLevel); return this; @@ -218,13 +222,28 @@ public static OperationResult FromError(string message, int? code = null, LogLev return result.AppendError(message, code, logLevel, details); } - protected static LogLevel GetLogLevel(LogLevel? optionalLevel) => optionalLevel ?? LogLevel.Error; - /// /// Appends an to the internal errors collection. /// /// An instance of to add to the internal errors collection. protected void AppendErrorInternal(IOperationError error) => this.Errors.Add(error); + + /// + /// Logs to the internal logger if it is set, otherwise it will add the error to the internal errors collection. + /// + /// The to log. + /// The log level. + private void LogInternal(IOperationError error, LogLevel? logLevel) + { + if (this._logger is null) + { + this._errorsNotLogged.Add((Error: error, LogLevel: logLevel)); + } + else + { + this._logger.Log(logLevel ?? LogLevel.Error, error.Message); + } + } } /// @@ -291,33 +310,6 @@ public OperationResult(TResult resultObject) : base() return this; } - /// - /// Appends error messages from to the current instance. - /// - /// The to append from. - /// A type that inherits from . - /// The original with the appended messages from . - [Obsolete("Please use AppendErrors instead. This method will be removed to avoid confusion.")] - public OperationResult AppendErrorMessages(TOther otherOperationResult) - where TOther : OperationResult - { - base.AppendErrors(otherOperationResult); - - return this; - } - - /// - /// Appends error from to the current instance. - /// - /// The to append from. - /// The original with the appended messages from . - public new OperationResult AppendErrors(OperationResult otherOperationResult) - { - base.AppendErrors(otherOperationResult); - - return this; - } - /// /// Appends an exception to the error message collection and logs the full exception as an Error level. A call to this method will set the Success property to false. /// @@ -325,7 +317,7 @@ public OperationResult AppendErrorMessages(TOther otherOperatio /// The error code. /// The logging severity. /// The current instance of the . - public new OperationResult AppendException(Exception exception, int? errorCode = null, LogLevel? logLevel = null) + public new OperationResult AppendException(Exception exception, int errorCode = 0, LogLevel? logLevel = null) { base.AppendException(exception, errorCode, logLevel); diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs index 808fa9e..b18b318 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs @@ -62,7 +62,7 @@ public void AppendErrorsT_ShouldListAllErrors() operationResultBase.AppendError(message2, errorCode2, LogLevel.Debug, detail2); // Act - AppendErrorMessages is to be removed - operationResultBase.AppendErrorMessages(operationResultTarget); + operationResultBase.AppendErrors(operationResultTarget); // Assert Assert.False(operationResultBase.Success); diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/Serialization/OperationResultSerializationSystemTextJsonTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/Serialization/OperationResultSerializationSystemTextJsonTests.cs index 4e347cb..6e66093 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/Serialization/OperationResultSerializationSystemTextJsonTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/Serialization/OperationResultSerializationSystemTextJsonTests.cs @@ -56,7 +56,7 @@ public async Task SerializeWithSystemTextJsonIncludesTypeDiscriminator() // Assert Assert.Contains(testText, text); Assert.Contains(operationErrorText, text); - var expectText = @"{""Success"":false,""Errors"":[{""type"":""operation_error"",""Code"":123,""Message"":""Test"",""Details"":""\u0022type\u0022"",""LogLevel"":null}]}"; + var expectText = @"{""Success"":false,""Errors"":[{""type"":""operation_error"",""Code"":123,""Message"":""Test"",""Details"":""\u0022type\u0022""}]}"; Assert.Equal(expectText, text); } @@ -67,7 +67,7 @@ public async Task SerializeWithSystemTextJsonSetsExpectedJson() var testText = "\"type\""; var outputStream = new MemoryStream(); var operationResult = new OperationResult(); - operationResult.AppendError(new OperationError(message: "Test") { Code = 123, Details = testText, LogLevel = Microsoft.Extensions.Logging.LogLevel.Warning }); + operationResult.AppendError(new OperationError(message: "Test") { Code = 123, Details = testText }); // Act await JsonSerializer.SerializeAsync(outputStream, operationResult, GetSerializationOptions()); @@ -75,7 +75,7 @@ public async Task SerializeWithSystemTextJsonSetsExpectedJson() string text = new StreamReader(outputStream).ReadToEnd(); // Assert - var expectText = @"{""Success"":false,""Errors"":[{""type"":""operation_error"",""Code"":123,""Message"":""Test"",""Details"":""\u0022type\u0022"",""LogLevel"":3}]}"; + var expectText = @"{""Success"":false,""Errors"":[{""type"":""operation_error"",""Code"":123,""Message"":""Test"",""Details"":""\u0022type\u0022""}]}"; Assert.Equal(expectText, text); } From eea09f6272b66575f4b00884e7761fd4f546bf46 Mon Sep 17 00:00:00 2001 From: Radi Atanassov Date: Wed, 5 Mar 2025 13:47:04 +0200 Subject: [PATCH 7/7] .NET 9 versions --- .github/workflows/main.yml | 2 +- .github/workflows/pull-request-validation.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7abd306..19e34ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,7 +20,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 9.x - name: Restore dependencies run: dotnet restore ./src/OneBitSoftware.Utilities.OperationResult.sln diff --git a/.github/workflows/pull-request-validation.yml b/.github/workflows/pull-request-validation.yml index 2a09b58..8e8339f 100644 --- a/.github/workflows/pull-request-validation.yml +++ b/.github/workflows/pull-request-validation.yml @@ -20,7 +20,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 9.x - name: Restore dependencies run: dotnet restore ./src/OneBitSoftware.Utilities.OperationResult.sln