Skip to content

Commit c603afd

Browse files
πŸ§‘β€πŸ’» chore(dx): add Result type inspired by Kotlin class (#16)
1 parent 078eed4 commit c603afd

3 files changed

Lines changed: 541 additions & 1 deletion

File tree

β€Žsrc/main/java/com/dddheroes/heroesofddd/shared/DomainRule.javaβ€Ž

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ public ViolatedException(String message, Throwable cause) {
1919

2020
default void verify() {
2121
if (isViolated()) {
22-
throw new ViolatedException(message());
22+
throw exception();
2323
}
2424
}
25+
26+
default ViolatedException exception() {
27+
return new ViolatedException(message());
28+
}
2529
}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package com.dddheroes.heroesofddd.shared.common;
2+
3+
import java.util.Objects;
4+
import java.util.function.Function;
5+
import java.util.function.Supplier;
6+
7+
/**
8+
* A discriminated union that encapsulates a successful outcome with a value of type T
9+
* or a failure with an arbitrary Throwable exception.
10+
* Inspired by the Result type in Kotlin.
11+
*
12+
* @param <T> The type of the value in case of success
13+
*
14+
* @author Mateusz Nowak
15+
*/
16+
public sealed interface Result<T> permits Result.Success, Result.Failure {
17+
18+
/**
19+
* Creates a successful Result representing completion without a value
20+
*
21+
* @return A new Success instance representing void completion
22+
*/
23+
static Result<Void> success() {
24+
return new Success<>(null);
25+
}
26+
27+
/**
28+
* Creates a successful Result with the given value
29+
*
30+
* @param value The success value
31+
* @param <T> The type of the value
32+
* @return A new Success instance
33+
*/
34+
static <T> Result<T> success(T value) {
35+
return new Success<>(value);
36+
}
37+
38+
/**
39+
* Creates a failure Result with the given exception
40+
*
41+
* @param exception The exception representing the failure
42+
* @param <T> The type parameter
43+
* @return A new Failure instance
44+
*/
45+
static <T> Result<T> failure(RuntimeException exception) {
46+
return new Failure<>(exception);
47+
}
48+
49+
/**
50+
* Wraps a supplier operation in a Result
51+
*
52+
* @param supplier The operation to execute
53+
* @param <T> The type of the result
54+
* @return A Result containing either the operation's result or any thrown exception
55+
*/
56+
static <T> Result<T> of(Supplier<T> supplier) {
57+
Objects.requireNonNull(supplier);
58+
try {
59+
return success(supplier.get());
60+
} catch (RuntimeException e) {
61+
return failure(e);
62+
}
63+
}
64+
65+
/**
66+
* Wraps a runnable operation in a Result
67+
*
68+
* @param runnable The operation to execute
69+
* @return A Result containing either the operation's result or any thrown exception
70+
*/
71+
static Result<Void> of(Runnable runnable) {
72+
Objects.requireNonNull(runnable);
73+
try {
74+
return success();
75+
} catch (RuntimeException e) {
76+
return failure(e);
77+
}
78+
}
79+
80+
/**
81+
* Returns true if this is a Success instance
82+
*/
83+
boolean isSuccess();
84+
85+
/**
86+
* Returns true if this is a Failure instance
87+
*/
88+
default boolean isFailure() {
89+
return !isSuccess();
90+
}
91+
92+
/**
93+
* Returns the value if this is success or throws the exception if this is failure
94+
*
95+
* @return The encapsulated value
96+
* @throws RuntimeException The encapsulated exception if this is a failure
97+
*/
98+
T getOrThrow() throws RuntimeException;
99+
100+
/**
101+
* Returns the encapsulated value if this is success or null if this is failure
102+
*/
103+
T getOrNull();
104+
105+
/**
106+
* Returns the encapsulated exception if this is failure or null if this is success
107+
*/
108+
RuntimeException exceptionOrNull();
109+
110+
/**
111+
* Maps the success value using the given transformation
112+
*/
113+
<R> Result<R> map(Function<T, R> transform);
114+
115+
/**
116+
* Returns the encapsulated value if this is success or the default value if this is failure
117+
*/
118+
default T getOrDefault(T defaultValue) {
119+
return isSuccess() ? getOrNull() : defaultValue;
120+
}
121+
122+
/**
123+
* Maps the success value using a transformation that may throw
124+
*/
125+
default <R> Result<R> flatMap(Function<T, Result<R>> transform) {
126+
if (isSuccess()) {
127+
try {
128+
return transform.apply(getOrNull());
129+
} catch (RuntimeException e) {
130+
return failure(e);
131+
}
132+
}
133+
return failure(exceptionOrNull());
134+
}
135+
136+
final class Success<T> implements Result<T> {
137+
private final T value;
138+
139+
private Success(T value) {
140+
this.value = value;
141+
}
142+
143+
@Override
144+
public boolean isSuccess() {
145+
return true;
146+
}
147+
148+
@Override
149+
public T getOrThrow() {
150+
return value;
151+
}
152+
153+
@Override
154+
public T getOrNull() {
155+
return value;
156+
}
157+
158+
@Override
159+
public RuntimeException exceptionOrNull() {
160+
return null;
161+
}
162+
163+
@Override
164+
public <R> Result<R> map(Function<T, R> transform) {
165+
Objects.requireNonNull(transform);
166+
try {
167+
return Result.success(transform.apply(value));
168+
} catch (RuntimeException e) {
169+
return Result.failure(e);
170+
}
171+
}
172+
173+
@Override
174+
public boolean equals(Object obj) {
175+
if (this == obj) return true;
176+
if (!(obj instanceof Success<?> success)) return false;
177+
return Objects.equals(value, success.value);
178+
}
179+
180+
@Override
181+
public int hashCode() {
182+
return Objects.hash(value);
183+
}
184+
185+
@Override
186+
public String toString() {
187+
return "Success[" + value + "]";
188+
}
189+
}
190+
191+
final class Failure<T> implements Result<T> {
192+
private final RuntimeException exception;
193+
194+
private Failure(RuntimeException exception) {
195+
this.exception = Objects.requireNonNull(exception);
196+
}
197+
198+
@Override
199+
public boolean isSuccess() {
200+
return false;
201+
}
202+
203+
@Override
204+
public T getOrThrow() throws RuntimeException {
205+
throw exception;
206+
}
207+
208+
@Override
209+
public T getOrNull() {
210+
return null;
211+
}
212+
213+
@Override
214+
public RuntimeException exceptionOrNull() {
215+
return exception;
216+
}
217+
218+
@Override
219+
public <R> Result<R> map(Function<T, R> transform) {
220+
Objects.requireNonNull(transform);
221+
return Result.failure(exception);
222+
}
223+
224+
@Override
225+
public boolean equals(Object obj) {
226+
if (this == obj) return true;
227+
if (!(obj instanceof Failure<?> failure)) return false;
228+
return Objects.equals(exception, failure.exception);
229+
}
230+
231+
@Override
232+
public int hashCode() {
233+
return Objects.hash(exception);
234+
}
235+
236+
@Override
237+
public String toString() {
238+
return "Failure[" + exception + "]";
239+
}
240+
}
241+
}

0 commit comments

Comments
Β (0)