diff --git a/README.md b/README.md index 3394f29..586d5a9 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ using it is as simple as adding a single line to Gradle. ##### Gradle ```kotlin -implementation("org.audux.bgg:bggclient:0.6.0") +implementation("org.audux.bgg:bggclient:0.8.0") ``` ##### Maven @@ -35,7 +35,7 @@ implementation("org.audux.bgg:bggclient:0.6.0") org.audux.bgg bggclient - 0.6.0 + 0.8.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 1fafe7d..9a65890 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { `maven-publish` signing alias(libs.plugins.com.gradleup.nmcp) + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ktfmt.gradle) alias(libs.plugins.org.jetbrains.dokka) alias(libs.plugins.org.jetbrains.kotlin.jvm) @@ -22,7 +23,7 @@ publishing { create("mavenJava") { groupId = "org.audux.bgg" artifactId = "bggclient" - version = "0.6.0" + version = "0.8.0" pom { name = "Unofficial JVM BGG client" @@ -100,6 +101,7 @@ dependencies { // Testing dependencies. testApi(libs.ktor.client.mock) + testImplementation(libs.kotlin.serialization.json) testImplementation(libs.junit5.jupiter) testImplementation(libs.junit5.params) testImplementation(libs.truth) diff --git a/examples/android/app/build.gradle.kts b/examples/android/app/build.gradle.kts index 6063c74..c6ebce2 100644 --- a/examples/android/app/build.gradle.kts +++ b/examples/android/app/build.gradle.kts @@ -68,7 +68,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") implementation("io.coil-kt:coil-compose:2.5.0") - implementation("org.audux.bgg:bggclient:0.6.0") + implementation("org.audux.bgg:bggclient:0.8.0") testImplementation("junit:junit:4.13.2") diff --git a/examples/java/build.gradle.kts b/examples/java/build.gradle.kts index 3404827..738f184 100644 --- a/examples/java/build.gradle.kts +++ b/examples/java/build.gradle.kts @@ -12,7 +12,7 @@ repositories { } dependencies { - implementation("org.audux.bgg:bggclient:0.6.0") + implementation("org.audux.bgg:bggclient:0.8.0") testImplementation(platform("org.junit:junit-bom:5.9.1")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/examples/paginate/build.gradle.kts b/examples/paginate/build.gradle.kts index d4da7d5..8bbdba7 100644 --- a/examples/paginate/build.gradle.kts +++ b/examples/paginate/build.gradle.kts @@ -20,7 +20,7 @@ application { } dependencies { - implementation("org.audux.bgg:bggclient:0.6.0") + implementation("org.audux.bgg:bggclient:0.8.0") testImplementation("org.jetbrains.kotlin:kotlin-test") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 43e5152..e5b9942 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ ktor = "2.3.8" org-jetbrains-dokka = "1.9.10" org-jetbrains-kotlin-jvm = "1.9.22" org-jetbrains-kotlin-jdk8 = "1.8.0" -serialization = "1.6.3" +serialization = "1.5.0" slf4j = "2.0.12" truth = "1.4.1" @@ -24,6 +24,7 @@ junit5-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } kotlin-coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "org-jetbrains-kotlin-jdk8" } kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" } +kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm", version.ref = "serialization" } ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" } @@ -35,4 +36,5 @@ org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "org ktfmt-gradle = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt-gradle" } com-gradleup-nmcp = { id = "com.gradleup.nmcp", version.ref = "com-gradleup-nmcp" } org-jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "org-jetbrains-dokka" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "org-jetbrains-kotlin-jvm" } diff --git a/src/main/kotlin/org/audux/bgg/common/Types.kt b/src/main/kotlin/org/audux/bgg/common/Types.kt index f844402..06415ff 100644 --- a/src/main/kotlin/org/audux/bgg/common/Types.kt +++ b/src/main/kotlin/org/audux/bgg/common/Types.kt @@ -16,6 +16,7 @@ package org.audux.bgg.common import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +import kotlinx.serialization.Serializable import org.audux.bgg.response.WrappedDoubleDeserializer import org.audux.bgg.response.WrappedIntDeserializer @@ -23,6 +24,7 @@ import org.audux.bgg.response.WrappedIntDeserializer * The different kind/type of things the API may return such as a board game or expansion etc. * [See docs for more info](https://boardgamegeek.com/wiki/page/BGG_XML_API2#Thing_Items). */ +@Serializable enum class ThingType(val param: String) { UNKNOWN(""), // Used whenever the type is empty or not recognized. BOARD_GAME("boardgame"), @@ -47,6 +49,7 @@ enum class ThingType(val param: String) { * * Note: About half of these types actually do _not_ return any list. */ +@Serializable enum class HotListType(val param: String) { UNKNOWN(""), // Used whenever the type is empty or not recognized. BOARD_GAME("boardgame"), @@ -60,6 +63,7 @@ enum class HotListType(val param: String) { } /** Different sub types returned in the [org.audux.bgg.request.plays] request/ response */ +@Serializable enum class SubType(val param: String) { UNKNOWN(""), // Used whenever the type is empty or not recognized. BOARD_GAME("boardgame"), @@ -81,6 +85,7 @@ enum class SubType(val param: String) { * The different kind/type of families the API may return such as a board game or rpgs. * [See docs for more info](https://boardgamegeek.com/wiki/page/BGG_XML_API2#Family_Items). */ +@Serializable enum class FamilyType(val param: String) { UNKNOWN(""), // Used whenever the type is empty or not recognized. RPG("rpg"), @@ -96,6 +101,7 @@ enum class FamilyType(val param: String) { * Used to map the id in the forumlist request to either a family ot thing. * [See docs for more info](https://boardgamegeek.com/wiki/page/BGG_XML_API2#Forum_Lists). */ +@Serializable enum class ForumListType(val param: String) { UNKNOWN(""), // Used whenever the type is empty or not recognized. THING("thing"), @@ -107,6 +113,7 @@ enum class ForumListType(val param: String) { } /** Used to show what type of thing it is when played, either a thing or a family(?) */ +@Serializable enum class PlayThingType(val param: String) { UNKNOWN(""), // Used whenever the type is empty or not recognized. THING("thing"), @@ -121,6 +128,7 @@ enum class PlayThingType(val param: String) { * Used to either include or exclude certain items in a request, see * [org.audux.bgg.request.collection] and [org.audux.bgg.request.user]. */ +@Serializable enum class Inclusion { INCLUDE, EXCLUDE; @@ -129,6 +137,7 @@ enum class Inclusion { } /** Different domains used for the users' hot- and top-10. */ +@Serializable enum class Domain(val param: String, val address: String) { BOARD_GAME_GEEK("boardgame", "https://boardgamegeek.com"), RPG_GEEK("rpg", "https://rpggeek.com"), @@ -136,6 +145,7 @@ enum class Domain(val param: String, val address: String) { } /** Encapsulates the name of a Thing either primary or alternate name. */ +@Serializable data class Name( /** The actual name. */ @JacksonXmlProperty(isAttribute = true) val value: String, @@ -151,6 +161,7 @@ data class Name( ) /** Wrapper for [Ratings]. */ +@Serializable data class Statistics( /** Unused attribute? */ @JacksonXmlProperty(isAttribute = true) val page: Int?, @@ -163,6 +174,7 @@ data class Statistics( * Contains rating aggregated and other statistics like average rating, standard deviation, number * of comments. */ +@Serializable data class Ratings( /** A user rating if available. */ @JacksonXmlProperty(isAttribute = true) val value: String? = null, @@ -211,6 +223,7 @@ data class Ratings( ) /** Represents a rank in a single ranking (Consisting of type & name). */ +@Serializable data class Rank( /** Unique of the ranking type - ID and type+name should always be a coupled. */ @JacksonXmlProperty(isAttribute = true) val id: Int, @@ -248,6 +261,7 @@ data class Rank( * * And so on. */ +@Serializable data class Link( /** * The id for the link, most of these cannot be retrieved via the API although a 'family'-API diff --git a/src/main/kotlin/org/audux/bgg/response/Collection.kt b/src/main/kotlin/org/audux/bgg/response/Collection.kt index aa460e7..1613e38 100644 --- a/src/main/kotlin/org/audux/bgg/response/Collection.kt +++ b/src/main/kotlin/org/audux/bgg/response/Collection.kt @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonRootName import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import java.time.LocalDateTime +import kotlinx.serialization.Serializable import org.audux.bgg.common.Ratings import org.audux.bgg.common.ThingType @@ -26,6 +27,7 @@ import org.audux.bgg.common.ThingType * Response wrapper for the user's Collection. These contain lists of items like board games wrapped * in [CollectionItem]. */ +@Serializable @JsonRootName("items") data class Collection( /** Terms of use of the BGG API. */ @@ -43,6 +45,7 @@ data class Collection( /** An item in the collection e.g. a board game, rpg etc. */ @JsonIgnoreProperties("objecttype") +@Serializable data class CollectionItem( @JacksonXmlProperty(isAttribute = true, localName = "collid") val collectionId: Int, @JacksonXmlProperty(isAttribute = true) val objectId: Int, @@ -84,6 +87,7 @@ data class CollectionItem( ) /** The status of collection item e.g. whether the user owns it, wants it etc. */ +@Serializable data class Status( /** Whether item is currently owned. */ @JsonDeserialize(using = NumberToBooleanDeserializer::class) @@ -131,10 +135,12 @@ data class Status( /** Whether item is currently owned. */ @JacksonXmlProperty(isAttribute = true) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Serializable(with = LocalDateTimeSerializer::class) val lastModified: LocalDateTime? = null, ) /** Statistics for a collection item. */ +@Serializable data class CollectionStatistics( /** Minimum number of players. */ @JacksonXmlProperty(localName = "minplayers") val minimumPlayers: Int?, diff --git a/src/main/kotlin/org/audux/bgg/response/Family.kt b/src/main/kotlin/org/audux/bgg/response/Family.kt index b9891ae..5695c6b 100644 --- a/src/main/kotlin/org/audux/bgg/response/Family.kt +++ b/src/main/kotlin/org/audux/bgg/response/Family.kt @@ -16,12 +16,14 @@ package org.audux.bgg.response import com.fasterxml.jackson.annotation.JsonRootName import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +import kotlinx.serialization.Serializable import org.audux.bgg.common.FamilyType import org.audux.bgg.common.Link import org.audux.bgg.common.Name /** Response wrapper for Family items, e.g. games having a related theme or gameplay. */ @JsonRootName("items") +@Serializable data class Family( /** Terms of use of the BGG API. */ @JacksonXmlProperty(isAttribute = true) val termsOfUse: String, @@ -31,6 +33,7 @@ data class Family( ) /** The actual Family item e.g. a `boardgamefamily`, rpg etc. */ +@Serializable data class FamilyItem( /** Unique ID that can be used to look up more information using the thing endpoint. */ @JacksonXmlProperty(isAttribute = true) val id: Int, diff --git a/src/main/kotlin/org/audux/bgg/response/Forum.kt b/src/main/kotlin/org/audux/bgg/response/Forum.kt index 8a2406d..d036ea3 100644 --- a/src/main/kotlin/org/audux/bgg/response/Forum.kt +++ b/src/main/kotlin/org/audux/bgg/response/Forum.kt @@ -19,12 +19,14 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import java.time.LocalDateTime +import kotlinx.serialization.Serializable /** * Encapsulates a forum containing a list of thread summaries - these can be retrieve using in * [org.audux.bgg.request.thread] endpoint. */ @JsonRootName("forum") +@Serializable data class Forum( /** Terms of use of the BGG API. */ @JacksonXmlProperty(isAttribute = true) val termsOfUse: String, @@ -51,6 +53,7 @@ data class Forum( /** The date and time a post was last made. */ @JsonFormat(pattern = "E, dd MMM yyyy HH:mm:ss Z") @JacksonXmlProperty(isAttribute = true) + @Serializable(with = LocalDateTimeSerializer::class) val lastPostDate: LocalDateTime?, /** The list of threads in this forum. */ @@ -60,6 +63,7 @@ data class Forum( /** * Summary of a thread in the forum, contains some stats and aggregated data but no articles/posts. */ +@Serializable data class ThreadSummary( /** Unique ID that can be used to look up more information using the thread endpoint. */ @JacksonXmlProperty(isAttribute = true) val id: Int, @@ -80,10 +84,12 @@ data class ThreadSummary( /** The date and time this thread was created. */ @JsonFormat(pattern = "E, dd MMM yyyy HH:mm:ss Z") @JacksonXmlProperty(isAttribute = true) + @Serializable(with = LocalDateTimeSerializer::class) val postDate: LocalDateTime?, /** The date and time a post was last made in this thread. */ @JsonFormat(pattern = "E, dd MMM yyyy HH:mm:ss Z") @JacksonXmlProperty(isAttribute = true) + @Serializable(with = LocalDateTimeSerializer::class) val lastPostDate: LocalDateTime?, ) diff --git a/src/main/kotlin/org/audux/bgg/response/ForumList.kt b/src/main/kotlin/org/audux/bgg/response/ForumList.kt index 45e362e..60dd21c 100644 --- a/src/main/kotlin/org/audux/bgg/response/ForumList.kt +++ b/src/main/kotlin/org/audux/bgg/response/ForumList.kt @@ -18,10 +18,12 @@ import com.fasterxml.jackson.annotation.JsonRootName import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import java.time.LocalDateTime +import kotlinx.serialization.Serializable import org.audux.bgg.common.ForumListType /** Response wrapper for a list of forums for the given id/thing pair. */ @JsonRootName("forums") +@Serializable data class ForumList( /** Terms of use of the BGG API. */ @JacksonXmlProperty(isAttribute = true) val termsOfUse: String, @@ -42,6 +44,7 @@ data class ForumList( * Encapsulates the summary of a forum - these can be retrieve using the * [org.audux.bgg.request.forum] endpoint. */ +@Serializable data class ForumSummary( /** Unique ID that can be used to look up more information using the forum endpoint. */ @JacksonXmlProperty(isAttribute = true) val id: Int, @@ -73,5 +76,6 @@ data class ForumSummary( /** The date and time a post was last made. */ @JsonFormat(pattern = "E, dd MMM yyyy HH:mm:ss Z") @JacksonXmlProperty(isAttribute = true) + @Serializable(with = LocalDateTimeSerializer::class) val lastPostDate: LocalDateTime?, ) diff --git a/src/main/kotlin/org/audux/bgg/response/GeekList.kt b/src/main/kotlin/org/audux/bgg/response/GeekList.kt index 6a27bc6..37f4274 100644 --- a/src/main/kotlin/org/audux/bgg/response/GeekList.kt +++ b/src/main/kotlin/org/audux/bgg/response/GeekList.kt @@ -17,15 +17,18 @@ import com.fasterxml.jackson.annotation.JsonFormat import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonRootName +import com.fasterxml.jackson.annotation.JsonSetter import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText import java.time.LocalDateTime +import kotlinx.serialization.Serializable import org.audux.bgg.common.Constants /** Encapsulates a geek list including its items and optionally its comments. */ @JsonRootName("geeklist") @JsonIgnoreProperties("postdate_timestamp", "editdate_timestamp") +@Serializable data class GeekList( /** Terms of use of the BGG API. */ @JacksonXmlProperty(isAttribute = true) val termsOfUse: String, @@ -34,10 +37,14 @@ data class GeekList( @JacksonXmlProperty(isAttribute = true) val id: Int, /** The date and time the geek list was posted/published. */ - @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) val postDate: LocalDateTime, + @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) + @Serializable(with = LocalDateTimeSerializer::class) + val postDate: LocalDateTime, /** The date and time the geek list was last edited - the same as [postDate] if not edited. */ - @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) val editDate: LocalDateTime, + @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) + @Serializable(with = LocalDateTimeSerializer::class) + val editDate: LocalDateTime, /** The number of thumbs up/likes the geek list received. */ val thumbs: Int, @@ -66,6 +73,7 @@ data class GeekList( } /** A single comment on either a Geek list or a Geek list item. */ +@Serializable data class GeekListComment( /** The username of the user that left the comment. */ @JacksonXmlProperty(isAttribute = true) val username: String, @@ -73,16 +81,19 @@ data class GeekListComment( /** The date the comment was originally posted. */ @JacksonXmlProperty(isAttribute = true) @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) + @Serializable(with = LocalDateTimeSerializer::class) val date: LocalDateTime, /** The date the comment was originally posted. */ @JacksonXmlProperty(isAttribute = true) @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) + @Serializable(with = LocalDateTimeSerializer::class) val postDate: LocalDateTime, /** The date the comment was last edited - the same as [postDate] if not edited. */ @JacksonXmlProperty(isAttribute = true) @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) + @Serializable(with = LocalDateTimeSerializer::class) val editDate: LocalDateTime, /** The number of thumbs up/likes on this item. */ @@ -97,6 +108,7 @@ data class GeekListComment( } /** An item in the geek list e.g. a board game including a description/[body] */ +@Serializable data class GeekListItem( /** The ID of the geek list item - NOT the id of the object. */ @JacksonXmlProperty(isAttribute = true) val id: Int, @@ -124,11 +136,13 @@ data class GeekListItem( /** The original date this item was added/posted to the list. */ @JacksonXmlProperty(isAttribute = true) @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) + @Serializable(with = LocalDateTimeSerializer::class) val postDate: LocalDateTime, /** The date this item was last edited/changed. */ @JacksonXmlProperty(isAttribute = true) @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) + @Serializable(with = LocalDateTimeSerializer::class) val editDate: LocalDateTime, /** The number of thumbs up/likes this item has received. */ @@ -140,11 +154,12 @@ data class GeekListItem( */ @JacksonXmlProperty(isAttribute = true) val imageId: Int? = null, @JsonDeserialize(using = TrimmedStringDeserializer::class) val body: String, -) { + /** The list of comments of this list. */ - @JsonProperty("comment") - var comments: List = mutableListOf() - set(value) { - field = field + value - } + var comments: List = mutableListOf(), +) { + @JsonSetter("comment") + fun internalSetComment(value: List) { + comments = comments + value + } } diff --git a/src/main/kotlin/org/audux/bgg/response/Guild.kt b/src/main/kotlin/org/audux/bgg/response/Guild.kt index 0522119..e223c72 100644 --- a/src/main/kotlin/org/audux/bgg/response/Guild.kt +++ b/src/main/kotlin/org/audux/bgg/response/Guild.kt @@ -18,10 +18,12 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonRootName import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import java.time.LocalDateTime +import kotlinx.serialization.Serializable import org.audux.bgg.common.Constants /** Response wrapper for Guilds to be returned. */ @JsonRootName("guild") +@Serializable data class Guild( /** Terms of use of the BGG API. */ @JacksonXmlProperty(isAttribute = true) val termsOfUse: String, @@ -35,6 +37,7 @@ data class Guild( /** The date and time the guild was created. */ @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) @JacksonXmlProperty(isAttribute = true, localName = "created") + @Serializable(with = LocalDateTimeSerializer::class) val createdAt: LocalDateTime?, /** The category of the Guild e.g. an interest group, regional group etc. */ @@ -57,6 +60,7 @@ data class Guild( ) /** The physical location of the guild. */ +@Serializable data class Location( /** First address line. */ @JsonProperty("addr1") val addressLine1: String, @@ -78,6 +82,7 @@ data class Location( ) /** Represents a (partial) list of members of the guild. */ +@Serializable data class GuildMembers( /** The total number of guild members. */ @JacksonXmlProperty(isAttribute = true) val count: Int, @@ -97,6 +102,7 @@ data class GuildMembers( * A GuildMember entry, consisting of simply a username (can be used to retrieve more user info) and * a join date and time. */ +@Serializable data class GuildMember( /** The username of the guild member. */ @JacksonXmlProperty(isAttribute = true) val name: String, @@ -104,5 +110,6 @@ data class GuildMember( /** When the user joined the guild. */ @JsonFormat(pattern = Constants.DAY_FIRST_DATE_TIME_FORMAT) @JacksonXmlProperty(isAttribute = true, localName = "date") + @Serializable(with = LocalDateTimeSerializer::class) val joinDate: LocalDateTime, ) diff --git a/src/main/kotlin/org/audux/bgg/response/HotList.kt b/src/main/kotlin/org/audux/bgg/response/HotList.kt index db7f6f2..e123cd2 100644 --- a/src/main/kotlin/org/audux/bgg/response/HotList.kt +++ b/src/main/kotlin/org/audux/bgg/response/HotList.kt @@ -16,9 +16,11 @@ package org.audux.bgg.response import com.fasterxml.jackson.annotation.JsonRootName import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +import kotlinx.serialization.Serializable /** Response wrapper for Hot lists to be returned. */ @JsonRootName("items") +@Serializable data class HotList( /** Terms of use of the BGG API. */ @JacksonXmlProperty(isAttribute = true) val termsOfUse: String, @@ -28,6 +30,7 @@ data class HotList( ) /** Encapsulates a ranked item in the hot list. */ +@Serializable data class HotListItem( /** Unique ID that can be used to look up more information using the thing endpoint. */ @JacksonXmlProperty(isAttribute = true) val id: Int, diff --git a/src/main/kotlin/org/audux/bgg/response/TypeDeserializers.kt b/src/main/kotlin/org/audux/bgg/response/JsonDeserializers.kt similarity index 100% rename from src/main/kotlin/org/audux/bgg/response/TypeDeserializers.kt rename to src/main/kotlin/org/audux/bgg/response/JsonDeserializers.kt diff --git a/src/main/kotlin/org/audux/bgg/response/KSerializers.kt b/src/main/kotlin/org/audux/bgg/response/KSerializers.kt new file mode 100644 index 0000000..1c3740f --- /dev/null +++ b/src/main/kotlin/org/audux/bgg/response/KSerializers.kt @@ -0,0 +1,80 @@ +package org.audux.bgg.response + +import java.net.URI +import java.time.LocalDate +import java.time.LocalDateTime +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SealedClassSerializer +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 + +/** [KSerializer] for a [LocalDate]. */ +internal class LocalDateSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: LocalDate) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): LocalDate = LocalDate.parse(decoder.decodeString()) +} + +/** [KSerializer] for a [LocalDateTime]. */ +internal class LocalDateTimeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: LocalDateTime) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): LocalDateTime = + LocalDateTime.parse(decoder.decodeString()) +} + +/** [KSerializer] for a [URI]. */ +internal class URISerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("URI", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: URI) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): URI = URI.create(decoder.decodeString()) +} + +/** [KSerializer] for [Poll] and all sub-classes. */ +@OptIn(InternalSerializationApi::class) +internal class PollSerializer : KSerializer { + private val serializer = + SealedClassSerializer( + "Poll", + Poll::class, + arrayOf( + LanguageDependencePoll::class, + PlayerAgePoll::class, + NumberOfPlayersPoll::class + ), + arrayOf( + LanguageDependencePoll.serializer(), + PlayerAgePoll.serializer(), + NumberOfPlayersPoll.serializer(), + ), + ) + + override val descriptor: SerialDescriptor = serializer.descriptor + + override fun deserialize(decoder: Decoder): Poll { + return serializer.deserialize(decoder) + } + + override fun serialize(encoder: Encoder, value: Poll) { + serializer.serialize(encoder, value) + } +} diff --git a/src/main/kotlin/org/audux/bgg/response/Plays.kt b/src/main/kotlin/org/audux/bgg/response/Plays.kt index a78f33f..65c69e4 100644 --- a/src/main/kotlin/org/audux/bgg/response/Plays.kt +++ b/src/main/kotlin/org/audux/bgg/response/Plays.kt @@ -18,10 +18,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import java.time.LocalDate +import kotlinx.serialization.Serializable import org.audux.bgg.common.PlayThingType /** Response wrapper for plays by the user to be returned. */ @JsonRootName("plays") +@Serializable data class Plays( /** Terms of use of the BGG API. */ @JacksonXmlProperty(isAttribute = true) val termsOfUse: String, @@ -46,12 +48,15 @@ data class Plays( * Represents a single(or batched as specified by `quantity`) play, including the 'thing'/game and * its players. */ +@Serializable data class Play( /** Unique ID of the play - not used */ @JacksonXmlProperty(isAttribute = true) val id: Int, /** The date the play took place. */ - @JacksonXmlProperty(isAttribute = true) val date: LocalDate, + @JacksonXmlProperty(isAttribute = true) + @Serializable(with = LocalDateSerializer::class) + val date: LocalDate, /** The number of plays, of the same game with the same players. */ @JacksonXmlProperty(isAttribute = true) val quantity: Int, @@ -86,6 +91,7 @@ data class Play( ) /** Represents the item/thing that was played e.g. a board game. */ +@Serializable data class PlayItem( /** The name of the item. */ @JacksonXmlProperty(isAttribute = true) val name: String, @@ -107,6 +113,7 @@ data class PlayItem( ) /** A SubType of a thing e.g. board game. */ +@Serializable data class SubType( @JsonDeserialize(using = WrappedSubTypeDeserializer::class) val subtype: org.audux.bgg.common.SubType @@ -116,6 +123,7 @@ data class SubType( * Represents a person in the play i.e. their username, id, what color they played, how they did * etc. */ +@Serializable data class Player( /** Optional username of the player. */ @JacksonXmlProperty(isAttribute = true) val username: String?, diff --git a/src/main/kotlin/org/audux/bgg/response/Search.kt b/src/main/kotlin/org/audux/bgg/response/Search.kt index c081fd8..68f2652 100644 --- a/src/main/kotlin/org/audux/bgg/response/Search.kt +++ b/src/main/kotlin/org/audux/bgg/response/Search.kt @@ -16,11 +16,13 @@ package org.audux.bgg.response import com.fasterxml.jackson.annotation.JsonRootName import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +import kotlinx.serialization.Serializable import org.audux.bgg.common.Name import org.audux.bgg.common.ThingType /** Response wrapper for the search results/items to be returned. */ @JsonRootName("items") +@Serializable data class SearchResults( /** Terms of use of the BGG API. */ @JacksonXmlProperty(isAttribute = true) val termsOfUse: String, @@ -33,6 +35,7 @@ data class SearchResults( ) /** Encapsulates a single search result. */ +@Serializable data class SearchResult( /** Primary or alternative name. */ val name: Name, diff --git a/src/main/kotlin/org/audux/bgg/response/Sitemap.kt b/src/main/kotlin/org/audux/bgg/response/Sitemap.kt index 57d480b..c07107e 100644 --- a/src/main/kotlin/org/audux/bgg/response/Sitemap.kt +++ b/src/main/kotlin/org/audux/bgg/response/Sitemap.kt @@ -19,10 +19,12 @@ import com.fasterxml.jackson.annotation.JsonRootName import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import java.time.LocalDate +import kotlinx.serialization.Serializable /** Represents a single Sitemap containing a set of URLs. */ @JsonRootName("urlset") @JsonIgnoreProperties("schemaLocation") +@Serializable data class Sitemap( /** List of the URLs in this Sitemap. */ @JacksonXmlProperty(localName = "url") val sitemaps: List @@ -32,6 +34,7 @@ data class Sitemap( * Single URL/Location contained in the Sitemap; could, for example, represent a single Board game * page. */ +@Serializable data class SitemapUrl( /** The actual URL/Web address */ @JsonProperty("loc") @@ -45,5 +48,7 @@ data class SitemapUrl( val priority: Double?, /** The date the page was last modified. */ - @JsonProperty("lastmod") val lastModified: LocalDate?, + @JsonProperty("lastmod") + @Serializable(with = LocalDateSerializer::class) + val lastModified: LocalDate?, ) diff --git a/src/main/kotlin/org/audux/bgg/response/SitemapIndex.kt b/src/main/kotlin/org/audux/bgg/response/SitemapIndex.kt index 636ddad..3ca6037 100644 --- a/src/main/kotlin/org/audux/bgg/response/SitemapIndex.kt +++ b/src/main/kotlin/org/audux/bgg/response/SitemapIndex.kt @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonRootName import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty +import kotlinx.serialization.Serializable import org.audux.bgg.common.SitemapLocationType /** @@ -26,12 +27,14 @@ import org.audux.bgg.common.SitemapLocationType */ @JsonRootName("sitemapindex") @JsonIgnoreProperties("schemaLocation") +@Serializable data class SitemapIndex( /** List of sitemap locations. */ @JacksonXmlProperty(localName = "sitemap") val sitemaps: List ) /** A URL/Location of a single sitemap/sitemap page. */ +@Serializable data class SitemapLocation( /** The URL/Web address of the sitemap. */ @JsonProperty("loc") diff --git a/src/main/kotlin/org/audux/bgg/response/Things.kt b/src/main/kotlin/org/audux/bgg/response/Things.kt index 438d7be..45f5a19 100644 --- a/src/main/kotlin/org/audux/bgg/response/Things.kt +++ b/src/main/kotlin/org/audux/bgg/response/Things.kt @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonFormat import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonRootName +import com.fasterxml.jackson.annotation.JsonSetter import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.databind.annotation.JsonDeserialize @@ -25,12 +26,14 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty import java.net.URI import java.time.LocalDate import java.time.LocalDateTime +import kotlinx.serialization.Serializable import org.audux.bgg.common.Link import org.audux.bgg.common.Name import org.audux.bgg.common.Statistics import org.audux.bgg.common.ThingType /** Response wrapper for the things to be returned. */ +@Serializable @JsonRootName("items") data class Things( /** Terms of use of the BGG API. */ @@ -52,6 +55,7 @@ data class Things( * * @see org.audux.bgg.request.things */ +@Serializable data class Thing( /** Unique BGG identifier. */ val id: Int, @@ -83,7 +87,9 @@ data class Thing( @JsonDeserialize(using = WrappedStringDeserializer::class) val datePublished: String?, /** The year it was released in e.g. `2019`. (For video games) */ - @JsonDeserialize(using = WrappedLocalDateDeserializer::class) val releaseDate: LocalDate?, + @JsonDeserialize(using = WrappedLocalDateDeserializer::class) + @Serializable(with = LocalDateSerializer::class) + val releaseDate: LocalDate?, /** Minimum number of players required. */ @JsonDeserialize(using = WrappedIntDeserializer::class) val minPlayers: Int?, @@ -138,88 +144,40 @@ data class Thing( @JsonDeserialize(using = WrappedIntDeserializer::class) val issueIndex: Int?, /** Primary name. */ - @JsonIgnore var name: String = "" -) { + @JsonIgnore var name: String = "", + /** Contains a list of polls such as the [PlayerAgePoll]. */ - @JsonProperty("poll") - var polls: List = listOf() - set(value) { - field = field + value - } + var polls: List = listOf(), /** * Depending on the [type] this list may contain different links e.g. for boardgames links such * as: `boardgamecategory`, `boardgamefamily`, `boardgamemechanic` etc. may be included. For * `rpgitem` similar but different links are returned e.g. `rpgitemcategory` etc. */ - @JacksonXmlProperty(localName = "link") - var links: List = listOf() - set(value) { - field = field + value - } + var links: List = listOf(), /** Names of the thing, consisting of a primary and optionally alternatives. */ - @JsonProperty("name") - var names: List = listOf() - set(value) { - field = field + value - field.forEach { if (it.type == "primary") name = it.value } - } - - fun copy( - id: Int = this.id, - type: ThingType = this.type, - thumbnail: String? = this.thumbnail, - image: String? = this.image, - description: String? = this.description, - yearPublished: Int? = this.yearPublished, - datePublished: String? = this.datePublished, - releaseDate: LocalDate? = this.releaseDate, - minPlayers: Int? = this.minPlayers, - maxPlayers: Double? = this.maxPlayers, - playingTimeInMinutes: Int? = this.playingTimeInMinutes, - minPlayingTimeInMinutes: Int? = this.minPlayingTimeInMinutes, - maxPlayingTimeInMinutes: Int? = this.maxPlayingTimeInMinutes, - minAge: Int? = this.minAge, - videos: List