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

Release Candidate 0.2.0 #1

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 16 additions & 0 deletions src/Deveel.Results/IValidationError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;

namespace Deveel
{
/// <summary>
/// Represents an error that occurred while validating a
/// a command or operation.
/// </summary>
public interface IValidationError : IOperationError
{
/// <summary>
/// Gets the list of validation results that caused the error.
/// </summary>
IReadOnlyList<ValidationResult> ValidationResults { get; }
}
}
24 changes: 23 additions & 1 deletion src/Deveel.Results/OperationResult.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Deveel
using System.ComponentModel.DataAnnotations;

namespace Deveel
{
/// <summary>
/// An operation result that has no value returned.
Expand Down Expand Up @@ -71,6 +73,26 @@ public static OperationResult Fail(IOperationError error)
public static OperationResult Fail(string code, string domain, string? message = null, IOperationError? inner = null)
=> Fail(new OperationError(code, domain, message, inner));

/// <summary>
/// Creates a new instance of an operation result that has failed
/// because of a validation error.
/// </summary>
/// <param name="code">
/// The code of the error that has caused the operation to fail.
/// </param>
/// <param name="domain">
/// The domain where the error has occurred.
/// </param>
/// <param name="validationResults">
/// The list of validation results that have caused the operation to fail.
/// </param>
/// <returns>
/// Returns an instance of <see cref="OperationResult"/> that represents
/// a failed operation.
/// </returns>
public static OperationResult ValidationFailed(string code, string domain, IEnumerable<ValidationResult> validationResults)
=> Fail(new OperationValidationError(code, domain, validationResults.ToList()));

/// <summary>
/// Implicitly converts an instance of an error to an operation result.
/// </summary>
Expand Down
73 changes: 72 additions & 1 deletion src/Deveel.Results/OperationResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,94 @@
namespace Deveel
using System.ComponentModel.DataAnnotations;

namespace Deveel
{
/// <summary>
/// Extensions for the <see cref="IOperationResult"/> contract.
/// </summary>
public static class OperationResultExtensions
{
/// <summary>
/// Determines if the operation result is a success.
/// </summary>
/// <param name="result">
/// The operation result to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the operation result is a success,
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsSuccess(this IOperationResult result)
=> result.ResultType == OperationResultType.Success;

/// <summary>
/// Determines if the operation result is an error.
/// </summary>
/// <param name="result">
/// The operation result to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the operation result is an error,
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsError(this IOperationResult result)
=> result.ResultType == OperationResultType.Error;

/// <summary>
/// Determines if the operation result is cancelled.
/// </summary>
/// <param name="result">
/// The operation result to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the operation result is cancelled,
/// otherwise <see langword="false"/>.
/// </returns>
public static bool IsCancelled(this IOperationResult result)
=> result.ResultType == OperationResultType.Cancelled;

/// <summary>
/// Determines if the operation has caused no changes
/// to the state of an object.
/// </summary>
/// <param name="result">
/// The operation result to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the operation has not
/// caused any changes to the object, otherwise <see langword="false"/>.
/// </returns>
public static bool IsUnchanged(this IOperationResult result)
=> result.ResultType == OperationResultType.Unchanged;

/// <summary>
/// Checks if the operation result has validation errors.
/// </summary>
/// <param name="result">
/// The operation result to check.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the operation result is
/// an error and the type of the error is <see cref="IValidationError"/>,
/// otherwise <see langword="false"/>.
/// </returns>
public static bool HasValidationErrors(this IOperationResult result)
=> result.IsError() && (result.Error is IValidationError);

/// <summary>
/// Gets the list of validation results that caused an operation
/// to fail.
/// </summary>
/// <param name="result">
/// The result that contains the validation errors.
/// </param>
/// <returns>
/// Returns a list of <see cref="ValidationResult"/> objects that
/// describe the validation errors that caused the operation to fail.
/// </returns>
public static IReadOnlyList<ValidationResult> ValidationResults(this IOperationResult result)
=> result.HasValidationErrors() ? ((IValidationError)result.Error!).ValidationResults : Array.Empty<ValidationResult>();

public static TResult Match<TResult>(this IOperationResult result,

Check warning on line 91 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.Match<TResult>(IOperationResult, Func<TResult>?, Func<IOperationError?, TResult>?, Func<TResult>?)'

Check warning on line 91 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.Match<TResult>(IOperationResult, Func<TResult>?, Func<IOperationError?, TResult>?, Func<TResult>?)'

Check warning on line 91 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.Match<TResult>(IOperationResult, Func<TResult>?, Func<IOperationError?, TResult>?, Func<TResult>?)'

Check warning on line 91 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.Match<TResult>(IOperationResult, Func<TResult>?, Func<IOperationError?, TResult>?, Func<TResult>?)'

Check warning on line 91 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.Match<TResult>(IOperationResult, Func<TResult>?, Func<IOperationError?, TResult>?, Func<TResult>?)'

Check warning on line 91 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.Match<TResult>(IOperationResult, Func<TResult>?, Func<IOperationError?, TResult>?, Func<TResult>?)'
Func<TResult>? ifSuccess = null,
Func<IOperationError?, TResult>? ifError = null,
Func<TResult>? ifUnchanged = null)
Expand Down Expand Up @@ -45,7 +116,7 @@
throw new InvalidOperationException("The operation result is in an unknown state.");
}

public static Task<TResult> MatchAsync<TResult>(this IOperationResult result,

Check warning on line 119 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.MatchAsync<TResult>(IOperationResult, Func<Task<TResult>>?, Func<IOperationError?, Task<TResult>>?, Func<Task<TResult>>?)'

Check warning on line 119 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.MatchAsync<TResult>(IOperationResult, Func<Task<TResult>>?, Func<IOperationError?, Task<TResult>>?, Func<Task<TResult>>?)'

Check warning on line 119 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.MatchAsync<TResult>(IOperationResult, Func<Task<TResult>>?, Func<IOperationError?, Task<TResult>>?, Func<Task<TResult>>?)'

Check warning on line 119 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.MatchAsync<TResult>(IOperationResult, Func<Task<TResult>>?, Func<IOperationError?, Task<TResult>>?, Func<Task<TResult>>?)'

Check warning on line 119 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.MatchAsync<TResult>(IOperationResult, Func<Task<TResult>>?, Func<IOperationError?, Task<TResult>>?, Func<Task<TResult>>?)'

Check warning on line 119 in src/Deveel.Results/OperationResultExtensions.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResultExtensions.MatchAsync<TResult>(IOperationResult, Func<Task<TResult>>?, Func<IOperationError?, Task<TResult>>?, Func<Task<TResult>>?)'
Func<Task<TResult>>? ifSuccess = null,
Func<IOperationError?, Task<TResult>>? ifError = null,
Func<Task<TResult>>? ifUnchanged = null)
Expand Down
58 changes: 57 additions & 1 deletion src/Deveel.Results/OperationResult_T.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace Deveel
using System.ComponentModel.DataAnnotations;

namespace Deveel
{
public readonly struct OperationResult<T> : IOperationResult<T>

Check warning on line 5 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>'

Check warning on line 5 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>'

Check warning on line 5 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>'

Check warning on line 5 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>'

Check warning on line 5 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>'

Check warning on line 5 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>'
{
private OperationResult(OperationResultType resultType, T? value, IOperationError? error)
{
Expand Down Expand Up @@ -42,24 +44,78 @@
return new OperationResult<T>(OperationResultType.Success, value, null);
}

/// <summary>
/// Creates a new instance of an operation result that has failed
/// because of an error.
/// </summary>
/// <param name="error">
/// The error that caused the operation to fail.
/// </param>
/// <returns>
/// Returns an instance of <see cref="OperationResult{T}"/> that
/// represents a failure in the operation.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when the <paramref name="error"/> is <see langword="null"/>.
/// </exception>
public static OperationResult<T> Fail(IOperationError error)
{
ArgumentNullException.ThrowIfNull(error, nameof(error));
return new OperationResult<T>(OperationResultType.Error, default, error);
}

/// <summary>
/// Creates a new instance of an operation result that has failed
/// because of an error.
/// </summary>
/// <param name="code">
/// The code of the error that caused the operation to fail.
/// </param>
/// <param name="domain">
/// The domain where the error occurred.
/// </param>
/// <param name="message">
/// A message that describes the error.
/// </param>
/// <param name="inner">
/// An inner error that caused the error.
/// </param>
/// <returns>
/// Returns an instance of <see cref="OperationResult{T}"/> that
/// represents a failure in the operation.
/// </returns>
public static OperationResult<T> Fail(string code, string domain, string? message = null, IOperationError? inner = null)
{
ArgumentNullException.ThrowIfNull(code, nameof(code));
ArgumentNullException.ThrowIfNull(domain, nameof(domain));
return new OperationResult<T>(OperationResultType.Error, default, new OperationError(code, domain, message, inner));
}

/// <summary>
/// Creates a new instance of an operation result that has failed
/// because of a validation error.
/// </summary>
/// <param name="code">
/// The code of the error that caused the operation to fail.
/// </param>
/// <param name="domain">
/// The domain where the error occurred.
/// </param>
/// <param name="results">
/// The list of validation results that caused the error.
/// </param>
/// <returns>
/// Returns an instance of <see cref="OperationResult{T}"/> that
/// represents a failure in the operation.
/// </returns>
public static OperationResult<T> ValidationFailed(string code, string domain, IEnumerable<ValidationResult> results)
=> Fail(new OperationValidationError(code, domain, results.ToList()));

public static implicit operator OperationResult<T>(OperationResult result)

Check warning on line 114 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationResult)'

Check warning on line 114 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationResult)'

Check warning on line 114 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationResult)'

Check warning on line 114 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationResult)'

