Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialize time as Duration #586

Merged
merged 14 commits into from
Apr 16, 2022
12 changes: 8 additions & 4 deletions common/src/main/kotlin/entity/AuditLog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package dev.kord.common.entity
import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalSnowflake
import dev.kord.common.entity.optional.orEmpty
import dev.kord.common.serialization.DurationInWholeDaysSerializer
import dev.kord.common.serialization.DurationInWholeSecondsSerializer
import kotlinx.datetime.Instant
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlin.time.Duration
import dev.kord.common.Color as CommonColor
import dev.kord.common.entity.DefaultMessageNotificationLevel as CommonDefaultMessageNotificationLevel
import dev.kord.common.entity.ExplicitContentFilter as CommonExplicitContentFilter
Expand Down Expand Up @@ -181,7 +184,7 @@ public sealed class AuditLogChangeKey<T>(public val name: String, public val ser
public object AfkChannelId : AuditLogChangeKey<Snowflake>("afk_channel_id", serializer())

@SerialName("afk_timeout")
public object AfkTimeout : AuditLogChangeKey<Int>("afk_timeout", serializer())
public object AfkTimeout : AuditLogChangeKey<Duration>("afk_timeout", DurationInWholeSecondsSerializer)

@SerialName("mfa_level")
public object MFALevel : AuditLogChangeKey<CommonMFALevel>("mfa_level", serializer())
Expand Down Expand Up @@ -237,7 +240,8 @@ public sealed class AuditLogChangeKey<T>(public val name: String, public val ser
public object ApplicationId : AuditLogChangeKey<Snowflake>("application_id", serializer())

@SerialName("rate_limit_per_user")
public object RateLimitPerUser : AuditLogChangeKey<Int>("rate_limit_per_user", serializer())
public object RateLimitPerUser :
AuditLogChangeKey<Duration>("rate_limit_per_user", DurationInWholeSecondsSerializer)

@SerialName("permissions")
public object Permissions : AuditLogChangeKey<CommonPermissions>("permissions", serializer())
Expand Down Expand Up @@ -279,7 +283,7 @@ public sealed class AuditLogChangeKey<T>(public val name: String, public val ser
public object Uses : AuditLogChangeKey<Int>("uses", serializer())

@SerialName("max_age")
public object MaxAges : AuditLogChangeKey<Int>("max_age", serializer())
public object MaxAges : AuditLogChangeKey<Duration>("max_age", DurationInWholeSecondsSerializer)

@SerialName("temporary")
public object Temporary : AuditLogChangeKey<Boolean>("temporary", serializer())
Expand Down Expand Up @@ -345,7 +349,7 @@ public sealed class AuditLogChangeKey<T>(public val name: String, public val ser
public object ExpireBehavior : AuditLogChangeKey<IntegrationExpireBehavior>("expire_behavior", serializer())

@SerialName("expire_grace_period")
public object ExpireGracePeriod : AuditLogChangeKey<Int>("expire_grace_period", serializer())
public object ExpireGracePeriod : AuditLogChangeKey<Duration>("expire_grace_period", DurationInWholeDaysSerializer)

@SerialName("user_limit")
public object UserLimit : AuditLogChangeKey<Int>("user_limit", serializer())
Expand Down
30 changes: 17 additions & 13 deletions common/src/main/kotlin/entity/DiscordChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalBoolean
import dev.kord.common.entity.optional.OptionalInt
import dev.kord.common.entity.optional.OptionalSnowflake
import dev.kord.common.serialization.DurationInWholeMinutesSerializer
import dev.kord.common.serialization.DurationInWholeSeconds
import kotlinx.datetime.Instant
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
Expand All @@ -14,6 +16,8 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlin.DeprecationLevel.WARNING
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

/**
* A representation of a [Discord Channel Structure](https://discord.com/developers/docs/resources/channel).
Expand All @@ -29,7 +33,7 @@ import kotlin.DeprecationLevel.WARNING
* @param lastMessageId The id of the last message sent in this channel (may not point to an existing or valid message).
* @param bitrate The bitrate (in bits) of the voice channel.
* @param userLimit The user limit of the voice channel.
* @param rateLimitPerUser amount of seconds a user has to wait before sending another message; bots,
* @param rateLimitPerUser amount of time a user has to wait before sending another message; bots,
* as well as users with the permission [Permission.ManageMessages] or [Permission.ManageChannels] are unaffected.
* @param recipients The recipients of the DM.
* @param icon The icon hash.
Expand All @@ -56,7 +60,7 @@ public data class DiscordChannel(
@SerialName("user_limit")
val userLimit: OptionalInt = OptionalInt.Missing,
@SerialName("rate_limit_per_user")
val rateLimitPerUser: OptionalInt = OptionalInt.Missing,
val rateLimitPerUser: Optional<DurationInWholeSeconds> = Optional.Missing(),
val recipients: Optional<List<DiscordUser>> = Optional.Missing(),
val icon: Optional<String?> = Optional.Missing(),
@SerialName("owner_id")
Expand Down Expand Up @@ -194,24 +198,24 @@ public data class DiscordThreadMetadata(
)

@Serializable(with = ArchiveDuration.Serializer::class)
public sealed class ArchiveDuration(public val duration: Int) {
public class Unknown(duration: Int) : ArchiveDuration(duration)
public object Hour : ArchiveDuration(60)
public object Day : ArchiveDuration(1440)
public object ThreeDays : ArchiveDuration(4320)
public object Week : ArchiveDuration(10080)
public sealed class ArchiveDuration(public val duration: Duration) {
public class Unknown(duration: Duration) : ArchiveDuration(duration)
public object Hour : ArchiveDuration(60.minutes)
public object Day : ArchiveDuration(1440.minutes)
public object ThreeDays : ArchiveDuration(4320.minutes)
public object Week : ArchiveDuration(10080.minutes)

public object Serializer : KSerializer<ArchiveDuration> {

override val descriptor: SerialDescriptor get() = DurationInWholeMinutesSerializer.descriptor

override fun deserialize(decoder: Decoder): ArchiveDuration {
val value = decoder.decodeInt()
val value = decoder.decodeSerializableValue(DurationInWholeMinutesSerializer)
return values.firstOrNull { it.duration == value } ?: Unknown(value)
}

override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("AutoArchieveDuration", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: ArchiveDuration) {
encoder.encodeInt(value.duration)
encoder.encodeSerializableValue(DurationInWholeMinutesSerializer, value.duration)
}
}

Expand Down
5 changes: 3 additions & 2 deletions common/src/main/kotlin/entity/DiscordGuild.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalBoolean
import dev.kord.common.entity.optional.OptionalInt
import dev.kord.common.entity.optional.OptionalSnowflake
import dev.kord.common.serialization.DurationInWholeSeconds
import kotlinx.datetime.Instant
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
Expand Down Expand Up @@ -40,7 +41,7 @@ public data class DiscordUnavailableGuild(
* @param permissions The total permissions for [DiscordUser] in the guild (excludes [overwrites][Overwrite]).
* @param region [DiscordVoiceRegion] id for the guild.
* @param afkChannelId The id of afk channel.
* @param afkTimeout The afk timeout in seconds.
* @param afkTimeout The afk timeout.
* @param widgetEnabled True if the server widget is enabled.
* @param widgetChannelId The channel id that the widget will generate an invite to, or `null` if set to no invite.
* @param verificationLevel [VerificationLevel] required for the guild.
Expand Down Expand Up @@ -93,7 +94,7 @@ public data class DiscordGuild(
ReplaceWith("DiscordChannel#rtcRegion")
) val region: String,
@SerialName("afk_channel_id") val afkChannelId: Snowflake?,
@SerialName("afk_timeout") val afkTimeout: Int,
@SerialName("afk_timeout") val afkTimeout: DurationInWholeSeconds,
@SerialName("widget_enabled") val widgetEnabled: OptionalBoolean = OptionalBoolean.Missing,
@SerialName("widget_channel_id") val widgetChannelId: OptionalSnowflake? = OptionalSnowflake.Missing,
@SerialName("verification_level") val verificationLevel: VerificationLevel,
Expand Down
3 changes: 2 additions & 1 deletion common/src/main/kotlin/entity/DiscordIntegration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.kord.common.entity

import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalBoolean
import dev.kord.common.serialization.DurationInWholeDays
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
Expand All @@ -25,7 +26,7 @@ public data class DiscordIntegration(
@SerialName("expire_behavior")
val expireBehavior: IntegrationExpireBehavior,
@SerialName("expire_grace_period")
val expireGracePeriod: Int,
val expireGracePeriod: DurationInWholeDays,
val user: DiscordUser,
val account: DiscordIntegrationsAccount,
@SerialName("synced_at")
Expand Down
3 changes: 2 additions & 1 deletion common/src/main/kotlin/entity/DiscordInvite.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.kord.common.entity

import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalInt
import dev.kord.common.serialization.DurationInWholeSeconds
import kotlinx.datetime.Instant
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
Expand Down Expand Up @@ -78,7 +79,7 @@ public data class DiscordInviteWithMetadata(
@SerialName("max_uses")
val maxUses: Int,
@SerialName("max_age")
val maxAge: Int,
val maxAge: DurationInWholeSeconds,
val temporary: Boolean,
@SerialName("created_at")
val createdAt: Instant,
Expand Down
92 changes: 92 additions & 0 deletions common/src/main/kotlin/serialization/DurationSerializers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package dev.kord.common.serialization

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.DurationUnit.*
import kotlin.time.toDuration


/** Serializer that encodes and decodes [Duration]s. */
public sealed class DurationSerializer(private val unit: DurationUnit, name: String) : KSerializer<Duration> {

final override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("dev.kord.common.serialization.$name", PrimitiveKind.LONG)

final override fun serialize(encoder: Encoder, value: Duration) {
encoder.encodeLong(value.toLong(unit))
}

final override fun deserialize(decoder: Decoder): Duration {
return decoder.decodeLong().toDuration(unit)
}
}


// nanoseconds

/** Serializer that encodes and decodes [Duration]s in [whole nanoseconds][Duration.inWholeNanoseconds]. */
public object DurationInWholeNanosecondsSerializer : DurationSerializer(NANOSECONDS, "DurationInWholeNanoseconds")
lukellmann marked this conversation as resolved.
Show resolved Hide resolved

/** A [Duration] that is [serializable][Serializable] with [DurationInWholeNanosecondsSerializer]. */
public typealias DurationInWholeNanoseconds = @Serializable(with = DurationInWholeNanosecondsSerializer::class) Duration


// microseconds

/** Serializer that encodes and decodes [Duration]s in [whole microseconds][Duration.inWholeMicroseconds]. */
public object DurationInWholeMicrosecondsSerializer : DurationSerializer(MICROSECONDS, "DurationInWholeMicroseconds")

/** A [Duration] that is [serializable][Serializable] with [DurationInWholeMicrosecondsSerializer]. */
public typealias DurationInWholeMicroseconds = @Serializable(with = DurationInWholeMicrosecondsSerializer::class) Duration


// milliseconds

/** Serializer that encodes and decodes [Duration]s in [whole milliseconds][Duration.inWholeMilliseconds]. */
public object DurationInWholeMillisecondsSerializer : DurationSerializer(MILLISECONDS, "DurationInWholeMilliseconds")

/** A [Duration] that is [serializable][Serializable] with [DurationInWholeMillisecondsSerializer]. */
public typealias DurationInWholeMilliseconds = @Serializable(with = DurationInWholeMillisecondsSerializer::class) Duration


// seconds

/** Serializer that encodes and decodes [Duration]s in [whole seconds][Duration.inWholeSeconds]. */
public object DurationInWholeSecondsSerializer : DurationSerializer(SECONDS, "DurationInWholeSeconds")

/** A [Duration] that is [serializable][Serializable] with [DurationInWholeSecondsSerializer]. */
public typealias DurationInWholeSeconds = @Serializable(with = DurationInWholeSecondsSerializer::class) Duration


// minutes

/** Serializer that encodes and decodes [Duration]s in [whole minutes][Duration.inWholeMinutes]. */
public object DurationInWholeMinutesSerializer : DurationSerializer(MINUTES, "DurationInWholeMinutes")

/** A [Duration] that is [serializable][Serializable] with [DurationInWholeMinutesSerializer]. */
public typealias DurationInWholeMinutes = @Serializable(with = DurationInWholeMinutesSerializer::class) Duration


// hours

/** Serializer that encodes and decodes [Duration]s in [whole hours][Duration.inWholeHours]. */
public object DurationInWholeHoursSerializer : DurationSerializer(HOURS, "DurationInWholeHours")

/** A [Duration] that is [serializable][Serializable] with [DurationInWholeHoursSerializer]. */
public typealias DurationInWholeHours = @Serializable(with = DurationInWholeHoursSerializer::class) Duration


// days

/** Serializer that encodes and decodes [Duration]s in [whole days][Duration.inWholeDays]. */
public object DurationInWholeDaysSerializer : DurationSerializer(DAYS, "DurationInWholeDays")

/** A [Duration] that is [serializable][Serializable] with [DurationInWholeDaysSerializer]. */
public typealias DurationInWholeDays = @Serializable(with = DurationInWholeDaysSerializer::class) Duration
3 changes: 2 additions & 1 deletion common/src/test/kotlin/json/ChannelTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dev.kord.common.entity.DiscordChannel
import dev.kord.common.entity.optional.value
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds

private fun file(name: String): String {
val loader = ChannelTest::class.java.classLoader
Expand Down Expand Up @@ -106,7 +107,7 @@ class ChannelTest {
type.value shouldBe 0
position.asNullable!! shouldBe 6
permissionOverwrites.value shouldBe emptyList()
rateLimitPerUser.asNullable shouldBe 2
rateLimitPerUser.value shouldBe 2.seconds
nsfw.value shouldBe true
topic.value shouldBe "24/7 chat about how to gank Mike #2"
lastMessageId.value?.toString() shouldBe "155117677105512449"
Expand Down
3 changes: 2 additions & 1 deletion common/src/test/kotlin/json/GuildTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package json
import dev.kord.common.entity.*
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds


private fun file(name: String): String {
Expand Down Expand Up @@ -40,7 +41,7 @@ class GuildTest {
@Suppress("DEPRECATION")
region shouldBe "us-west"
afkChannelId shouldBe null
afkTimeout shouldBe 300
afkTimeout shouldBe 300.seconds
systemChannelId shouldBe null
widgetEnabled shouldBe true
widgetChannelId shouldBe null
Expand Down
Loading