Skip to content

Commit 27a3ade

Browse files
committed
Merge branch 'dev'
2 parents 7d74f7d + 5800782 commit 27a3ade

File tree

9 files changed

+115
-77
lines changed

9 files changed

+115
-77
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
.DS_STORE
23
# Created by https://www.toptal.com/developers/gitignore/api/gradle,kotlin,android,intellij+all,node
34
# Edit at https://www.toptal.com/developers/gitignore?templates=gradle,kotlin,android,intellij+all,node
45

@@ -333,3 +334,4 @@ gradle-app.setting
333334
**/build/
334335

335336
# End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin,android,intellij+all,node
337+
/docs/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ repositories {
3737
jcenter()
3838
}
3939
40-
implementation("com.adamratzman:spotify-api-kotlin-core:3.3.01")
40+
implementation("com.adamratzman:spotify-api-kotlin-core:3.3.03")
4141
```
4242

4343
Note that images and profiles are not supported on the Kotlin/JS target.

build.gradle.kts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ buildscript {
3232
}
3333

3434
group = "com.adamratzman"
35-
version = "3.3.01"
35+
version = "3.3.03"
3636

3737
tasks.withType<Test> {
3838
this.testLogging {
@@ -64,10 +64,6 @@ android {
6464
}
6565
testOptions {
6666
this.unitTests.isReturnDefaultValues = true
67-
@Suppress("UNCHECKED_CAST")
68-
this.unitTests.all(closureOf<Test> {
69-
// this.useJUnitPlatform()
70-
} as groovy.lang.Closure<Test>)
7167
}
7268
sourceSets {
7369
getByName("main") {
@@ -300,12 +296,10 @@ tasks {
300296
}
301297

302298

303-
val publishJvm by registering(Task::class) {
299+
val publishAllPublicationsToNexusRepositoryWithTests by registering(Task::class) {
304300
dependsOn.add(check)
305-
dependsOn.add(dokkaHtml)
306-
dependsOn.add("publishJvmPublicationToNexusRepository")
301+
dependsOn.add("publishAllPublicationsToNexusRepository")
307302
}
308-
309303
}
310304

311305

src/commonMain/kotlin/com.adamratzman.spotify/endpoints/public/BrowseApi.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.adamratzman.spotify.utils.Locale
2424
import com.adamratzman.spotify.utils.Market
2525
import com.adamratzman.spotify.utils.formatDate
2626
import kotlin.reflect.KClass
27+
import kotlinx.serialization.Serializable
2728
import kotlinx.serialization.builtins.ListSerializer
2829
import kotlinx.serialization.builtins.serializer
2930

@@ -311,14 +312,19 @@ public class BrowseApi(api: GenericSpotifyApi) : SpotifyEndpoint(api) {
311312
*
312313
* **[Api Reference](https://developer.spotify.com/documentation/web-api/reference/browse/get-recommendations/)**
313314
*
314-
* @param attribute The spotify id for the track attribute
315+
* @param attribute The spotify id for the track attribute.
316+
* @param integerOnly Whether this attribute can only take integers.
317+
* @param min The minimum value allowed for this attribute.
318+
* @param max The maximum value allowed for this attribute.
319+
* @param typeClass The type, a subclass of [Number], one of [Float] or [Int] that corresponds to this attribute.
315320
*/
321+
@Serializable
316322
public sealed class TuneableTrackAttribute<T : Number>(
317323
public val attribute: String,
318324
public val integerOnly: Boolean,
319325
public val min: T?,
320326
public val max: T?,
321-
private val tClazz: KClass<T>
327+
public val typeClass: KClass<T>
322328
) {
323329
/**
324330
* A confidence measure from 0.0 to 1.0 of whether the track is acoustic.
@@ -429,7 +435,7 @@ public sealed class TuneableTrackAttribute<T : Number>(
429435
require(!(max != null && max.toDouble() < value.toDouble())) { "Attribute value for $this must be less than $max!" }
430436

431437
@Suppress("UNCHECKED_CAST")
432-
return TrackAttribute(this, when (tClazz) {
438+
return TrackAttribute(this, when (typeClass) {
433439
Int::class -> value.toInt() as T
434440
Float::class -> value.toFloat() as T
435441
Double::class -> value.toDouble() as T
@@ -459,7 +465,11 @@ public sealed class TuneableTrackAttribute<T : Number>(
459465

460466
/**
461467
* The track attribute wrapper contains a set value for a specific [TuneableTrackAttribute]
468+
*
469+
* @param tuneableTrackAttribute The [TuneableTrackAttribute] that this [TrackAttribute] will correspond to.
470+
* @param value The value of the [tuneableTrackAttribute].
462471
*/
472+
@Serializable
463473
public data class TrackAttribute<T : Number>(val tuneableTrackAttribute: TuneableTrackAttribute<T>, val value: T) {
464474
public companion object {
465475
public fun <T : Number> create(tuneableTrackAttribute: TuneableTrackAttribute<T>, value: T): TrackAttribute<T> =

src/commonMain/kotlin/com.adamratzman.spotify/http/Endpoints.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import com.adamratzman.spotify.utils.getCurrentTimeMs
1414
import kotlin.math.ceil
1515
import kotlinx.coroutines.TimeoutCancellationException
1616
import kotlinx.coroutines.withTimeout
17+
import kotlinx.serialization.Serializable
18+
import kotlinx.serialization.Transient
1719

1820
public abstract class SpotifyEndpoint(public val api: GenericSpotifyApi) {
1921
public val cache: SpotifyCache = SpotifyCache()
@@ -225,7 +227,9 @@ public data class SpotifyRequest(
225227
val api: GenericSpotifyApi
226228
)
227229

230+
@Serializable
228231
public data class CacheState(val data: String, val eTag: String?, val expireBy: Long = 0) {
232+
@Transient
229233
private val cacheRegex = "max-age=(\\d+)".toRegex()
230234
internal fun isStillValid(): Boolean = getCurrentTimeMs() <= this.expireBy
231235

src/commonMain/kotlin/com.adamratzman.spotify/http/HttpConnection.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import io.ktor.http.content.ByteArrayContent
2424
import io.ktor.utils.io.core.toByteArray
2525
import kotlinx.coroutines.CancellationException
2626
import kotlinx.coroutines.delay
27+
import kotlinx.serialization.Serializable
2728

2829
public enum class HttpRequestMethod(internal val externalMethod: HttpMethod) {
2930
GET(HttpMethod.Get),
@@ -32,8 +33,10 @@ public enum class HttpRequestMethod(internal val externalMethod: HttpMethod) {
3233
DELETE(HttpMethod.Delete);
3334
}
3435

36+
@Serializable
3537
public data class HttpHeader(val key: String, val value: String)
3638

39+
@Serializable
3740
public data class HttpResponse(val responseCode: Int, val body: String, val headers: List<HttpHeader>)
3841

3942
/**

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public data class SimpleAlbum(
7171
public suspend fun toFullAlbum(market: Market? = null): Album? = api.albums.getAlbum(id, market)
7272
}
7373

74+
@Serializable
7475
public data class ReleaseDate(val year: Int, val month: Int?, val day: Int?)
7576

7677
/**

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.adamratzman.spotify.SpotifyScope
66
import com.adamratzman.spotify.utils.getCurrentTimeMs
77
import kotlinx.serialization.SerialName
88
import kotlinx.serialization.Serializable
9+
import kotlinx.serialization.Transient
910

1011
/**
1112
* Represents a Spotify Token, retrieved through instantiating a [SpotifyApi]
@@ -42,4 +43,8 @@ public data class Token(
4243
}
4344
}
4445

45-
public data class TokenValidityResponse(val isValid: Boolean, val exception: Exception?)
46+
@Serializable
47+
public data class TokenValidityResponse(
48+
val isValid: Boolean,
49+
@Transient val exception: Exception? = null
50+
)

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

Lines changed: 82 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import com.adamratzman.spotify.utils.Locale
1111
import com.adamratzman.spotify.utils.Market
1212
import com.adamratzman.spotify.utils.getCurrentTimeMs
1313
import kotlin.test.Test
14+
import kotlin.test.assertEquals
15+
import kotlin.test.assertNotEquals
1416
import kotlin.test.assertNotNull
1517
import kotlin.test.assertNotSame
1618
import kotlin.test.assertTrue
@@ -37,8 +39,8 @@ class BrowseApiTest {
3739
if (!testPrereq()) return@runBlockingTest
3840

3941
assertNotSame(
40-
api.browse.getCategoryList(locale = Locale.ar_AE).items[0],
41-
api.browse.getCategoryList().items[0]
42+
api.browse.getCategoryList(locale = Locale.ar_AE).items[0],
43+
api.browse.getCategoryList().items[0]
4244
)
4345
assertTrue(api.browse.getCategoryList(4, 3, market = Market.CA).items.isNotEmpty())
4446
assertTrue(api.browse.getCategoryList(4, 3, locale = Locale.fr_FR, market = Market.CA).items.isNotEmpty())
@@ -62,7 +64,12 @@ class BrowseApiTest {
6264
runBlockingTest {
6365
if (!testPrereq()) return@runBlockingTest
6466

65-
assertFailsWithSuspend<SpotifyException.BadRequestException> { api.browse.getPlaylistsForCategory("no u", limit = 4) }
67+
assertFailsWithSuspend<SpotifyException.BadRequestException> {
68+
api.browse.getPlaylistsForCategory(
69+
"no u",
70+
limit = 4
71+
)
72+
}
6673
assertTrue(api.browse.getPlaylistsForCategory("pop", 10, 0, Market.FR).items.isNotEmpty())
6774
}
6875
}
@@ -73,12 +80,12 @@ class BrowseApiTest {
7380
if (!testPrereq()) return@runBlockingTest
7481

7582
assertTrue(
76-
api.browse.getFeaturedPlaylists(
77-
5,
78-
4,
79-
market = Market.US,
80-
timestamp = getCurrentTimeMs() - 10000000
81-
).playlists.total > 0
83+
api.browse.getFeaturedPlaylists(
84+
5,
85+
4,
86+
market = Market.US,
87+
timestamp = getCurrentTimeMs() - 10000000
88+
).playlists.total > 0
8289
)
8390
assertTrue(api.browse.getFeaturedPlaylists(offset = 32).playlists.total > 0)
8491
}
@@ -107,102 +114,114 @@ class BrowseApiTest {
107114
}
108115
assertTrue(api.browse.getTrackRecommendations(seedArtists = listOf("2C2sVVXanbOpymYBMpsi89")).tracks.isNotEmpty())
109116
assertTrue(
110-
api.browse.getTrackRecommendations(
111-
seedArtists = listOf(
112-
"2C2sVVXanbOpymYBMpsi89",
113-
"7lMgpN1tEBQKpRoUMKB8iw"
114-
)
115-
).tracks.isNotEmpty()
117+
api.browse.getTrackRecommendations(
118+
seedArtists = listOf(
119+
"2C2sVVXanbOpymYBMpsi89",
120+
"7lMgpN1tEBQKpRoUMKB8iw"
121+
)
122+
).tracks.isNotEmpty()
116123
)
117124

118125
assertFailsWithSuspend<SpotifyException.BadRequestException> {
119126
api.browse.getTrackRecommendations(seedTracks = listOf("abc"))
120127
}
121128
assertTrue(api.browse.getTrackRecommendations(seedTracks = listOf("3Uyt0WO3wOopnUBCe9BaXl")).tracks.isNotEmpty())
122129
assertTrue(
123-
api.browse.getTrackRecommendations(
124-
seedTracks = listOf(
125-
"6d9iYQG2JvTTEgcndW81lt",
126-
"3Uyt0WO3wOopnUBCe9BaXl"
127-
)
128-
).tracks.isNotEmpty()
130+
api.browse.getTrackRecommendations(
131+
seedTracks = listOf(
132+
"6d9iYQG2JvTTEgcndW81lt",
133+
"3Uyt0WO3wOopnUBCe9BaXl"
134+
)
135+
).tracks.isNotEmpty()
129136
)
130137

131138
api.browse.getTrackRecommendations(seedGenres = listOf("abc"))
132139
assertTrue(api.browse.getTrackRecommendations(seedGenres = listOf("pop")).tracks.isNotEmpty())
133140
assertTrue(
134-
api.browse.getTrackRecommendations(
135-
seedGenres = listOf(
136-
"pop",
137-
"latinx"
138-
)
139-
).tracks.isNotEmpty()
141+
api.browse.getTrackRecommendations(
142+
seedGenres = listOf(
143+
"pop",
144+
"latinx"
145+
)
146+
).tracks.isNotEmpty()
140147
)
141148

142149
api.browse.getTrackRecommendations(
143-
seedArtists = listOf("2C2sVVXanbOpymYBMpsi89"),
144-
seedTracks = listOf("6d9iYQG2JvTTEgcndW81lt", "3Uyt0WO3wOopnUBCe9BaXl"),
145-
seedGenres = listOf("pop")
150+
seedArtists = listOf("2C2sVVXanbOpymYBMpsi89"),
151+
seedTracks = listOf("6d9iYQG2JvTTEgcndW81lt", "3Uyt0WO3wOopnUBCe9BaXl"),
152+
seedGenres = listOf("pop")
146153
)
147154

148155
assertFailsWithSuspend<IllegalArgumentException> {
149156
api.browse.getTrackRecommendations(
150-
targetAttributes = listOf(
151-
TuneableTrackAttribute.Acousticness.asTrackAttribute(
152-
3f
153-
)
157+
targetAttributes = listOf(
158+
TuneableTrackAttribute.Acousticness.asTrackAttribute(
159+
3f
154160
)
161+
)
155162
)
156163
}
157164
assertTrue(
158-
api.browse.getTrackRecommendations(
159-
targetAttributes = listOf(
160-
TuneableTrackAttribute.Acousticness.asTrackAttribute(1f),
161-
TuneableTrackAttribute.Danceability.asTrackAttribute(0.5f)
162-
),
163-
seedGenres = listOf("pop")
164-
).tracks.isNotEmpty()
165+
api.browse.getTrackRecommendations(
166+
targetAttributes = listOf(
167+
TuneableTrackAttribute.Acousticness.asTrackAttribute(1f),
168+
TuneableTrackAttribute.Danceability.asTrackAttribute(0.5f)
169+
),
170+
seedGenres = listOf("pop")
171+
).tracks.isNotEmpty()
165172
)
166173

167174
assertFailsWithSuspend<IllegalArgumentException> {
168175
api.browse.getTrackRecommendations(
169-
minAttributes = listOf(
170-
TuneableTrackAttribute.Acousticness.asTrackAttribute(
171-
3f
172-
)
176+
minAttributes = listOf(
177+
TuneableTrackAttribute.Acousticness.asTrackAttribute(
178+
3f
173179
)
180+
)
174181
)
175182
}
176183
assertTrue(
177-
api.browse.getTrackRecommendations(
178-
minAttributes = listOf(
179-
TuneableTrackAttribute.Acousticness.asTrackAttribute(0.5f),
180-
TuneableTrackAttribute.Danceability.asTrackAttribute(0.5f)
181-
),
182-
seedGenres = listOf("pop")
183-
).tracks.isNotEmpty()
184+
api.browse.getTrackRecommendations(
185+
minAttributes = listOf(
186+
TuneableTrackAttribute.Acousticness.asTrackAttribute(0.5f),
187+
TuneableTrackAttribute.Danceability.asTrackAttribute(0.5f)
188+
),
189+
seedGenres = listOf("pop")
190+
).tracks.isNotEmpty()
184191
)
185192

186193
assertFailsWithSuspend<SpotifyException.BadRequestException> {
187194
api.browse.getTrackRecommendations(
188-
maxAttributes = listOf(
189-
TuneableTrackAttribute.Speechiness.asTrackAttribute(
190-
0.9f
191-
)
195+
maxAttributes = listOf(
196+
TuneableTrackAttribute.Speechiness.asTrackAttribute(
197+
0.9f
192198
)
199+
)
193200
)
194201
}
195202
assertTrue(
196-
api.browse.getTrackRecommendations(
197-
maxAttributes = listOf(
198-
TuneableTrackAttribute.Acousticness.asTrackAttribute(0.9f),
199-
TuneableTrackAttribute.Danceability.asTrackAttribute(0.9f)
200-
),
201-
seedGenres = listOf("pop")
202-
).tracks.isNotEmpty()
203+
api.browse.getTrackRecommendations(
204+
maxAttributes = listOf(
205+
TuneableTrackAttribute.Acousticness.asTrackAttribute(0.9f),
206+
TuneableTrackAttribute.Danceability.asTrackAttribute(0.9f)
207+
),
208+
seedGenres = listOf("pop")
209+
).tracks.isNotEmpty()
203210
)
204211

205212
assertTrue(TuneableTrackAttribute.values().first().asTrackAttribute(0f).value == 0f)
206213
}
207214
}
215+
216+
@Test
217+
fun testTuneableTrackAttributeTypes() {
218+
val float1: TuneableTrackAttribute<*> = TuneableTrackAttribute.Speechiness
219+
val float2: TuneableTrackAttribute<*> = TuneableTrackAttribute.Acousticness
220+
val int1: TuneableTrackAttribute<*> = TuneableTrackAttribute.Key
221+
val int2: TuneableTrackAttribute<*> = TuneableTrackAttribute.Popularity
222+
223+
assertEquals(float1.typeClass, float2.typeClass)
224+
assertEquals(int1.typeClass, int2.typeClass)
225+
assertNotEquals(float1.typeClass, int1.typeClass)
226+
}
208227
}

0 commit comments

Comments
 (0)