Skip to content

Commit 3eb44b7

Browse files
authored
remove Arrow dependency (#48)
1 parent f4efada commit 3eb44b7

File tree

4 files changed

+298
-8
lines changed

4 files changed

+298
-8
lines changed

apollo-execution-runtime/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ kotlin {
1717
api(libs.apollo.api)
1818
implementation(libs.atomicfu)
1919
api(libs.coroutines)
20-
implementation(libs.arrowCore)
2120
}
2221
}
2322

apollo-execution-runtime/src/commonMain/kotlin/com/apollographql/apollo/execution/GraphQLRequest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
package com.apollographql.apollo.execution
44

5-
import arrow.core.flatMap
65
import com.apollographql.apollo.annotations.ApolloInternal
76
import com.apollographql.apollo.api.http.internal.urlDecode
87
import com.apollographql.apollo.api.json.jsonReader
98
import com.apollographql.apollo.api.json.readAny
9+
import com.apollographql.apollo.execution.internal.flatMap
1010
import okio.Buffer
1111
import okio.BufferedSource
1212
import okio.use
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/**
2+
* A very minimal implementation of Arrow raise DSL, copied from https://github.com/arrow-kt/arrow/blob/27330d7c7388117b4cb8f9a87ef85e076644644d/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core
3+
*
4+
* Documentation has been stripped and all symbols moved to a new package name and marked as internal.
5+
* Behavioural modifications are indicated with the `XXX:` comment
6+
*/
7+
@file:OptIn(ExperimentalContracts::class, ExperimentalTypeInference::class)
8+
9+
package com.apollographql.apollo.execution.internal
10+
11+
import com.apollographql.apollo.execution.internal.Either.Left
12+
import com.apollographql.apollo.execution.internal.Either.Right
13+
import kotlinx.atomicfu.atomic
14+
import kotlin.Result.Companion.failure
15+
import kotlin.contracts.ExperimentalContracts
16+
import kotlin.contracts.InvocationKind
17+
import kotlin.contracts.InvocationKind.AT_MOST_ONCE
18+
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
19+
import kotlin.contracts.contract
20+
import kotlin.coroutines.cancellation.CancellationException
21+
import kotlin.experimental.ExperimentalTypeInference
22+
import kotlin.jvm.JvmName
23+
24+
internal sealed class Either<out A, out B> {
25+
26+
fun isLeft(): Boolean {
27+
contract {
28+
returns(true) implies (this@Either is Left<A>)
29+
returns(false) implies (this@Either is Right<B>)
30+
}
31+
return this@Either is Left<A>
32+
}
33+
34+
fun isRight(): Boolean {
35+
contract {
36+
returns(true) implies (this@Either is Right<B>)
37+
returns(false) implies (this@Either is Left<A>)
38+
}
39+
return this@Either is Right<B>
40+
}
41+
42+
inline fun isLeft(predicate: (A) -> Boolean): Boolean {
43+
contract {
44+
returns(true) implies (this@Either is Left<A>)
45+
callsInPlace(predicate, InvocationKind.AT_MOST_ONCE)
46+
}
47+
return this@Either is Left<A> && predicate(value)
48+
}
49+
50+
inline fun isRight(predicate: (B) -> Boolean): Boolean {
51+
contract {
52+
returns(true) implies (this@Either is Right<B>)
53+
callsInPlace(predicate, InvocationKind.AT_MOST_ONCE)
54+
}
55+
return this@Either is Right<B> && predicate(value)
56+
}
57+
58+
internal inline fun <C> fold(ifLeft: (left: A) -> C, ifRight: (right: B) -> C): C {
59+
contract {
60+
callsInPlace(ifLeft, InvocationKind.AT_MOST_ONCE)
61+
callsInPlace(ifRight, InvocationKind.AT_MOST_ONCE)
62+
}
63+
return when (this) {
64+
is Right -> ifRight(value)
65+
is Left -> ifLeft(value)
66+
}
67+
}
68+
69+
fun swap(): Either<B, A> =
70+
fold({ Right(it) }, { Left(it) })
71+
72+
inline fun <C> map(f: (right: B) -> C): Either<A, C> {
73+
contract {
74+
callsInPlace(f, InvocationKind.AT_MOST_ONCE)
75+
}
76+
return flatMap { Right(f(it)) }
77+
}
78+
79+
inline fun <C> mapLeft(f: (A) -> C): Either<C, B> {
80+
contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) }
81+
return when (this) {
82+
is Left -> Left(f(value))
83+
is Right -> Right(value)
84+
}
85+
}
86+
87+
inline fun onRight(action: (right: B) -> Unit): Either<A, B> {
88+
contract {
89+
callsInPlace(action, InvocationKind.AT_MOST_ONCE)
90+
}
91+
return also { if (it.isRight()) action(it.value) }
92+
}
93+
94+
inline fun onLeft(action: (left: A) -> Unit): Either<A, B> {
95+
contract {
96+
callsInPlace(action, InvocationKind.AT_MOST_ONCE)
97+
}
98+
return also { if (it.isLeft()) action(it.value) }
99+
}
100+
101+
fun getOrNull(): B? {
102+
contract {
103+
returns(null) implies (this@Either is Left<A>)
104+
returnsNotNull() implies (this@Either is Right<B>)
105+
}
106+
return getOrElse { null }
107+
}
108+
109+
fun leftOrNull(): A? {
110+
contract {
111+
returnsNotNull() implies (this@Either is Left<A>)
112+
returns(null) implies (this@Either is Right<B>)
113+
}
114+
return fold(::identity) { null }
115+
}
116+
117+
data class Left<out A> constructor(val value: A) : Either<A, Nothing>() {
118+
override fun toString(): String = "Either.Left($value)"
119+
120+
public companion object
121+
}
122+
123+
data class Right<out B> constructor(val value: B) : Either<Nothing, B>() {
124+
override fun toString(): String = "Either.Right($value)"
125+
126+
public companion object {
127+
@PublishedApi
128+
internal val unit: Either<Nothing, Unit> = Right(Unit)
129+
}
130+
}
131+
132+
override fun toString(): String = fold(
133+
{ "Either.Left($it)" },
134+
{ "Either.Right($it)" }
135+
)
136+
}
137+
138+
internal inline fun <A, B, C> Either<A, B>.flatMap(f: (right: B) -> Either<A, C>): Either<A, C> {
139+
contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) }
140+
return when (this) {
141+
is Right -> f(this.value)
142+
is Left -> this
143+
}
144+
}
145+
146+
internal inline infix fun <A, B> Either<A, B>.getOrElse(default: (A) -> B): B {
147+
contract { callsInPlace(default, InvocationKind.AT_MOST_ONCE) }
148+
return when (this) {
149+
is Left -> default(this.value)
150+
is Right -> this.value
151+
}
152+
}
153+
154+
@Suppress("NOTHING_TO_INLINE")
155+
internal inline fun <A> identity(a: A): A = a
156+
157+
internal fun <A> A.left(): Either<A, Nothing> = Left(this)
158+
159+
internal fun <A> A.right(): Either<Nothing, A> = Right(this)
160+
161+
@DslMarker
162+
internal annotation class RaiseDSL
163+
164+
internal interface Raise<in Error> {
165+
@RaiseDSL
166+
fun raise(r: Error): Nothing
167+
}
168+
169+
internal inline fun <Error, A> either(@BuilderInference block: Raise<Error>.() -> A): Either<Error, A> {
170+
contract { callsInPlace(block, AT_MOST_ONCE) }
171+
return fold(block, { Left(it) }, { Right(it) })
172+
}
173+
174+
@JvmName("_foldOrThrow")
175+
internal inline fun <Error, A, B> fold(
176+
@BuilderInference block: Raise<Error>.() -> A,
177+
recover: (error: Error) -> B,
178+
transform: (value: A) -> B,
179+
): B {
180+
contract {
181+
callsInPlace(block, AT_MOST_ONCE)
182+
callsInPlace(recover, AT_MOST_ONCE)
183+
callsInPlace(transform, AT_MOST_ONCE)
184+
}
185+
return fold(block, { throw it }, recover, transform)
186+
}
187+
188+
@JvmName("_fold")
189+
internal inline fun <Error, A, B> fold(
190+
@BuilderInference block: Raise<Error>.() -> A,
191+
catch: (throwable: Throwable) -> B,
192+
recover: (error: Error) -> B,
193+
transform: (value: A) -> B,
194+
): B {
195+
contract {
196+
callsInPlace(block, AT_MOST_ONCE)
197+
callsInPlace(catch, AT_MOST_ONCE)
198+
callsInPlace(recover, AT_MOST_ONCE)
199+
callsInPlace(transform, AT_MOST_ONCE)
200+
}
201+
val raise = DefaultRaise(false)
202+
return try {
203+
val res = block(raise)
204+
raise.complete()
205+
transform(res)
206+
} catch (e: RaiseCancellationException) {
207+
raise.complete()
208+
recover(e.raisedOrRethrow(raise))
209+
} catch (e: Throwable) {
210+
raise.complete()
211+
catch(e.nonFatalOrThrow())
212+
}
213+
}
214+
215+
internal class DefaultRaise(internal val isTraced: Boolean) : Raise<Any?> {
216+
private val isActive = atomic(true)
217+
218+
internal fun complete(): Boolean = isActive.getAndSet(false)
219+
220+
override fun raise(r: Any?): Nothing = when {
221+
isActive.value -> throw if (isTraced) Traced(r, this) else NoTrace(r, this)
222+
else -> throw RaiseLeakedException()
223+
}
224+
}
225+
226+
internal sealed class RaiseCancellationException(
227+
internal val raised: Any?,
228+
internal val raise: Raise<Any?>
229+
) : CancellationException(RaiseCancellationExceptionCaptured)
230+
231+
internal const val RaiseCancellationExceptionCaptured: String =
232+
"kotlin.coroutines.cancellation.CancellationException should never get swallowed. Always re-throw it if captured." +
233+
"This swallows the exception of Arrow's Raise, and leads to unexpected behavior." +
234+
"When working with Arrow prefer Either.catch or arrow.core.raise.catch to automatically rethrow CancellationException."
235+
236+
private class RaiseLeakedException : IllegalStateException(
237+
"""
238+
'raise' or 'bind' was leaked outside of its context scope.
239+
Make sure all calls to 'raise' and 'bind' occur within the lifecycle of nullable { }, either { } or similar builders.
240+
241+
See Arrow documentation on 'Typed errors' for further information.
242+
""".trimIndent()
243+
)
244+
245+
@Suppress("UNCHECKED_CAST")
246+
internal fun <R> CancellationException.raisedOrRethrow(raise: DefaultRaise): R =
247+
when {
248+
this is RaiseCancellationException && this.raise === raise -> raised as R
249+
else -> throw this
250+
}
251+
252+
internal fun Throwable.nonFatalOrThrow(): Throwable =
253+
if (NonFatal(this)) this else throw this
254+
255+
@RaiseDSL
256+
internal inline fun <Error, OtherError, A> Raise<Error>.withError(
257+
transform: (OtherError) -> Error,
258+
@BuilderInference block: Raise<OtherError>.() -> A
259+
): A {
260+
contract {
261+
callsInPlace(block, EXACTLY_ONCE)
262+
}
263+
recover({ return block(this) }) { raise(transform(it)) }
264+
}
265+
266+
@RaiseDSL
267+
internal inline fun <Error, A> recover(
268+
@BuilderInference block: Raise<Error>.() -> A,
269+
@BuilderInference recover: (error: Error) -> A,
270+
): A {
271+
contract {
272+
callsInPlace(block, AT_MOST_ONCE)
273+
callsInPlace(recover, AT_MOST_ONCE)
274+
}
275+
return fold(block, { throw it }, recover, ::identity)
276+
}
277+
278+
internal inline fun <A, B> Result<A>.flatMap(transform: (value: A) -> Result<B>): Result<B> {
279+
contract { callsInPlace(transform, AT_MOST_ONCE) }
280+
return map(transform).fold(::identity, ::failure)
281+
}
282+
283+
/**
284+
* XXX: Arrow handles the JVM better here
285+
*/
286+
internal fun NonFatal(t: Throwable): Boolean =
287+
when (t) {
288+
is CancellationException -> false
289+
else -> true
290+
}
291+
292+
/**
293+
* XXX: Arrow handles the JVM better here
294+
*/
295+
internal class NoTrace(raised: Any?, raise: Raise<Any?>) : RaiseCancellationException(raised, raise)
296+
297+
internal class Traced(raised: Any?, raise: Raise<Any?>, override val cause: Traced? = null): RaiseCancellationException(raised, raise)

apollo-execution-runtime/src/commonMain/kotlin/com/apollographql/apollo/execution/internal/prepare.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33

44
package com.apollographql.apollo.execution.internal
55

6-
import arrow.core.Either
7-
import arrow.core.left
8-
import arrow.core.raise.Raise
9-
import arrow.core.raise.either
10-
import arrow.core.raise.withError
11-
import arrow.core.right
126
import com.apollographql.apollo.annotations.ApolloExperimental
137
import com.apollographql.apollo.api.Error
148
import com.apollographql.apollo.ast.*

0 commit comments

Comments
 (0)