Check warning on line 114 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationResult)'

Check warning on line 114 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationResult)'
=> new OperationResult<T>(result.ResultType, default, result.Error);

public static implicit operator OperationResult<T>(T value) => Success(value);

Check warning on line 117 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(T)'

Check warning on line 117 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(T)'

Check warning on line 117 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(T)'

public static implicit operator OperationResult<T>(OperationException error) => Fail(error);

Check warning on line 119 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationException)'

Check warning on line 119 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (7.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationException)'

Check warning on line 119 in src/Deveel.Results/OperationResult_T.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Missing XML comment for publicly visible type or member 'OperationResult<T>.implicit operator OperationResult<T>(OperationException)'
}
}
50 changes: 50 additions & 0 deletions src/Deveel.Results/OperationValidationError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;

namespace Deveel
{
/// <summary>
/// An error that occurred during the validation of an operation.
/// </summary>
public readonly struct OperationValidationError : IValidationError
{
/// <summary>
/// Constructs an instance of an <see cref="OperationValidationError"/> object.
/// </summary>
/// <param name="code">
/// The code of the error, that is unique within the
/// given domain.
/// </param>
/// <param name="domain">
/// The domain where the error occurred.
/// </param>
/// <param name="validationResults">
/// A list of validation results that caused the error.
/// </param>
public OperationValidationError(string code, string domain, IReadOnlyList<ValidationResult> validationResults)
{
ArgumentNullException.ThrowIfNull(code, nameof(code));
ArgumentNullException.ThrowIfNull(domain, nameof(domain));
ArgumentNullException.ThrowIfNull(validationResults, nameof(validationResults));

Code = code;
Domain = domain;
ValidationResults = validationResults;
}

/// <inheritdoc/>
public IReadOnlyList<ValidationResult> ValidationResults { get; }

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

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

[ExcludeFromCodeCoverage]
string? IOperationError.Message => null;

[ExcludeFromCodeCoverage]
IOperationError? IOperationError.InnerError => null;
}
}
19 changes: 18 additions & 1 deletion test/Deveel.Results.XUnit/OperationErrorTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Deveel
using System.ComponentModel.DataAnnotations;

