commit a5eca42d2dea30ab5265953f10765e5f87e4be3d
parent 3743d650067816b5cd38967be75a4ddb7cd3b035
Author: Markus Hanetzok <markus@hanetzok.net>
Date: Fri, 24 Oct 2025 21:31:58 +0200
Update project references and improve result pattern documentation
- Enhanced XML documentation for `Result` and `TypedResult` classes.
- Added usage examples and detailed remarks for better developer guidance.
- Introduced improved factory method descriptions and safe access functions (`TryGetValue`, `TryGetError`).
Diffstat:
2 files changed, 338 insertions(+), 59 deletions(-)
diff --git a/SimpleResults/Results/Result.cs b/SimpleResults/Results/Result.cs
@@ -1,18 +1,18 @@
/*
- This file is part of Foobar.
+ This file is part of SimpleResults.
- Foobar is free software: you can redistribute it and/or modify
+ SimpleResults is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
- Foobar is distributed in the hope that it will be useful,
+ SimpleResults is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with Foobar. If not, see <http://www.gnu.org/licenses/>.
+ along with SimpleResults. If not, see <http://www.gnu.org/licenses/>.
*/
using SimpleResults.Errors;
@@ -25,19 +25,52 @@ namespace SimpleResults.Results;
/// Represents the result of an operation that does not return a value, indicating either success or failure.
/// </summary>
/// <remarks>
+/// <para>
/// This class implements the Result pattern, providing a type-safe way to handle operation outcomes
/// without throwing exceptions. It encapsulates success/failure state and error details.
+/// </para>
+/// <para>
/// Use <see cref="TypedResult{T}"/> when the operation needs to return a value on success.
+/// </para>
/// </remarks>
+/// <example>
+/// <code>
+/// public Result ValidateAndSaveData(Data data)
+/// {
+/// if (data == null)
+/// {
+/// return Result.Failure(400, "Data cannot be null", null);
+/// }
+///
+/// try
+/// {
+/// _repository.Save(data);
+/// return Result.Success();
+/// }
+/// catch (Exception ex)
+/// {
+/// return Result.Failure(500, "Failed to save data", ex);
+/// }
+/// }
+///
+/// // Usage
+/// var result = ValidateAndSaveData(myData);
+/// if (result.IsFailure)
+/// {
+/// var error = result.TryGetError();
+/// _logger.LogError(error.Message);
+/// }
+/// </code>
+/// </example>
public class Result
{
/// <summary>
- /// Gets or sets a value indicating whether the operation completed successfully.
+ /// Gets a value indicating whether the operation completed successfully.
/// </summary>
/// <value>
/// <c>true</c> if the operation succeeded; otherwise, <c>false</c>.
/// </value>
- private bool IsSuccess { get; }
+ public bool IsSuccess { get; }
/// <summary>
/// Gets a value indicating whether the operation failed.
@@ -49,20 +82,24 @@ public class Result
public bool IsFailure => !IsSuccess;
/// <summary>
- /// Gets or sets the error details when the operation fails.
+ /// Contains the error details when the operation fails.
/// </summary>
/// <value>
/// An <see cref="ErrorDetail"/> object containing information about the failure,
/// or <c>null</c> if the operation succeeded.
/// </value>
- private ErrorDetail? Error { get; }
+ /// <remarks>
+ /// This property should not be accessed directly. Use <see cref="TryGetError"/> instead
+ /// to safely retrieve error details with proper validation.
+ /// </remarks>
+ private readonly ErrorDetail? _error;
/// <summary>
/// Initializes a new instance of the <see cref="Result"/> class representing a successful operation.
/// </summary>
/// <remarks>
/// This constructor is protected and should not be called directly.
- /// Use the <see cref="Success"/> factory method instead.
+ /// Use the <see cref="Success"/> factory method instead to create successful results.
/// </remarks>
protected Result()
{
@@ -75,23 +112,32 @@ public class Result
/// <param name="errorDetail">The details of the error that caused the operation to fail.</param>
/// <remarks>
/// This constructor is protected and should not be called directly.
- /// Use the Failure factory methods instead.
+ /// Use the <see cref="Failure(ErrorDetail)"/> or <see cref="Failure(int, string, Exception?)"/>
+ /// factory methods instead to create failure results.
/// </remarks>
protected Result(ErrorDetail errorDetail)
{
IsSuccess = false;
- Error = errorDetail;
+ _error = errorDetail;
}
/// <summary>
/// Creates a new <see cref="Result"/> representing a successful operation.
/// </summary>
- /// <returns>A <see cref="Result"/> instance with <see cref="IsSuccess"/> set to <c>true</c>.</returns>
+ /// <returns>A <see cref="Result"/> instance with <see cref="IsSuccess"/> set to <c>true</c>
+ /// and <see cref="IsFailure"/> set to <c>false</c>.</returns>
+ /// <remarks>
+ /// Use this factory method to indicate that an operation completed successfully without errors.
+ /// Each call creates a new instance of <see cref="Result"/>.
+ /// </remarks>
/// <example>
/// <code>
- /// public Result ProcessData()
+ /// public Result ProcessData(Data data)
/// {
- /// // ... perform operation ...
+ /// // Perform operation
+ /// _repository.Save(data);
+ ///
+ /// // Return success
/// return Result.Success();
/// }
/// </code>
@@ -102,18 +148,26 @@ public class Result
/// Creates a new <see cref="Result"/> representing a failed operation with the specified error details.
/// </summary>
/// <param name="error">The error details describing why the operation failed.</param>
- /// <returns>A <see cref="Result"/> instance with <see cref="IsSuccess"/> set to <c>false</c>
- /// and <see cref="ErrorDetail"/> populated with the provided error information.</returns>
+ /// <returns>A <see cref="Result"/> instance with <see cref="IsSuccess"/> set to <c>false</c>,
+ /// <see cref="IsFailure"/> set to <c>true</c>, and <see cref="_error"/> populated with the provided
+ /// error information.</returns>
+ /// <remarks>
+ /// Use this factory method when you already have an <see cref="ErrorDetail"/> object.
+ /// If you need to create the error detail inline, consider using
+ /// <see cref="Failure(int, string, Exception?)"/> instead.
+ /// </remarks>
/// <example>
/// <code>
- /// public Result ProcessData()
+ /// public Result ProcessData(Data data)
/// {
/// if (data == null)
/// {
- /// var error = new ErrorDetail(404, "Data not found");
+ /// var error = new ErrorDetail(400, "Data cannot be null");
/// return Result.Failure(error);
/// }
- /// // ... continue processing ...
+ ///
+ /// _repository.Save(data);
+ /// return Result.Success();
/// }
/// </code>
/// </example>
@@ -124,30 +178,86 @@ public class Result
/// </summary>
/// <param name="code">A numeric error code identifying the type of failure.</param>
/// <param name="msg">A human-readable message describing the failure.</param>
- /// <param name="e">An optional exception that caused the failure. Can be <c>null</c>.</param>
- /// <returns>A <see cref="Result"/> instance with <see cref="IsSuccess"/> set to <c>false</c>
- /// and <see cref="ErrorDetail"/> populated with the provided error information.</returns>
+ /// <param name="e">An optional exception that caused the failure. Pass <c>null</c> if the failure
+ /// was not caused by an exception (e.g. validation failures).</param>
+ /// <returns>A <see cref="Result"/> instance with <see cref="IsSuccess"/> set to <c>false</c>,
+ /// <see cref="IsFailure"/> set to <c>true</c>, and <see cref="_error"/> populated with an
+ /// <see cref="ErrorDetail"/> created from the provided parameters.</returns>
+ /// <remarks>
+ /// This is a convenience factory method that creates an <see cref="ErrorDetail"/> internally.
+ /// Use this method when you want to create a failure result without explicitly creating
+ /// an <see cref="ErrorDetail"/> object first.
+ /// </remarks>
/// <example>
/// <code>
- /// public Result ProcessData()
+ /// public Result ProcessData(Data data)
/// {
+ /// if (data == null)
+ /// {
+ /// return Result.Failure(400, "Data cannot be null", null);
+ /// }
+ ///
/// try
/// {
- /// // ... perform operation ...
+ /// _repository.Save(data);
+ /// return Result.Success();
/// }
- /// catch (Exception ex)
+ /// catch (DbException ex)
/// {
- /// return Result.Failure(500, "Processing failed", ex);
+ /// return Result.Failure(500, "Failed to save data to database", ex);
/// }
- /// return Result.Success();
/// }
/// </code>
/// </example>
public static Result Failure(int code, string msg, Exception? e) => new(new ErrorDetail(code, msg, e));
+ /// <summary>
+ /// Attempts to retrieve the error details from a failed operation.
+ /// </summary>
+ /// <returns>An <see cref="ErrorDetail"/> object containing information about the failure.</returns>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown when attempting to get the error from a successful result.
+ /// Always check <see cref="IsFailure"/> before calling this method.
+ /// </exception>
+ /// <exception cref="NullReferenceException">
+ /// Thrown if the internal error is null, which should not occur in normal usage
+ /// but indicates a programming error if it does.
+ /// </exception>
+ /// <remarks>
+ /// <para>
+ /// This method provides safe access to error details with validation. It ensures that:
+ /// <list type="number">
+ /// <item><description>The result is in a failure state before returning error details</description></item>
+ /// <item><description>The error details are not null</description></item>
+ /// </list>
+ /// </para>
+ /// <para>
+ /// Best practice: Always check <see cref="IsFailure"/> before calling this method to avoid exceptions.
+ /// </para>
+ /// </remarks>
+ /// <example>
+ /// <code>
+ /// var result = ProcessData(myData);
+ ///
+ /// if (result.IsFailure)
+ /// {
+ /// var error = result.TryGetError();
+ /// _logger.LogError($"Operation failed with code {error.ErrorCode}: {error.Message}");
+ ///
+ /// if (error.Exception != null)
+ /// {
+ /// _logger.LogError(error.Exception, "Exception details");
+ /// }
+ /// }
+ /// else
+ /// {
+ /// _logger.LogInformation("Operation completed successfully");
+ /// }
+ /// </code>
+ /// </example>
public ErrorDetail TryGetError()
{
if (IsSuccess) throw new InvalidOperationException("Can't get error: Result state is success!");
- return Error ?? throw new NullReferenceException("TypedResult.Error is null");
+ return _error ?? throw new NullReferenceException("TypedResult.Error is null");
}
}
\ No newline at end of file
diff --git a/SimpleResults/Results/TypedResult.cs b/SimpleResults/Results/TypedResult.cs
@@ -1,18 +1,18 @@
/*
- This file is part of Foobar.
+ This file is part of SimpleResults.
- Foobar is free software: you can redistribute it and/or modify
+ SimpleResults is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
- Foobar is distributed in the hope that it will be useful,
+ SimpleResults is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with Foobar. If not, see <http://www.gnu.org/licenses/>.
+ along with SimpleResults. If not, see <http://www.gnu.org/licenses/>.
*/
using SimpleResults.Errors;
@@ -27,23 +27,65 @@ namespace SimpleResults.Results;
/// Represents the result of an operation that returns a value of type <typeparamref name="T"/>,
/// indicating either success with a value or failure with error details.
/// </summary>
-/// <typeparam name="T">The type of the value returned on successful operation.
-/// Must have a parameterless constructor.</typeparam>
+/// <typeparam name="T">The type of the value returned on successful operation.</typeparam>
/// <remarks>
+/// <para>
/// This class implements the Result pattern with a typed return value, providing a type-safe way
/// to handle operation outcomes without throwing exceptions. It encapsulates a success/failure state,
/// a return value (on success), and error details (on failure).
+/// </para>
+/// <para>
/// Use <see cref="Result"/> when the operation does not need to return a value.
+/// </para>
/// </remarks>
+/// <example>
+/// <code>
+/// public TypedResult<User> GetUserById(int id)
+/// {
+/// if (id <= 0)
+/// {
+/// return TypedResult<User>.Failure(400, "Invalid user ID", null);
+/// }
+///
+/// try
+/// {
+/// var user = _repository.FindById(id);
+/// if (user == null)
+/// {
+/// return TypedResult<User>.Failure(404, "User not found", null);
+/// }
+///
+/// return TypedResult<User>.Success(user);
+/// }
+/// catch (Exception ex)
+/// {
+/// return TypedResult<User>.Failure(500, "Failed to retrieve user", ex);
+/// }
+/// }
+///
+/// // Usage
+/// var result = GetUserById(123);
+/// if (result.IsFailure)
+/// {
+/// var error = result.TryGetError();
+/// _logger.LogError($"Error {error.ErrorCode}: {error.Message}");
+/// }
+/// else
+/// {
+/// var user = result.TryGetValue();
+/// Console.WriteLine($"Found user: {user.Name}");
+/// }
+/// </code>
+/// </example>
public class TypedResult<T>
{
/// <summary>
- /// Gets or sets a value indicating whether the operation completed successfully.
+ /// Gets a value indicating whether the operation completed successfully.
/// </summary>
/// <value>
/// <c>true</c> if the operation succeeded; otherwise, <c>false</c>.
/// </value>
- private bool IsSuccess { get; }
+ public bool IsSuccess { get; }
/// <summary>
/// Gets a value indicating whether the operation failed.
@@ -55,24 +97,29 @@ public class TypedResult<T>
public bool IsFailure => !IsSuccess;
/// <summary>
- /// Gets or sets the value returned by the successful operation.
+ /// Gets the value returned by the successful operation.
/// </summary>
/// <value>
- /// The value of type <typeparamref name="T"/> produced by the operation if it succeeded.
- /// If the operation failed, this will be a default instance created using the parameterless constructor.
+ /// The value of type <typeparamref name="T"/> produced by the operation if it succeeded,
+ /// or <c>null</c> if the operation failed.
/// </value>
/// <remarks>
- /// Always check <see cref="IsSuccess"/> before accessing this property to ensure the operation succeeded.
+ /// This field should not be accessed directly. Use <see cref="TryGetValue"/> instead
+ /// to safely retrieve the value with proper validation.
/// </remarks>
private readonly T? _value;
/// <summary>
- /// Gets or sets the error details when the operation fails.
+ /// Gets the error details when the operation fails.
/// </summary>
/// <value>
- /// An <see cref="_error"/> object containing information about the failure,
+ /// An <see cref="ErrorDetail"/> object containing information about the failure,
/// or <c>null</c> if the operation succeeded.
/// </value>
+ /// <remarks>
+ /// This field should not be accessed directly. Use <see cref="TryGetError"/> instead
+ /// to safely retrieve error details with proper validation.
+ /// </remarks>
private readonly ErrorDetail? _error;
/// <summary>
@@ -82,7 +129,7 @@ public class TypedResult<T>
/// <param name="value">The value produced by the successful operation.</param>
/// <remarks>
/// This constructor is protected and should not be called directly.
- /// Use the <see cref="Success"/> factory method instead.
+ /// Use the <see cref="Success"/> factory method instead to create successful results.
/// </remarks>
protected TypedResult(T value)
{
@@ -96,7 +143,8 @@ public class TypedResult<T>
/// <param name="error">The details of the error that caused the operation to fail.</param>
/// <remarks>
/// This constructor is protected and should not be called directly.
- /// Use the Failure factory methods instead.
+ /// Use the <see cref="Failure(ErrorDetail)"/> or <see cref="Failure(int, string, Exception?)"/>
+ /// factory methods instead to create failure results.
/// </remarks>
protected TypedResult(ErrorDetail error)
{
@@ -108,14 +156,24 @@ public class TypedResult<T>
/// Creates a new <see cref="TypedResult{T}"/> representing a successful operation with the specified value.
/// </summary>
/// <param name="value">The value produced by the successful operation.</param>
- /// <returns>A <see cref="TypedResult{T}"/> instance with <see cref="IsSuccess"/> set to <c>true</c>
- /// and <see cref="_value"/> set to the provided value.</returns>
+ /// <returns>A <see cref="TypedResult{T}"/> instance with <see cref="IsSuccess"/> set to <c>true</c>,
+ /// <see cref="IsFailure"/> set to <c>false</c>, and the internal value set to the provided parameter.</returns>
+ /// <remarks>
+ /// Use this factory method to indicate that an operation completed successfully and produced a value.
+ /// Each call creates a new instance of <see cref="TypedResult{T}"/>.
+ /// The value can be retrieved later using <see cref="TryGetValue"/>.
+ /// </remarks>
/// <example>
/// <code>
/// public TypedResult<User> GetUser(int id)
/// {
- /// var user = database.FindUser(id);
- /// return TypedResult<User>.Success(user);
+ /// var user = _repository.FindById(id);
+ /// if (user != null)
+ /// {
+ /// return TypedResult<User>.Success(user);
+ /// }
+ ///
+ /// return TypedResult<User>.Failure(404, "User not found", null);
/// }
/// </code>
/// </example>
@@ -125,18 +183,25 @@ public class TypedResult<T>
/// Creates a new <see cref="TypedResult{T}"/> representing a failed operation with the specified error details.
/// </summary>
/// <param name="error">The error details describing why the operation failed.</param>
- /// <returns>A <see cref="TypedResult{T}"/> instance with <see cref="IsSuccess"/> set to <c>false</c>
- /// and <see cref="_error"/> populated with the provided error information.</returns>
+ /// <returns>A <see cref="TypedResult{T}"/> instance with <see cref="IsSuccess"/> set to <c>false</c>,
+ /// <see cref="IsFailure"/> set to <c>true</c>, and the internal error populated with the provided
+ /// error information.</returns>
+ /// <remarks>
+ /// Use this factory method when you already have an <see cref="ErrorDetail"/> object.
+ /// If you need to create the error detail inline, consider using
+ /// <see cref="Failure(int, string, Exception?)"/> instead.
+ /// </remarks>
/// <example>
/// <code>
/// public TypedResult<User> GetUser(int id)
/// {
- /// var user = database.FindUser(id);
+ /// var user = _repository.FindById(id);
/// if (user == null)
/// {
/// var error = new ErrorDetail(404, "User not found");
/// return TypedResult<User>.Failure(error);
/// }
+ ///
/// return TypedResult<User>.Success(user);
/// }
/// </code>
@@ -147,35 +212,139 @@ public class TypedResult<T>
/// Creates a new <see cref="TypedResult{T}"/> representing a failed operation
/// with the specified error information.
/// </summary>
- /// <param name="code">A numeric error code identifying the type of failure.</param>
- /// <param name="msg">A human-readable message describing the failure.</param>
- /// <param name="e">An optional exception that caused the failure. Can be <c>null</c>.</param>
- /// <returns>A <see cref="TypedResult{T}"/> instance with <see cref="IsSuccess"/> set to <c>false</c>
- /// and <see cref="_error"/> populated with the provided error information.</returns>
+ /// <param name="code">A numeric error code identifying the type of failure.
+ /// Common conventions include HTTP status codes (e.g., 400 for bad request, 404 for not found, 500 for server error).</param>
+ /// <param name="msg">A human-readable message describing the failure. This message should be
+ /// clear and informative for logging and debugging purposes.</param>
+ /// <param name="e">An optional exception that caused the failure. Pass <c>null</c> if the failure
+ /// was not caused by an exception (e.g., validation failures, not found scenarios).</param>
+ /// <returns>A <see cref="TypedResult{T}"/> instance with <see cref="IsSuccess"/> set to <c>false</c>,
+ /// <see cref="IsFailure"/> set to <c>true</c>, and the internal error populated with an
+ /// <see cref="ErrorDetail"/> created from the provided parameters.</returns>
+ /// <remarks>
+ /// This is a convenience factory method that creates an <see cref="ErrorDetail"/> internally.
+ /// Use this method when you want to create a failure result without explicitly creating
+ /// an <see cref="ErrorDetail"/> object first.
+ /// </remarks>
/// <example>
/// <code>
/// public TypedResult<User> GetUser(int id)
/// {
+ /// if (id <= 0)
+ /// {
+ /// return TypedResult<User>.Failure(400, "Invalid user ID", null);
+ /// }
+ ///
/// try
/// {
- /// var user = database.FindUser(id);
+ /// var user = _repository.FindById(id);
+ /// if (user == null)
+ /// {
+ /// return TypedResult<User>.Failure(404, "User not found", null);
+ /// }
+ ///
/// return TypedResult<User>.Success(user);
/// }
- /// catch (Exception ex)
+ /// catch (DbException ex)
/// {
- /// return TypedResult<User>.Failure(500, "Database error", ex);
+ /// return TypedResult<User>.Failure(500, "Failed to retrieve user from database", ex);
/// }
/// }
/// </code>
/// </example>
public static TypedResult<T> Failure(int code, string msg, Exception? e) => new(new ErrorDetail(code, msg, e));
+ /// <summary>
+ /// Attempts to retrieve the value from a successful operation.
+ /// </summary>
+ /// <returns>The value of type <typeparamref name="T"/> produced by the successful operation.</returns>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown when attempting to get the value from a failed result.
+ /// Always check <see cref="IsFailure"/> before calling this method.
+ /// </exception>
+ /// <exception cref="NullReferenceException">
+ /// Thrown if the internal value is null, which should not occur in normal usage
+ /// but indicates a programming error if it does.
+ /// </exception>
+ /// <remarks>
+ /// <para>
+ /// This method provides safe access to the result value with validation. It ensures that:
+ /// <list type="number">
+ /// <item><description>The result is in a success state before returning the value</description></item>
+ /// <item><description>The value is not null</description></item>
+ /// </list>
+ /// </para>
+ /// <para>
+ /// Best practice: Always check <see cref="IsSuccess"/> (or its inverse) before calling this method to avoid exceptions.
+ /// </para>
+ /// </remarks>
+ /// <example>
+ /// <code>
+ /// var result = GetUserById(123);
+ ///
+ /// if (!result.IsFailure)
+ /// {
+ /// var user = result.TryGetValue();
+ /// Console.WriteLine($"User found: {user.Name}");
+ /// }
+ /// else
+ /// {
+ /// var error = result.TryGetError();
+ /// _logger.LogWarning($"Failed to get user: {error.Message}");
+ /// }
+ /// </code>
+ /// </example>
public T TryGetValue()
{
if (IsFailure) throw new InvalidOperationException("Can't get value: Result state is failure!");
return _value ?? throw new NullReferenceException("TypedResult.Value is null");
}
+ /// <summary>
+ /// Attempts to retrieve the error details from a failed operation.
+ /// </summary>
+ /// <returns>An <see cref="ErrorDetail"/> object containing information about the failure.</returns>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown when attempting to get the error from a successful result.
+ /// Always check <see cref="IsFailure"/> before calling this method.
+ /// </exception>
+ /// <exception cref="NullReferenceException">
+ /// Thrown if the internal error is null, which should not occur in normal usage
+ /// but indicates a programming error if it does.
+ /// </exception>
+ /// <remarks>
+ /// <para>
+ /// This method provides safe access to error details with validation. It ensures that:
+ /// <list type="number">
+ /// <item><description>The result is in a failure state before returning error details</description></item>
+ /// <item><description>The error details are not null</description></item>
+ /// </list>
+ /// </para>
+ /// <para>
+ /// Best practice: Always check <see cref="IsFailure"/> before calling this method to avoid exceptions.
+ /// </para>
+ /// </remarks>
+ /// <example>
+ /// <code>
+ /// var result = GetUserById(123);
+ ///
+ /// if (result.IsFailure)
+ /// {
+ /// var error = result.TryGetError();
+ /// _logger.LogError($"Operation failed with code {error.ErrorCode}: {error.Message}");
+ ///
+ /// if (error.Exception != null)
+ /// {
+ /// _logger.LogError(error.Exception, "Exception details");
+ /// }
+ /// }
+ /// else
+ /// {
+ /// var user = result.TryGetValue();
+ /// ProcessUser(user);
+ /// }
+ /// </code>
+ /// </example>
public ErrorDetail TryGetError()
{
if (IsSuccess) throw new InvalidOperationException("Can't get error: Result state is success!");