Skip to content

Commit 624f82c

Browse files
initial code
1 parent 2fb6894 commit 624f82c

4 files changed

Lines changed: 287 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.1.32228.430
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneBitSoftware.Utilities.OperationResult", "OneBitSoftware.Utilities.OperationResult\OneBitSoftware.Utilities.OperationResult.csproj", "{ACC326A2-84FE-4C2A-92C4-F785E34F9F60}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{ACC326A2-84FE-4C2A-92C4-F785E34F9F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{ACC326A2-84FE-4C2A-92C4-F785E34F9F60}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{ACC326A2-84FE-4C2A-92C4-F785E34F9F60}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{ACC326A2-84FE-4C2A-92C4-F785E34F9F60}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {3A384A11-1CD9-4A02-A6BB-3EC782DBF254}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace OneBitSoftware.Utilities.OperationResult
2+
{
3+
public class Error
4+
{
5+
public int Code { get; set; }
6+
public string Message { get; set; } = string.Empty;
7+
8+
public override string ToString()
9+
{
10+
if (this.Code != 0) return $"{this.Code}: {this.Message}";
11+
return this.Message;
12+
}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
namespace OneBitSoftware.Utilities.OperationResult
2+
{
3+
using Microsoft.Extensions.Logging;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
8+
/// <summary>
9+
/// A class for a system operation result.
10+
/// </summary>
11+
public class OperationResult
12+
{
13+
private readonly List<Error> _errors = new();
14+
private readonly ILogger? _logger;
15+
16+
/// <summary>
17+
/// Gets or sets a value indicating whether the operation is successful or not.
18+
/// </summary>
19+
public bool Success => !this.Fail;
20+
21+
/// <summary>
22+
/// Gets a value indicating whether the operation has failed.
23+
/// </summary>
24+
public bool Fail => this.Errors.Any();
25+
26+
/// <summary>
27+
/// Gets an <see cref="List{T}"/> containing the error codes and messages of the <see cref="OperationResult{T}" />.
28+
/// </summary>
29+
public IReadOnlyCollection<Error> Errors => this._errors.AsReadOnly();
30+
31+
/// <summary>
32+
/// Gets or sets the first exception that resulted from the operation.
33+
/// </summary>
34+
public Exception? InitialException { get; private set; }
35+
36+
/// <summary>
37+
/// Initializes a new instance of the <see cref="OperationResult"/> class.
38+
/// </summary>
39+
/// <remarks>If the operation is a get operation, an empty result must return a truthy Success value.</remarks>
40+
public OperationResult()
41+
{
42+
}
43+
44+
/// <summary>
45+
/// Initializes a new instance of the <see cref="OperationResult"/> class.
46+
/// </summary>
47+
/// <param name="loggerService">An instance of <see cref="ILogger"/> to use for automatic logging.</param>
48+
/// <remarks>If the operation is a get operation, an empty result (no results) must return a truthy Success value.</remarks>
49+
public OperationResult(ILogger loggerService)
50+
{
51+
this._logger = loggerService;
52+
}
53+
54+
public void AppendErrors(OperationResult otherOperationResult)
55+
{
56+
if (otherOperationResult is null) return;
57+
58+
// Append the error message without logging (presuming that there is already a log message).
59+
foreach (var error in otherOperationResult.Errors) this.AppendError(error);
60+
}
61+
62+
/// <summary>
63+
/// This method will append an error with a specific `user-friendly` message to this operation result instance.
64+
/// </summary>
65+
/// <param name="message">A label consuming component defining the 'user-friendly' message.</param>
66+
/// <param name="errorCode">The unique code of the error.</param>
67+
/// <param name="logLevel">The logging severity.</param>
68+
public void AppendError(string message, int errorCode = 0, LogLevel? logLevel = null)
69+
{
70+
if (message is null) throw new ArgumentNullException(nameof(message));
71+
if (string.IsNullOrWhiteSpace(message)) throw new ArgumentNullException(nameof(message));
72+
73+
var error = new Error { Message = message,Code = errorCode };
74+
this.AppendError(error, logLevel);
75+
}
76+
77+
/// <summary>
78+
/// This method will append an error with a default `user-friendly` message to this operation result instance.
79+
/// </summary>
80+
/// <param name="debugMessage">A debug message that should be logged and provide additional information in debug mode.</param>
81+
/// <param name="errorCode">The unique code of the error.</param>
82+
/// <param name="logLevel">The logging severity.</param>
83+
public void AppendErrorMessage(string message, int errorCode = 0, LogLevel? logLevel = null) => this.AppendError(message, errorCode, logLevel);
84+
85+
/// <summary>
86+
/// Appends an exception to the error message collection and logs the full exception as an Error <see cref="LogEventLevel"/> level. A call to this method will set the Success property to false.
87+
/// </summary>
88+
/// <param name="exception">The exception to log.</param>
89+
/// <param name="errorCode">The error code.</param>
90+
/// <param name="logLevel">The <see cref="LogEventLevel"/> logging severity.</param>
91+
public void AppendException(Exception exception, int errorCode = 0, LogLevel? logLevel = null)
92+
{
93+
if (exception is null) throw new ArgumentNullException(nameof(exception));
94+
95+
// Append the exception as a first if it is not yet set.
96+
this.InitialException ??= exception;
97+
98+
var error = new Error { Message = exception.ToString(), Code = errorCode };
99+
this.AppendError(error, logLevel);
100+
}
101+
102+
/// <summary>
103+
/// Use this method to check if a value is a valid string.
104+
/// If <paramref name="value"/> is null, empty or consists only of whitespace characters, an error message should be appended and a log of the passed <paramref name="level"/> severity would be created.
105+
/// </summary>
106+
/// <param name="value">The value that should be validated.</param>
107+
/// <param name="className">The name of the class where the <paramref name="methodName"/> is defined.</param>
108+
/// <param name="methodName">The name of he method where <paramref name="value"/> is used.</param>
109+
/// <param name="propertyName">The name of the property.</param>
110+
/// <param name="level">The logging severity.</param>
111+
public void ValidateNullOrWhitespace(string value, string className, string methodName, string propertyName, LogLevel level = LogLevel.Error)
112+
{
113+
// If the passed value is null, empty or consists only of whitespace characters, log and append an error message.
114+
if (string.IsNullOrWhiteSpace(value) == false) return;
115+
116+
var errorMessage = $"{className}, {methodName} - The {propertyName} is null, empty or consists only of whitespace characters.";
117+
this.AppendErrorMessage(errorMessage, logLevel: level);
118+
}
119+
120+
/// <summary>
121+
/// Use this method to check if a value is not null.
122+
/// If you want to validate that an entity exists, use the "ValidateExist" extension method.
123+
/// If you want to validate that the currently authenticated user is not null, use the "ValidateUser" extension method.
124+
/// If <paramref name="value"/> is null, an error message should be appended and a log of the passed <paramref name="level"/> severity would be created.
125+
/// </summary>
126+
/// <param name="value">The value that should be validated.</param>
127+
/// <param name="className">The name of the class where the <paramref name="methodName"/> is defined.</param>
128+
/// <param name="methodName">The name of he method where <paramref name="value"/> is used.</param>
129+
/// <param name="propertyName">The name of the property.</param>
130+
/// <param name="level">The logging severity.</param>
131+
public void ValidateNull(object value, string className, string methodName, string propertyName, LogLevel level = LogLevel.Error)
132+
{
133+
// If the passed value is null, log and append an error message.
134+
if (value != null) return;
135+
136+
var errorMessage = $"{className}, {methodName} - The {propertyName} is null.";
137+
this.AppendErrorMessage(errorMessage, logLevel: level);
138+
}
139+
140+
/// <summary>
141+
/// Use this method to check if a value is equal to its default value.
142+
/// If <paramref name="value"/> is equal to its default value, an error message should be appended and a log of the passed <paramref name="level"/> severity would be created.
143+
/// </summary>
144+
/// <typeparam name="TValue">The type of the <paramref name="value"/>.</typeparam>
145+
/// <param name="value">The value that should be validated.</param>
146+
/// <param name="className">The name of the class where the <paramref name="methodName"/> is defined.</param>
147+
/// <param name="methodName">The name of he method where <paramref name="value"/> is used.</param>
148+
/// <param name="propertyName">The name of the property.</param>
149+
/// <param name="level">The logging severity.</param>
150+
public void ValidateDefault<TValue>(TValue value, string className, string methodName, string propertyName, LogLevel level = LogLevel.Error)
151+
where TValue : struct, IEquatable<TValue>
152+
{
153+
// If the passed value is null, log and append an error message.
154+
if (value.Equals(default) == false) return;
155+
156+
var errorMessage = $"{className}, {methodName} - The {propertyName} has a default value.";
157+
this.AppendErrorMessage(errorMessage, logLevel: level);
158+
}
159+
160+
/// <summary>
161+
/// Use this method to check if a value is not null and not an empty collection.
162+
/// If <paramref name="value"/> is null or an empty collection, an error message should be appended and a log of the passed <paramref name="level"/> severity would be created.
163+
/// </summary>
164+
/// <typeparam name="T">The type of the underlying entities that are stored within the requested collection.</typeparam>
165+
/// <param name="value">The collection that should be validated.</param>
166+
/// <param name="className">The name of the class where the <paramref name="methodName"/> is defined.</param>
167+
/// <param name="methodName">The name of he method where <paramref name="value"/> is used.</param>
168+
/// <param name="identifierPropertyName">The name of the entity's unique identifier property.</param>
169+
/// <param name="level">The logging severity.</param>
170+
public void ValidateAny<T>(IEnumerable<T> value, string className, string methodName, string identifierPropertyName, LogLevel level = LogLevel.Error)
171+
{
172+
// If the passed value is null, log and append an error message.
173+
if (value == null)
174+
{
175+
var errorMessage = $"{className}, {methodName} - An object with that {identifierPropertyName} is null.";
176+
this.AppendErrorMessage(errorMessage, logLevel: level);
177+
}
178+
179+
// If the passed value is an empty collection, log and append an error message.
180+
else if (!value.Any())
181+
{
182+
var errorMessage = $"{className}, {methodName} - The collection with that {identifierPropertyName} is empty.";
183+
this.AppendErrorMessage(errorMessage, logLevel: level);
184+
}
185+
}
186+
187+
/// <summary>
188+
/// Use this method to get a string with all error messages.
189+
/// </summary>
190+
/// <returns>All error messages, joined with a new line character.</returns>
191+
public override string ToString() => string.Join(Environment.NewLine, this.Errors);
192+
193+
private static LogLevel GetLogLevel(LogLevel? optionalLevel) => optionalLevel ?? LogLevel.Error;
194+
195+
private void AppendError(Error error, LogLevel? logLevel)
196+
{
197+
this.AppendError(error);
198+
199+
if (this._logger is not null)
200+
{
201+
this._logger.Log(GetLogLevel(logLevel), error.Message);
202+
}
203+
}
204+
205+
private void AppendError(Error error) => this._errors.Add(error);
206+
}
207+
208+
/// <summary>
209+
/// A class for a system operation result with a generic result object type.
210+
/// </summary>
211+
public class OperationResult<TResult> : OperationResult
212+
{
213+
/// <summary>
214+
/// Initializes a new instance of the <see cref="OperationResult"/> class.
215+
/// </summary>
216+
/// <param name="loggerService">An instance of <see cref="ILoggerService"/>.</param>
217+
/// <remarks>If the operation is a get operation, an empty result must return a truthy Success value.</remarks>
218+
public OperationResult(ILogger loggerService) : base(loggerService)
219+
{
220+
}
221+
222+
/// <summary>
223+
/// Initializes a new instance of the <see cref="OperationResult"/> class and sets the passed result object. Internally, this will set the Success result to True.
224+
/// </summary>
225+
/// <param name="resultObject">An initial failure message for the operation result. This will fail the success status.</param>
226+
/// <param name="loggerService">An instance of <see cref="ILoggerService"/>.</param>
227+
/// <remarks>If the operation is a get operation, an empty result must return a truthy Success value.</remarks>
228+
public OperationResult(ILogger loggerService, TResult resultObject)
229+
: base(loggerService)
230+
{
231+
this.RelatedObject = resultObject;
232+
}
233+
234+
/// <summary>
235+
/// Gets or sets the related object of the operation.
236+
/// </summary>
237+
public TResult? RelatedObject { get; set; }
238+
}
239+
}

0 commit comments

Comments
 (0)