Skip to content

Commit b750c7f

Browse files
authored
Merge pull request #145 from adamint/uri_enhancement
Uri enhancements and fixes
2 parents 3cfae05 + 706063e commit b750c7f

File tree

11 files changed

+461
-183
lines changed

11 files changed

+461
-183
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ data class SimpleAlbum(
4848
@SerialName("total_tracks") val totalTracks: Int? = null,
4949
@SerialName("album_group") private val albumGroupString: String? = null
5050
) : CoreObject(href, id, AlbumUri(uriString), externalUrlsString) {
51+
override val uri: AlbumUri get() = super.uri as AlbumUri
52+
5153
@Transient
5254
val availableMarkets = availableMarketsString.map { CountryCode.valueOf(it) }
5355

@@ -135,6 +137,8 @@ data class Album(
135137
@SerialName("total_tracks") val totalTracks: Int,
136138
val restrictions: Restrictions? = null
137139
) : CoreObject(href, id, AlbumUri(uriString), externalUrlsString) {
140+
override val uri: AlbumUri get() = super.uri as AlbumUri
141+
138142
@Transient
139143
val availableMarkets = availableMarketsString.map { CountryCode.valueOf(it) }
140144

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ data class SimpleArtist(
2222
val name: String,
2323
val type: String
2424
) : CoreObject(href, id, ArtistUri(uriString), externalUrlsString) {
25+
override val uri: ArtistUri get() = super.uri as ArtistUri
26+
2527
/**
2628
* Converts this [SimpleArtist] into a full [Artist] object
2729
*/
@@ -55,7 +57,9 @@ data class Artist(
5557
val name: String,
5658
val popularity: Int,
5759
val type: String
58-
) : CoreObject(href, id, ArtistUri(uriString), externalUrlsString)
60+
) : CoreObject(href, id, ArtistUri(uriString), externalUrlsString) {
61+
override val uri: ArtistUri get() = super.uri as ArtistUri
62+
}
5963

6064
@Serializable
6165
internal data class ArtistList(val artists: List<Artist?>)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ abstract class AbstractPagingObject<T : Any>(
207207
@Transient open val previous: String? = null,
208208
@Transient open val total: Int = -1
209209
) : List<T> {
210-
override val size: Int = items.size
210+
override val size: Int get() = items.size
211211

212212
override fun contains(element: T) = items.contains(element)
213213

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ data class PlayHistoryContext(
1919
@SerialName("uri") private val uriString: String,
2020

2121
val type: String
22-
) : CoreObject(href, href, TrackUri(uriString), externalUrlsString)
22+
) : CoreObject(href, href, TrackUri(uriString), externalUrlsString) {
23+
override val uri: TrackUri get() = super.uri as TrackUri
24+
}
2325

2426
/**
2527
* Information about a previously-played track

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ data class SimplePlaylist(
4545
val tracks: PlaylistTrackInfo,
4646
val type: String
4747
) : CoreObject(href, id, PlaylistUri(uriString), externalUrlsString) {
48+
override val uri: PlaylistUri get() = super.uri as PlaylistUri
49+
4850
@Transient
4951
val snapshot: PlaylistSnapshot = PlaylistSnapshot(snapshotIdString)
5052

@@ -118,6 +120,8 @@ data class Playlist(
118120
val tracks: PagingObject<PlaylistTrack>,
119121
val type: String
120122
) : CoreObject(href, id, PlaylistUri(uriString), externalUrlsString) {
123+
override val uri: PlaylistUri get() = super.uri as PlaylistUri
124+
121125
@Transient
122126
val snapshot: PlaylistSnapshot = PlaylistSnapshot(snapshotIdString)
123127
}

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
Lines changed: 134 additions & 43 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(val input: String, type: String) {
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)
52+
this.id = it.remove(type)
5853
}
5954
}
6055

@@ -73,60 +68,156 @@ sealed class SpotifyUri(val input: String, val type: UriType) {
7368
return "SpotifyUri($uri)"
7469
}
7570

76-
enum class UriType(private val typeStr: String) {
77-
ALBUM("album"),
78-
ARTIST("artist"),
79-
TRACK("track"),
80-
USER("user"),
81-
PLAYLIST("playlist"),
82-
LOCAL_TRACK("local");
71+
@Serializer(forClass = SpotifyUri::class)
72+
companion object : KSerializer<SpotifyUri> {
73+
override val descriptor: SerialDescriptor = StringDescriptor
74+
override fun deserialize(decoder: Decoder): SpotifyUri = SpotifyUri(decoder.decodeString())
75+
override fun serialize(encoder: Encoder, obj: SpotifyUri) = encoder.encodeString(obj.uri)
76+
77+
/**
78+
* This function safely instantiates a SpotifyUri from given constructor.
79+
* */
80+
inline fun <T : SpotifyUri> safeInitiate(uri: String, ctor: (String) -> T): T? {
81+
return try {
82+
ctor(uri)
83+
} catch (e: SpotifyUriException) {
84+
null
85+
}
86+
}
87+
88+
/**
89+
* Creates a abstract SpotifyUri of given input. Doesn't allow ambiguity by disallowing creation by id.
90+
* */
91+
operator fun invoke(input: String): SpotifyUri {
92+
val constructors = listOf(::AlbumUri, ::ArtistUri, TrackUri.Companion::invoke, ::UserUri, ::PlaylistUri)
93+
for (ctor in constructors) {
94+
safeInitiate(input, ctor)?.takeIf { it.uri == input }?.also { return it }
95+
}
8396

84-
override fun toString() = typeStr
85-
}
97+
throw SpotifyUriException("Illegal Spotify ID/URI: '$input' isn't convertible to any arbitrary id")
98+
}
8699

87-
companion object {
88-
fun isUriType(uri: String, type: UriType) = uri.matchType(type.toString()) != null
100+
/**
101+
* This function returns whether or not the given input IS a given type.
102+
*
103+
* @example ```Kotlin
104+
* SpotifyUri.isType<UserUri>("abc") // returns: false
105+
* SpotifyUri.isType<UserUri>("spotify:user:abc") // returns: true
106+
* SpotifyUri.isType<UserUri>("spotify:track:abc") // returns: false
107+
* ```
108+
* */
109+
inline fun <reified T: SpotifyUri> isType(input: String): Boolean {
110+
return safeInitiate(input, ::invoke)?.let { it is T } ?: false
111+
}
112+
113+
/**
114+
* This function returns whether ot not the given input CAN be a given type.
115+
*
116+
* @example ```Kotlin
117+
* SpotifyUri.canBeType<UserUri>("abc") // returns: true
118+
* SpotifyUri.canBeType<UserUri>("spotify:user:abc") // returns: true
119+
* SpotifyUri.canBeType<UserUri>("spotify:track:abc") // returns: false
120+
* ```
121+
* */
122+
inline fun <reified T: SpotifyUri> canBeType(input: String): Boolean {
123+
return isType<T>(input) || !input.contains(':')
124+
}
89125
}
90126
}
91127

