Nuke.Cola
Loading...
Searching...
No Matches
ErrorHandling.cs
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Threading.Tasks;
5using EverythingSearchClient;
6
7namespace Nuke.Cola;
8
9/// <summary>
10/// A record that can represent an attempt at an arbitrary action
11/// </summary>
12public record class Attempt(Exception[]? Error = null)
13{
14 public static implicit operator Attempt (Exception[] e) => new(Error: e);
15 public static implicit operator Attempt (Exception e) => new(Error: [e]);
16 public static implicit operator Exception? (Attempt from) => from.Error?[0];
17 public static implicit operator bool (Attempt from) => from.Error == null;
18}
19
20/// <summary>
21/// A union that can hold either a correct value or an array of errors
22/// </summary>
23public record class ValueOrError<T>(T? Value = default, Exception[]? Error = null)
24{
25 public static implicit operator ValueOrError<T> (T val) => new(val);
26 public static implicit operator ValueOrError<T> (Exception[]? e) => new(Error: e);
27 public static implicit operator ValueOrError<T> (Exception e) => new(Error: [e]);
28 public static implicit operator T? (ValueOrError<T> from) => from.Value;
29 public static implicit operator Exception? (ValueOrError<T> from) => from.Error?[0];
30 public static implicit operator bool (ValueOrError<T> from) => from.Error == null;
31}
32
33public static class ErrorHandling
34{
35 /// <summary>
36 /// Try to gwt a value from an input function which may throw an exception. If an exception is
37 /// thrown then wrap it inside a ValueOrError for others to handle
38 /// </summary>
39 /// <param name="getter">The function returning T which may however throw an exception</param>
40 /// <param name="onFailure">Optionally react to the failure of the getter function</param>
41 /// <param name="previousErrors">Optionally provide previous failures which has led to this one</param>
42 /// <returns>The result of the getter function or an error</returns>
43 public static ValueOrError<T> TryGet<T>(Func<T> getter, Action<Exception>? onFailure = null, Exception[]? previousErrors = null)
44 {
45 try { return getter(); }
46 catch (Exception e)
47 {
48 onFailure?.Invoke(e);
49 var prevErrors = previousErrors ?? [];
50 return prevErrors.Prepend(e).ToArray();
51 }
52 }
53
54 /// <summary>
55 /// Work on the value inside a ValueOrError but only if input ValueOrError is valid. Return aggregated errors
56 /// otherwise.
57 /// </summary>
58 /// <param name="self"></param>
59 /// <param name="transform"></param>
60 /// <typeparam name="TResult"></typeparam>
61 /// <typeparam name="TInput"></typeparam>
62 /// <returns></returns>
63 public static ValueOrError<TResult> Transform<TResult, TInput>(this ValueOrError<TInput> self, Func<TInput, TResult> transform)
64 {
65 if (!self) return self.Error;
66 return TryGet(() => transform(self.Get()));
67 }
68
69 /// <summary>
70 /// If input ValueOrError is an error then attempt to execute the input getter function
71 /// (which may also fail)
72 /// If input ValueOrError is a value then just return that immediately
73 /// </summary>
74 /// <param name="self"></param>
75 /// <param name="getter">The function returning T which may however throw an exception</param>
76 /// <param name="onFailure">Optionally react to the failure of the getter function</param>
77 /// <returns>
78 /// The input correct value, or if that's erronous then the result of the getter function, or an
79 /// error if that also fails.
80 /// </returns>
81 public static ValueOrError<T> Else<T>(this ValueOrError<T> self, Func<T> getter, Action<Exception>? onFailure = null)
82 {
83 if (self) return self;
84 return TryGet(getter, onFailure, self.Error);
85 }
86
87 /// <summary>
88 /// If input ValueOrError is an error then attempt to execute the input getter function
89 /// (which may also fail) only when condition is true.
90 /// If condition is false or when input ValueOrError is a value then just return that immediately
91 /// </summary>
92 /// <param name="self"></param>
93 /// <param name="condition">Consider fallback only when this condition is true</param>
94 /// <param name="getter">The function returning T which may however throw an exception</param>
95 /// <param name="onFailure">Optionally react to the failure of the getter function</param>
96 /// <returns>
97 /// The input correct value, or if that's erronous then the result of the getter function, or an
98 /// error if that also fails.
99 /// </returns>
100 public static ValueOrError<T> Else<T>(this ValueOrError<T> self, bool condition, Func<T> getter, Action<Exception>? onFailure = null)
101 {
102 if (self || !condition) return self;
103 return TryGet(getter, onFailure, self.Error);
104 }
105
106 /// <summary>
107 /// Guarantee the result of an input ValueOrError otherwise throw the aggregated exceptions
108 /// inside the error.
109 /// </summary>
110 /// <param name="self"></param>
111 /// <param name="message">Optional message for when input is an error</param>
112 /// <typeparam name="T"></typeparam>
113 /// <returns>Guaranteed value (or throwing an exception)</returns>
114 public static T Get<T>(this ValueOrError<T> self, string? message = null)
115 {
116 if (self) return self!;
117 if (self.Error!.Length == 1) throw self.Error[0];
118 throw message == null
119 ? new AggregateException(self.Error!)
120 : new AggregateException(message, self.Error!);
121 }
122
123 /// <summary>
124 /// Attempt to try something which may throw an exception. If an exception is thrown then wrap
125 /// it inside a ValueOrError for others to handle
126 /// </summary>
127 /// <param name="action">which may throw an exception</param>
128 /// <param name="onFailure">Optionally react to the failure of the action</param>
129 /// <param name="previousErrors">Optionally provide previous failures which has led to this one</param>
130 /// <returns>An attempt</returns>
131 public static Attempt Try(Action action, Action<Exception>? onFailure = null, Exception[]? previousErrors = null)
132 {
133 try
134 {
135 action();
136 return new();
137 }
138 catch (Exception e)
139 {
140 onFailure?.Invoke(e);
141 var prevErrors = previousErrors ?? [];
142 return prevErrors.Prepend(e).ToArray();
143 }
144 }
145
146 /// <summary>
147 /// If input attempt is an error then attempt to execute another input action
148 /// (which may also fail)
149 /// </summary>
150 /// <param name="self"></param>
151 /// <param name="action">which may throw an exception</param>
152 /// <param name="onFailure">Optionally react to the failure of the action</param>
153 /// <returns>An attempt</returns>
154 public static Attempt Else(this Attempt self, Action action, Action<Exception>? onFailure = null)
155 {
156 if (self) return self;
157 return Try(action, onFailure, self.Error);
158 }
159
160 /// <summary>
161 /// If input attempt is an error then attempt to execute another input action
162 /// (which may also fail) only when condition is true.
163 /// </summary>
164 /// <param name="self"></param>
165 /// <param name="condition">Consider fallback only when this condition is true</param>
166 /// <param name="action">which may throw an exception</param>
167 /// <param name="onFailure">Optionally react to the failure of the action</param>
168 /// <returns>
169 /// The input correct attempt, or if that has failed before then the attempt at this input
170 /// action, or an error if that also fails.
171 /// </returns>
172 public static Attempt Else(this Attempt self, bool condition, Action action, Action<Exception>? onFailure = null)
173 {
174 if (self || !condition) return self;
175 return Try(action, onFailure, self.Error);
176 }
177
178 /// <summary>
179 /// Guarantee that one of the chain of attempts proceeding this function has succeeded otherwise
180 /// throw the aggregated exceptions inside the error.
181 /// </summary>
182 /// <param name="self"></param>
183 /// <param name="message">Optional message for when input is an error</param>
184 public static void Assume(this Attempt self, string? message = null)
185 {
186 if (self) return;
187 if (self.Error!.Length == 1) throw self.Error[0];
188 throw message == null
189 ? new AggregateException(self.Error!)
190 : new AggregateException(message, self.Error!);
191 }
192}
static ValueOrError< T > TryGet< T >(Func< T > getter, Action< Exception >? onFailure=null, Exception[]? previousErrors=null)
Try to gwt a value from an input function which may throw an exception. If an exception is thrown the...
static Attempt Try(Action action, Action< Exception >? onFailure=null, Exception[]? previousErrors=null)
Attempt to try something which may throw an exception. If an exception is thrown then wrap it inside ...
static void Assume(this Attempt self, string? message=null)
Guarantee that one of the chain of attempts proceeding this function has succeeded otherwise throw th...
static ValueOrError< T > Else< T >(this ValueOrError< T > self, Func< T > getter, Action< Exception >? onFailure=null)
If input ValueOrError is an error then attempt to execute the input getter function (which may also f...
static ValueOrError< TResult > Transform< TResult, TInput >(this ValueOrError< TInput > self, Func< TInput, TResult > transform)
Work on the value inside a ValueOrError but only if input ValueOrError is valid. Return aggregated er...
static T Get< T >(this ValueOrError< T > self, string? message=null)
Guarantee the result of an input ValueOrError otherwise throw the aggregated exceptions inside the er...
static Attempt Else(this Attempt self, bool condition, Action action, Action< Exception >? onFailure=null)
If input attempt is an error then attempt to execute another input action (which may also fail) only ...
static Attempt Else(this Attempt self, Action action, Action< Exception >? onFailure=null)
If input attempt is an error then attempt to execute another input action (which may also fail)
record class Attempt(Exception[]? Error=null)
A record that can represent an attempt at an arbitrary action.
record class ValueOrError< T >(T? Value=default, Exception[]? Error=null)
A union that can hold either a correct value or an array of errors.