11package dev.slne.surf.cloud.api.common.util
22
3+ import dev.slne.surf.cloud.api.common.util.Either.Companion.left
4+ import dev.slne.surf.cloud.api.common.util.Either.Companion.right
5+ import kotlinx.serialization.SerialName
36import kotlinx.serialization.Serializable
47
8+ /* *
9+ * Represents a value of one of two possible types.
10+ * An [Either] is either a [Left] containing a value of type [L],
11+ * or a [Right] containing a value of type [R].
12+ *
13+ * This implementation is **neutral**: neither side implies success or failure.
14+ * It is useful whenever a value can be one of two distinct, equally valid types.
15+ *
16+ * @param L the type of the left value.
17+ * @param R the type of the right value.
18+ * @property left the left value if present, otherwise `null`.
19+ * @property right the right value if present, otherwise `null`.
20+ */
521@Serializable
622sealed class Either <L , R > {
723
8- abstract fun <C , D > mapBoth (
9- leftMapper : (L ) -> C ,
10- rightMapper : (R ) -> D
11- ): Either <C , D >
12-
13- abstract fun <T > map (leftMapper : (L ) -> T , rightMapper : (R ) -> T ): T
14-
24+ /* * The left value if present, otherwise `null`. */
1525 abstract val left: L ?
26+
27+ /* * The right value if present, otherwise `null`. */
1628 abstract val right: R ?
1729
18- fun <T > mapLeft (mapper : (L ) -> T ): Either <T , R > = map({ left(mapper(it)) }, ::right)
19- fun <T > mapRight (mapper : (R ) -> T ): Either <L , T > = map(::left) { right(mapper(it)) }
30+ /* *
31+ * Indicates whether this instance holds a left value.
32+ *
33+ * @return `true` if this is a [Left]; `false` otherwise.
34+ */
35+ val isLeft: Boolean get() = left != null
2036
21- fun leftOrThrow (): L {
22- return left ? : throw NoSuchElementException (" Either is Right, no Left value present" )
23- }
37+ /* *
38+ * Indicates whether this instance holds a right value.
39+ *
40+ * @return `true` if this is a [Right]; `false` otherwise.
41+ */
42+ val isRight: Boolean get() = right != null
2443
25- fun rightOrThrow (): R {
26- return right ? : throw NoSuchElementException (" Either is Left, no Right value present" )
27- }
44+ /* *
45+ * Returns the left value if present.
46+ *
47+ * @return the non-null left value.
48+ * @throws NoSuchElementException if no left value is present.
49+ */
50+ fun leftOrThrow (): L = left ? : throw NoSuchElementException (" Either contains no left value" )
2851
29- fun swap (): Either <R , L > = map(::right, ::left)
30- fun <L2 > flatMap (leftMapper : (L ) -> Either <L2 , R >): Either <L2 , R > = map(leftMapper, ::right)
52+ /* *
53+ * Returns the right value if present.
54+ *
55+ * @return the non-null right value.
56+ * @throws NoSuchElementException if no right value is present.
57+ */
58+ fun rightOrThrow (): R = right ? : throw NoSuchElementException (" Either contains no right value" )
3159
32- companion object {
33- fun <L , R > left (value : L ): Either <L , R > = Left (value)
34- fun <L , R > right (value : R ): Either <L , R > = Right (value)
35- fun <U > unwrap (either : Either <out U , out U >): U = either.map({ it }, { it })
36- }
60+ /* *
61+ * Returns the left value or `null` if absent.
62+ *
63+ * @return the left value, or `null` if this is a [Right].
64+ */
65+ fun leftOrNull (): L ? = left
3766
38- @PublishedApi
39- @Serializable
40- internal class Left <L , R >(override val left : L ) : Either<L, R>() {
41- @Transient
42- override val right: R ? = null
43-
44- override fun <C , D > mapBoth (
45- leftMapper : (L ) -> C ,
46- rightMapper : (R ) -> D
47- ): Either <C , D > = Left (leftMapper(left))
67+ /* *
68+ * Returns the right value or `null` if absent.
69+ *
70+ * @return the right value, or `null` if this is a [Left].
71+ */
72+ fun rightOrNull (): R ? = right
4873
49- override fun <T > map (leftMapper : (L ) -> T , rightMapper : (R ) -> T ): T = leftMapper(left)
74+ /* *
75+ * Returns the left value if present, otherwise computes a default.
76+ *
77+ * @param default a function invoked to produce a value when no left value is present.
78+ * @return the left value or the result of [default].
79+ */
80+ inline fun getLeftOrElse (default : () -> L ): L = left ? : default()
5081
51- override fun toString (): String = " Either.Left($left )"
82+ /* *
83+ * Returns the right value if present, otherwise computes a default.
84+ *
85+ * @param default a function invoked to produce a value when no right value is present.
86+ * @return the right value or the result of [default].
87+ */
88+ inline fun getRightOrElse (default : () -> R ): R = right ? : default()
5289
53- override fun equals (other : Any? ): Boolean {
54- if (this == = other) return true
55- if (other !is Left <* , * >) return false
90+ /* *
91+ * Applies one of the given functions based on which side is present and returns its result.
92+ *
93+ * @param T the result type.
94+ * @param ifLeft function to apply when this is a [Left].
95+ * @param ifRight function to apply when this is a [Right].
96+ * @return the result of applying the appropriate function.
97+ */
98+ inline fun <T > fold (ifLeft : (L ) -> T , ifRight : (R ) -> T ): T = when (this ) {
99+ is Left -> ifLeft(left)
100+ is Right -> ifRight(right)
101+ }
56102
57- if (left != other.left) return false
103+ /* *
104+ * Alias for [fold]. Transforms the contained value to a single common type.
105+ *
106+ * @param T the result type.
107+ * @param ifLeft function to apply when this is a [Left].
108+ * @param ifRight function to apply when this is a [Right].
109+ * @return the result of applying the appropriate function.
110+ */
111+ inline fun <T > map (ifLeft : (L ) -> T , ifRight : (R ) -> T ): T = fold(ifLeft, ifRight)
58112
59- return true
113+ /* *
114+ * Transforms both possible sides independently and returns a new [Either] with the mapped types.
115+ *
116+ * @param L2 the mapped left type.
117+ * @param R2 the mapped right type.
118+ * @param leftMapper function to apply to the left value.
119+ * @param rightMapper function to apply to the right value.
120+ * @return a new [Either] containing the transformed value.
121+ */
122+ inline fun <L2 , R2 > mapBoth (leftMapper : (L ) -> L2 , rightMapper : (R ) -> R2 ): Either <L2 , R2 > =
123+ when (this ) {
124+ is Left -> Left (leftMapper(left))
125+ is Right -> Right (rightMapper(right))
60126 }
61127
62- override fun hashCode (): Int {
63- return left?.hashCode() ? : 0
64- }
128+ /* *
129+ * Transforms the left value if present, keeping the right value as-is.
130+ *
131+ * @param L2 the mapped left type.
132+ * @param leftMapper function to apply to the left value.
133+ * @return a new [Either] with the transformed left type.
134+ */
135+ inline fun <L2 > mapLeft (leftMapper : (L ) -> L2 ): Either <L2 , R > = when (this ) {
136+ is Left -> Left (leftMapper(left))
137+ is Right -> Right (right)
65138 }
66139
140+ /* *
141+ * Transforms the right value if present, keeping the left value as-is.
142+ *
143+ * @param R2 the mapped right type.
144+ * @param rightMapper function to apply to the right value.
145+ * @return a new [Either] with the transformed right type.
146+ */
147+ inline fun <R2 > mapRight (rightMapper : (R ) -> R2 ): Either <L , R2 > = when (this ) {
148+ is Left -> Left (left)
149+ is Right -> Right (rightMapper(right))
150+ }
67151
68- @Serializable
69- @PublishedApi
70- internal class Right <L , R >(override val right : R ) : Either<L, R>() {
71- @Transient
72- override val left: L ? = null
152+ /* *
153+ * Swaps the sides of this instance: left becomes right and vice versa.
154+ *
155+ * @return a new [Either] with left and right values swapped.
156+ */
157+ fun swap (): Either <R , L > = fold(::right, ::left)
73158
74- override fun <C , D > mapBoth (
75- leftMapper : (L ) -> C ,
76- rightMapper : (R ) -> D
77- ): Either <C , D > = Right (rightMapper(right))
159+ /* *
160+ * Executes [action] only when this is a [Left], then returns this instance for chaining.
161+ *
162+ * @param action the side-effect to perform on the left value.
163+ * @return this instance.
164+ */
165+ inline fun ifLeft (action : (L ) -> Unit ): Either <L , R > {
166+ if (this is Left ) action(left)
167+ return this
168+ }
78169
79- override fun <T > map (leftMapper : (L ) -> T , rightMapper : (R ) -> T ): T = rightMapper(right)
80170
81- override fun toString (): String = " Either.Right($right )"
171+ /* *
172+ * Executes [action] only when this is a [Right], then returns this instance for chaining.
173+ *
174+ * @param action the side-effect to perform on the right value.
175+ * @return this instance.
176+ */
177+ inline fun ifRight (action : (R ) -> Unit ): Either <L , R > {
178+ if (this is Right ) action(right)
179+ return this
180+ }
82181
83- override fun equals (other : Any? ): Boolean {
84- if (this == = other) return true
85- if (other !is Right <* , * >) return false
182+ /* *
183+ * Combines two [Either] values **only if** they are of the same side type.
184+ *
185+ * If both are [Left], [combineLeft] is used. If both are [Right], [combineRight] is used.
186+ * If they differ, this instance is returned unchanged.
187+ *
188+ * @param L2 the left type of [other].
189+ * @param R2 the right type of [other].
190+ * @param other the other [Either] to combine with.
191+ * @param combineLeft function to combine two left values into one.
192+ * @param combineRight function to combine two right values into one.
193+ * @return a combined [Either] when sides match; otherwise this instance.
194+ */
195+ inline fun <L2 , R2 > zipSame (
196+ other : Either <L2 , R2 >,
197+ combineLeft : (L , L2 ) -> L ,
198+ combineRight : (R , R2 ) -> R
199+ ): Either <L , R > = when (this ) {
200+ is Left if other is Left -> Left (combineLeft(this .left, other.left))
201+ is Right if other is Right -> Right (combineRight(this .right, other.right))
202+ else -> this
203+ }
86204
87- if (right != other.right) return false
205+ /* *
206+ * Applies [leftMapper] when this is a [Left]; returns the right side unchanged otherwise.
207+ *
208+ * @param L2 the mapped left type.
209+ * @param leftMapper function transforming the left value into another [Either].
210+ * @return the mapped [Either] when left; otherwise the unchanged right side.
211+ */
212+ fun <L2 > flatMap (leftMapper : (L ) -> Either <L2 , R >): Either <L2 , R > = map(leftMapper, ::right)
88213
89- return true
90- }
214+ companion object {
215+
216+ /* *
217+ * Creates an [Either] containing the given left value.
218+ *
219+ * @param value the value to store on the left side.
220+ * @return an [Either] representing [value] as [Left].
221+ */
222+ fun <L , R > left (value : L ): Either <L , R > = Left (value)
223+
224+ /* *
225+ * Creates an [Either] containing the given right value.
226+ *
227+ * @param value the value to store on the right side.
228+ * @return an [Either] representing [value] as [Right].
229+ */
230+ fun <L , R > right (value : R ): Either <L , R > = Right (value)
231+
232+ /* *
233+ * Unwraps an [Either] whose left and right types are identical.
234+ *
235+ * @param either the [Either] instance to unwrap.
236+ * @param U the common value type.
237+ * @return the contained value regardless of side.
238+ */
239+ fun <U > unwrap (either : Either <out U , out U >): U = either.fold({ it }, { it })
91240
92- override fun hashCode (): Int {
93- return right?.hashCode() ? : 0
241+ /* *
242+ * Creates an [Either] from nullable left and right values. Exactly **one** must be non-null.
243+ *
244+ * @param left the candidate left value; may be `null`.
245+ * @param right the candidate right value; may be `null`.
246+ * @return an [Either] containing the single non-null value.
247+ * @throws IllegalStateException if both values are null or both are non-null.
248+ */
249+ fun <L , R > of (left : L ? , right : R ? ): Either <L , R > = when {
250+ left != null && right == null -> Left (left)
251+ right != null && left == null -> Right (right)
252+ left == null -> error(" Either must have a left or right value" )
253+ else -> error(" Either cannot have both left and right values" )
94254 }
95255 }
96- }
97256
98- inline fun <L , R > Either <L , R >.ifLeft (action : (L ) -> Unit ): Either <L , R > {
99- if (this is Either .Left ) {
100- action(left)
257+ @PublishedApi
258+ @Serializable
259+ @SerialName(" left" )
260+ internal data class Left <L , R >(override val left : L ) : Either<L, R>() {
261+ @Transient
262+ override val right: R ? = null
263+ override fun toString (): String = " Either.Left($left )"
101264 }
102- return this
103- }
104265
105- inline fun <L , R > Either <L , R >.ifRight (action : (R ) -> Unit ): Either <L , R > {
106- if (this is Either .Right ) {
107- action(right)
266+
267+ @Serializable
268+ @PublishedApi
269+ @SerialName(" right" )
270+ internal data class Right <L , R >(override val right : R ) : Either<L, R>() {
271+ @Transient
272+ override val left: L ? = null
273+ override fun toString (): String = " Either.Right($right )"
108274 }
109- return this
110275}
0 commit comments