All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.entity.optional.Optional.kt Maven / Gradle / Ivy

package dev.kord.common.entity.optional

import dev.kord.common.entity.Snowflake
import dev.kord.common.entity.optional.Optional.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlin.js.JsName
import kotlin.jvm.JvmName

/**
 * Represents a value that encapsulates all [three possible states of a value in the Discord API](https://discord.com/developers/docs/reference#nullable-and-optional-resource-fields).
 * Specifically:
 *
 * * [Missing] - a field that was not present in the serialized entity
 * * [Null] - a field that was assigned null in the serialized entity
 * * [Value] - a field that was assigned a non-null value in the serialized entity.
 *
 * The base class is  (de)serializable with kotlinx.serialization and should be used as follows:
 *
 * * `Optional` - a field that is only optional but not nullable.
 * * `Optional` - A field that is both optional and nullable.
 * * A field that is only nullable should be represented as `T?` instead.
 *
 * Trying to deserialize `null` as `Optional` will result in a [SerializationException] being thrown.
 *
 * Note that kotlinx.serialization does **not** call serializers for values that are not
 * present in the serialized format. `Optional` fields should have a default value of `Optional.Missing`:
 *
 * ```kotlin
 * @Serializable
 * class DiscordUser(
 *     val id: Long,
 *     val username: String,
 *     val bot: Optional = Optional.Missing()
 * )
 * ```
 */
@Serializable(with = OptionalSerializer::class)
public sealed class Optional {

    /**
     * The value this optional wraps.
     * * Both [Missing] and [Null] will always return `null`.
     * * [Value] will always return a non-null value.
     */
    public open val value: T?
        get() = throw UnsupportedOperationException("This is implemented in implementation classes")

    /**
     * Represents a field that was not present in the serialized entity.
     */
    public class Missing private constructor() : Optional() {

        /**
         * The value this optional wraps, always `null`.
         */
        override val value: T?
            get() = null

        override fun toString(): String = "Optional.Missing"

        override fun equals(other: Any?): Boolean {
            return other is Missing<*>
        }

        override fun hashCode(): Int = 0

        public companion object {
            private val constantNull = Missing()

            public operator fun  invoke(): Missing = constantNull
        }
    }

    /**
     * Represents a field that was assigned null in the serialized entity.
     */
    public class Null private constructor() : Optional() {

        /**
         * The value this optional wraps, always `null`.
         */
        override val value: T?
            get() = null

        override fun toString(): String = "Optional.Null"

        override fun equals(other: Any?): Boolean {
            return other is Null<*>
        }

        override fun hashCode(): Int = 0

        public companion object {
            private val constantNull = Null()

            public operator fun  invoke(): Null = constantNull
        }
    }

    /**
     * Represents a field that was assigned a non-null value in the serialized entity.
     * Equality and hashcode is implemented through its [value].
     *
     * @param value the value this optional wraps.
     */
    public class Value(override val value: T) : Optional() {
        override fun toString(): String = "Optional.Something(content=$value)"

        /**
         * Destructures this optional to its [value].
         */
        public operator fun component1(): T = value

        override fun equals(other: Any?): Boolean {
            val value = other as? Value<*> ?: return false
            return value.value == this.value
        }

        override fun hashCode(): Int = value.hashCode()
    }

    public companion object {

        public fun > missingOnEmpty(value: C): Optional =
            if (value.isEmpty()) Missing()
            else Value(value)

        /**
         * Returns a [Missing] optional of type [T].
         */
        public operator fun  invoke(): Missing = Missing()

        /**
         * Returns a [Value] optional of type [T] with the given [value].
         */
        public operator fun  invoke(value: T): Value = Value(value)

        /**
         * Returns an [Optional] that is either [value] on a non-null [value], or [Null] on `null`.
         */
        @JvmName("invokeNullable")
        @JsName("invokeNullable")
        public operator fun  invoke(value: T?): Optional = when (value) {
            null -> Null()
            else -> Value(value)
        }
    }

    internal class OptionalSerializer(private val contentSerializer: KSerializer) : KSerializer> {
        override val descriptor: SerialDescriptor = contentSerializer.descriptor

        @OptIn(ExperimentalSerializationApi::class)
        override fun deserialize(decoder: Decoder): Optional {
            /**
             * let's clear up any inconsistencies, an Optional cannot be  and be represented as nullable.
             */
            if (!descriptor.isNullable && !decoder.decodeNotNullMark()) {
                throw SerializationException("descriptor for ${descriptor.serialName} was not nullable but null mark was encountered")
            }

            /**
             * This is rather ugly; I can't figure out a way to convince the compiler that  isn't nullable,
             * we have personally proven above that the serializer cannot return null so we'll just act as if we
             * know what we're doing.
             */
            val optional: Optional = when {
                !decoder.decodeNotNullMark() -> {
                    decoder.decodeNull()
                    Null()
                }
                else -> Optional(decoder.decodeSerializableValue(contentSerializer))
            }

            @Suppress("UNCHECKED_CAST")
            return optional as Optional
        }

