Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Migrate from Errors to Exceptions for invalid use of library #105

Merged
merged 13 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update packages and update all unexpected scnearios to throw an excep…
…tion
  • Loading branch information
amantinband committed May 9, 2024
commit 62ee3b46ad23ca95662fcb1216810970c57c16b0
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ dotnet_diagnostic.IDE0130.severity = none

# SA1642: Constructor summary documentation should begin with standard text
dotnet_diagnostic.SA1642.severity = none

# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Project>

<PropertyGroup>
<LangVersion>10.0</LangVersion>
<LangVersion>12.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand All @@ -12,7 +12,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Expand Down
3 changes: 3 additions & 0 deletions src/ErrorOr.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -24,6 +26,7 @@
<ItemGroup>
<None Include="../assets/icon-square.png" Pack="true" Visible="false" PackagePath="" />
<None Include="../README.md" Pack="true" PackagePath="" />
<None Include="bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml" Pack="true" PackagePath="lib\$(TargetFramework)\" />
<None Include="Stylecop.json" />
<AdditionalFiles Include="Stylecop.json" />
</ItemGroup>
Expand Down
3 changes: 0 additions & 3 deletions src/ErrorOr/ErrorOr.Else.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
/// <summary>
Expand Down
118 changes: 59 additions & 59 deletions src/ErrorOr/ErrorOr.Equality.cs
Original file line number Diff line number Diff line change
@@ -1,59 +1,59 @@
namespace ErrorOr;

public readonly partial record struct ErrorOr<TValue>
{
public bool Equals(ErrorOr<TValue> other)
{
if (!IsError)
{
return !other.IsError && EqualityComparer<TValue>.Default.Equals(_value, other._value);
}

return other.IsError && CheckIfErrorsAreEqual(_errors, other._errors);
}

private static bool CheckIfErrorsAreEqual(List<Error> errors1, List<Error> errors2)
{
// This method is currently implemented with strict ordering in mind, so the errors
// of the two lists need to be in the exact same order.
// This avoids allocating a hash set. We could provide a dedicated EqualityComparer for
// ErrorOr<TValue> when arbitrary orders should be supported.
if (ReferenceEquals(errors1, errors2))
{
return true;
}

if (errors1.Count != errors2.Count)
{
return false;
}

for (var i = 0; i < errors1.Count; i++)
{
if (!errors1[i].Equals(errors2[i]))
{
return false;
}
}

return true;
}

public override int GetHashCode()
{
if (!IsError)
{
return _value.GetHashCode();
}

#pragma warning disable SA1129 // HashCode needs to be instantiated this way
var hashCode = new HashCode();
#pragma warning restore SA1129
for (var i = 0; i < _errors.Count; i++)
{
hashCode.Add(_errors[i]);
}

return hashCode.ToHashCode();
}
}
namespace ErrorOr;
public readonly partial record struct ErrorOr<TValue>
{
public bool Equals(ErrorOr<TValue> other)
{
if (!IsError)
{
return !other.IsError && EqualityComparer<TValue>.Default.Equals(_value, other._value);
}
return other.IsError && CheckIfErrorsAreEqual(_errors, other._errors);
}
public override int GetHashCode()
{
if (!IsError)
{
return _value.GetHashCode();
}
#pragma warning disable SA1129 // HashCode needs to be instantiated this way
var hashCode = new HashCode();
#pragma warning restore SA1129
for (var i = 0; i < _errors.Count; i++)
{
hashCode.Add(_errors[i]);
}
return hashCode.ToHashCode();
}
private static bool CheckIfErrorsAreEqual(List<Error> errors1, List<Error> errors2)
{
// This method is currently implemented with strict ordering in mind, so the errors
// of the two lists need to be in the exact same order.
// This avoids allocating a hash set. We could provide a dedicated EqualityComparer for
// ErrorOr<TValue> when arbitrary orders should be supported.
if (ReferenceEquals(errors1, errors2))
{
return true;
}
if (errors1.Count != errors2.Count)
{
return false;
}
for (var i = 0; i < errors1.Count; i++)
{
if (!errors1[i].Equals(errors2[i]))
{
return false;
}
}
return true;
}
}
3 changes: 0 additions & 3 deletions src/ErrorOr/ErrorOr.FailIf.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
/// <summary>
Expand Down
3 changes: 0 additions & 3 deletions src/ErrorOr/ErrorOr.ImplicitConverters.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
/// <summary>
Expand Down
3 changes: 0 additions & 3 deletions src/ErrorOr/ErrorOr.Match.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
/// <summary>
Expand Down
3 changes: 0 additions & 3 deletions src/ErrorOr/ErrorOr.Switch.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
/// <summary>
Expand Down
3 changes: 0 additions & 3 deletions src/ErrorOr/ErrorOr.Then.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
namespace ErrorOr;

