Joost Klitsie
10/15/2021, 7:48 AMJoost Klitsie
10/15/2021, 7:49 AMsealed interface Result<T> {
val value: T?
}
fun <T> success(value: T): Success<T> = Success(value)
fun <T> failure(error: CommonException): Failure<T> = Failure(error)
fun <T> loading(): Loading<T> = Loading<T>()
@JvmInline
value class Success<T> constructor(private val _value: Any?) : Result<T> {
override val value: T
get() = _value as T
}
@JvmInline
value class Failure<T> constructor(
val error: CommonException
) : Result<T> {
override val value: T?
get() = null
}
class Loading<T> : Result<T> {
override val value: T?
get() = null
override fun equals(other: Any?): Boolean = other is Loading<*>
override fun hashCode(): Int = 1
override fun toString(): String = "Loading"
}
inspired by https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/util/Result.ktJoost Klitsie
10/15/2021, 7:49 AM<T>
will always be a serializable class, but it is not sealed.Joost Klitsie
10/15/2021, 7:50 AMephemient
10/15/2021, 7:51 AM@JvmInline
like that is rather pointless, since it's only accessible via the public interface which will box itโฆJoost Klitsie
10/15/2021, 7:52 AMJoost Klitsie
10/15/2021, 7:52 AMJoost Klitsie
10/15/2021, 7:54 AMephemient
10/15/2021, 8:04 AM@Serializable
sealed class Result<out T> {
abstract val value: T?
}
@Serializable
class Success<T> internal constructor(override val value: T) : Result<T>()
@Serializable
class Failure internal constructor(val error: CommonException) : Result<Nothing>() {
override val value: Nothing?
get() = null
}
(assuming CommonException is serializable) this will result in an auto-generated fun <T> Result.Companion.serializer(tSerializer: KSerializer<T>): KSerializer<Result<T>>
ephemient
10/15/2021, 8:12 AMsealed interface
for whatever reason, then sure, you could replicate by hand what the plugin would generate in the other case:
@Serializable(with = ResultSerializer::class)
sealed interface Result<out T> {
val value: T?
}
class ResultSerializer<T>(private val tSerializer: KSerializer<T>) : KSerializer<Result<T>> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Result", tSerializer.descriptor) {
element("value", tSerializer.descriptor)
element("error", CommonException.serializer().descriptor)
}
override fun serialize(encoder: Encoder, value: Result<T>) {
encoder.encodeStructure(descriptor) {
when (value) {
is Success -> encodeSerializableElement(descriptor, 0, tSerializer, value.value)
is Failure -> encodeSerializableElement(descriptor, 1, CommonException.serializer(), value.error)
}
}
}
override fun deserialize(decoder: Decoder): Result<T> {
var result: Result<T>? = null
decoder.decodeStructure(descriptor) {
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> result = success(decodeSerializableElement(descriptor, 0, tSerializer))
1 -> result = failure(decodeSerializableElement(descriptor, 1, CommonException.serializer()))
CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("unknown index: $index")
}
}
}
return result ?: throw SerializationException("missing field: value|error")
}
}
ephemient
10/15/2021, 8:13 AM@JvmInline
doesn't buy you anything if you're using Result<T>
, so I don't see any reason to do thatJoost Klitsie
10/15/2021, 8:15 AMephemient
10/15/2021, 8:21 AMephemient
10/15/2021, 8:21 AMval number: Number = 1
is a java.lang.Integer, not an int; ditto interface Result<T>
ephemient
10/15/2021, 8:22 AM@JvmInline
, you have to pull shenanigans like kotlin.Result
does:
private class Failure(val error: CommonException)
@JvmInline
@Serializable(with = ResultSerializer::class)
value class Result<out T> internal constructor(private val _value: Any?) {
val isSuccess: Boolean
get() = _value !is Failure
val isFailure: Boolean
get() = _value is Failure
val value: T
get() {
check(_value !is Failure)
return _value as T
}
val error: CommonException
get() {
check(_value is Failure)
return _value.error
}
}
fun <T> success(value: T): Result<T> = Result(value)
fun failure(error: CommonException): Result<Nothing> = Result(Failure(error))
class ResultSerializer<T>(private val tSerializer: KSerializer<T>) : KSerializer<Result<T>> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Result", tSerializer.descriptor) {
element("value", tSerializer.descriptor)
element("error", CommonException.serializer().descriptor)
}
override fun serialize(encoder: Encoder, value: Result<T>) {
encoder.encodeStructure(descriptor) {
if (value.isSuccess) {
encodeSerializableElement(descriptor, 0, tSerializer, value.value)
} else {
encodeSerializableElement(descriptor, 1, CommonException.serializer(), value.error)
}
}
}
override fun deserialize(decoder: Decoder): Result<T> {
var result: Result<T>? = null
decoder.decodeStructure(descriptor) {
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> result = success(decodeSerializableElement(descriptor, 0, tSerializer))
1 -> result = failure(decodeSerializableElement(descriptor, 1, CommonException.serializer()))
CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("unknown index: $index")
}
}
}
return result ?: throw SerializationException("missing field: value|error")
}
}
christophsturm
10/15/2021, 8:24 AMJoost Klitsie
10/15/2021, 9:34 AM@Serializable
data class Test(val text: String)
fun main() {
val successTest: Result<Test> = success(Test("Whatever"))
println("format: ${format.encodeToString(successTest)}")
}
Error:
Class 'Test' is not registered for polymorphic serialization in the scope of 'Any'.
Joost Klitsie
10/15/2021, 9:34 AMJoost Klitsie
10/15/2021, 9:34 AM@Serializable
class Failure constructor(
val error: CommonException
) : Result<@Serializable(NotSerializable::class) Nothing>() {
@Contextual
override val value: Nothing?
get() = null
}
Joost Klitsie
10/15/2021, 9:35 AMdata class Success<T> (@Serializable(SOMETHING_HERE_PERHAPS?) override val value: T): Result<T>()
Joost Klitsie
10/15/2021, 9:51 AM@Serializable
sealed class Result<out T> {
abstract val value: T?
}
@Serializable
data class Success<T> constructor(override val value: T) : Result<T>()
@Serializable
data class Failure constructor(
val error: CommonException
) : Result<@Serializable(NotSerializable::class) Nothing>() {
@Contextual
override val value: Nothing?
get() = null
}
ephemient
10/15/2021, 9:52 AMJoost Klitsie
10/15/2021, 9:52 AMfun main() {
val failureTest: Result<Test> = failure(NotFoundException(""))
println("format: ${format.encodeToString(failureTest)}")
}
result: format: {"type":"com.klitsie.common.base.Failure","error":{"type":"NotFoundException","message":""}}
Joost Klitsie
10/15/2021, 9:53 AMephemient
10/15/2021, 10:12 AMephemient
10/15/2021, 10:12 AM@Serializable
ephemient
10/15/2021, 10:23 AMJoost Klitsie
10/15/2021, 10:35 AMreturn result ?: loading()
at the end, my Loading class is also supported ๐ I guess that wouldn't be useful in practice, but good to know that I can ๐Joost Klitsie
10/15/2021, 10:40 AMval successTest: Result<Test> = success(Test("Whatever"))
val failureTest: Result<Test> = failure(NotFoundException(""))
val loadingTest: Result<Test> = loading()
val successEncoded = format.encodeToString(successTest)
val failureEncoded = format.encodeToString(failureTest)
val loadingEncoded = format.encodeToString(loadingTest)
val successResult = format.decodeFromString<Result<Test>>(successEncoded)
val failureResult = format.decodeFromString<Result<Test>>(failureEncoded)
val loadingResult = format.decodeFromString<Result<Test>>(loadingEncoded)
println("success: $successResult")
println("failure: $failureResult")
println("loading: $loadingResult")
failureResult.onError { error ->
results:
success: Success(value=Test(text=Whatever))
failure: Failure(error=com.klitsie.common.base.NotFoundException: )
loading: Loading
pdvrieze
10/18/2021, 10:05 AMJoost Klitsie
10/18/2021, 10:09 AM