Skip to content

Commit

Permalink
Orleans.Results: updated to 1.0.0-preview.2
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentH-Net committed Aug 29, 2022
1 parent 9484ff5 commit 350ac84
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 33 deletions.
8 changes: 7 additions & 1 deletion src/Orleans.Results/ErrorCode.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
namespace Example;

[Flags]
public enum ErrorCode
{
UserNotFound = 1
UserNotFound = 1,
NoUsersAtAddress = 2,

ValidationError = 1024,
InvalidZipCode = 2 | ValidationError,
InvalidHouseNr = 3 | ValidationError,
}
98 changes: 66 additions & 32 deletions src/Orleans.Results/Result.cs
Original file line number Diff line number Diff line change
@@ -1,64 +1,73 @@
// Version: 1.0.0-preview.1 (Using https://semver.org/)
// Updated: 2022-06-12
// Version: 1.0.0-preview.2 (Using https://semver.org/)
// Updated: 2022-08-26
// See https://github.com/Applicita/Orleans.Results for updates to this file.

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Orleans;

namespace Example;

/// <summary>
/// Result without value; use to return either <see cref="Ok"/> or <see cref="ResultBase{ErrorCode}.Error"/>(s)
/// </summary>
[GenerateSerializer, Immutable]
public class Result : ResultBase<ErrorCode>
{
public static Result Ok { get; } = new();

public Result(ImmutableArray<Error> errors) : base(errors) { }
public Result(IEnumerable<Error> errors) : base(ImmutableArray.CreateRange(errors)) { }
Result() { }
Result(Error error) : base(error) { }
Result(ImmutableArray<Error> errors) : base(errors) { }

public static implicit operator Result(Error error) => new(error);
public static implicit operator Result(ErrorCode code) => new(code);
public static implicit operator Result((ErrorCode code, string message) error) => new(error);

public Result With(Error error) => new(Errors.Add(error));
public Result With(ErrorCode code) => new(Errors.Add(code));
public Result With(ErrorCode code, string message) => new(Errors.Add((code, message)));
public static implicit operator Result(List<Error> errors) => new(errors);
}

[GenerateSerializer, Immutable]
public class Result<T> : ResultBase<ErrorCode, T>
/// <summary>
/// Result with value; use to return either a <typeparamref name="TValue"/> or <see cref="ResultBase{ErrorCode}.Error"/>(s)
/// </summary>
[GenerateSerializer]
public class Result<TValue> : ResultBase<ErrorCode, TValue>
{
Result(T value) : base(value) { }
public Result(ImmutableArray<Error> errors) : base(errors) { }
public Result(IEnumerable<Error> errors) : base(ImmutableArray.CreateRange(errors)) { }
Result(TValue value) : base(value) { }
Result(Error error) : base(error) { }
Result(ImmutableArray<Error> errors) : base(errors) { }

public static implicit operator Result<T>(T value) => new(value);
public static implicit operator Result<T>(Error error) => new(error);
public static implicit operator Result<T>(ErrorCode code) => new(code);
public static implicit operator Result<T>((ErrorCode code, string message) error) => new(error);

public Result<T> With(Error error) => new(Errors.Add(error));
public Result<T> With(ErrorCode code) => new(Errors.Add(code));
public Result<T> With(ErrorCode code, string message) => new(Errors.Add((code, message)));
public static implicit operator Result<TValue>(TValue value) => new(value);
public static implicit operator Result<TValue>(Error error) => new(error);
public static implicit operator Result<TValue>(ErrorCode code) => new(code);
public static implicit operator Result<TValue>((ErrorCode code, string message) error) => new(error);
public static implicit operator Result<TValue>(List<Error> errors) => new(errors);
}

