View Javadoc
1   package com.guinetik.rr.result;
2   
3   import java.util.NoSuchElementException;
4   import java.util.Optional;
5   import java.util.function.Consumer;
6   import java.util.function.Function;
7   import java.util.function.Supplier;
8   
9   /**
10   * A container object representing either a successful result or an error.
11   *
12   * <p>Inspired by Rust's {@code Result<T, E>} and Scala's {@code Either[L, R]}, this class
13   * provides a functional approach to error handling without exceptions. It forces explicit
14   * handling of both success and failure cases, leading to more robust code.
15   *
16   * <h2>Creating Results</h2>
17   * <pre class="language-java"><code>
18   * // Success case
19   * Result&lt;User, ApiError&gt; success = Result.success(new User("John"));
20   *
21   * // Failure case
22   * Result&lt;User, ApiError&gt; failure = Result.failure(new ApiError(404, "Not found"));
23   * </code></pre>
24   *
25   * <h2>Checking and Extracting Values</h2>
26   * <pre class="language-java"><code>
27   * Result&lt;User, ApiError&gt; result = client.fluent().get("/users/1", User.class);
28   *
29   * if (result.isSuccess()) {
30   *     User user = result.getValue();
31   *     System.out.println("Found: " + user.getName());
32   * } else {
33   *     ApiError error = result.getError();
34   *     System.err.println("Error: " + error.getMessage());
35   * }
36   * </code></pre>
37   *
38   * <h2>Pattern Matching with match()</h2>
39   * <pre class="language-java"><code>
40   * result.match(
41   *     user -&gt; System.out.println("Success: " + user.getName()),
42   *     error -&gt; System.err.println("Failed: " + error.getMessage())
43   * );
44   * </code></pre>
45   *
46   * <h2>Functional Transformations</h2>
47   * <pre class="language-java"><code>
48   * // Transform the success value
49   * Result&lt;String, ApiError&gt; nameResult = result.map(user -&gt; user.getName());
50   *
51   * // Transform the error
52   * Result&lt;User, String&gt; stringError = result.mapError(err -&gt; err.getMessage());
53   *
54   * // Chain operations
55   * String name = result
56   *     .map(User::getName)
57   *     .map(String::toUpperCase)
58   *     .getOrElse("Unknown");
59   * </code></pre>
60   *
61   * <h2>Safe Value Extraction</h2>
62   * <pre class="language-java"><code>
63   * // With default value
64   * User user = result.getOrElse(User.anonymous());
65   *
66   * // With lazy default
67   * User user = result.getOrElseGet(() -&gt; loadDefaultUser());
68   *
69   * // Throw on failure
70   * User user = result.getOrElseThrow(() -&gt; new UserNotFoundException());
71   *
72   * // Unwrap (throws RuntimeException on failure)
73   * User user = result.unwrap();
74   *
75   * // Convert to Optional
76   * Optional&lt;User&gt; optional = result.toOptional();
77   * </code></pre>
78   *
79   * @param <T> the type of the success value
80   * @param <E> the type of the error value
81   * @author guinetik &lt;guinetik@gmail.com&gt;
82   * @see com.guinetik.rr.RocketRest.FluentApi
83   * @see ApiError
84   * @since 1.0.0
85   */
86  public class Result<T, E> {
87  
88      private final T value;
89      private final E error;
90      private final boolean isSuccess;
91  
92      private Result(T value, E error, boolean isSuccess) {
93          this.value = value;
94          this.error = error;
95          this.isSuccess = isSuccess;
96      }
97  
98      /**
99       * Creates a successful Result containing the given value.
100      *
101      * @param value the value
102      * @param <T> the type of the value
103      * @param <E> the type of the error
104      * @return a successful Result containing the value
105      */
106     public static <T, E> Result<T, E> success(T value) {
107         return new Result<>(value, null, true);
108     }
109 
110     /**
111      * Creates a failed Result containing the given error.
112      *
113      * @param error the error
114      * @param <T> the type of the value
115      * @param <E> the type of the error
116      * @return a failed Result containing the error
117      */
118     public static <T, E> Result<T, E> failure(E error) {
119         return new Result<>(null, error, false);
120     }
121 
122     /**
123      * Returns whether this Result is a success.
124      *
125      * @return true if this Result is a success, false otherwise
126      */
127     public boolean isSuccess() {
128         return isSuccess;
129     }
130 
131     /**
132      * Returns whether this Result is a failure.
133      *
134      * @return true if this Result is a failure, false otherwise
135      */
136     public boolean isFailure() {
137         return !isSuccess;
138     }
139 
140     /**
141      * Gets the value contained in this Result.
142      *
143      * @return the value
144      * @throws NoSuchElementException if this Result is a failure
145      */
146     public T getValue() {
147         if (!isSuccess) {
148             throw new NoSuchElementException("Cannot get value from a failure Result");
149         }
150         return value;
151     }
152 
153     /**
154      * Gets the error contained in this Result.
155      *
156      * @return the error
157      * @throws NoSuchElementException if this Result is a success
158      */
159     public E getError() {
160         if (isSuccess) {
161             throw new NoSuchElementException("Cannot get error from a success Result");
162         }
163         return error;
164     }
165 
166     /**
167      * Gets the value contained in this Result or the given default value if this Result is a failure.
168      *
169      * @param defaultValue the value to return if this Result is a failure
170      * @return the value if this Result is a success, otherwise the default value
171      */
172     public T getOrElse(T defaultValue) {
173         return isSuccess ? value : defaultValue;
174     }
175 
176     /**
177      * Gets the value contained in this Result or the value supplied by the given Supplier if this Result is a failure.
178      *
179      * @param supplier the Supplier to provide the default value
180      * @return the value if this Result is a success, otherwise the value from the supplier
181      */
182     public T getOrElseGet(Supplier<? extends T> supplier) {
183         return isSuccess ? value : supplier.get();
184     }
185 
186     /**
187      * Gets the value contained in this Result or throws the given exception if this Result is a failure.
188      *
189      * @param exceptionSupplier the Supplier to provide the exception to throw
190      * @param <X> the type of the exception to throw
191      * @return the value if this Result is a success
192      * @throws X if this Result is a failure
193      */
194     public <X extends Throwable> T getOrElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
195         if (isSuccess) {
196             return value;
197         }
198         throw exceptionSupplier.get();
199     }
200     
201     /**
202      * Returns the value if this Result is a success, or unwraps the error by throwing an exception.
203      * This is similar to Rust's unwrap() method.
204      * 
205      * @return the contained value
206      * @throws RuntimeException if this is a failure, with the error toString() as the message
207      */
208     public T unwrap() {
209         if (isSuccess) {
210             return value;
211         }
212         throw new RuntimeException("Unwrapped a failure Result: " + error);
213     }
214 
215     /**
216      * Maps the value of this Result if it's a success, using the given mapping function.
217      *
218      * @param mapper the function to apply to the value
219      * @param <U> the type of the result of the mapping function
220      * @return a new Result with the mapped value if this Result is a success, otherwise a new Result with the same error
221      */
222     public <U> Result<U, E> map(Function<? super T, ? extends U> mapper) {
223         if (isSuccess) {
224             return Result.success(mapper.apply(value));
225         }
226         return Result.failure(error);
227     }
228 
229     /**
230      * Maps the error of this Result if it's a failure, using the given mapping function.
231      *
232      * @param mapper the function to apply to the error
233      * @param <F> the type of the result of the mapping function
234      * @return a new Result with the mapped error if this Result is a failure, otherwise a new Result with the same value
235      */
236     public <F> Result<T, F> mapError(Function<? super E, ? extends F> mapper) {
237         if (isSuccess) {
238             return Result.success(value);
239         }
240         return Result.failure(mapper.apply(error));
241     }
242 
243     /**
244      * Executes the given consumer if this Result is a success.
245      *
246      * @param consumer the consumer to execute
247      * @return this Result
248      */
249     public Result<T, E> ifSuccess(Consumer<? super T> consumer) {
250         if (isSuccess) {
251             consumer.accept(value);
252         }
253         return this;
254     }
255 
256     /**
257      * Executes the given consumer if this Result is a failure.
258      *
259      * @param consumer the consumer to execute
260      * @return this Result
261      */
262     public Result<T, E> ifFailure(Consumer<? super E> consumer) {
263         if (!isSuccess) {
264             consumer.accept(error);
265         }
266         return this;
267     }
268 
269     /**
270      * Converts this Result to an Optional containing the value if this Result is a success,
271      * or an empty Optional if this Result is a failure.
272      *
273      * @return an Optional containing the value if this Result is a success, otherwise an empty Optional
274      */
275     public Optional<T> toOptional() {
276         return isSuccess ? Optional.ofNullable(value) : Optional.empty();
277     }
278 
279     /**
280      * Pattern-matches over this Result, executing one of the consumers depending on success/failure.
281      * <pre>
282      * result.match(value -&gt; System.out.println(value), err -&gt; log.error(err));
283      * </pre>
284      *
285      * @param successConsumer runs if this result is a success (receives the value)
286      * @param errorConsumer   runs if this result is a failure (receives the error)
287      * @return this Result for fluent chaining
288      */
289     public Result<T, E> match(Consumer<? super T> successConsumer, Consumer<? super E> errorConsumer) {
290         if (isSuccess) {
291             successConsumer.accept(value);
292         } else {
293             errorConsumer.accept(error);
294         }
295         return this;
296     }
297 
298     @Override
299     public String toString() {
300         if (isSuccess) {
301             return "Success[" + value + "]";
302         }
303         return "Failure[" + error + "]";
304     }
305 }