/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
/// <summary>
Expand Down
57 changes: 29 additions & 28 deletions src/ErrorOr/ErrorOr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ namespace ErrorOr;
/// <summary>
/// A discriminated union of errors or a value.
/// </summary>
/// <typeparam name="TValue">The type of the underlying <see cref="Value"/>.</typeparam>
public readonly partial record struct ErrorOr<TValue> : IErrorOr<TValue>
{
private readonly TValue? _value = default;
private readonly List<Error>? _errors = null;

/// <summary>
/// Prevents a default <see cref="ErrorOr"/> struct from being created.
/// </summary>
Expand All @@ -15,8 +19,23 @@ public ErrorOr()
throw new InvalidOperationException("Default construction of ErrorOr<TValue> is invalid. Please use provided factory methods to instantiate.");
}

private readonly TValue? _value = default;
private readonly List<Error>? _errors = null;
private ErrorOr(Error error)
{
_errors = new List<Error> { error };
IsError = true;
}

private ErrorOr(List<Error> errors)
{
_errors = errors;
IsError = true;
}

private ErrorOr(TValue value)
{
_value = value;
IsError = false;
}

/// <summary>
/// Gets a value indicating whether the state is error.
Expand All @@ -30,20 +49,12 @@ public ErrorOr()
/// <summary>
/// Gets the list of errors. If the state is not error, the list will contain a single error representing the state.
/// </summary>
public List<Error> Errors => IsError ? _errors! : KnownErrors.CachedNoErrorsList;
public List<Error> Errors => IsError ? _errors! : throw new InvalidOperationException("The Errors property cannot be accessed when no errors have been recorded. Check IsError before accessing Errors.");

/// <summary>
/// Gets the list of errors. If the state is not error, the list will be empty.
/// </summary>
public List<Error> ErrorsOrEmptyList => IsError ? _errors! : KnownErrors.CachedEmptyErrorsList;

/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
public static ErrorOr<TValue> From(List<Error> errors)
{
return errors;
}
public List<Error> ErrorsOrEmptyList => IsError ? _errors! : [];

/// <summary>
/// Gets the value.
Expand All @@ -59,28 +70,18 @@ public Error FirstError
{
if (!IsError)
{
return KnownErrors.NoFirstError;
throw new InvalidOperationException("The FirstError property cannot be accessed when no errors have been recorded. Check IsError before accessing FirstError.");
}

return _errors![0];
}
}

private ErrorOr(Error error)
{
_errors = new List<Error> { error };
IsError = true;
}

private ErrorOr(List<Error> errors)
{
_errors = errors;
IsError = true;
}

private ErrorOr(TValue value)
/// <summary>
/// Creates an <see cref="ErrorOr{TValue}"/> from a list of errors.
/// </summary>
public static ErrorOr<TValue> From(List<Error> errors)
{
_value = value;
IsError = false;
return errors;
}
}
60 changes: 30 additions & 30 deletions src/Errors/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ namespace ErrorOr;
/// </summary>
public readonly record struct Error
{
private Error(string code, string description, ErrorType type, Dictionary<string, object>? metadata)
{
Code = code;
Description = description;
Type = type;
NumericType = (int)type;
Metadata = metadata;
}

/// <summary>
/// Gets the unique error code.
/// </summary>
Expand Down Expand Up @@ -129,15 +138,6 @@ public static Error Custom(
Dictionary<string, object>? metadata = null) =>
new(code, description, (ErrorType)type, metadata);

private Error(string code, string description, ErrorType type, Dictionary<string, object>? metadata)
{
Code = code;
Description = description;
Type = type;
NumericType = (int)type;
Metadata = metadata;
}

public bool Equals(Error other)
{
if (Type != other.Type ||
Expand All @@ -156,6 +156,27 @@ public bool Equals(Error other)
return other.Metadata is not null && CompareMetadata(Metadata, other.Metadata);
}

public override int GetHashCode() =>
Metadata is null ? HashCode.Combine(Code, Description, Type, NumericType) : ComposeHashCode();

private int ComposeHashCode()
{
#pragma warning disable SA1129 // HashCode needs to be instantiated this way
var hashCode = new HashCode();
#pragma warning restore SA1129
hashCode.Add(Code);
hashCode.Add(Description);
hashCode.Add(Type);
hashCode.Add(NumericType);
foreach (var keyValuePair in Metadata!)
{
hashCode.Add(keyValuePair.Key);
hashCode.Add(keyValuePair.Value);
}

return hashCode.ToHashCode();
}

private static bool CompareMetadata(Dictionary<string, object> metadata, Dictionary<string, object> otherMetadata)
{
if (ReferenceEquals(metadata, otherMetadata))
Expand All @@ -179,25 +200,4 @@ private static bool CompareMetadata(Dictionary<string, object> metadata, Diction

return true;
}

public override int GetHashCode() =>
Metadata is null ? HashCode.Combine(Code, Description, Type, NumericType) : ComposeHashCode();

private int ComposeHashCode()
{
#pragma warning disable SA1129 // HashCode needs to be instantiated this way
var hashCode = new HashCode();
#pragma warning restore SA1129
hashCode.Add(Code);
hashCode.Add(Description);
hashCode.Add(Type);
hashCode.Add(NumericType);
foreach (var keyValuePair in Metadata!)
{
hashCode.Add(keyValuePair.Key);
hashCode.Add(keyValuePair.Value);
}

return hashCode.ToHashCode();
}
}
Loading
Loading