Skip to content

Commit

Permalink
1.0.2 (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nek-12 committed Dec 11, 2023
2 parents 3bb61b9 + c96143d commit 2b37dfd
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 78 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -170,3 +170,4 @@ hs_err_pid*
# yarn.lock location & other
/kotlin-js-store/**
/.idea/appInsightsSettings.xml
/.idea/deploymentTargetSelector.xml
Expand Up @@ -7,8 +7,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import pro.respawn.apiresult.ApiResult
import pro.respawn.apiresult.ApiResult.Loading.isLoading
import pro.respawn.apiresult.asResult
import pro.respawn.apiresult.chain
import pro.respawn.apiresult.fold
import pro.respawn.apiresult.map
Expand Down Expand Up @@ -41,9 +40,9 @@ data class UiState(
)

class MainViewModel(
val transactionRepository: TransactionRepository = MockTransactionRepository(),
val securityRepository: SecurityRepository = MockSecurityRepository(),
val userRepository: UserRepository = MockUserRepository(),
private val transactionRepository: TransactionRepository = MockTransactionRepository(),
private val securityRepository: SecurityRepository = MockSecurityRepository(),
private val userRepository: UserRepository = MockUserRepository(),
) : ViewModel() {

private val _state = MutableStateFlow(UiState())
Expand All @@ -52,7 +51,8 @@ class MainViewModel(
fun onClickPurchase() = viewModelScope.launch {
_state.update { it.copy(isLoading = true, result = null) }

ApiResult(state.value.userId)
state.value.userId
.asResult
.requireNotNull()
.then { userRepository.getUser(it) }
.recover { userRepository.getAnonymousUser() }
Expand Down
3 changes: 1 addition & 2 deletions buildSrc/src/main/kotlin/Config.kt
Expand Up @@ -17,7 +17,7 @@ object Config {

const val majorRelease = 1
const val minorRelease = 0
const val patch = 1
const val patch = 2
const val postfix = ""
const val versionName = "$majorRelease.$minorRelease.$patch$postfix"
const val url = "https://github.com/respawn-app/ApiResult"
Expand Down Expand Up @@ -45,7 +45,6 @@ feature-rich.
addAll(compilerArgs)
add("-Xjvm-default=all") // enable all jvm optimizations
add("-Xcontext-receivers")
// add("-Xuse-k2")
addAll(optIns.map { "-opt-in=$it" })
}

Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/ConfigureAndroid.kt
Expand Up @@ -5,7 +5,7 @@ import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project

fun Project.configureAndroid(
commonExtension: CommonExtension<*, *, *, *, *>,
commonExtension: CommonExtension<*, *, *, *, *, *>,
) = commonExtension.apply {
compileSdk = Config.compileSdk
defaultConfig {
Expand Down
8 changes: 3 additions & 5 deletions buildSrc/src/main/kotlin/ConfigureMultiplatform.kt
@@ -1,19 +1,17 @@
@file:Suppress("MissingPackageDeclaration", "unused", "UNUSED_VARIABLE", "UndocumentedPublicFunction", "LongMethod")
@file:Suppress("MissingPackageDeclaration", "unused", "UndocumentedPublicFunction", "LongMethod")

import org.gradle.api.Project
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.getting
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

@OptIn(ExperimentalKotlinGradlePluginApi::class)
fun Project.configureMultiplatform(
ext: KotlinMultiplatformExtension,
) = ext.apply {
val libs by versionCatalog
explicitApi()

targetHierarchy.default()
applyDefaultHierarchyTemplate()

linuxX64()
linuxArm64()
Expand All @@ -26,7 +24,7 @@ fun Project.configureMultiplatform(
}

androidTarget {
publishAllLibraryVariants()
publishLibraryVariants("release")
}

jvm {
Expand Down
5 changes: 2 additions & 3 deletions buildSrc/src/main/kotlin/ConfigurePublication.kt
@@ -1,7 +1,6 @@
@file:Suppress("MissingPackageDeclaration", "unused")

import com.android.build.api.dsl.LibraryExtension
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
import com.android.build.gradle.tasks.BundleAar
import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
Expand All @@ -17,7 +16,7 @@ import org.gradle.plugins.signing.Sign
* Configures Maven publishing to sonatype for this project
*/
fun Project.publishMultiplatform() {
val properties = gradleLocalProperties(rootDir)
val properties by localProperties
val isReleaseBuild = properties["release"]?.toString().toBoolean()

val javadocTask = tasks.named("emptyJavadocJar") // TODO: Dokka does not support KMP javadocs for now
Expand Down Expand Up @@ -52,7 +51,7 @@ fun Project.publishAndroid(ext: LibraryExtension) = with(ext) {
}

afterEvaluate {
val properties = gradleLocalProperties(rootDir)
val properties by localProperties
val isReleaseBuild = properties["release"]?.toString().toBoolean()

requireNotNull(extensions.findByType<PublishingExtension>()).apply {
Expand Down
10 changes: 10 additions & 0 deletions buildSrc/src/main/kotlin/Util.kt
Expand Up @@ -10,7 +10,10 @@ import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType
import org.gradle.plugin.use.PluginDependency
import java.io.File
import java.io.FileInputStream
import java.util.Base64
import java.util.Properties

/**
* Load version catalog for usage in places where it is not available yet with gradle 7.x.
Expand Down Expand Up @@ -50,3 +53,10 @@ fun List<String>.toJavaArrayString() = buildString {
}

fun String.toBase64() = Base64.getEncoder().encodeToString(toByteArray())

val Project.localProperties
get() = lazy {
Properties().apply {
load(FileInputStream(File(rootProject.rootDir, "local.properties")))
}
}
61 changes: 37 additions & 24 deletions core/src/commonMain/kotlin/pro/respawn/apiresult/ApiResult.kt
Expand Up @@ -6,6 +6,8 @@
"NOTHING_TO_INLINE",
"TooManyFunctions",
"ThrowingExceptionsWithoutMessageOrCause",
"INVISIBLE_REFERENCE",
"INVISIBLE_MEMBER",
)

package pro.respawn.apiresult
Expand All @@ -22,9 +24,8 @@ import kotlin.jvm.JvmName

/**
* A class that represents a result of an operation.
* Create an instance with [ApiResult.invoke] and use various operators on the resulting object.
*
* This class is **extremely efficient**: no actual objects are created,
* This class is **efficient**: no actual objects are created unless dynamic type resolution is required,
* all operations are inlined and no function resolution is performed.
* ApiResult is **not** an Rx-style callback chain -
* the operators that are invoked are called **immediately** and in-place.
Expand All @@ -40,7 +41,7 @@ public sealed interface ApiResult<out T> {
* ```
* @see orNull
*/
public operator fun component1(): T? = (this as? Success<T>)?.result
public operator fun component1(): T? = orNull()

/**
* Get the [Error] component of this result or null
Expand All @@ -59,14 +60,6 @@ public sealed interface ApiResult<out T> {
*/
public operator fun not(): T = orThrow()

/**
* A loading state of an [ApiResult]
*/
public data object Loading : ApiResult<Nothing> {

override fun toString(): String = "ApiResult.Loading"
}

/**
* A value of [ApiResult] for its successful state.
* @param result a successful result value
Expand Down Expand Up @@ -102,7 +95,14 @@ public sealed interface ApiResult<out T> {
*/
public val isLoading: Boolean get() = this is Loading

public companion object {
/**
* A loading state of an [ApiResult]
*/
public companion object Loading : ApiResult<Nothing> {

override fun equals(other: Any?): Boolean = other is Loading
override fun hashCode(): Int = 42
override fun toString(): String = "ApiResult.Loading"

/**
* Execute [call], catching any exceptions, and wrap it in an [ApiResult].
Expand Down Expand Up @@ -134,7 +134,7 @@ public sealed interface ApiResult<out T> {
* Use this for applying operators such as `require` and `mapWrapping` to build chains of operators that should
* start with an empty value.
*/
public inline operator fun invoke(): ApiResult<Unit> = ApiResult(Unit)
public inline operator fun invoke(): ApiResult<Unit> = this
}
}

Expand Down Expand Up @@ -189,12 +189,12 @@ public inline infix fun <T, R : T> ApiResult<T>.or(defaultValue: R): T = orElse
/**
* @return null if [this] is an [ApiResult.Error] or [ApiResult.Loading], otherwise return self.
*/
public inline fun <T> ApiResult<T>.orNull(): T? = or(null)
public inline fun <T> ApiResult<T>?.orNull(): T? = this?.or(null)

/**
* @return exception if [this] is [Error] or null
*/
public inline fun <T> ApiResult<T>.exceptionOrNull(): Exception? = (this as? Error)?.e
public inline fun <T> ApiResult<T>?.exceptionOrNull(): Exception? = (this as? Error)?.e

/**
* Throws [ApiResult.Error.e], or [NotFinishedException] if the request has not been completed yet.
Expand All @@ -206,6 +206,13 @@ public inline fun <T> ApiResult<T>.orThrow(): T = when (this) {
is Success -> result
}

/**
* Throws if [this] result is an [Error] and [Error.e] is of type [T]. Ignores all other exceptions.
*
* @return a result that can be [Error] but is guaranteed to not have an exception of type [T] wrapped.
*/
public inline fun <reified T : Exception, R> ApiResult<R>.rethrow(): ApiResult<R> = mapError<T, R> { throw it }

/**
* Fold [this] returning the result of [onSuccess] or [onError]
* By default, maps [Loading] to [Error] with [NotFinishedException]
Expand Down Expand Up @@ -327,7 +334,7 @@ public inline fun <T> ApiResult<T>.errorOnLoading(
/**
* Alias for [errorOnNull]
*/
public inline fun <T> ApiResult<T?>.requireNotNull(): ApiResult<T & Any> = errorOnNull()
public inline fun <T> ApiResult<T?>?.requireNotNull(): ApiResult<T & Any> = errorOnNull()

/**
* Throws if [this] is not [Success] and returns [Success] otherwise.
Expand Down Expand Up @@ -446,9 +453,9 @@ public inline infix fun <T, R> ApiResult<T>.tryMap(block: (T) -> R): ApiResult<R
* @see errorIf
* @see errorIfEmpty
*/
public inline fun <T> ApiResult<T?>.errorOnNull(
public inline fun <T> ApiResult<T?>?.errorOnNull(
exception: () -> Exception = { ConditionNotSatisfiedException("Value was null") },
): ApiResult<T & Any> = errorIf(exception) { it == null }.map { requireNotNull(it) }
): ApiResult<T & Any> = this?.errorIf(exception) { it == null }?.map { requireNotNull(it) } ?: Error(exception())

/**
* Maps [Error] values to nulls
Expand Down Expand Up @@ -483,7 +490,7 @@ public inline infix fun <T> ApiResult<T>.recover(another: (e: Exception) -> ApiR
*/
@JvmName("tryRecoverTyped")
public inline infix fun <reified T : Exception, R> ApiResult<R>.tryRecover(block: (T) -> R): ApiResult<R> =
recover<T, R> { ApiResult { block(it) } }
recover<T, R>(another = { ApiResult { block(it) } })

/**
* Calls [recover] catching and wrapping any exceptions thrown inside [block].
Expand Down Expand Up @@ -576,13 +583,19 @@ public inline fun <T, R> ApiResult<T>.flatMap(another: (T) -> ApiResult<R>): Api
public inline fun <T> ApiResult<T>.require(
message: () -> String? = { null },
predicate: (T) -> Boolean
): ApiResult<T> =
errorUnless(
exception = { IllegalArgumentException(message()) },
predicate = predicate
)
): ApiResult<T> = errorUnless(
exception = { IllegalArgumentException(message()) },
predicate = predicate
)

/**
* Map [this] result to [Unit], discarding the value
*/
public inline fun ApiResult<*>.unit(): ApiResult<Unit> = map {}

/**
* Create an [ApiResult] from `this` value, based on the type of it.
*
* @see ApiResult.invoke
*/
public inline val <T> T.asResult: ApiResult<T> get() = ApiResult(this)
Expand Up @@ -159,14 +159,21 @@ public inline fun <T> Sequence<ApiResult<T?>>.filterNulls(): Sequence<ApiResult<
/**
* Merges all [Success] results into a single [List], or if any has failed, returns [Error].
*/
public inline fun <T> Iterable<ApiResult<T>>.merge(): ApiResult<List<T>> = ApiResult { map { it.orThrow() } }
public inline fun <T> Iterable<ApiResult<T>>.merge(): ApiResult<List<T>> = ApiResult { map { !it } }

/**
* Merges all [results] into a single [List], or if any has failed, returns [Error].
*/
public inline fun <T> ApiResult.Companion.merge(vararg results: ApiResult<T>): ApiResult<List<T>> =
public inline fun <T> ApiResult.Loading.merge(vararg results: ApiResult<T>): ApiResult<List<T>> =
results.asIterable().merge()

/**
* Merges [this] results and all other [results] into a single result of type [T].
*/
public inline fun <T> ApiResult<T>.merge(
vararg results: ApiResult<T>
): ApiResult<List<T>> = ApiResult.merge(this, *results)

/**
* Returns a list of only [Success] values, discarding any errors
*/
Expand Down
Expand Up @@ -7,7 +7,7 @@ import pro.respawn.apiresult.ApiResult.Loading
* result is [Loading]
*/
public class NotFinishedException(
message: String? = "ApiResult is still in Loading state",
message: String? = "ApiResult is still in the Loading state",
) : IllegalArgumentException(message)

/**
Expand Down
Expand Up @@ -53,7 +53,7 @@ public suspend inline fun <T> SuspendResult(
* Emits [ApiResult.Loading], then executes [call] and wraps it.
* @see Flow.asApiResult
*/
public inline fun <T> ApiResult.Companion.flow(
public inline fun <T> Loading.tryFlow(
crossinline call: suspend () -> T
): Flow<ApiResult<T>> = kotlinx.coroutines.flow.flow {
emit(Loading)
Expand All @@ -65,7 +65,7 @@ public inline fun <T> ApiResult.Companion.flow(
* @see Flow.asApiResult
*/
@JvmName("flowWithResult")
public inline fun <T> ApiResult.Companion.flow(
public inline fun <T> Loading.flow(
crossinline result: suspend () -> ApiResult<T>,
): Flow<ApiResult<T>> = kotlinx.coroutines.flow.flow {
emit(Loading)
Expand Down

0 comments on commit 2b37dfd

Please sign in to comment.