92128
/**
93129
* Represents a Spotify **Album** URI, parsed from either a Spotify ID or taken from an endpoint.
94130
*/
95131
@Serializable
96-
class AlbumUri(private val inputString: String) : SpotifyUri(inputString, UriType.ALBUM)
132+
class AlbumUri(input: String) : SpotifyUri(input, "album") {
133+
@Serializer(forClass = AlbumUri::class)
134+
companion object : KSerializer<AlbumUri> by SimpleUriSerializer(::AlbumUri)
135+
}
136+
137+
@Deprecated("renamed", ReplaceWith("AlbumUri", "com.adamratzman.spotify.models.AlbumUri"))
97138
typealias AlbumURI = AlbumUri
98139

99140
/**
100141
* Represents a Spotify **Artist** URI, parsed from either a Spotify ID or taken from an endpoint.
101142
*/
102143
@Serializable
103-
class ArtistUri(private val inputString: String) : SpotifyUri(inputString, UriType.ARTIST)
104-
typealias ArtistURI = ArtistUri
144+
class ArtistUri(input: String) : SpotifyUri(input, "artist") {
145+
@Serializer(forClass = ArtistUri::class)
146+
companion object : KSerializer<ArtistUri> by SimpleUriSerializer(::ArtistUri)
147+
}
105148

106-
/**
107-
* Represents a Spotify **Track** URI, parsed from either a Spotify ID or taken from an endpoint.
108-
*/
109-
@Serializable
110-
class TrackUri(private val inputString: String) : SpotifyUri(inputString, UriType.TRACK)
111-
typealias TrackURI = TrackUri
149+
@Deprecated("renamed", ReplaceWith("ArtistUri", "com.adamratzman.spotify.models.ArtistUri"))
150+
typealias ArtistURI = ArtistUri
112151

113152
/**
114153
* Represents a Spotify **User** URI, parsed from either a Spotify ID or taken from an endpoint.
115154
*/
116155
@Serializable
117-
class UserUri(private val inputString: String) : SpotifyUri(inputString, UriType.USER)
156+
class UserUri(input: String) : SpotifyUri(input, "user") {
157+
@Serializer(forClass = UserUri::class)
158+
companion object : KSerializer<UserUri> by SimpleUriSerializer(::UserUri)
159+
}
160+
161+
@Deprecated("renamed", ReplaceWith("UserUri", "com.adamratzman.spotify.models.UserUri"))
118162
typealias UserURI = UserUri
119163

120164
/**
121165
* Represents a Spotify **Playlist** URI, parsed from either a Spotify ID or taken from an endpoint.
122166
*/
123167
@Serializable
124-
class PlaylistUri(private val inputString: String) : SpotifyUri(inputString, UriType.PLAYLIST)
168+
class PlaylistUri(input: String) : SpotifyUri(input, "playlist") {
169+
@Serializer(forClass = PlaylistUri::class)
170+
companion object : KSerializer<PlaylistUri> by SimpleUriSerializer(::PlaylistUri)
171+
}
172+
173+
@Deprecated("renamed", ReplaceWith("PlaylistUri", "com.adamratzman.spotify.models.PlaylistUri"))
125174
typealias PlaylistURI = PlaylistUri
126175

