commonMain.com.adamratzman.spotify.models.SpotifyUris.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotify-api-kotlin-jvm Show documentation
Show all versions of spotify-api-kotlin-jvm Show documentation
A Kotlin wrapper for the Spotify Web API.
/* Spotify Web API, Kotlin Wrapper; MIT License, 2017-2021; Original author: Adam Ratzman */
@file:Suppress("EXPERIMENTAL_API_USAGE")
package com.adamratzman.spotify.models
import com.adamratzman.spotify.SpotifyException
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
/**
* Exception instantiating or deserializing a uri perceived as invalid
*/
public class SpotifyUriException(message: String) : SpotifyException.BadRequestException(message)
private fun String.matchType(type: String, allowColon: Boolean): String? {
val uriContent = "[^:]".takeUnless { allowColon } ?: "."
val typeRegex = "^spotify:(?:.*:)?$type:($uriContent*)(?::.*)*$|^([^:]+)\$".toRegex()
val match = typeRegex.matchEntire(this)?.groupValues ?: return null
return match[1].takeIf { it.isNotBlank() || match[2].isEmpty() } ?: match[2].takeIf { it.isNotEmpty() }
}
private fun String.add(type: String, allowColon: Boolean): String {
this.matchType(type, allowColon)?.let {
return "spotify:$type:${it.trim()}"
}
throw SpotifyUriException("Illegal Spotify ID/URI: '$this' isn't convertible to '$type' uri")
}
private fun String.remove(type: String, allowColon: Boolean): String {
this.matchType(type, allowColon)?.let {
return it.trim()
}
throw SpotifyUriException("Illegal Spotify ID/URI: '$this' isn't convertible to '$type' id")
}
private class SimpleUriSerializer(val ctor: (String) -> T) : KSerializer {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SimpleUri", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): T {
val str = decoder.decodeString()
return ctor(str)
}
override fun serialize(encoder: Encoder, value: T) = encoder.encodeString(value.uri)
}
/**
* Represents a Spotify URI, parsed from either a Spotify ID or taken from an endpoint.
*
* @param type The type (per Spotify) corresponding to the Uri.
*
* @property uri retrieve this URI as a string
* @property id representation of this uri as an id
*/
@Serializable(with = SpotifyUriSerializer::class)
public sealed class SpotifyUri(input: String, public val type: String, allowColon: Boolean = false) {
public val uri: String
public val id: String
init {
input.replace(" ", "").also {
this.uri = it.add(type, allowColon)
this.id = it.remove(type, allowColon)
}
}
override fun equals(other: Any?): Boolean {
val spotifyUri = other as? SpotifyUri ?: return false
return spotifyUri.uri == this.uri
}
override fun hashCode(): Int {
var result = uri.hashCode()
result = 31 * result + id.hashCode()
return result
}
override fun toString(): String {
return "SpotifyUri(type=$type, uri=$uri)"
}
public companion object {
/**
* This function safely instantiates a SpotifyUri from given constructor.
* */
public inline fun safeInitiate(uri: String, ctor: (String) -> T): T? {
return try {
ctor(uri)
} catch (e: SpotifyUriException) {
null
}
}
/**
* Creates a abstract SpotifyUri of given input. Doesn't allow ambiguity by disallowing creation by id.
* */
public operator fun invoke(input: String): SpotifyUri {
val constructors = listOf(
::ArtistUri,
PlayableUri.Companion::invoke,
ImmutableCollectionUri.Companion::invoke,
::UserUri,
::PlaylistUri
)
for (ctor in constructors) {
safeInitiate(input, ctor)?.takeIf { it.uri == input }?.also { return it }
}
throw SpotifyUriException("Illegal Spotify ID/URI: '$input' isn't convertible to any arbitrary id")
}
/**
* This function returns whether or not the given input IS a given type.
*
* @example ```Kotlin
* SpotifyUri.isType("abc") // returns: false
* SpotifyUri.isType("spotify:user:abc") // returns: true
* SpotifyUri.isType("spotify:track:abc") // returns: false
* ```
* */
public inline fun isType(input: String): Boolean {
return safeInitiate(input, ::invoke)?.let { it is T } ?: false
}
/**
* This function returns whether ot not the given input CAN be a given type.
*
* @example ```Kotlin
* SpotifyUri.canBeType("abc") // returns: true
* SpotifyUri.canBeType("spotify:user:abc") // returns: true
* SpotifyUri.canBeType("spotify:track:abc") // returns: false
* ```
* */
public inline fun canBeType(input: String): Boolean {
return isType(input) || !input.contains(':')
}
}
}
// TODO replace serialization with JSON specific code
public object SpotifyUriSerializer : KSerializer {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SpotifyUri", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): SpotifyUri = SpotifyUri(decoder.decodeString())
override fun serialize(encoder: Encoder, value: SpotifyUri): Unit = encoder.encodeString(value.uri)
}
@Serializable(with = CollectionUriSerializer::class)
public sealed class CollectionUri(input: String, type: String, allowColon: Boolean = false) :
SpotifyUri(input, type, allowColon) {
public companion object {
public operator fun invoke(input: String): CollectionUri {
val constructors = listOf(::PlaylistUri, ImmutableCollectionUri.Companion::invoke)
for (ctor in constructors) {
safeInitiate(input, ctor)?.also { return it }
}
throw SpotifyUriException("Illegal Spotify ID/URI: '$input' isn't convertible to 'playlist' or 'predefinedCollection' id")
}
}
}
public object CollectionUriSerializer : KSerializer {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("CollectionUri", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): CollectionUri {
return CollectionUri(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: CollectionUri): Unit = encoder.encodeString(value.uri)
}
@Serializable(with = ImmutableCollectionUriSerializer::class)
public sealed class ImmutableCollectionUri(input: String, type: String, allowColon: Boolean = false) :
CollectionUri(input, type, allowColon) {
public companion object {
public operator fun invoke(input: String): ImmutableCollectionUri {
val constructors = listOf(::AlbumUri, ::ShowUri)
for (ctor in constructors) {
safeInitiate(input, ctor)?.also { return it }
}
throw SpotifyUriException("Illegal Spotify ID/URI: '$input' isn't convertible to 'album' or 'show' id")
}
}
}
public object ImmutableCollectionUriSerializer : KSerializer {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ImmutableCollection", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): ImmutableCollectionUri =
ImmutableCollectionUri(decoder.decodeString())
override fun serialize(encoder: Encoder, value: ImmutableCollectionUri): Unit = encoder.encodeString(value.uri)
}
@Serializable(with = PlayableUriSerializer::class)
public sealed class PlayableUri(input: String, type: String, allowColon: Boolean = false) :
SpotifyUri(input, type, allowColon) {
public companion object {
/**
* Creates a abstract TrackURI of given input. Prefers SpotifyTrackUri if the input is ambiguous.
* */
public operator fun invoke(input: String): PlayableUri {
val constructors = listOf(::SpotifyTrackUri, ::LocalTrackUri, ::EpisodeUri)
for (ctor in constructors) {
safeInitiate(input, ctor)?.also { return it }
}
throw SpotifyUriException("Illegal Spotify ID/URI: '$input' isn't convertible to 'track' or 'localTrack' or 'episode' id")
}
}
}
public object PlayableUriSerializer : KSerializer {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("PlayableUri", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): PlayableUri = PlayableUri(decoder.decodeString())
override fun serialize(encoder: Encoder, value: PlayableUri): Unit = encoder.encodeString(value.uri)
}
/**
* Represents a Spotify **Album** URI, parsed from either a Spotify ID or taken from an endpoint.
*/
@Serializable(with = AlbumUriSerializer::class)
public class AlbumUri(input: String) : ImmutableCollectionUri(input, "album")
public object AlbumUriSerializer : KSerializer by SimpleUriSerializer(::AlbumUri)
/**
* Represents a Spotify **Artist** URI, parsed from either a Spotify ID or taken from an endpoint.
*/
@Serializable(with = ArtistUriSerializer::class)
public class ArtistUri(input: String) : SpotifyUri(input, "artist")
public object ArtistUriSerializer : KSerializer by SimpleUriSerializer(::ArtistUri)
/**
* Represents a Spotify **User** URI, parsed from either a Spotify ID or taken from an endpoint.
*/
@Serializable(with = UserUriSerializer::class)
public class UserUri(input: String) : SpotifyUri(input, "user")
public object UserUriSerializer : KSerializer by SimpleUriSerializer(::UserUri)
/**
* Represents a Spotify **Playlist** URI, parsed from either a Spotify ID or taken from an endpoint.
*/
@Serializable(with = PlaylistUriSerializer::class)
public class PlaylistUri(input: String) : CollectionUri(input, "playlist")
public object PlaylistUriSerializer : KSerializer by SimpleUriSerializer(::PlaylistUri)
/**
* Represents a Spotify **Track** URI, parsed from either a Spotify ID or taken from an endpoint.
*/
@Serializable(with = SpotifyTrackUriSerializer::class)
public class SpotifyTrackUri(input: String) : PlayableUri(input, "track")
public object SpotifyTrackUriSerializer : KSerializer by SimpleUriSerializer(::SpotifyTrackUri)
/**
* Represents a Spotify **local track** URI
*/
@Serializable(with = LocalTrackUriSerializer::class)
public class LocalTrackUri(input: String) : PlayableUri(input, "local", allowColon = true)
public object LocalTrackUriSerializer : KSerializer by SimpleUriSerializer(::LocalTrackUri)
@Serializable(with = EpisodeUriSerializer::class)
public class EpisodeUri(input: String) : PlayableUri(input, "episode")
public object EpisodeUriSerializer : KSerializer by SimpleUriSerializer(::EpisodeUri)
@Serializable(with = ShowUriSerializer::class)
public class ShowUri(input: String) : ImmutableCollectionUri(input, "show")
public object ShowUriSerializer : KSerializer by SimpleUriSerializer(::ShowUri)