diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index befaeda..19e34ad 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.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..8e8339f 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.x
- name: Restore dependencies
run: dotnet restore ./src/OneBitSoftware.Utilities.OperationResult.sln
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/IOperationError.cs b/src/OneBitSoftware.Utilities.OperationResult/Errors/IOperationError.cs
index bec1ac6..d9abc5e 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; }
diff --git a/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs b/src/OneBitSoftware.Utilities.OperationResult/Errors/OperationError.cs
index 9a50063..0b4d0d5 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
{
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/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs
index 10de94e..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();
- protected readonly ILogger? _logger;
+ private readonly List _successMessages = new();
+
+ 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,6 +98,19 @@ public OperationResult AppendErrors(OperationResult otherOperationResult)
{
if (otherOperationResult is null) return this;
+ // 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
+ {
+ 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);
@@ -146,25 +164,19 @@ 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)
- {
-#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
- }
+ 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));
@@ -210,14 +222,28 @@ 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;
-
///
/// 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);
+ }
+ }
}
///
@@ -284,53 +310,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.
- ///
- /// 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.
///
@@ -338,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/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/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
diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs
index cd8ef05..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);
@@ -111,4 +111,70 @@ 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_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()
+ {
+ // 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));
+ }
+ }
+ }
+}