Skip to content

Commit 8fc1983

Browse files
committed
Fix SpotifyUri serialization
* Added uri serialization test
1 parent 1ad434b commit 8fc1983

File tree

4 files changed

+192
-142
lines changed

4 files changed

+192
-142
lines changed

src/commonMain/kotlin/com.adamratzman.spotify/models/ResultObjects.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import kotlinx.serialization.Transient
1111

1212
internal const val TRANSIENT_EMPTY_STRING = ""
1313
internal val TRANSIENT_NULL = null
14-
internal val TRANSIENT_URI = UserUri("spotify:user:")
14+
internal val TRANSIENT_URI = UserUri("TRANSIENT_URI")
1515

1616
/**
1717
* Represents an identifiable Spotify object such as an Album or Recommendation Seed

src/commonMain/kotlin/com.adamratzman.spotify/models/SpotifyUris.kt

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
/* Spotify Web API - Kotlin Wrapper; MIT License, 2019; Original author: Adam Ratzman */
22
package com.adamratzman.spotify.models
33

4+
import kotlinx.serialization.Decoder
5+
import kotlinx.serialization.Encoder
6+
import kotlinx.serialization.KSerializer
7+
import kotlinx.serialization.SerialDescriptor
48
import kotlinx.serialization.Serializable
5-
import kotlinx.serialization.modules.SerializersModule
6-
7-
internal val spotifyUriSerializersModule = SerializersModule {
8-
polymorphic(SpotifyUri::class) {
9-
AlbumUri::class with AlbumUri.serializer()
10-
ArtistUri::class with ArtistUri.serializer()
11-
TrackUri::class with TrackUri.serializer()
12-
LocalTrackUri::class with LocalTrackUri.serializer()
13-
UserUri::class with UserUri.serializer()
14-
PlaylistUri::class with PlaylistUri.serializer()
15-
}
16-
}
9+
import kotlinx.serialization.Serializer
10+
import kotlinx.serialization.internal.StringDescriptor
1711