[GenerateSerializer, Immutable]
public abstract class ResultBase<TErrorCode, T> : ResultBase<TErrorCode> where TErrorCode : Enum
[GenerateSerializer]
public abstract class ResultBase<TErrorCode, TValue> : ResultBase<TErrorCode> where TErrorCode : Enum
{
[Id(0)] T? value;
[Id(0)] TValue? value;

protected ResultBase(T value) => this.value = value;
protected ResultBase(TValue value) => this.value = value;
protected ResultBase(Error error) : base(error) { }
protected ResultBase(ImmutableArray<Error> errors) : base(errors) { }

public T? ValueOrDefault => value;
/// <summary>
/// Returns the value for a success result, or the <typeparamref name="TValue"/> default for a failed result
/// </summary>
public TValue? ValueOrDefault => value;

public T Value
/// <summary>
/// Get or set the value for a success result; throws <see cref="InvalidOperationException"/> for a failed result
/// </summary>
public TValue Value
{
get
{
ThrowIfFailed();
return value is null ? throw new InvalidOperationException("Attempt to access the value of an uninitialized result") : value;
return value!;
}

set
Expand All @@ -71,7 +80,7 @@ public T Value
void ThrowIfFailed() { if (IsFailed) throw new InvalidOperationException("Attempt to access the value of a failed result"); }
}

[GenerateSerializer, Immutable]
[GenerateSerializer]
public abstract class ResultBase<TErrorCode> where TErrorCode : Enum
{
public bool IsSuccess => !IsFailed;
Expand All @@ -80,22 +89,47 @@ public abstract class ResultBase<TErrorCode> where TErrorCode : Enum
[Id(0)]
readonly ImmutableArray<Error>? errors;

/// <summary>
/// Returns the errors for a failed result; throws an <see cref="InvalidOperationException"/> for a success result
/// </summary>
public ImmutableArray<Error> Errors => errors ?? throw new InvalidOperationException("Attempt to access the errors of a success result");

/// <summary>
/// Returns the errorcode for a failed result with a single error; otherwise throws an exception
/// </summary>
public TErrorCode ErrorCode => Errors.Single().Code;

/// <summary>
/// Returns all errors formatted in a single string for a failed result; throws an <see cref="InvalidOperationException"/> for a success result
/// </summary>
public string ErrorsText => string.Join(Environment.NewLine, Errors);

/// <remarks>Intended for use with <see cref="Microsoft.AspNetCore.Mvc.ValidationProblemDetails"/> (in controllers) or <see cref="Microsoft.AspNetCore.Http.Results.ValidationProblem"/> (in minimal api's) </remarks>
/// <summary>
/// Supports serializing validation errors into a https://tools.ietf.org/html/rfc7807 based problem details format
/// </summary>
/// <remarks>Intended for use with <see cref="Microsoft.AspNetCore.Mvc.ValidationProblemDetails"/> (in MVC controllers) or <see cref="Microsoft.AspNetCore.Http.Results.ValidationProblem"/> (in minimal api's) </remarks>
/// <param name="validationErrorFlag">The enum flag used to identify an error as a validation error</param>
/// <param name="validationErrors">If the return value is true, receives all errors in a dictionary suitable for serializing into a https://tools.ietf.org/html/rfc7807 based format; otherwise set to null</param>
/// <returns>True for a failed result that has the <paramref name="validationErrorFlag"/> set in the <typeparamref name="TErrorCode"/> for <b>all</b> errors; false otherwise</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0001:Simplify Names", Justification = "Full name is necessary to ensure link works independently of global usings")]
public Dictionary<string, string[]> ValidationErrors => new(
Errors.GroupBy(error => error.Code, error => error.Message)
.Select(group => new KeyValuePair<string, string[]>($"Error {group.Key}", group.ToArray())));
public bool TryAsValidationErrors(TErrorCode validationErrorFlag, [NotNullWhen(true)] out Dictionary<string, string[]>? validationErrors)
{
if (IsFailed && Errors.All(error => error.Code.HasFlag(validationErrorFlag)))
{
validationErrors = new(Errors
.GroupBy(error => error.Code, error => error.Message)
.Select(group => new KeyValuePair<string, string[]>(group.Key.ToString(), group.ToArray())));
return true;
}
validationErrors = null;
return false;
}

protected ResultBase() { }
protected ResultBase(Error error) => errors = ImmutableArray<Error>.Empty.Add(error);
protected ResultBase(Error error) => errors = ImmutableArray.Create(error);
protected ResultBase(ImmutableArray<Error> errors) => this.errors = errors;

/// <returns>A <see cref="NotImplementedException"/> with <paramref name="message"/> and <see cref="ErrorsText"/> for a failed result; <b>throws</b> an <see cref="InvalidOperationException"/> exception for a success result</returns>
public NotImplementedException UnhandledErrorException(string? message = null) => new($"{message}Unhandled error(s): " + ErrorsText);

[GenerateSerializer, Immutable]
Expand Down

0 comments on commit 350ac84

Please sign in to comment.