/ GUNRPG.Application / Results / ServiceResult.cs
ServiceResult.cs
 1  namespace GUNRPG.Application.Results;
 2  
 3  /// <summary>
 4  /// Discriminated status for service results to enable pattern matching.
 5  /// </summary>
 6  public enum ResultStatus
 7  {
 8      Success,
 9      NotFound,
10      InvalidState,
11      ValidationError
12  }
13  
14  /// <summary>
15  /// Represents the outcome of a service operation with explicit success/failure states.
16  /// </summary>
17  public abstract class ServiceResultBase
18  {
19      public bool IsSuccess { get; }
20      public ResultStatus Status { get; }
21      public string? ErrorMessage { get; }
22  
23      protected ServiceResultBase(bool isSuccess, ResultStatus status, string? errorMessage = null)
24      {
25          IsSuccess = isSuccess;
26          Status = status;
27          ErrorMessage = errorMessage;
28      }
29  }
30  
31  /// <summary>
32  /// Service result that carries a value on success.
33  /// </summary>
34  public sealed class ServiceResult<T> : ServiceResultBase
35  {
36      public T? Value { get; }
37  
38      private ServiceResult(bool isSuccess, ResultStatus status, T? value, string? errorMessage)
39          : base(isSuccess, status, errorMessage)
40      {
41          Value = value;
42      }
43  
44      public static ServiceResult<T> Success(T value) => new(true, ResultStatus.Success, value, null);
45      public static ServiceResult<T> NotFound(string? message = null) => new(false, ResultStatus.NotFound, default, message ?? "Resource not found");
46      public static ServiceResult<T> InvalidState(string message) => new(false, ResultStatus.InvalidState, default, message);
47      public static ServiceResult<T> ValidationError(string message) => new(false, ResultStatus.ValidationError, default, message);
48      
49      /// <summary>
50      /// Creates a ServiceResult&lt;T&gt; from a non-generic ServiceResult, preserving the error state.
51      /// This should only be used to convert error results; Success results should use Success(T value) directly.
52      /// </summary>
53      public static ServiceResult<T> FromResult(ServiceResult result)
54      {
55          return result.Status switch
56          {
57              ResultStatus.Success => throw new InvalidOperationException("Cannot convert Success result without a value. Use Success(T value) instead."),
58              ResultStatus.NotFound => NotFound(result.ErrorMessage),
59              ResultStatus.InvalidState => InvalidState(result.ErrorMessage!),
60              ResultStatus.ValidationError => ValidationError(result.ErrorMessage!),
61              _ => InvalidState(result.ErrorMessage!)
62          };
63      }
64  
65      /// <summary>
66      /// Creates a ServiceResult&lt;T&gt; from another ServiceResult&lt;TOther&gt;, preserving the error state.
67      /// This should only be used to convert error results; Success results should use Success(T value) directly.
68      /// </summary>
69      public static ServiceResult<T> FromResult<TOther>(ServiceResult<TOther> result)
70      {
71          return result.Status switch
72          {
73              ResultStatus.Success => throw new InvalidOperationException("Cannot convert Success result without a value. Use Success(T value) instead."),
74              ResultStatus.NotFound => NotFound(result.ErrorMessage),
75              ResultStatus.InvalidState => InvalidState(result.ErrorMessage!),
76              ResultStatus.ValidationError => ValidationError(result.ErrorMessage!),
77              _ => InvalidState(result.ErrorMessage!)
78          };
79      }
80  }
81  
82  /// <summary>
83  /// Service result without a value.
84  /// </summary>
85  public sealed class ServiceResult : ServiceResultBase
86  {
87      private ServiceResult(bool isSuccess, ResultStatus status, string? errorMessage)
88          : base(isSuccess, status, errorMessage)
89      {
90      }
91  
92      public static ServiceResult Success() => new(true, ResultStatus.Success, null);
93      public static ServiceResult NotFound(string? message = null) => new(false, ResultStatus.NotFound, message ?? "Resource not found");
94      public static ServiceResult InvalidState(string message) => new(false, ResultStatus.InvalidState, message);
95      public static ServiceResult ValidationError(string message) => new(false, ResultStatus.ValidationError, message);
96  }