
commonMain.com.skydoves.sandwich.ApiResponse.kt Maven / Gradle / Ivy
/*
* Designed and developed by 2020 skydoves (Jaewoong Eum)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.skydoves.sandwich
import com.skydoves.sandwich.annotations.InternalSandwichApi
import com.skydoves.sandwich.mappers.ApiResponseFailureMapper
import com.skydoves.sandwich.mappers.ApiResponseFailureSuspendMapper
import com.skydoves.sandwich.operators.ApiResponseOperator
import com.skydoves.sandwich.operators.ApiResponseSuspendOperator
import kotlinx.coroutines.launch
/**
* @author skydoves (Jaewoong Eum)
*
* ApiResponse is an interface for constructing standard responses from the retrofit call.
*/
public sealed interface ApiResponse {
/**
* @author skydoves (Jaewoong Eum)
*
* API Success response class from OkHttp request call.
* The [data] is a nullable generic type. (A response without data)
* @property data The de-serialized response body of a successful data.
* @property tag An additional value that can be held to distinguish the origin of the [data] or to facilitate post-processing of successful data.
*/
public data class Success(public val data: T, public val tag: Any? = null) : ApiResponse
/**
* @author skydoves (Jaewoong Eum)
*
* API Failure response class from OkHttp request call.
* There are two subtypes: [ApiResponse.Failure.Error] and [ApiResponse.Failure.Exception].
*/
public sealed interface Failure : ApiResponse {
/**
* API response error case.
* API communication conventions do not match or applications need to handle errors.
* e.g., internal server error.
*
* @property payload An error payload that can contain detailed error information.
*/
public open class Error(public val payload: Any?) : Failure {
override fun equals(other: Any?): Boolean = other is Error &&
payload == other.payload
override fun hashCode(): Int {
var result = 17
result = 31 * result + payload.hashCode()
return result
}
override fun toString(): String = payload.toString()
}
/**
* @author skydoves (Jaewoong Eum)
*
* API request Exception case.
* An unexpected exception occurs while creating requests or processing an response in the client side.
* e.g., network connection error, timeout.
*
* @param throwable An throwable exception.
*
* @property message The localized message from the exception.
*/
public open class Exception(public val throwable: Throwable) : Failure {
public val message: String? = throwable.message
override fun equals(other: Any?): Boolean = other is Exception &&
throwable == other.throwable
override fun hashCode(): Int {
var result = 17
result = 31 * result + throwable.hashCode()
return result
}
override fun toString(): String = message.orEmpty()
}
}
public companion object {
/**
* @author skydoves (Jaewoong Eum)
*
* [Failure] factory function. Only receives [Throwable] as an argument.
*
* @param ex A throwable.
*
* @return A [ApiResponse.Failure.Exception] based on the throwable.
*/
public fun exception(ex: Throwable): Failure.Exception =
Failure.Exception(ex).operate().maps() as Failure.Exception
/**
* @author skydoves (Jaewoong Eum)
*
* ApiResponse Factory.
*
* Create an [ApiResponse] from the given executable [f].
*
* If the [f] doesn't throw any exceptions, it creates [ApiResponse.Success].
* If the [f] throws an exception, it creates [ApiResponse.Failure.Exception].
*/
public inline fun of(tag: Any? = null, crossinline f: () -> T): ApiResponse {
return try {
val result = f()
Success(
data = result,
tag = tag,
)
} catch (e: Exception) {
exception(e)
}.operate().maps()
}
/**
* @author skydoves (Jaewoong Eum)
*
* ApiResponse Factory.
*
* Create an [ApiResponse] from the given executable [f].
*
* If the [f] doesn't throw any exceptions, it creates [ApiResponse.Success].
* If the [f] throws an exception, it creates [ApiResponse.Failure.Exception].
*/
@SuspensionFunction
public suspend inline fun suspendOf(
tag: Any? = null,
crossinline f: suspend () -> T,
): ApiResponse {
val result = f()
return of(tag = tag) { result }
}
/**
* @author skydoves (Jaewoong Eum)
*
* Operates if there is a global [com.skydoves.sandwich.operators.SandwichOperator]
* which operates on [ApiResponse]s globally on each response and returns the target [ApiResponse].
*
* @return [ApiResponse] A target [ApiResponse].
*/
@InternalSandwichApi
@Suppress("UNCHECKED_CAST")
public fun ApiResponse.operate(): ApiResponse = apply {
val globalOperators = SandwichInitializer.sandwichOperators
globalOperators.forEach { globalOperator ->
if (globalOperator is ApiResponseOperator<*>) {
operator(globalOperator as ApiResponseOperator)
} else if (globalOperator is ApiResponseSuspendOperator<*>) {
val scope = SandwichInitializer.sandwichScope
scope.launch {
suspendOperator(globalOperator as ApiResponseSuspendOperator)
}
}
}
}
@InternalSandwichApi
@Suppress("UNCHECKED_CAST")
public fun ApiResponse.maps(): ApiResponse {
val mappers = SandwichInitializer.sandwichFailureMappers
var response: ApiResponse = this
mappers.forEach { mapper ->
if (response is Failure) {
if (mapper is ApiResponseFailureMapper) {
response = mapper.map(response as Failure) as ApiResponse
} else if (mapper is ApiResponseFailureSuspendMapper) {
val scope = SandwichInitializer.sandwichScope
scope.launch {
response = mapper.map(response as Failure) as ApiResponse
}
}
}
}
return response
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy