Skip to content

Commit

Permalink
New overloads and changes to underlying collections. (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
jscarle authored Jul 5, 2024
1 parent 4a0d78a commit dff7e47
Show file tree
Hide file tree
Showing 25 changed files with 1,120 additions and 442 deletions.
14 changes: 12 additions & 2 deletions src/LightResults/Common/IActionableResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public interface IActionableResult<out TResult> : IResult
/// <param name="errorMessage">The error message associated with the failure.</param>
/// <param name="metadata">The metadata associated with the failure.</param>
/// <returns>A new instance of <typeparamref name="TResult" /> representing a failed result with the specified error message and metadata.</returns>
static abstract TResult Fail(string errorMessage, IDictionary<string, object> metadata);
static abstract TResult Fail(string errorMessage, IReadOnlyDictionary<string, object> metadata);

/// <summary>Creates a failed result with the given error.</summary>
/// <param name="error">The error associated with the failure.</param>
Expand All @@ -39,6 +39,11 @@ public interface IActionableResult<out TResult> : IResult
/// <param name="errors">A collection of errors associated with the failure.</param>
/// <returns>A new instance of <typeparamref name="TResult" /> representing a failed result with the specified errors.</returns>
static abstract TResult Fail(IEnumerable<IError> errors);

/// <summary>Creates a failed result with the given errors.</summary>
/// <param name="errors">A collection of errors associated with the failure.</param>
/// <returns>A new instance of <typeparamref name="TResult" /> representing a failed result with the specified errors.</returns>
static abstract TResult Fail(IReadOnlyList<IError> errors);
}

/// <summary>Defines an actionable result.</summary>
Expand Down Expand Up @@ -69,7 +74,7 @@ public interface IActionableResult<TValue, out TResult> : IResult<TValue>
/// <param name="errorMessage">The error message associated with the failure.</param>
/// <param name="metadata">The metadata associated with the failure.</param>
/// <returns>A new instance of <typeparamref name="TResult" /> representing a failed result with the specified error message and metadata.</returns>
static abstract TResult Fail(string errorMessage, IDictionary<string, object> metadata);
static abstract TResult Fail(string errorMessage, IReadOnlyDictionary<string, object> metadata);

/// <summary>Creates a failed result with the given error.</summary>
/// <param name="error">The error associated with the failure.</param>
Expand All @@ -80,5 +85,10 @@ public interface IActionableResult<TValue, out TResult> : IResult<TValue>
/// <param name="errors">A collection of errors associated with the failure.</param>
/// <returns>A new instance of <typeparamref name="TResult" /> representing a failed result with the specified errors.</returns>
static abstract TResult Fail(IEnumerable<IError> errors);

/// <summary>Creates a failed result with the given errors.</summary>
/// <param name="errors">A collection of errors associated with the failure.</param>
/// <returns>A new instance of <typeparamref name="TResult" /> representing a failed result with the specified errors.</returns>
static abstract TResult Fail(IReadOnlyList<IError> errors);
}
#endif
67 changes: 24 additions & 43 deletions src/LightResults/Error.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,11 @@
using System.Collections.Immutable;
using LightResults.Common;
#if NET8_0_OR_GREATER
using System.Collections.Frozen;
#endif
using LightResults.Common;

namespace LightResults;

