diff --git a/src/ResultObject/Result.Constructor.cs b/src/ResultObject/Result.Constructor.cs index 78150a4..8596c50 100755 --- a/src/ResultObject/Result.Constructor.cs +++ b/src/ResultObject/Result.Constructor.cs @@ -1,40 +1,34 @@ namespace ResultObject; /// -/// Helper class to create instances with type inference. +/// Helper class to create instances with type inference. /// public static class Result { - /// - /// Creates a successful result with a default value of . - /// - /// A successful instance with a value of null. - public static Result Success() => new(default, null); - /// /// Creates a successful result with the specified value. /// - /// The type of the result value. - /// The value of the successful result. - /// A successful instance containing the provided value. - public static Result Success(T value) => new(value, null); + /// The type of the result value. + /// The value associated with a successful result. + /// A successful instance containing the provided value. + public static Result Success(TValue value) => new(value, null); /// /// Creates a failed result with the specified error information. /// - /// The type of the result value. - /// A that contains detailed information about the failure. - /// A failed instance with no value and the provided error. - public static Result Failure(ResultError error) => new(default, error); + /// The type of the result value, which will be null in case of failure. + /// A containing detailed information about the failure. + /// A failed instance with no value and the provided error. + public static Result Failure(ResultError error) => new(default, error); /// /// Creates a failed result with the specified error code, reason, and message. /// - /// The type of the result value. - /// The error code representing the type of failure. - /// A short description of the failure reason. - /// A detailed message explaining the error. - /// A failed instance with no value and the provided error information. - public static Result Failure(string code, string reason, string message) => + /// The type of the result value, which will be null in case of failure. + /// A code representing the type of failure. + /// A brief description of the failure reason. + /// A detailed message explaining the failure. + /// A failed instance with no value and an error containing the specified details. + public static Result Failure(string code, string reason, string message) => new(default, new ResultError(code, reason, message)); } \ No newline at end of file diff --git a/src/ResultObject/Result.Operator.cs b/src/ResultObject/Result.Operator.cs deleted file mode 100755 index 93d0f5a..0000000 --- a/src/ResultObject/Result.Operator.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace ResultObject; - -/// -/// Helper class encapsulating the implicit operators of the Result class. -/// -public partial class Result -{ - /// - /// Implicitly converts a to its value. - /// - /// - /// This conversion will return the value if the result is successful and the value is not null. - /// If the result is unsuccessful or the value is null, it will return the default value for the type (e.g., null for reference types, 0 for integers). - ///
- /// This is useful in scenarios where you want a convenient way to retrieve the result's value without explicit null-checking or error handling, such as non-critical operations where a default value is acceptable. - ///
- /// The result object to convert. - /// The value of the result if successful and not null; otherwise, the default value of . - public static implicit operator T?(Result result) => - result.IsFailure || result.Value is null ? default : result.Value; - - /// - /// Implicitly converts the value to a successful result. - /// - /// The value to convert. - public static implicit operator Result(T value) => new(value, null); -} \ No newline at end of file diff --git a/src/ResultObject/Result.cs b/src/ResultObject/Result.cs index 9b7b18c..6991993 100755 --- a/src/ResultObject/Result.cs +++ b/src/ResultObject/Result.cs @@ -1,70 +1,82 @@ namespace ResultObject; /// -/// Represents the result of an operation, which can either be a success or a failure. +/// Represents the result of an operation, which can either be a success with a value or a failure with an error. /// -/// The type of the value in case of a successful result. -public partial class Result(T? value, ResultError? error) +/// The type of the value associated with a successful result. +public class Result(TValue? value, ResultError? error) { /// - /// Gets a value indicating whether the result is successful. + /// Gets a value indicating whether the result represents a successful outcome (i.e., no error occurred). /// - public bool IsSuccess => Error is null; + public bool IsSuccess { get; } = error == null; /// - /// Gets a value indicating whether the result is a failure. + /// Gets a value indicating whether the result represents a failure (i.e., an error occurred). /// - public bool IsFailure => Error is not null; + public bool IsFailure { get; } = error != null; /// - /// Gets the value associated with a successful result, or null if the result is a failure. + /// Gets the value of a successful result. + /// Throws an exception if the result represents a failure or if the value is null even though the result is marked as successful. /// - public T? Value { get; } = value; + /// + /// Thrown when trying to access the value of a failed result, or when the result is successful but the value is null. + /// + public TValue Value + { + get + { + if (IsFailure) + { + throw new InvalidOperationException("Cannot retrieve value from a failed result."); + } - /// - /// Gets the error details associated with a failed result, or null if the result is successful. - /// - public ResultError? Error { get; } = error; + if (value == null) + { + throw new InvalidOperationException("Value is null despite the operation being successful."); + } + + return value; + } + } /// - /// - /// Creates a new Result instance representing the same failure as this result, - /// but with the value type cast to a different type. - /// - /// - /// The new result will contain the same error information, - /// but the value will be set to the default value of the new type. - /// + /// Gets the value if the result is successful, or the default value for the type if the result is a failure. /// - /// The new type of the value in the returned Result. - /// A new Result instance representing the failure, with the value type changed. - public Result ToFailureResult() => new(default, Error); + public TValue? ValueOrDefault => value; /// - /// Retrieves the value from the , throwing an exception if the result is unsuccessful or the value is null. + /// Gets the error associated with a failed result. + /// Throws an exception if accessed on a successful result. /// - /// - /// This method should be used in scenarios where the result's value is critical to the operation, and you expect it to be non-null. - /// If the result is a failure or the value is null, an is thrown, ensuring that invalid states are not silently handled. - ///
- /// This is recommended for business-critical logic where you need strict guarantees that the value is present and valid. - ///
/// - /// Thrown if the result is a failure or if the value is null in a successful result. + /// Thrown when trying to access the error of a successful result. /// - /// The value of the result if successful and non-null. - public T MustGetValue() + public ResultError Error { - if (IsFailure) + get { - throw new InvalidOperationException("Cannot retrieve value from a failed result."); - } + if (IsSuccess) + { + throw new InvalidOperationException("Cannot retrieve error from a successful result."); + } - if (Value is null) - { - throw new InvalidOperationException("Result was successful but the value is null."); + return error!; } - - return Value; } + + /// + /// Gets the error if the result is a failure, or null if the result is successful. + /// + public ResultError? ErrorOrDefault => error; + + /// + /// Creates a new Result instance representing the same failure as this result, + /// but with the value type cast to a different type. + /// The new result will contain the same error, while the value will be set to the default value of the new type. + /// + /// The new type for the value in the returned Result. + /// A new Result instance representing the failure, with the value type changed to . + public Result ToFailureResult() => new(default, Error); } \ No newline at end of file diff --git a/src/ResultObject/ResultError.cs b/src/ResultObject/ResultError.cs index fab3f1c..cb0c5b2 100755 --- a/src/ResultObject/ResultError.cs +++ b/src/ResultObject/ResultError.cs @@ -1,6 +1,18 @@ namespace ResultObject; /// -/// Represents the details of an result error. +/// Represents the details of a result error, including an error code, a reason for the failure, and a detailed message. /// -public record ResultError(string Code, string Reason, string Message); \ No newline at end of file +public record ResultError(string Code, string Reason, string Message) +{ + /// + /// Returns a string that represents the current error, providing a summary with the error code, reason, and message. + /// + /// + /// A string that contains the error code, reason, and message for this . + /// + public override string ToString() + { + return $"Code: {Code}, Reason: {Reason}, Message: {Message}"; + } +} \ No newline at end of file diff --git a/src/ResultObject/ResultObject.csproj b/src/ResultObject/ResultObject.csproj index 837e052..eac5324 100755 --- a/src/ResultObject/ResultObject.csproj +++ b/src/ResultObject/ResultObject.csproj @@ -7,7 +7,7 @@ ResultObject - 1.0.1 + 1.0.2 Ahmed Kamal A simple result object for DotNet. MIT diff --git a/test/ResultObject.Tests/ResultTests.cs b/test/ResultObject.Tests/ResultTests.cs index 1063ff1..de23a4e 100755 --- a/test/ResultObject.Tests/ResultTests.cs +++ b/test/ResultObject.Tests/ResultTests.cs @@ -3,79 +3,183 @@ namespace ResultObject.Tests; public class ResultTests { [Fact] - public void Success_ShouldCreateSuccessfulResult() + public void SuccessResult_IsSuccess_ShouldBeTrue() { - const int value = 42; - - var result = Result.Success(value); + // Arrange + var result = Result.Success("Test Value"); + // Act & Assert Assert.True(result.IsSuccess); Assert.False(result.IsFailure); - Assert.Equal(value, result.Value); - Assert.Null(result.Error); } [Fact] - public void Failure_ShouldCreateFailedResult_WithError() + public void SuccessResult_Value_ShouldReturnCorrectValue() { - var error = new ResultError("HTTP_500", "InternalError", "Something went wrong"); + // Arrange + var result = Result.Success("Test Value"); - var result = Result.Failure(error); + // Act + var value = result.Value; - Assert.False(result.IsSuccess); - Assert.True(result.IsFailure); - Assert.Null(result.Value); - Assert.Equal(error, result.Error); + // Assert + Assert.Equal("Test Value", value); } [Fact] - public void Failure_ShouldCreateFailedResult_WithErrorDetails() + public void SuccessResult_ValueOrDefault_ShouldReturnCorrectValue() { - const string code = "HTTP_404"; - const string reason = "NotFound"; - const string message = "The item was not found"; + // Arrange + var result = Result.Success("Test Value"); - var result = Result.Failure(code, reason, message); + // Act + var valueOrDefault = result.ValueOrDefault; - Assert.False(result.IsSuccess); + // Assert + Assert.Equal("Test Value", valueOrDefault); + } + + [Fact] + public void SuccessResult_Error_ShouldThrowInvalidOperationException() + { + // Arrange + var result = Result.Success("Test Value"); + + // Act & Assert + Assert.Throws(() => result.Error); + } + + [Fact] + public void SuccessResult_ErrorOrDefault_ShouldReturnNull() + { + // Arrange + var result = Result.Success("Test Value"); + + // Act + var errorOrDefault = result.ErrorOrDefault; + + // Assert + Assert.Null(errorOrDefault); + } + + [Fact] + public void FailureResult_IsFailure_ShouldBeTrue() + { + // Arrange + var error = new ResultError("404", "NotFound", "The item was not found."); + var result = Result.Failure(error); + + // Act & Assert Assert.True(result.IsFailure); - Assert.NotNull(result.Error); - Assert.Equal(default, result.Value); - Assert.Equal(code, result.Error?.Code); - Assert.Equal(reason, result.Error?.Reason); - Assert.Equal(message, result.Error?.Message); + Assert.False(result.IsSuccess); + } + + [Fact] + public void FailureResult_Value_ShouldThrowInvalidOperationException() + { + // Arrange + var error = new ResultError("404", "NotFound", "The item was not found."); + var result = Result.Failure(error); + + // Act & Assert + Assert.Throws(() => result.Value); } [Fact] - public void ImplicitConversionToValue_ShouldReturnCorrectValue_ForSuccessfulResult() + public void FailureResult_ValueOrDefault_ShouldReturnDefaultValue() { - const int value = 99; - var result = Result.Success(value); + // Arrange + var error = new ResultError("404", "NotFound", "The item was not found."); + var result = Result.Failure(error); - int actualValue = result; + // Act + var valueOrDefault = result.ValueOrDefault; - Assert.Equal(value, actualValue); + // Assert + Assert.Null(valueOrDefault); } [Fact] - public void ImplicitConversionToValue_ShouldReturnDefaultValue_ForFailedResult() + public void FailureResult_Error_ShouldReturnCorrectError() { - var error = new ResultError("HTTP_500", "InternalError", "Something went wrong"); - int result = Result.Failure(error); + // Arrange + var error = new ResultError("404", "NotFound", "The item was not found."); + var result = Result.Failure(error); - Assert.Equal(default, result); + // Act + var resultError = result.Error; + + // Assert + Assert.Equal("404", resultError.Code); + Assert.Equal("NotFound", resultError.Reason); + Assert.Equal("The item was not found.", resultError.Message); } [Fact] - public void ImplicitConversionFromValue_ShouldCreateSuccessfulResult() + public void FailureResult_ErrorOrDefault_ShouldReturnCorrectError() { - const int value = 77; + // Arrange + var error = new ResultError("404", "NotFound", "The item was not found."); + var result = Result.Failure(error); - Result result = value; + // Act + var errorOrDefault = result.ErrorOrDefault; - Assert.True(result.IsSuccess); - Assert.False(result.IsFailure); - Assert.Equal(value, result.Value); - Assert.Null(result.Error); + // Assert + Assert.Equal(error, errorOrDefault); + } + + [Fact] + public void FailureResult_WithErrorCode_ShouldReturnCorrectError() + { + // Arrange + var result = Result.Failure("500", "InternalError", "An unexpected error occurred."); + + // Act + var error = result.Error; + + // Assert + Assert.Equal("500", error.Code); + Assert.Equal("InternalError", error.Reason); + Assert.Equal("An unexpected error occurred.", error.Message); + } + + [Fact] + public void ToFailureResult_ShouldConvertToNewFailureResult() + { + // Arrange + var error = new ResultError("404", "NotFound", "The item was not found."); + var result = Result.Failure(error); + + // Act + var newResult = result.ToFailureResult(); + + // Assert + Assert.True(newResult.IsFailure); + Assert.Equal(error, newResult.Error); + Assert.Equal(default, newResult.ValueOrDefault); + } + + [Fact] + public void ResultError_ToString_ShouldReturnFormattedString() + { + // Arrange + var error = new ResultError("500", "InternalError", "An unexpected error occurred."); + + // Act + var resultString = error.ToString(); + + // Assert + Assert.Equal("Code: 500, Reason: InternalError, Message: An unexpected error occurred.", resultString); + } + + [Fact] + public void SuccessResult_WithNullValue_ShouldThrowInvalidOperationException() + { + // Arrange + var result = Result.Success(null); + + // Act & Assert + Assert.Throws(() => result.Value); } -} +} \ No newline at end of file