Skip to content

Commit 706063e

Browse files
committed
Implement type safe usage of uris
* Fixed some serialization errors caused by non transient fields * Added SpotifyTrackUri * Removed SpotifyUri#UriType and reimplemented SpotifyUri#isUriType * Marked the typealiases *URI to *Uri as deprecated * Added some Uri tests
1 parent 8fc1983 commit 706063e

File tree

9 files changed

+351
-123
lines changed

9 files changed

+351
-123
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/SpotifyUris.kt

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ private class SimpleUriSerializer<T : SpotifyUri>(val ctor: (String) -> T) : KSe
4242
* @property id representation of this uri as an id
4343
*/
4444
@Serializable
45-
sealed class SpotifyUri(input: String, 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-
this.uri = it.add(type.toString())
52-
this.id = it.remove(type.toString())
51+
this.uri = it.add(type)
52+
this.id = it.remove(type)
5353
}
5454
}
5555

@@ -68,100 +68,156 @@ sealed class SpotifyUri(input: String, type: UriType) {
6868
return "SpotifyUri($uri)"
6969
}
7070

71-
enum class UriType(private val typeStr: String) {
72-
ALBUM("album"),
73-
ARTIST("artist"),
74-
TRACK("track"),
75-
USER("user"),
76-
PLAYLIST("playlist"),
77-
LOCAL_TRACK("local");
78-
79-
override fun toString() = typeStr
80-
}
81-
8271
@Serializer(forClass = SpotifyUri::class)
8372
companion object : KSerializer<SpotifyUri> {
84-
fun isUriType(uri: String, type: UriType) = uri.matchType(type.toString()) != null
85-
8673
override val descriptor: SerialDescriptor = StringDescriptor
8774
override fun deserialize(decoder: Decoder): SpotifyUri = SpotifyUri(decoder.decodeString())
8875
override fun serialize(encoder: Encoder, obj: SpotifyUri) = encoder.encodeString(obj.uri)
8976

90-
private inline fun <T : SpotifyUri> safeInitiate(uri: String, ctor: (String) -> T): T? {
77+
/**
78+
* This function safely instantiates a SpotifyUri from given constructor.
79+
* */
80+
inline fun <T : SpotifyUri> safeInitiate(uri: String, ctor: (String) -> T): T? {
9181
return try {
92-
ctor(uri).takeIf { it.uri == uri }
82+
ctor(uri)
9383
} catch (e: SpotifyUriException) {
9484
null
9585
}
9686
}
9787

88+
/**
89+
* Creates a abstract SpotifyUri of given input. Doesn't allow ambiguity by disallowing creation by id.
90+
* */
9891
operator fun invoke(input: String): SpotifyUri {
99-
val constructors = listOf(::AlbumUri, ::ArtistUri, ::TrackUri, ::UserUri, ::PlaylistUri)
92+
val constructors = listOf(::AlbumUri, ::ArtistUri, TrackUri.Companion::invoke, ::UserUri, ::PlaylistUri)
10093
for (ctor in constructors) {
101-
safeInitiate(input, ctor)?.also { return it }
94+
safeInitiate(input, ctor)?.takeIf { it.uri == input }?.also { return it }
10295
}
10396

10497
throw SpotifyUriException("Illegal Spotify ID/URI: '$input' isn't convertible to any arbitrary id")
10598
}
99+
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+
}
106125
}
107126
}
108127

109128
/**
110129
* Represents a Spotify **Album** URI, parsed from either a Spotify ID or taken from an endpoint.
111130
*/
112131
@Serializable
113-
class AlbumUri(val input: String) : SpotifyUri(input, UriType.ALBUM) {
132+
class AlbumUri(input: String) : SpotifyUri(input, "album") {
114133
@Serializer(forClass = AlbumUri::class)
115134
companion object : KSerializer<AlbumUri> by SimpleUriSerializer(::AlbumUri)
116135
}
136+
137+
@Deprecated("renamed", ReplaceWith("AlbumUri", "com.adamratzman.spotify.models.AlbumUri"))
117138
typealias AlbumURI = AlbumUri
118139

119140
/**
120141
* Represents a Spotify **Artist** URI, parsed from either a Spotify ID or taken from an endpoint.
121142
*/
122143
@Serializable
123-
class ArtistUri(val input: String) : SpotifyUri(input, UriType.ARTIST) {
144+
class ArtistUri(input: String) : SpotifyUri(input, "artist") {
124145
@Serializer(forClass = ArtistUri::class)
125146
companion object : KSerializer<ArtistUri> by SimpleUriSerializer(::ArtistUri)
126147
}
127-
typealias ArtistURI = ArtistUri
128148

129-
/**
130-
* Represents a Spotify **Track** URI, parsed from either a Spotify ID or taken from an endpoint.
131-
*/
132-
@Serializable
133-
class TrackUri(val input: String) : SpotifyUri(input, UriType.TRACK) {
134-
@Serializer(forClass = TrackUri::class)
135-
companion object : KSerializer<TrackUri> by SimpleUriSerializer(::TrackUri)
136-
}
137-
typealias TrackURI = TrackUri
149+
@Deprecated("renamed", ReplaceWith("ArtistUri", "com.adamratzman.spotify.models.ArtistUri"))
150+
typealias ArtistURI = ArtistUri
138151

139152
/**
140153
* Represents a Spotify **User** URI, parsed from either a Spotify ID or taken from an endpoint.
141154
*/
142155
@Serializable
143-
class UserUri(val input: String) : SpotifyUri(input, UriType.USER) {
156+
class UserUri(input: String) : SpotifyUri(input, "user") {
144157
@Serializer(forClass = UserUri::class)
145158
companion object : KSerializer<UserUri> by SimpleUriSerializer(::UserUri)
146159
}
160+
161+
@Deprecated("renamed", ReplaceWith("UserUri", "com.adamratzman.spotify.models.UserUri"))
147162
typealias UserURI = UserUri
148163

149164
/**
150165
* Represents a Spotify **Playlist** URI, parsed from either a Spotify ID or taken from an endpoint.
151166
*/
152167
@Serializable
153-
class PlaylistUri(val input: String) : SpotifyUri(input, UriType.PLAYLIST) {
168+
class PlaylistUri(input: String) : SpotifyUri(input, "playlist") {
154169
@Serializer(forClass = PlaylistUri::class)
155170
companion object : KSerializer<PlaylistUri> by SimpleUriSerializer(::PlaylistUri)
156171
}
172+
173+
@Deprecated("renamed", ReplaceWith("PlaylistUri", "com.adamratzman.spotify.models.PlaylistUri"))
157174
typealias PlaylistURI = PlaylistUri
158175

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+
159213
/**
160214
* Represents a Spotify **local track** URI
161215
*/
162216
@Serializable
163-
class LocalTrackUri(val input: String) : SpotifyUri(input, UriType.LOCAL_TRACK) {
217+
class LocalTrackUri(input: String) : TrackUri(input, "local") {
164218
@Serializer(forClass = LocalTrackUri::class)
165219
companion object : KSerializer<LocalTrackUri> by SimpleUriSerializer(::LocalTrackUri)
166220
}
221+
222+
@Deprecated("renamed", ReplaceWith("LocalTrackUri", "com.adamratzman.spotify.models.LocalTrackUri"))
167223
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

0 commit comments

Comments
 (0)