/// <summary>Represents an error with a message and associated metadata.</summary>
public class Error : IError
{
/// <summary>Gets an empty error.</summary>
public static IError Empty { get; } = new Error();

internal static IReadOnlyCollection<IError> EmptyCollection { get; } = ImmutableArray<IError>.Empty;
internal static IReadOnlyCollection<IError> DefaultCollection { get; } = ImmutableArray.Create(Empty);

/// <inheritdoc/>
public string Message { get; }

/// <inheritdoc/>
public IReadOnlyDictionary<string, object> Metadata => _metadata;

#if NET8_0_OR_GREATER
private readonly FrozenDictionary<string, object> _metadata;
#else
private readonly ImmutableDictionary<string, object> _metadata;
#endif
private static readonly IReadOnlyDictionary<string, object> EmptyMetaData = new Dictionary<string, object>();

/// <summary>Initializes a new instance of the <see cref="Error"/> class.</summary>
public Error()
Expand All @@ -38,11 +18,7 @@ public Error()
public Error(string message)
{
Message = message;
#if NET8_0_OR_GREATER
_metadata = FrozenDictionary<string, object>.Empty;
#else
_metadata = ImmutableDictionary<string, object>.Empty;
#endif
Metadata = EmptyMetaData;
}

/// <summary>Initializes a new instance of the <see cref="Error"/> class with the specified metadata.</summary>
Expand All @@ -58,40 +34,45 @@ public Error((string Key, object Value) metadata)
public Error(string message, (string Key, object Value) metadata)
{
Message = message;
#if NET8_0_OR_GREATER
var dictionary = new Dictionary<string, object> { { metadata.Key, metadata.Value } };
_metadata = dictionary.ToFrozenDictionary();
#else
var builder = ImmutableDictionary.CreateBuilder<string, object>();
builder.Add(metadata.Key, metadata.Value);
_metadata = builder.ToImmutable();
#endif
Metadata = new Dictionary<string, object>(1)
{
{ metadata.Key, metadata.Value },
};
}

/// <summary>Initializes a new instance of the <see cref="Error"/> class with the specified metadata.</summary>
/// <param name="metadata">The metadata associated with the error.</param>
public Error(IDictionary<string, object> metadata)
public Error(IReadOnlyDictionary<string, object> metadata)
: this("", metadata)
{
}

/// <summary>Initializes a new instance of the <see cref="Error"/> class with the specified error message and metadata.</summary>
/// <param name="message">The error message.</param>
/// <param name="metadata">The metadata associated with the error.</param>
public Error(string message, IDictionary<string, object> metadata)
public Error(string message, IReadOnlyDictionary<string, object> metadata)
{
Message = message;
#if NET8_0_OR_GREATER
_metadata = metadata.ToFrozenDictionary();
#else
_metadata = metadata.ToImmutableDictionary();
#endif
Metadata = metadata;
}

/// <summary>Gets an empty error.</summary>
public static IError Empty { get; } = new Error();

internal static IReadOnlyList<IError> EmptyErrorList { get; } = [];
internal static IReadOnlyList<IError> DefaultErrorList { get; } = [Empty];

/// <inheritdoc/>
public string Message { get; }

/// <inheritdoc/>
public IReadOnlyDictionary<string, object> Metadata { get; }

/// <inheritdoc/>
public override string ToString()
{
var errorType = GetType().Name;
var errorType = GetType()
.Name;

if (Message.Length == 0)
return errorType;
Expand Down
2 changes: 1 addition & 1 deletion src/LightResults/IResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public interface IResult
{
/// <summary>Gets a collection of errors associated with the result.</summary>
/// <returns>An <see cref="IReadOnlyCollection{T}"/> of <see cref="IError"/> representing the errors.</returns>
IReadOnlyCollection<IError> Errors { get; }
IReadOnlyList<IError> Errors { get; }

/// <summary>Gets whether the result was successful or not.</summary>
/// <returns><c>true</c> if the result was successful; otherwise, <c>false</c>.</returns>
Expand Down
6 changes: 3 additions & 3 deletions src/LightResults/LightResults.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.27.0.93347">
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.28.0.94264">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand All @@ -37,7 +37,7 @@
<!-- Output -->
<PropertyGroup>
<AssemblyName>LightResults</AssemblyName>
<Version>9.0.0-preview.11</Version>
<Version>9.0.0-preview.12</Version>
<AssemblyVersion>9.0.0.0</AssemblyVersion>
<FileVersion>9.0.0.0</FileVersion>
<NeutralLanguage>en-US</NeutralLanguage>
Expand Down Expand Up @@ -120,7 +120,7 @@
<Compile Update="Result`1.Ok.cs">
<DependentUpon>Result`1.cs</DependentUpon>
</Compile>
<Compile Update="Result`1.AsFailed.cs">
<Compile Update="Result`1.ToFailed.cs">
<DependentUpon>Result`1.cs</DependentUpon>
</Compile>
<Compile Update="Result`1.ToString.cs">
Expand Down
2 changes: 2 additions & 0 deletions src/LightResults/MigrationNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ IsFailed property => IsFailed method
Error property => gone
Result<TValue>.Ok() and Result<TValue>.Fail() methods => gone
IsIActionableResult still worth it?
Changes to Errors Interface
Changes to Metadata method overloads
4 changes: 2 additions & 2 deletions src/LightResults/Result.Equality.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ partial struct Result
/// <returns><c>true</c> if the specified <see cref="Result"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(Result other)
{
return Nullable.Equals(_errors, other._errors);
return Equals(_errors, other._errors);
}

/// <summary>Determines whether the specified object is equal to this instance.</summary>
Expand All @@ -22,7 +22,7 @@ public override bool Equals(object? obj)
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
return _errors.GetHashCode();
return _errors?.GetHashCode() ?? 0;
}

/// <summary>Determines whether two <see cref="Result"/> instances are equal.</summary>
Expand Down
26 changes: 11 additions & 15 deletions src/LightResults/Result.Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ namespace LightResults;
partial struct Result
{
/// <inheritdoc/>
public IReadOnlyCollection<IError> Errors
public IReadOnlyList<IError> Errors
{
get
{
if (_errors.HasValue)
if (_errors is not null)
return _errors;

if (_isSuccess)
return Error.EmptyCollection;
return Error.EmptyErrorList;

return Error.DefaultCollection;
return Error.DefaultErrorList;
}
}

Expand All @@ -26,18 +26,16 @@ public bool HasError<TError>()
if (_isSuccess)
return false;

if (_errors.HasValue)
{
if (_errors is not null)
// Do not convert to LINQ, this creates unnecessary heap allocations.
// For is the most efficient way to loop. It is the fastest and does not allocate.
// ReSharper disable once ForCanBeConvertedToForeach
// ReSharper disable once LoopCanBeConvertedToQuery
for (var index = 0; index < _errors.Value.Length; index++)
for (var index = 0; index < _errors.Count; index++)
{
if (_errors.Value[index] is TError)
if (_errors[index] is TError)
return true;
}
}

return typeof(TError) == typeof(Error);
}
Expand All @@ -52,21 +50,19 @@ public bool HasError<TError>([MaybeNullWhen(false)] out TError error)
return false;
}

if (_errors.HasValue)
{
if (_errors is not null)
// Do not convert to LINQ, this creates unnecessary heap allocations.
// For is the most efficient way to loop. It is the fastest and does not allocate.
// ReSharper disable once ForCanBeConvertedToForeach
// ReSharper disable once LoopCanBeConvertedToQuery
for (var index = 0; index < _errors.Value.Length; index++)
for (var index = 0; index < _errors.Count; index++)
{
if (_errors.Value[index] is not TError)
if (_errors[index] is not TError tError)
continue;

error = (TError)_errors.Value[index];
error = tError;
return true;
}
}

if (typeof(TError) == typeof(Error))
{
Expand Down
10 changes: 9 additions & 1 deletion src/LightResults/Result.Fail.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static Result Fail(string errorMessage, (string Key, object Value) metada
/// <param name="metadata">The metadata associated with the failure.</param>
/// <param name="errorMessage">The error message associated with the failure.</param>
/// <returns>A new instance of <see cref="Result"/> representing a failed result with the specified error message and metadata.</returns>
public static Result Fail(string errorMessage, IDictionary<string, object> metadata)
public static Result Fail(string errorMessage, IReadOnlyDictionary<string, object> metadata)
{
var error = new Error(errorMessage, metadata);
return new Result(error);
Expand All @@ -55,4 +55,12 @@ public static Result Fail(IEnumerable<IError> errors)
{
return new Result(errors);
}

/// <summary>Creates a failed result with the given errors.</summary>
/// <param name="errors">A collection of errors associated with the failure.</param>
/// <returns>A new instance of <see cref="Result"/> representing a failed result with the specified errors.</returns>
public static Result Fail(IReadOnlyList<IError> errors)
{
return new Result(errors);
}
}
11 changes: 10 additions & 1 deletion src/LightResults/Result.Fail`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static Result<TValue> Fail<TValue>(string errorMessage, (string Key, obje
/// <param name="metadata">The metadata associated with the failure.</param>
/// <typeparam name="TValue">The type of the value of the result.</typeparam>
/// <returns>A new instance of <see cref="Result{TValue}"/> representing a failed result with the specified error message and metadata.</returns>
public static Result<TValue> Fail<TValue>(string errorMessage, IDictionary<string, object> metadata)
public static Result<TValue> Fail<TValue>(string errorMessage, IReadOnlyDictionary<string, object> metadata)
{
var error = new Error(errorMessage, metadata);
return new Result<TValue>(error);
Expand All @@ -59,4 +59,13 @@ public static Result<TValue> Fail<TValue>(IEnumerable<IError> errors)
{
return new Result<TValue>(errors);
}

/// <summary>Creates a failed result with the given errors.</summary>
/// <param name="errors">A collection of errors associated with the failure.</param>
/// <typeparam name="TValue">The type of the value of the result.</typeparam>
/// <returns>A new instance of <see cref="Result{TValue}"/> representing a failed result with the specified errors.</returns>
public static Result<TValue> Fail<TValue>(IReadOnlyList<IError> errors)
{
return new Result<TValue>(errors);
}
}
4 changes: 2 additions & 2 deletions src/LightResults/Result.IsFailed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public bool IsFailed([MaybeNullWhen(false)] out IError error)
{
if (_isSuccess)
error = default;
else if (_errors.HasValue)
error = _errors.Value[0];
else if (_errors is not null)
error = _errors[0];
else
error = Error.Empty;

Expand Down
4 changes: 2 additions & 2 deletions src/LightResults/Result.ToString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public override string ToString()
if (_isSuccess)
return $"{nameof(Result)} {{ IsSuccess = True }}";

if (_errors.HasValue && _errors.Value[0].Message.Length > 0)
return StringHelper.GetResultErrorString(_errors.Value[0].Message);
if (_errors is not null && _errors[0].Message.Length > 0)
return StringHelper.GetResultErrorString(_errors[0].Message);

return $"{nameof(Result)} {{ IsSuccess = False }}";
}
Expand Down
13 changes: 6 additions & 7 deletions src/LightResults/Result.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Immutable;
#if NET7_0_OR_GREATER
using LightResults.Common;
#endif
Expand All @@ -14,7 +13,7 @@ namespace LightResults;
#endif
{
private readonly bool _isSuccess = false;
private readonly ImmutableArray<IError>? _errors;
private readonly IReadOnlyList<IError>? _errors;

private Result(bool isSuccess)
{
Expand All @@ -23,16 +22,16 @@ private Result(bool isSuccess)

private Result(IError error)
{
_errors = ImmutableArray.Create(error);
_errors = [error];
}

internal Result(ImmutableArray<IError> errors)
private Result(IEnumerable<IError> errors)
{
_errors = errors;
_errors = errors.ToArray();
}

private Result(IEnumerable<IError> errors)
internal Result(IReadOnlyList<IError> errors)
{
_errors = errors.ToImmutableArray();
_errors = errors;
}
}
Loading

0 comments on commit dff7e47

Please sign in to comment.