namespace Deveel
{
public static class OperationErrorTests
{
Expand Down Expand Up @@ -94,5 +96,20 @@ public static void OperationException_WithMessageAndInner()
Assert.Equal("err.2", innerError.Code);
Assert.Equal("biz", innerError.Domain);
}

[Fact]
public static void ValidationError_WithResults()
{
var results = new[] {
new ValidationResult("First error of the validation", new []{ "Member1" }),
new ValidationResult("Second error of the validation", new []{"Member2"})
};

var error = new OperationValidationError("err.1", "biz", results);

Assert.Equal("err.1", error.Code);
Assert.Equal("biz", error.Domain);
Assert.Same(results, error.ValidationResults);
}
}
}
23 changes: 22 additions & 1 deletion test/Deveel.Results.XUnit/OperationResultOfTTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Deveel
using System.ComponentModel.DataAnnotations;

namespace Deveel
{
public static class OperationResultOfTTests
{
Expand Down Expand Up @@ -136,6 +138,25 @@ public static void ImplicitlyConvertFromOperationResult_Success()

}

[Fact]
public static void OperationResult_FailForValidation()
{
var validations = new[] {
new ValidationResult("err.1", new []{ "Member1" }),
new ValidationResult("err.2", new []{ "Member2" })
};

var result = OperationResult<int>.ValidationFailed("err.1", "biz", validations);

Assert.Equal(OperationResultType.Error, result.ResultType);
Assert.NotNull(result.Error);
Assert.Equal("err.1", result.Error.Code);
Assert.Equal("biz", result.Error.Domain);
var opError = Assert.IsType<OperationValidationError>(result.Error);

Assert.Equal(validations.Length, opError.ValidationResults.Count);
}

class DomainException : OperationException
{
public DomainException(string code)
Expand Down
17 changes: 17 additions & 0 deletions test/Deveel.Results.XUnit/OperationResultTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Deveel;

using System.ComponentModel.DataAnnotations;

namespace Deveel;

public static class OperationResultTests
Expand Down Expand Up @@ -184,4 +186,19 @@ public static void OperationResult_IsUnchanged()
var result = OperationResult.NotChanged;
Assert.True(result.IsUnchanged());
}

[Fact]
public static void OperationResult_ValidationErrors()
{
var errors = new List<ValidationResult> {
new ValidationResult("The value is required", new[] { "value" }),
new ValidationResult("The value must be greater than 0", new[] { "value" })
};

var error = new OperationValidationError("err.1", "biz", errors);
var result = OperationResult.Fail(error);

Assert.True(result.HasValidationErrors());
Assert.Equal(errors, result.ValidationResults());
}
}