176+
/**
177+
* Represents a Spotify **Track** URI, ether LocalTrack or SpotifyTrack, parsed from either a Spotify ID or taken
178+
* from an endpoint
179+
* */
180+
@Serializable
181+
sealed class TrackUri(input: String, type: String) : SpotifyUri(input, type) {
182+
@Serializer(forClass = TrackUri::class)
183+
companion object : KSerializer<TrackUri> {
184+
override val descriptor: SerialDescriptor = StringDescriptor
185+
override fun deserialize(decoder: Decoder): TrackUri = TrackUri(decoder.decodeString())
186+
override fun serialize(encoder: Encoder, obj: TrackUri) = encoder.encodeString(obj.uri)
187+
188+
/**
189+
* Creates a abstract TrackURI of given input. Prefers SpotifyTrackUri if the input is ambiguous.
190+
* */
191+
operator fun invoke(input: String): TrackUri {
192+
val constructors = listOf(::SpotifyTrackUri, ::LocalTrackUri)
193+
for (ctor in constructors) {
194+
safeInitiate(input, ctor)?.also { return it }
195+
}
196+
throw SpotifyUriException("Illegal Spotify ID/URI: '$input' isn't convertible to 'track' or 'local' id")
197+
}
198+
}
199+
}
200+
201+
@Deprecated("renamed", ReplaceWith("TrackUri", "com.adamratzman.spotify.models.TrackUri"))
202+
typealias TrackURI = TrackUri
203+
204+
/**
205+
* Represents a Spotify **Track** URI, parsed from either a Spotify ID or taken from an endpoint.
206+
*/
207+
@Serializable
208+
class SpotifyTrackUri(input: String) : TrackUri(input, "track") {
209+
@Serializer(forClass = SpotifyTrackUri::class)
210+
companion object : KSerializer<SpotifyTrackUri> by SimpleUriSerializer(::SpotifyTrackUri)
211+
}
212+
127213
/**
128214
* Represents a Spotify **local track** URI
129215
*/
130216
@Serializable
131-
class LocalTrackUri(private val inputString: String) : SpotifyUri(inputString, UriType.LOCAL_TRACK)
217+
class LocalTrackUri(input: String) : TrackUri(input, "local") {
218+
@Serializer(forClass = LocalTrackUri::class)
219+
companion object : KSerializer<LocalTrackUri> by SimpleUriSerializer(::LocalTrackUri)
220+
}
221+
222+
@Deprecated("renamed", ReplaceWith("LocalTrackUri", "com.adamratzman.spotify.models.LocalTrackUri"))
132223
typealias LocalTrackURI = LocalTrackUri

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ data class SimpleTrack(
5858
val popularity: Int? = null,
5959
val restrictions: Restrictions? = null
6060
) : RelinkingAvailableResponse(linkedFrom, href, id, TrackUri(uriString), externalUrlsString) {
61+
override val uri: TrackUri get() = super.uri as TrackUri
62+
6163
@Transient
6264
val availableMarkets = availableMarketsString.map { CountryCode.valueOf(it) }
6365

@@ -142,6 +144,8 @@ data class Track(
142144
if (uriString.contains("local:")) LocalTrackUri(uriString) else TrackUri(uriString),
143145
externalUrlsString
144146
) {
147+
override val uri: TrackUri get() = super.uri as TrackUri
148+
145149
@Transient
146150
val availableMarkets = availableMarketsString.map { CountryCode.valueOf(it) }
147151

@@ -166,6 +170,7 @@ data class LinkedTrack(
166170

167171
val type: String
168172
) : CoreObject(href, id, TrackUri(uriString), externalUrlsString) {
173+
override val uri: TrackUri get() = super.uri as TrackUri
169174

170175
/**
171176
* Retrieves the full [Track] object associated with this [LinkedTrack] with the given market

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ data class SpotifyUserInformation(
4040
val product: String?,
4141
@SerialName("explicit_content") val explicitContentSettings: ExplicitContentSettings?,
4242
val type: String
43-
) : CoreObject(href, id, UserUri(uriString), externalUrlsString)
43+
) : CoreObject(href, id, UserUri(uriString), externalUrlsString) {
44+
override val uri: UserUri get() = super.uri as UserUri
45+
}
4446

4547
/**
4648
* Public information about a Spotify user
@@ -63,7 +65,9 @@ data class SpotifyPublicUser(
6365
val followers: Followers = Followers(null, -1),
6466
val images: List<SpotifyImage> = listOf(),
6567
val type: String
66-
) : CoreObject(href, id, UserUri(uriString), externalUrlsString)
68+
) : CoreObject(href, id, UserUri(uriString), externalUrlsString) {
69+
override val uri: UserUri get() = super.uri as UserUri
70+
}
6771

6872
/**
6973
* Information about a Spotify user's followers

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)