        @OptIn(ExperimentalSerializationApi::class)
        override fun serialize(encoder: Encoder, value: Optional) = when (value) {
            is Missing<*> -> throw SerializationException("missing values cannot be serialized")
            is Null<*> -> encoder.encodeNull()
            is Value -> encoder.encodeSerializableValue(contentSerializer, value.value)
        }
    }
}

public fun  Optional.switchOnMissing(value: T): Optional = when (this) {
    is Missing -> Value(value)
    is Null<*>, is Value -> this
}

public fun  Optional.switchOnMissing(value: Optional): Optional = when (this) {
    is Missing -> value
    is Null<*>, is Value -> this
}

public fun  Optional>.orEmpty(): List = when (this) {
    is Missing, is Null<*> -> emptyList()
    is Value -> value
}

public fun  Optional>.orEmpty(): Set = when (this) {
    is Missing, is Null<*> -> emptySet()
    is Value -> value
}

@Suppress("UNCHECKED_CAST")
public inline fun  Optional>.mapList(mapper: (E) -> T): Optional> = when (this) {
    is Missing, is Null<*> -> this as Optional>
    is Value -> Value(value.map(mapper))
}

@JvmName("mapNullableList")
public inline fun  Optional?>.mapList(mapper: (E) -> T): Optional?> = when (this) {
    is Missing -> Missing()
    is Null -> Null()
    is Value -> Value(value!!.map(mapper))
}

public fun  Optional>.mapCopy(): Optional> = map { mutable -> mutable.toList() }

@JvmName("mapCopyOfMap")
public fun  Optional>.mapCopy(): Optional> = map { mutable -> mutable.toMap() }


@Suppress("UNCHECKED_CAST")
public inline fun  Optional>.mapValues(mapper: (Map.Entry) -> R): Optional> = when (this) {
    is Missing, is Null<*> -> this as Optional>
    is Value -> Value(value.mapValues(mapper))
}


public inline fun  Optional>.filterList(mapper: (E) -> Boolean): Optional> = when (this) {
    is Missing, is Null<*> -> this
    is Value -> Value(value.filter(mapper))
}

@Suppress("UNCHECKED_CAST")
public inline fun  Optional>.filterInstanceOfList(): Optional> = when (this) {
    is Missing, is Null<*> -> this as Optional>
    is Value -> Value(value.filterIsInstance())
}


@Suppress("UNCHECKED_CAST")
public inline fun  Optional.map(mapper: (E) -> T): Optional = when (this) {
    is Missing, is Null<*> -> this as Optional
    is Value -> Value(mapper(value))
}

/**
 * Applies the [mapper] to the optional if it is a [Value], returns the same optional otherwise.
 */
@Suppress("UNCHECKED_CAST")
public inline fun  Optional.flatMap(mapper: (E) -> Optional): Optional = when (this) {
    is Missing, is Null<*> -> this as Optional
    is Value -> mapper(value)
}

@Suppress("UNCHECKED_CAST")
@JvmName("mapNullableOptional")
@JsName("mapNullableOptional")
public inline fun  Optional.map(mapper: (E) -> T): Optional = when (this) {
    is Missing, is Null<*> -> this as Optional
    is Value -> Value(mapper(value!!))
}

@Suppress("UNCHECKED_CAST")
public inline fun  Optional.mapNullable(mapper: (E) -> T): Optional = when (this) {
    is Missing, is Null<*> -> this as Optional
    is Value -> Optional(mapper(value))
}

@Suppress("UNCHECKED_CAST")
public inline fun  Optional.mapNotNull(mapper: (E) -> T): Optional = when (this) {
    is Missing -> this as Optional
    is Null<*> -> this as Optional
    is Value -> Optional(mapper(value!!))
}

public inline fun  Optional>.firstOrNull(predicate: (E) -> Boolean): E? = when (this) {
    is Missing, is Null<*> -> null
    is Value -> value.firstOrNull(predicate)
}


public inline fun  Optional>.first(predicate: (E) -> Boolean): E = firstOrNull(predicate)!!


public inline fun  Optional.mapSnowflake(mapper: (E) -> Snowflake): OptionalSnowflake = when (this) {
    is Missing, is Null<*> -> OptionalSnowflake.Missing
    is Value -> OptionalSnowflake.Value(mapper(value))
}

@JvmName("mapNullableSnowflake")
@JsName("mapNullableSnowflake")
public inline fun  Optional.mapSnowflake(mapper: (E) -> Snowflake): OptionalSnowflake = when (this) {
    is Missing, is Null<*> -> OptionalSnowflake.Missing
    is Value -> OptionalSnowflake.Value(mapper(value!!))
}

public inline fun  Optional.unwrap(mapper: (T) -> R): R? = when (this) {
    is Missing, is Null<*> -> null
    is Value -> mapper(value)
}

@Suppress("UNCHECKED_CAST")
public fun  Optional.coerceToMissing(): Optional = when (this) {
    is Missing, is Null -> Missing()
    is Value -> this as Value
}

@Suppress("RemoveRedundantQualifierName")
public fun  T.optional(): Optional.Value = Optional.Value(this)

public fun  T?.optional(): Optional = Optional(this)

public fun Optional.toPrimitive(): OptionalBoolean = when (this) {
    is Value -> OptionalBoolean.Value(value)
    is Missing, is Null<*> -> OptionalBoolean.Missing
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy