package dev.inmo.micro_utils.common import kotlinx.serialization.* import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* /** * Realization of this interface will contains at least one not null - [t1] or [t2] * * @see EitherFirst * @see EitherSecond * @see Either.Companion.first * @see Either.Companion.second * @see Either.onFirst * @see Either.onSecond */ @Serializable(EitherSerializer::class) sealed interface Either { val t1: T1? val t2: T2? companion object { fun serializer( t1Serializer: KSerializer, t2Serializer: KSerializer, ): KSerializer> = EitherSerializer(t1Serializer, t2Serializer) } } class EitherSerializer( t1Serializer: KSerializer, t2Serializer: KSerializer, ) : KSerializer> { @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class) override val descriptor: SerialDescriptor = buildSerialDescriptor( "TypedSerializer", SerialKind.CONTEXTUAL ) { element("type", String.serializer().descriptor) element("value", ContextualSerializer(Either::class).descriptor) } private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer) private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer) @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class) override fun deserialize(decoder: Decoder): Either { return decoder.decodeStructure(descriptor) { var type: String? = null lateinit var result: Either while (true) { when (val index = decodeElementIndex(descriptor)) { 0 -> type = decodeStringElement(descriptor, 0) 1 -> { result = when (type) { "t1" -> decodeSerializableElement( descriptor, 1, t1EitherSerializer ) "t2" -> decodeSerializableElement( descriptor, 1, t2EitherSerializer ) else -> error("Unknown type of either: $type") } } CompositeDecoder.DECODE_DONE -> break else -> error("Unexpected index: $index") } } result } } @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class) override fun serialize(encoder: Encoder, value: Either) { encoder.encodeStructure(descriptor) { when (value) { is EitherFirst -> { encodeStringElement(descriptor, 0, "t1") encodeSerializableElement(descriptor, 1, t1EitherSerializer, value) } is EitherSecond -> { encodeStringElement(descriptor, 0, "t2") encodeSerializableElement(descriptor, 1, t2EitherSerializer, value) } } } } } /** * This type [Either] will always have not nullable [t1] */ @Serializable data class EitherFirst( override val t1: T1 ) : Either { override val t2: T2? get() = null } /** * This type [Either] will always have not nullable [t2] */ @Serializable data class EitherSecond( override val t2: T2 ) : Either { override val t1: T1? get() = null } /** * @return New instance of [EitherFirst] */ inline fun Either.Companion.first(t1: T1): Either = EitherFirst(t1) /** * @return New instance of [EitherSecond] */ inline fun Either.Companion.second(t2: T2): Either = EitherSecond(t2) /** * Will call [block] in case when [Either.t1] of [this] is not null */ inline fun > E.onFirst(block: (T1) -> Unit): E { val t1 = t1 t1 ?.let(block) return this } /** * Will call [block] in case when [Either.t2] of [this] is not null */ inline fun > E.onSecond(block: (T2) -> Unit): E { val t2 = t2 t2 ?.let(block) return this } inline fun Any.either() = when (this) { is T1 -> Either.first(this) is T2 -> Either.second(this) else -> error("Incorrect type of either argument $this") }