1812
private fun String.matchType(type: String): String? {
1913
val typeRegex = "^spotify:(?:.*:)*$type:([^:]+)(?::.*)*$|^([^:]+)$".toRegex()
@@ -35,26 +29,27 @@ private fun String.remove(type: String): String {
3529
throw SpotifyUriException("Illegal Spotify ID/URI: '$this' isn't convertible to '$type' id")
3630
}
3731

32+
private class SimpleUriSerializer<T : SpotifyUri>(val ctor: (String) -> T) : KSerializer<T> {
33+
override val descriptor: SerialDescriptor = StringDescriptor
34+
override fun deserialize(decoder: Decoder): T = ctor(decoder.decodeString())
35+
override fun serialize(encoder: Encoder, obj: T) = encoder.encodeString(obj.uri)
36+
}
37+
3838
/**
3939
* Represents a Spotify URI, parsed from either a Spotify ID or taken from an endpoint.
4040
*
4141
* @property uri retrieve this URI as a string
4242
* @property id representation of this uri as an id
4343
*/
4444
@Serializable
45-
sealed class SpotifyUri(val input: String, val type: UriType) {
45+
sealed class SpotifyUri(input: String, type: UriType) {
4646
val uri: String
4747
val id: String
4848

4949
init {
5050
input.replace(" ", "").let {
51-
if (input == "spotify:user:") {
52-
this.uri = input
53-
this.id = input
54-
} else {
55-
this.uri = it.add(type.toString())
56-
this.id = it.remove(type.toString())
57-
}
51+
this.uri = it.add(type.toString())
52+
this.id = it.remove(type.toString())
5853
}
5954
}
6055

@@ -84,49 +79,89 @@ sealed class SpotifyUri(val input: String, val type: UriType) {
8479
override fun toString() = typeStr
8580
}
8681

87-
companion object {
82+
@Serializer(forClass = SpotifyUri::class)
83+
companion object : KSerializer<SpotifyUri> {
8884
fun isUriType(uri: String, type: UriType) = uri.matchType(type.toString()) != null
85+
86+
override val descriptor: SerialDescriptor = StringDescriptor
87+
override fun deserialize(decoder: Decoder): SpotifyUri = SpotifyUri(decoder.decodeString())
88+
override fun serialize(encoder: Encoder, obj: SpotifyUri) = encoder.encodeString(obj.uri)
89+
90+
private inline fun <T : SpotifyUri> safeInitiate(uri: String, ctor: (String) -> T): T? {
91+
return try {
92+
ctor(uri).takeIf { it.uri == uri }
93+
} catch (e: SpotifyUriException) {
94+
null
95+
}
96+
}
97+
98+
operator fun invoke(input: String): SpotifyUri {
99+
val constructors = listOf(::AlbumUri, ::ArtistUri, ::TrackUri, ::UserUri, ::PlaylistUri)
100+
for (ctor in constructors) {
101+
safeInitiate(input, ctor)?.also { return it }
102+
}
103+
104+
throw SpotifyUriException("Illegal Spotify ID/URI: '$input' isn't convertible to any arbitrary id")
105+
}
89106
}
90107
}
91108

92109
/**
93110
* Represents a Spotify **Album** URI, parsed from either a Spotify ID or taken from an endpoint.
94111
*/
95112
@Serializable
96-
class AlbumUri(private val inputString: String) : SpotifyUri(inputString, UriType.ALBUM)
113+
class AlbumUri(val input: String) : SpotifyUri(input, UriType.ALBUM) {
114+
@Serializer(forClass = AlbumUri::class)
115+
companion object : KSerializer<AlbumUri> by SimpleUriSerializer(::AlbumUri)
116+
}
97117
typealias AlbumURI = AlbumUri
98118

99119
/**
100120
* Represents a Spotify **Artist** URI, parsed from either a Spotify ID or taken from an endpoint.
101121
*/
102122
@Serializable
103-
class ArtistUri(private val inputString: String) : SpotifyUri(inputString, UriType.ARTIST)
123+
class ArtistUri(val input: String) : SpotifyUri(input, UriType.ARTIST) {
124+
@Serializer(forClass = ArtistUri::class)
125+
companion object : KSerializer<ArtistUri> by SimpleUriSerializer(::ArtistUri)
126+
}
104127
typealias ArtistURI = ArtistUri
105128

106129
/**
107130
* Represents a Spotify **Track** URI, parsed from either a Spotify ID or taken from an endpoint.
108131
*/
109132
@Serializable
110-
class TrackUri(private val inputString: String) : SpotifyUri(inputString, UriType.TRACK)
133+
class TrackUri(val input: String) : SpotifyUri(input, UriType.TRACK) {
134+
@Serializer(forClass = TrackUri::class)
135+
companion object : KSerializer<TrackUri> by SimpleUriSerializer(::TrackUri)
136+
}
111137
typealias TrackURI = TrackUri
112138

113139
/**
114140
* Represents a Spotify **User** URI, parsed from either a Spotify ID or taken from an endpoint.
115141
*/
116142
@Serializable
117-
class UserUri(private val inputString: String) : SpotifyUri(inputString, UriType.USER)
143+
class UserUri(val input: String) : SpotifyUri(input, UriType.USER) {
144+
@Serializer(forClass = UserUri::class)
145+
companion object : KSerializer<UserUri> by SimpleUriSerializer(::UserUri)
146+
}
118147
typealias UserURI = UserUri
119148

120149
/**
121150
* Represents a Spotify **Playlist** URI, parsed from either a Spotify ID or taken from an endpoint.
122151
*/
123152
@Serializable
124-
class PlaylistUri(private val inputString: String) : SpotifyUri(inputString, UriType.PLAYLIST)
153+
class PlaylistUri(val input: String) : SpotifyUri(input, UriType.PLAYLIST) {
154+
@Serializer(forClass = PlaylistUri::class)
155+
companion object : KSerializer<PlaylistUri> by SimpleUriSerializer(::PlaylistUri)
156+
}
125157
typealias PlaylistURI = PlaylistUri
126158

127159
/**
128160
* Represents a Spotify **local track** URI
129161
*/
130162
@Serializable
131-
class LocalTrackUri(private val inputString: String) : SpotifyUri(inputString, UriType.LOCAL_TRACK)
163+
class LocalTrackUri(val input: String) : SpotifyUri(input, UriType.LOCAL_TRACK) {
164+
@Serializer(forClass = LocalTrackUri::class)
165+
companion object : KSerializer<LocalTrackUri> by SimpleUriSerializer(::LocalTrackUri)
166+
}
132167
typealias LocalTrackURI = LocalTrackUri

src/commonMain/kotlin/com.adamratzman.spotify/models/serialization/SerializationUtils.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import com.adamratzman.spotify.models.CursorBasedPagingObject
99
import com.adamratzman.spotify.models.NeedsApi
1010
import com.adamratzman.spotify.models.PagingObject
1111
import com.adamratzman.spotify.models.instantiatePagingObjects
12-
import com.adamratzman.spotify.models.spotifyUriSerializersModule
1312
import kotlinx.serialization.KSerializer
1413
import kotlinx.serialization.Serializable
1514
import kotlinx.serialization.json.Json
@@ -21,7 +20,7 @@ import kotlinx.serialization.serializer
2120

2221
@Suppress("EXPERIMENTAL_API_USAGE")
2322
internal val json =
24-
Json(JsonConfiguration.Stable, spotifyUriSerializersModule)
23+
Json(JsonConfiguration.Stable)
2524
// Json(JsonConfiguration.Stable.copy(strictMode = false, useArrayPolymorphism = true), spotifyUriSerializersModule)
2625

2726
internal inline fun <reified T : Any> String.toObjectNullable(serializer: KSerializer<T>, api: SpotifyApi?): T? = try {

0 commit comments

Comments
 (0)