Skip to content

Commit 1b2c9f8

Browse files
authored
Merge pull request #337 from adamint/dev/adamint/fix-incorrect-logic-has-scopes
Several bug fixes
2 parents 3c7b55d + d50cc64 commit 1b2c9f8

File tree

23 files changed

+49
-37
lines changed

23 files changed

+49
-37
lines changed

build.gradle.kts

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.databind.json.JsonMapper
44
import org.jetbrains.dokka.gradle.DokkaTask
5+
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
56
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
67
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
78
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput.Target
@@ -46,7 +47,7 @@ version = libraryVersion
4647

4748
android {
4849
namespace = "com.adamratzman.spotify"
49-
compileSdk = 30
50+
compileSdk = 31
5051
compileOptions {
5152
sourceCompatibility = JavaVersion.VERSION_17
5253
targetCompatibility = JavaVersion.VERSION_17
@@ -56,7 +57,7 @@ android {
5657
}
5758
defaultConfig {
5859
minSdk = 23
59-
setCompileSdkVersion(30)
60+
setCompileSdkVersion(31)
6061
testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
6162
}
6263

@@ -81,6 +82,10 @@ val dokkaJar: TaskProvider<Jar> by tasks.registering(Jar::class) {
8182
}
8283

8384
kotlin {
85+
@OptIn(ExperimentalKotlinGradlePluginApi::class)
86+
compilerOptions {
87+
freeCompilerArgs.add("-Xexpect-actual-classes")
88+
}
8489
explicitApiWarning()
8590
jvmToolchain(17)
8691

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ kotlinxCoroutinesVersion=1.7.3
2121
androidBuildToolsVersion=8.2.2
2222
androidSpotifyAuthVersion=2.1.1
2323
androidCryptoVersion=1.1.0-alpha06
24-
androidxCompatVersion=1.6.1
24+
androidxCompatVersion=1.7.0-alpha03
2525

2626
sparkVersion=2.9.4

src/commonJvmLikeTest/kotlin/com/adamratzman/spotify/CommonImpl.kt

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ private suspend fun buildSpotifyApiInternal(): GenericSpotifyApi? {
3838

3939
val optionsCreator: (SpotifyApiOptions.() -> Unit) = {
4040
this.enableDebugMode = logHttp
41+
retryOnInternalServerErrorTimes = 0
4142
}
4243

4344
return when {

src/commonMain/kotlin/com.adamratzman.spotify/SpotifyApi.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ public open class SpotifyClientApi(
644644
if (token.scopes == null) {
645645
null
646646
} else {
647-
!isTokenValid(false).isValid &&
647+
isTokenValid(false).isValid &&
648648
token.scopes?.contains(scope) == true &&
649649
scopes.all { token.scopes?.contains(it) == true }
650650
}

src/commonMain/kotlin/com.adamratzman.spotify/endpoints/pub/EpisodeApi.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.adamratzman.spotify.endpoints.pub
33

44
import com.adamratzman.spotify.GenericSpotifyApi
55
import com.adamratzman.spotify.SpotifyAppApi
6+
import com.adamratzman.spotify.SpotifyClientApi
67
import com.adamratzman.spotify.SpotifyException.BadRequestException
78
import com.adamratzman.spotify.SpotifyScope
89
import com.adamratzman.spotify.http.SpotifyEndpoint
@@ -60,7 +61,7 @@ public open class EpisodeApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
6061
* Users can view the country that is associated with their account in the account settings. Required for [SpotifyAppApi], but **you may use [Market.FROM_TOKEN] to get the user market**
6162
*
6263
* @return List of possibly-null [Episode] objects.
63-
* @throws BadRequestException If any invalid show id is provided
64+
* @throws BadRequestException If any invalid show id is provided, if this is a [SpotifyClientApi]
6465
*/
6566
public suspend fun getEpisodes(vararg ids: String, market: Market): List<Episode?> {
6667
checkBulkRequesting(50, ids.size)

src/commonMain/kotlin/com.adamratzman.spotify/endpoints/pub/SearchApi.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ public open class SearchApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
338338
market: Market,
339339
language: Language? = null
340340
): SpotifySearchResult =
341-
search(query, filters = filters, searchTypes = SearchType.values(), limit = limit, offset = offset, market = market)
341+
search(query, filters = filters, searchTypes = SearchType.entries.toTypedArray(), limit = limit, offset = offset, market = market, language = language)
342342

343343
protected fun build(
344344
query: String,

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public data class SimpleAlbum(
4141
override val uri: SpotifyUri,
4242

4343
val artists: List<SimpleArtist>,
44-
val images: List<SpotifyImage>,
44+
val images: List<SpotifyImage>? = null,
4545
val name: String,
4646
val type: String,
4747
val restrictions: Restrictions? = null,
@@ -54,14 +54,14 @@ public data class SimpleAlbum(
5454

5555
val albumType: AlbumResultType
5656
get() = albumTypeString.let { _ ->
57-
AlbumResultType.values().first { it.id.equals(albumTypeString, true) }
57+
AlbumResultType.entries.first { it.id.equals(albumTypeString, true) }
5858
}
5959

6060
val releaseDate: ReleaseDate? get() = releaseDateString?.let { getReleaseDate(releaseDateString) }
6161

6262
val albumGroup: AlbumResultType?
6363
get() = albumGroupString?.let { _ ->
64-
AlbumResultType.values().find { it.id == albumGroupString }
64+
AlbumResultType.entries.find { it.id == albumGroupString }
6565
}
6666

6767
/**
@@ -142,7 +142,7 @@ public data class Album(
142142
val artists: List<SimpleArtist>,
143143
val copyrights: List<SpotifyCopyright>,
144144
val genres: List<String>,
145-
val images: List<SpotifyImage>,
145+
val images: List<SpotifyImage>? = null,
146146
val label: String,
147147
val name: String,
148148
val popularity: Double,
@@ -157,7 +157,7 @@ public data class Album(
157157

158158
val externalIds: List<ExternalId> get() = externalIdsString.map { ExternalId(it.key, it.value) }
159159

160-
val albumType: AlbumResultType get() = AlbumResultType.values().first { it.id == albumTypeString }
160+
val albumType: AlbumResultType get() = AlbumResultType.entries.first { it.id == albumTypeString }
161161

162162
val releaseDate: ReleaseDate get() = getReleaseDate(releaseDateString)
163163

@@ -181,7 +181,7 @@ public data class SpotifyCopyright(
181181
.removePrefix("(C)")
182182
.trim()
183183

184-
val type: CopyrightType get() = CopyrightType.values().match(typeString)!!
184+
val type: CopyrightType get() = CopyrightType.entries.toTypedArray().match(typeString)!!
185185
}
186186

187187
@Serializable

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public data class Artist(
5959

6060
val followers: Followers,
6161
val genres: List<String>,
62-
val images: List<SpotifyImage>,
62+
val images: List<SpotifyImage>? = null,
6363
val name: String? = null,
6464
val popularity: Double,
6565
val type: String

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public data class Token(
3333
val expiresAt: Long get() = getCurrentTimeMs() + expiresIn * 1000
3434

3535
val scopes: List<SpotifyScope>? get() = scopeString?.let { str ->
36-
str.split(" ").mapNotNull { scope -> SpotifyScope.values().find { it.uri.equals(scope, true) } }
36+
str.split(" ").mapNotNull { scope -> SpotifyScope.entries.find { it.uri.equals(scope, true) } }
3737
}
3838

3939
public fun shouldRefresh(): Boolean = getCurrentTimeMs() > expiresAt

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public data class Episode(
9696
@SerialName("external_urls") override val externalUrlsString: Map<String, String>,
9797
override val href: String,
9898
override val id: String,
99-
val images: List<SpotifyImage>,
99+
val images: List<SpotifyImage>? = null,
100100
@SerialName("is_externally_hosted") val isExternallyHosted: Boolean,
101101
@SerialName("is_playable") val isPlayable: Boolean,
102102
@Deprecated("This field is deprecated and might be removed in the future. Please use the languages field instead")
@@ -148,7 +148,7 @@ public data class SimpleEpisode(
148148
@SerialName("external_urls") override val externalUrlsString: Map<String, String>,
149149
override val href: String,
150150
override val id: String,
151-
val images: List<SpotifyImage>,
151+
val images: List<SpotifyImage>? = null,
152152
@SerialName("is_externally_hosted") val isExternallyHosted: Boolean,
153153
@SerialName("is_playable") val isPlayable: Boolean,
154154
@Deprecated("This field is deprecated and might be removed in the future. Please use the languages field instead")

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public data class SpotifyContext(
2424
val uri: ContextUri,
2525
@SerialName("type") val typeString: String
2626
) {
27-
val type: SpotifyContextType get() = SpotifyContextType.values().match(typeString)!!
27+
val type: SpotifyContextType get() = SpotifyContextType.entries.toTypedArray().match(typeString)!!
2828
val externalUrls: List<ExternalUrl> get() = getExternalUrls(externalUrlsString)
2929
}
3030

@@ -64,7 +64,7 @@ public data class Device(
6464
@SerialName("type") val typeString: String,
6565
@SerialName("volume_percent") val volumePercent: Int
6666
) : IdentifiableNullable() {
67-
val type: DeviceType get() = DeviceType.values().first { it.identifier.equals(typeString, true) }
67+
val type: DeviceType get() = DeviceType.entries.first { it.identifier.equals(typeString, true) }
6868

6969
override val href: String? = null
7070
}
@@ -118,7 +118,7 @@ public data class CurrentlyPlayingContext(
118118
val context: SpotifyContext? = null
119119
) {
120120
val repeatState: ClientPlayerApi.PlayerRepeatState
121-
get() = ClientPlayerApi.PlayerRepeatState.values().match(repeatStateString)!!
121+
get() = ClientPlayerApi.PlayerRepeatState.entries.toTypedArray().match(repeatStateString)!!
122122
}
123123

124124
/**
@@ -146,7 +146,7 @@ public data class CurrentlyPlayingObject(
146146
val actions: PlaybackActions
147147
) {
148148
val currentlyPlayingType: CurrentlyPlayingType
149-
get() = CurrentlyPlayingType.values().match(currentlyPlayingTypeString)!!
149+
get() = CurrentlyPlayingType.entries.toTypedArray().match(currentlyPlayingTypeString)!!
150150
}
151151

152152
/**
@@ -162,7 +162,7 @@ public data class PlaybackActions(
162162
val disallows: List<DisallowablePlaybackAction>
163163
get() = disallowsString.map {
164164
DisallowablePlaybackAction(
165-
PlaybackAction.values().match(it.key)!!,
165+
PlaybackAction.entries.toTypedArray().match(it.key)!!,
166166
it.value ?: false
167167
)
168168
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public data class SimplePlaylist(
3939
override val uri: SpotifyUri,
4040

4141
val collaborative: Boolean,
42-
val images: List<SpotifyImage>,
42+
val images: List<SpotifyImage>? = null,
4343
val name: String,
4444
val description: String? = null,
4545
val owner: SpotifyPublicUser,
@@ -126,7 +126,7 @@ public data class Playlist(
126126
val description: String? = null,
127127
val followers: Followers,
128128
@SerialName("primary_color") val primaryColor: String? = null,
129-
val images: List<SpotifyImage>,
129+
val images: List<SpotifyImage>? = null,
130130
val name: String,
131131
val owner: SpotifyPublicUser,
132132
val public: Boolean? = null,
@@ -150,7 +150,7 @@ public data class Playlist(
150150
@Serializable
151151
public data class PlaylistTrackInfo(
152152
val href: String,
153-
val total: Int
153+
val total: Int? = null
154154
)
155155

156156
@Serializable

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ public interface ISpotifyUri {
7676
*/
7777
@Serializable(with = SpotifyUriSerializer::class)
7878
public sealed class SpotifyUri(input: String, public val type: String, allowColon: Boolean = false) : ISpotifyUri {
79-
public override val uri: String
80-
public override val id: String
79+
public final override val uri: String
80+
public final override val id: String
8181

8282
init {
8383
input.replace(" ", "").also {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public data class SpotifyUserInformation(
4040
@SerialName("display_name") val displayName: String? = null,
4141
val email: String? = null,
4242
val followers: Followers,
43-
val images: List<SpotifyImage>,
43+
val images: List<SpotifyImage>? = null,
4444
val product: String? = null,
4545
@SerialName("explicit_content") val explicitContentSettings: ExplicitContentSettings? = null,
4646
val type: String

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

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal val nonstrictJson =
2323
ignoreUnknownKeys = true
2424
allowSpecialFloatingPointValues = true
2525
useArrayPolymorphism = true
26+
coerceInputValues = true
2627
}
2728

2829
// Parse function that catches on parse exception

src/commonMain/kotlin/com.adamratzman.spotify/utils/Locale.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ public enum class Locale(public val language: Language, public val country: Mark
806806

807807
public companion object {
808808
public fun from(language: Language, country: Market): Locale? {
809-
return values().find { locale -> locale.language == language && locale.country == country }
809+
return entries.find { locale -> locale.language == language && locale.country == country }
810810
}
811811
}
812812
}

src/commonMain/kotlin/com.adamratzman.spotify/utils/Utils.kt

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ internal suspend inline fun <T> catch(catchInternalServerError: Boolean = false,
2222
function()
2323
} catch (e: SpotifyException.BadRequestException) {
2424
if (e.statusCode !in listOf(400, 404)) throw e
25+
else if (e.statusCode in 500..599 && catchInternalServerError) throw e
26+
2527
// we should only ignore the exception if it's 400 or 404. Otherwise, it's a larger issue
2628
null
2729
}

src/commonTest/kotlin/com.adamratzman/spotify/AbstractTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ abstract class AbstractTest<T : GenericSpotifyApi> {
3737
return if (apiInitialized) {
3838
true
3939
} else {
40-
println("Api is not initialized. buildSpotifyApi returns ${buildSpotifyApi(testClassQualifiedName, "n/a")}")
40+
println("Api is not initialized or does not match the expected type. buildSpotifyApi returns ${buildSpotifyApi(testClassQualifiedName, "n/a")}")
4141
false
4242
}
4343
}

src/commonTest/kotlin/com.adamratzman/spotify/priv/ClientEpisodeApiTest.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ class ClientEpisodeApiTest : AbstractTest<SpotifyClientApi>() {
3030
buildApi<SpotifyClientApi>(::testGetEpisodes.name)
3131
if (!isApiInitialized()) return@runTestOnDefaultDispatcher
3232

33-
assertEquals(listOf(null, null), api.episodes.getEpisodes("hi", "dad"))
34-
assertEquals(null, api.episodes.getEpisodes("1cfOhXP4GQCd5ZFHoSF8gg", "j")[1])
33+
assertFailsWith<BadRequestException> { api.episodes.getEpisodes("hi", "dad") }
34+
assertFailsWith<BadRequestException> { api.episodes.getEpisodes("1cfOhXP4GQCd5ZFHoSF8gg", "j")[1] }
35+
3536
assertEquals(
3637
listOf("The Great Inflation (Classic)"),
3738
api.episodes.getEpisodes("3lMZTE81Pbrp0U12WZe27l").map { it?.name }

src/commonTest/kotlin/com.adamratzman/spotify/priv/ClientPlaylistApiTest.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ class ClientPlaylistApiTest : AbstractTest<SpotifyClientApi>() {
8585

8686
api.spotifyApiOptions.allowBulkRequests = true
8787

88-
suspend fun calculatePlaylistSize() = api.playlists.getClientPlaylist(createdPlaylist!!.id)!!.tracks.total
89-
val sizeBefore = calculatePlaylistSize()
88+
suspend fun calculatePlaylistSize(): Int? = api.playlists.getClientPlaylist(createdPlaylist!!.id)!!.tracks.total
89+
val sizeBefore = calculatePlaylistSize() ?: 0
9090
api.playlists.addPlayablesToClientPlaylist(createdPlaylist!!.id, playables = tracks.toTypedArray())
9191
assertEquals(sizeBefore + tracks.size, calculatePlaylistSize())
9292
api.playlists.removePlayablesFromClientPlaylist(createdPlaylist!!.id, playables = tracks.toTypedArray())
@@ -98,6 +98,7 @@ class ClientPlaylistApiTest : AbstractTest<SpotifyClientApi>() {
9898
}
9999

100100
@Test
101+
@Ignore // ignored because Spotify currently broke the ability to change `public` field
101102
fun testEditPlaylists(): TestResult = runTestOnDefaultDispatcher {
102103
buildApi<SpotifyClientApi>(::testEditPlaylists.name)
103104
if (!isApiInitialized()) return@runTestOnDefaultDispatcher
@@ -130,7 +131,7 @@ class ClientPlaylistApiTest : AbstractTest<SpotifyClientApi>() {
130131
assertTrue(updatedPlaylist.public == false)
131132
assertEquals("test playlist", updatedPlaylist.name)
132133
//assertEquals("description 2", fullPlaylist.description) <-- spotify is flaky about actually having description set
133-
assertTrue(updatedPlaylist.tracks.total == 2 && updatedPlaylist.images.isNotEmpty())
134+
assertTrue(updatedPlaylist.tracks.total == 2 && updatedPlaylist.images?.isNotEmpty() == true)
134135

135136
api.playlists.reorderClientPlaylistPlayables(updatedPlaylist.id, 1, insertionPoint = 0)
136137

src/commonTest/kotlin/com.adamratzman/spotify/pub/SearchApiTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class SearchApiTest : AbstractTest<GenericSpotifyApi>() {
2323
fun testSearchMultiple(): TestResult = runTestOnDefaultDispatcher {
2424
buildApi(::testSearchMultiple.name)
2525

26-
val query = api.search.search("lo", *SearchApi.SearchType.values(), market = Market.US)
26+
val query = api.search.search("lo", *SearchApi.SearchType.entries.toTypedArray(), market = Market.US)
2727
assertTrue(
2828
query.albums?.items?.isNotEmpty() == true && query.tracks?.items?.isNotEmpty() == true && query.artists?.items?.isNotEmpty() == true &&
2929
query.playlists?.items?.isNotEmpty() == true && query.shows?.items?.isNotEmpty() == true && query.episodes?.items?.isNotEmpty() == true

src/jsMain/kotlin/com/adamratzman/spotify/webplayer/WebPlaybackSdk.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import kotlin.js.Promise
1313
public external interface Album {
1414
public var uri: String
1515
public var name: String
16-
public var images: Array<Image>
16+
public var images: Array<Image>?
1717
}
1818

1919
public external interface Artist {

src/jvmTest/kotlin/com/adamratzman/spotify/PkceTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class PkceTest {
2727

2828
println(
2929
getSpotifyPkceAuthorizationUrl(
30-
*SpotifyScope.values(),
30+
*SpotifyScope.entries.toTypedArray(),
3131
clientId = clientId,
3232
redirectUri = serverRedirectUri,
3333
codeChallenge = getSpotifyPkceCodeChallenge(pkceCodeVerifier),

0 commit comments

Comments
 (0)