Skip to content

Commit db33ae4

Browse files
authored
2.0.0-beta02 (#8)
2 parents f0f07cf + 73c8585 commit db33ae4

File tree

12 files changed

+831
-63
lines changed

12 files changed

+831
-63
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ error handling **on steroids**.
1919

2020
## Features
2121

22-
* ApiResult is **lightweight**. The library tries to inline operators and reduce allocations where possible.
22+
* ApiResult is **lightweight**. The library creates no objects, makes no allocations or virtual function resolutions.
23+
Most of the code is inlined.
2324
* ApiResult offers 90+ operators covering most of possible use cases to turn your
2425
code from imperative and procedural to declarative and functional, which is more readable and extensible.
2526
* ApiResult defines a contract that you can use in your code. No one will be able to obtain the result of a computation
2627
without being forced to handle errors at compilation time.
28+
* The library has 129 tests for 92% operator coverage.
2729

2830
## Preview
2931

buildSrc/src/main/kotlin/Config.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ object Config {
1818
const val majorRelease = 2
1919
const val minorRelease = 0
2020
const val patch = 0
21-
const val postfix = "-alpha01"
21+
const val postfix = "-beta01"
2222
const val versionName = "$majorRelease.$minorRelease.$patch$postfix"
2323
const val url = "https://github.com/respawn-app/ApiResult"
2424
const val licenseName = "The Apache Software License, Version 2.0"

core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ android {
88

99
dependencies {
1010
commonMainApi(libs.kotlin.coroutines.core)
11+
jvmTestImplementation(libs.bundles.unittest)
1112
}

core/src/commonMain/kotlin/pro/respawn/apiresult/ApiResult.kt

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import kotlin.jvm.JvmName
3434
* the operators that are invoked are called **immediately** and in-place.
3535
*/
3636
@JvmInline
37-
public value class ApiResult<out T> @PublishedApi internal constructor(@PublishedApi internal val value: Any?) {
37+
public value class ApiResult<out T> private constructor(@PublishedApi internal val value: Any?) {
3838

3939
/**
4040
* Get the [Success] component of this result or null
@@ -45,7 +45,7 @@ public value class ApiResult<out T> @PublishedApi internal constructor(@Publishe
4545
* ```
4646
* @see orNull
4747
*/
48-
public operator fun component1(): T? = orNull()
48+
public inline operator fun component1(): T? = orNull()
4949

5050
/**
5151
* Get the [Error] component of this result or null
@@ -56,27 +56,35 @@ public value class ApiResult<out T> @PublishedApi internal constructor(@Publishe
5656
* ```
5757
* @see exceptionOrNull
5858
*/
59-
public operator fun component2(): Exception? = exceptionOrNull()
59+
public inline operator fun component2(): Exception? = exceptionOrNull()
6060

6161
/**
6262
* Bang operator returns the result or throws if it is an [Error] or [Loading]
6363
* This is equivalent to calling [orThrow]
6464
*/
65-
public operator fun not(): T = orThrow()
65+
public inline operator fun not(): T = orThrow()
6666

6767
/**
6868
* The state of [ApiResult] that represents an error.
6969
* @param e wrapped [Exception]
7070
*/
7171
@JvmInline
7272
@PublishedApi
73-
internal value class Error(@JvmField val e: Exception) {
73+
internal value class Error private constructor(@JvmField val e: Exception) {
7474

7575
override fun toString(): String = "ApiResult.Error: message=${e.message} and cause: $e"
76+
77+
companion object {
78+
79+
fun create(e: Exception) = Error(e)
80+
}
7681
}
7782

7883
@PublishedApi
79-
internal data object Loading
84+
internal data object Loading {
85+
86+
override fun toString(): String = "ApiResult.Loading"
87+
}
8088

8189
/**
8290
* Whether this is [Success]
@@ -91,10 +99,10 @@ public value class ApiResult<out T> @PublishedApi internal constructor(@Publishe
9199
/**
92100
* Whether this is [Loading]
93101
*/
94-
public inline val isLoading: Boolean get() = value is Loading
102+
public inline val isLoading: Boolean get() = value === Loading
95103

96104
override fun toString(): String = when {
97-
value is Error || value is Loading -> value.toString()
105+
value is Error || value === Loading -> value.toString()
98106
else -> "ApiResult.Success: $value"
99107
}
100108

@@ -103,17 +111,17 @@ public value class ApiResult<out T> @PublishedApi internal constructor(@Publishe
103111
/**
104112
* Create a successful [ApiResult] value
105113
*/
106-
public inline fun <T> Success(value: T): ApiResult<T> = ApiResult(value)
114+
public fun <T> Success(value: T): ApiResult<T> = ApiResult(value = value)
107115

108116
/**
109117
* Create an error [ApiResult] value
110118
*/
111-
public inline fun <T> Error(value: Exception): ApiResult<T> = ApiResult(Error(e = value))
119+
public fun <T> Error(e: Exception): ApiResult<T> = ApiResult(value = Error.create(e = e))
112120

113121
/**
114122
* Create a loading [ApiResult] value
115123
*/
116-
public inline fun <T> Loading(): ApiResult<T> = ApiResult(value = Loading)
124+
public fun <T> Loading(): ApiResult<T> = ApiResult(value = Loading)
117125

118126
/**
119127
* Create an [ApiResult] instance using the given value.
@@ -123,7 +131,7 @@ public value class ApiResult<out T> @PublishedApi internal constructor(@Publishe
123131
* If you want to directly create a success value of an [Exception], use [Success]
124132
*/
125133
public inline operator fun <T> invoke(value: T): ApiResult<T> = when (value) {
126-
is Exception -> Error(value = value)
134+
is Exception -> Error(e = value)
127135
else -> Success(value)
128136
}
129137

@@ -135,11 +143,11 @@ public value class ApiResult<out T> @PublishedApi internal constructor(@Publishe
135143
* [CancellationException]s are rethrown.
136144
*/
137145
public inline operator fun <T> invoke(call: () -> T): ApiResult<T> = try {
138-
Success(call())
146+
Success(value = call())
139147
} catch (e: CancellationException) {
140148
throw e
141149
} catch (expected: Exception) {
142-
Error(expected)
150+
Error(e = expected)
143151
}
144152

145153
/**
@@ -195,10 +203,13 @@ public inline infix fun <T, R : T> ApiResult<T>.orElse(block: (e: Exception) ->
195203
}
196204

197205
/**
198-
* If [this] is [Error], returns [defaultValue].
206+
* If [this] is [Error] or [Loading], returns [defaultValue].
199207
* @see orElse
200208
*/
201-
public inline infix fun <T, R : T> ApiResult<T>.or(defaultValue: R): T = orElse { defaultValue }
209+
public inline infix fun <T, R : T> ApiResult<T>.or(defaultValue: R): T = when (value) {
210+
is Error, is Loading -> defaultValue
211+
else -> value as T
212+
}
202213

203214
/**
204215
* @return null if [this] is an [ApiResult.Error] or [ApiResult.Loading], otherwise return self.
@@ -312,7 +323,9 @@ public inline fun <T> ApiResult<T>.errorIf(
312323
callsInPlace(predicate, InvocationKind.AT_MOST_ONCE)
313324
callsInPlace(exception, InvocationKind.AT_MOST_ONCE)
314325
}
315-
return if (isSuccess && predicate(value as T)) Error(value = exception()) else this
326+
if (!isSuccess) return this
327+
if (!predicate(value as T)) return this
328+
return Error(e = exception())
316329
}
317330

318331
/**
@@ -326,7 +339,7 @@ public inline fun <T> ApiResult<T>.errorOnLoading(
326339
}
327340

328341
return when (value) {
329-
is Loading -> Error(value = exception())
342+
is Loading -> Error(e = exception())
330343
else -> this
331344
}
332345
}
@@ -337,10 +350,10 @@ public inline fun <T> ApiResult<T>.errorOnLoading(
337350
public inline fun <T> ApiResult<T?>?.requireNotNull(): ApiResult<T & Any> = errorOnNull()
338351

339352
/**
340-
* Throws if [this] is not [Success] and returns [Success] otherwise.
353+
* Alias for [orThrow]
341354
* @see orThrow
342355
*/
343-
public inline fun <T> ApiResult<T>.require(): ApiResult<T> = Success(!this)
356+
public inline fun <T> ApiResult<T>.require(): T = orThrow()
344357

345358
/**
346359
* Change the type of the [Success] to [R] without affecting [Error]/[Loading] results
@@ -352,25 +365,19 @@ public inline infix fun <T, R> ApiResult<T>.map(block: (T) -> R): ApiResult<R> {
352365
contract {
353366
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
354367
}
355-
return fold(
356-
onSuccess = { Success(value = block(it)) },
357-
onError = { Error(value = it) },
358-
onLoading = { ApiResult.Loading() }
359-
)
368+
if (isSuccess) return Success(value = block(value as T))
369+
return this as ApiResult<R>
360370
}
361371

362372
/**
363373
* Map the [Success] result using [transform], and if the result is not a success, return [default]
364374
*/
365-
public inline fun <T, R> ApiResult<T>.mapOrDefault(default: () -> R, transform: (T) -> R): R {
375+
public inline fun <T, R> ApiResult<T>.mapOrDefault(default: (e: Exception) -> R, transform: (T) -> R): R {
366376
contract {
367377
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
368378
callsInPlace(default, InvocationKind.AT_MOST_ONCE)
369379
}
370-
return fold(
371-
onSuccess = { transform(it) },
372-
onError = { default() }
373-
)
380+
return map(transform).orElse(default)
374381
}
375382

376383
/**
@@ -379,10 +386,7 @@ public inline fun <T, R> ApiResult<T>.mapOrDefault(default: () -> R, transform:
379386
public inline fun <T, R> ApiResult<T>.mapEither(
380387
success: (T) -> R,
381388
error: (Exception) -> Exception,
382-
): ApiResult<R> = fold(
383-
onSuccess = { Success(success(it)) },
384-
onError = { Error(value = error(it)) }
385-
)
389+
): ApiResult<R> = map(success).mapError(error)
386390

387391
/**
388392
* Maps [Loading] to a [Success], not affecting other states.
@@ -414,7 +418,7 @@ public inline infix fun <reified R : Exception, T> ApiResult<T>.mapError(block:
414418
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
415419
}
416420
return when {
417-
value is Error && value.e is R -> Error(value = block(value.e))
421+
value is Error && value.e is R -> Error(e = block(value.e))
418422
else -> this
419423
}
420424
}
@@ -427,11 +431,10 @@ public inline fun <T> ApiResult<T>.mapErrorToCause(): ApiResult<T> = mapError {
427431
/**
428432
* Unwrap an ApiResult<ApiResult<T>> to be ApiResult<T>
429433
*/
430-
public inline fun <T> ApiResult<ApiResult<T>>.unwrap(): ApiResult<T> = fold(
431-
onSuccess = { it },
432-
onError = { Error(value = it) },
433-
onLoading = { ApiResult.Loading() },
434-
)
434+
public inline fun <T> ApiResult<ApiResult<T>>.unwrap(): ApiResult<T> = when (value) {
435+
is Error, is Loading -> this
436+
else -> value
437+
} as ApiResult<T>
435438

436439
/**
437440
* Change the type of successful result to [R], also wrapping [block]
@@ -450,17 +453,17 @@ public inline infix fun <T, R> ApiResult<T>.tryMap(
450453
* @see errorIf
451454
* @see errorIfEmpty
452455
*/
453-
public inline fun <T> ApiResult<T?>?.errorOnNull(
456+
public inline fun <T : Any> ApiResult<T?>?.errorOnNull(
454457
exception: () -> Exception = { ConditionNotSatisfiedException("Value was null") },
455-
): ApiResult<T & Any> {
458+
): ApiResult<T> {
456459
contract {
457-
returns() implies (this@errorOnNull != null)
460+
returnsNotNull()
458461
}
459-
return when (this?.value) {
460-
is Error -> Error(value = value.e)
462+
return when (val r = this?.value) {
463+
is Error -> Error(e = r.e)
461464
is Loading -> ApiResult.Loading()
462-
null -> Error(value = exception())
463-
else -> ApiResult(value = value)
465+
null -> Error(e = exception())
466+
else -> Success(value = r as T)
464467
}
465468
}
466469

@@ -531,10 +534,8 @@ public inline fun <T> ApiResult<T>.recoverIf(
531534
callsInPlace(condition, InvocationKind.AT_MOST_ONCE)
532535
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
533536
}
534-
return when {
535-
value is Error && condition(value.e) -> block(value.e)
536-
else -> ApiResult(value = value)
537-
}
537+
if (value !is Error || !condition(value.e)) return this
538+
return block(value.e)
538539
}
539540

540541
/**

core/src/commonMain/kotlin/pro/respawn/apiresult/SuspendResult.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public inline fun <T> ApiResult.Companion.flow(
8080
public inline fun <T> Flow<T>.asApiResult(): Flow<ApiResult<T>> = this
8181
.map { it.asResult }
8282
.onStart { emit(ApiResult.Loading()) }
83-
.catchExceptions { emit(ApiResult.Error(value = it)) }
83+
.catchExceptions { emit(ApiResult.Error(e = it)) }
8484

8585
/**
8686
* Maps each success value of [this] flow using [transform]

0 commit comments

Comments
 (0)