Skip to content

Commit 511b8ba

Browse files
committed
serialization poc
Signed-off-by: Adam Ratzman <adam@adamratzman.com>
1 parent 55eb953 commit 511b8ba

File tree

8 files changed

+222
-106
lines changed

8 files changed

+222
-106
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@ dependencies {
3737
}
3838
```
3939

40+
#### Android
41+
This library should work out of the box on Android too.
42+
43+
If you're using Proguard, please add this to `proguard-rules.pro`:
44+
```
45+
-keepattributes *Annotation*, InnerClasses
46+
-dontnote kotlinx.serialization.SerializationKt
47+
-keep,includedescriptorclasses class com.yourcompany.yourpackage.**$$serializer { *; } # <-- change package name to your app's
48+
-keepclassmembers class com.yourcompany.yourpackage.** { # <-- change package name to your app's
49+
*** Companion;
50+
}
51+
-keepclasseswithmembers class com.yourcompany.yourpackage.** { # <-- change package name to your app's
52+
kotlinx.serialization.KSerializer serializer(...);
53+
}
54+
55+
56+
```
57+
4058
## Creating a SpotifyAPI or SpotifyClientAPI object
4159
In order to use the methods in this library, you must create either a `SpotifyAPI` or `SpotifyClientAPI` object using their respective exposed builders. Client-specific methods are unable to be accessed with the generic SpotifyAPI, rather you must create an instance of the Client API.
4260

build.gradle

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1+
buildscript {
2+
ext.kotlin_version = '1.3.11'
3+
repositories { jcenter() }
4+
5+
dependencies {
6+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
7+
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
8+
}
9+
}
10+
11+
112
plugins {
213
id "com.diffplug.gradle.spotless" version "3.16.0"
314
id "base"
415
id "io.codearte.nexus-staging" version "0.12.0"
516
id "com.bmuschko.nexus" version "2.3.1"
6-
id "org.jetbrains.kotlin.jvm" version "1.3.11"
717
}
818

19+
apply plugin: 'kotlin'
20+
apply plugin: 'kotlinx-serialization'
21+
922
group 'com.adamratzman'
1023
version '2.0.0'
1124

@@ -16,15 +29,18 @@ sourceCompatibility = 1.8
1629
repositories {
1730
mavenCentral()
1831
maven { url "https://dl.bintray.com/spekframework/spek" }
32+
maven { url "https://kotlin.bintray.com/kotlinx" }
1933
}
2034

2135
dependencies {
2236
// Actual library dependencies
2337
implementation(group: 'org.json', name: 'json', version: '20180130')
24-
implementation("com.google.code.gson:gson:2.8.1")
2538

26-
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.11"
27-
compile "org.jetbrains.kotlin:kotlin-reflect:1.3.11"
39+
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
40+
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
41+
42+
43+
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.9.1"
2844

2945
// Spek testing requirements
3046
testImplementation('org.spekframework.spek2:spek-dsl-jvm:2.0.0-rc.1') {
@@ -37,7 +53,7 @@ dependencies {
3753

3854
testImplementation('org.junit.jupiter:junit-jupiter-api:5.2.0')
3955
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.2.0'
40-
testRuntimeOnly "org.jetbrains.kotlin:kotlin-reflect:1.3.11"
56+
testRuntimeOnly "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
4157
}
4258

4359
spotless {

src/main/kotlin/com/adamratzman/spotify/endpoints/client/ClientFollowingAPI.kt

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import com.adamratzman.spotify.utils.UserURI
1616
import com.adamratzman.spotify.utils.encode
1717
import com.adamratzman.spotify.utils.toCursorBasedPagingObject
1818
import com.adamratzman.spotify.utils.toObject
19+
import kotlinx.serialization.internal.ArrayListSerializer
20+
import kotlinx.serialization.internal.BooleanSerializer
1921
import java.util.function.Supplier
2022

2123
/**
@@ -46,7 +48,13 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
4648
* @throws [BadRequestException] if the playlist is not found
4749
*/
4850
fun isFollowingPlaylist(playlistOwner: String, playlistId: String): SpotifyRestAction<Boolean> {
49-
return toAction(Supplier { isFollowingPlaylist(playlistOwner, playlistId, (api as SpotifyClientAPI).userId).complete() })
51+
return toAction(Supplier {
52+
isFollowingPlaylist(
53+
playlistOwner,
54+
playlistId,
55+
(api as SpotifyClientAPI).userId
56+
).complete()
57+
})
5058
}
5159

5260
/**
@@ -58,8 +66,10 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
5866
*/
5967
fun isFollowingUsers(vararg users: String): SpotifyRestAction<List<Boolean>> {
6068
return toAction(Supplier {
61-
get(EndpointBuilder("/me/following/contains").with("type", "user")
62-
.with("ids", users.joinToString(",") { UserURI(it).id.encode() }).toString()).toObject(api, mutableListOf<Boolean>().javaClass).toList()
69+
get(
70+
EndpointBuilder("/me/following/contains").with("type", "user")
71+
.with("ids", users.joinToString(",") { UserURI(it).id.encode() }).toString()
72+
).toObject<List<Boolean>>(api, ArrayListSerializer(BooleanSerializer)).toList()
6373
})
6474
}
6575

@@ -85,8 +95,10 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
8595
*/
8696
fun isFollowingArtists(vararg artists: String): SpotifyRestAction<List<Boolean>> {
8797
return toAction(Supplier {
88-
get(EndpointBuilder("/me/following/contains").with("type", "artist")
89-
.with("ids", artists.joinToString(",") { ArtistURI(it).id.encode() }).toString()).toObject(api, mutableListOf<Boolean>().javaClass).toList()
98+
get(
99+
EndpointBuilder("/me/following/contains").with("type", "artist")
100+
.with("ids", artists.joinToString(",") { ArtistURI(it).id.encode() }).toString()
101+
).toObject<List<Boolean>>(api, ArrayListSerializer(BooleanSerializer)).toList()
90102
})
91103
}
92104

@@ -96,14 +108,23 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
96108
* @return [CursorBasedPagingObject] ([Information about them](https://github.com/adamint/spotify-web-api-kotlin/blob/master/README.md#the-benefits-of-linkedresults-pagingobjects-and-cursor-based-paging-objects)
97109
* with full [Artist] objects
98110
*/
99-
fun getFollowedArtists(limit: Int? = null, after: String? = null): SpotifyRestPagingAction<Artist, CursorBasedPagingObject<Artist>> {
111+
fun getFollowedArtists(
112+
limit: Int? = null,
113+
after: String? = null
114+
): SpotifyRestPagingAction<Artist, CursorBasedPagingObject<Artist>> {
100115
return toPagingObjectAction(Supplier {
101-
get(EndpointBuilder("/me/following").with("type", "artist").with("limit", limit).with("after", after).toString())
102-
.toCursorBasedPagingObject("artists", this, Artist::class.java)
116+
get(
117+
EndpointBuilder("/me/following").with("type", "artist").with("limit", limit).with(
118+
"after",
119+
after
120+
).toString()
121+
)
122+
.toCursorBasedPagingObject("artists", this, Artist::class.java)
103123
})
104124
}
105125

106-
fun getFollowedUsers(): SpotifyRestAction<List<SpotifyPublicUser>> = throw NotImplementedError("Though Spotify will implement this in the future, it is not currently supported.")
126+
fun getFollowedUsers(): SpotifyRestAction<List<SpotifyPublicUser>> =
127+
throw NotImplementedError("Though Spotify will implement this in the future, it is not currently supported.")
107128

108129
/**
109130
* Add the current user as a follower of another user
@@ -123,8 +144,10 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
123144
*/
124145
fun followUsers(vararg users: String): SpotifyRestAction<Unit> {
125146
return toAction(Supplier {
126-
put(EndpointBuilder("/me/following").with("type", "user")
127-
.with("ids", users.joinToString(",") { UserURI(it).id.encode() }).toString())
147+
put(
148+
EndpointBuilder("/me/following").with("type", "user")
149+
.with("ids", users.joinToString(",") { UserURI(it).id.encode() }).toString()
150+
)
128151
Unit
129152
})
130153
}
@@ -147,8 +170,10 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
147170
*/
148171
fun followArtists(vararg artists: String): SpotifyRestAction<Unit> {
149172
return toAction(Supplier {
150-
put(EndpointBuilder("/me/following").with("type", "artist")
151-
.with("ids", artists.joinToString(",") { ArtistURI(it).id.encode() }).toString())
173+
put(
174+
EndpointBuilder("/me/following").with("type", "artist")
175+
.with("ids", artists.joinToString(",") { ArtistURI(it).id.encode() }).toString()
176+
)
152177
Unit
153178
})
154179
}
@@ -165,7 +190,10 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
165190
*/
166191
fun followPlaylist(playlist: String, followPublicly: Boolean = true): SpotifyRestAction<Unit> {
167192
return toAction(Supplier {
168-
put(EndpointBuilder("/playlists/${PlaylistURI(playlist).id}/followers").toString(), "{\"public\": $followPublicly}")
193+
put(
194+
EndpointBuilder("/playlists/${PlaylistURI(playlist).id}/followers").toString(),
195+
"{\"public\": $followPublicly}"
196+
)
169197
Unit
170198
})
171199
}
@@ -192,8 +220,10 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
192220
*/
193221
fun unfollowUsers(vararg users: String): SpotifyRestAction<Unit> {
194222
return toAction(Supplier {
195-
delete(EndpointBuilder("/me/following").with("type", "user")
196-
.with("ids", users.joinToString(",") { UserURI(it).id.encode() }).toString())
223+
delete(
224+
EndpointBuilder("/me/following").with("type", "user")
225+
.with("ids", users.joinToString(",") { UserURI(it).id.encode() }).toString()
226+
)
197227
Unit
198228
})
199229
}
@@ -220,8 +250,10 @@ class ClientFollowingAPI(api: SpotifyAPI) : FollowingAPI(api) {
220250
*/
221251
fun unfollowArtists(vararg artists: String): SpotifyRestAction<Unit> {
222252
return toAction(Supplier {
223-
delete(EndpointBuilder("/me/following").with("type", "artist")
224-
.with("ids", artists.joinToString(",") { ArtistURI(it).id.encode() }).toString())
253+
delete(
254+
EndpointBuilder("/me/following").with("type", "artist")
255+
.with("ids", artists.joinToString(",") { ArtistURI(it).id.encode() }).toString()
256+
)
225257
Unit
226258
})
227259
}

src/main/kotlin/com/adamratzman/spotify/main/SpotifyAPI.kt

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ import com.adamratzman.spotify.utils.SpotifyEndpoint
2222
import com.adamratzman.spotify.utils.Token
2323
import com.adamratzman.spotify.utils.byteEncode
2424
import com.adamratzman.spotify.utils.toObject
25-
import com.google.gson.Gson
26-
import com.google.gson.GsonBuilder
2725
import java.util.concurrent.Executors
2826
import java.util.concurrent.TimeUnit
2927

@@ -213,7 +211,7 @@ class SpotifyApiBuilder {
213211
"Authorization",
214212
"Basic ${"$clientId:$clientSecret".byteEncode()}"
215213
)
216-
).body.toObject(Gson(), Token::class.java),
214+
).body.toObject(null, Token),
217215
automaticRefresh,
218216
redirectUri ?: throw IllegalArgumentException(),
219217
useCache
@@ -250,7 +248,6 @@ abstract class SpotifyAPI internal constructor(
250248
) {
251249
internal var expireTime = System.currentTimeMillis() + token.expires_in * 1000
252250
internal val executor = Executors.newScheduledThreadPool(2)
253-
internal val gson = GsonBuilder().setLenient().create()!!
254251

255252
abstract val search: SearchAPI
256253
abstract val albums: AlbumAPI
@@ -369,15 +366,14 @@ class SpotifyClientAPI internal constructor(
369366
fun cancelAutomatics() = executor.shutdown()
370367

371368
override fun refreshToken() {
372-
val tempToken = gson.fromJson(
369+
val tempToken =
373370
HttpConnection(
374371
url = "https://accounts.spotify.com/api/token",
375372
method = HttpRequestMethod.POST,
376373
body = "grant_type=refresh_token&refresh_token=${token.refresh_token ?: ""}",
377374
contentType = "application/x-www-form-urlencoded"
378-
).execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}")).body,
379-
Token::class.java
380-
)
375+
).execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}")).body
376+
.toObject<Token?>(null, Token)
381377
if (tempToken == null) {
382378
logger.logWarning("Spotify token refresh failed")
383379
} else {
@@ -412,12 +408,11 @@ private fun getAuthUrlFull(vararg scopes: SpotifyScope, clientId: String, redire
412408
if (scopes.isEmpty()) "" else "&scope=${scopes.joinToString("%20") { it.uri }}"
413409
}
414410

415-
private fun getCredentialedToken(clientId: String, clientSecret: String) = Gson().fromJson(
411+
private fun getCredentialedToken(clientId: String, clientSecret: String) =
416412
HttpConnection(
417413
url = "https://accounts.spotify.com/api/token",
418414
method = HttpRequestMethod.POST,
419415
body = "grant_type=client_credentials",
420416
contentType = "application/x-www-form-urlencoded"
421-
).execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}")).body,
422-
Token::class.java
423-
)
417+
).execute(HttpHeader("Authorization", "Basic ${"$clientId:$clientSecret".byteEncode()}")).body
418+
.toObject<Token>(null, Token)

src/main/kotlin/com/adamratzman/spotify/utils/AuthObjects.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
package com.adamratzman.spotify.utils
33

44
import com.adamratzman.spotify.main.SpotifyScope
5+
import kotlinx.serialization.Serializable
56

7+
@Serializable
68
data class Token(val access_token: String, val token_type: String, val expires_in: Int, val refresh_token: String?, val scope: String?) {
79
fun getScopes(): List<SpotifyScope> {
810
val scopes = mutableListOf<SpotifyScope>()
@@ -13,6 +15,7 @@ data class Token(val access_token: String, val token_type: String, val expires_i
1315
}
1416
}
1517

18+
@Serializable
1619
data class ErrorResponse(val error: ErrorObject)
1720

1821
data class ErrorObject(val status: Int, val message: String)

src/main/kotlin/com/adamratzman/spotify/utils/Endpoints.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.adamratzman.spotify.main.SpotifyAppAPI
66
import com.adamratzman.spotify.main.SpotifyRestAction
77
import com.adamratzman.spotify.main.SpotifyRestPagingAction
88
import com.adamratzman.spotify.main.base
9-
import com.google.gson.JsonParseException
109
import java.net.HttpURLConnection
1110
import java.util.function.Supplier
1211

@@ -91,9 +90,9 @@ abstract class SpotifyEndpoint(val api: SpotifyAPI) {
9190

9291
if (document.responseCode / 200 != 1 /* Check if status is 2xx */) {
9392
val message = try {
94-
api.gson.fromJson(responseBody, ErrorResponse::class.java).error
95-
} catch (e: JsonParseException) {
96-
ErrorObject(400, "malformed request (likely spaces)")
93+
document.body.toObject<ErrorResponse>(api, ErrorResponse).error
94+
} catch (e: Exception) {
95+
ErrorObject(400, "malformed request sent")
9796
}
9897
throw BadRequestException(message)
9998
} else if (document.responseCode == 202 && retry202) return null

0 commit comments

Comments
 (0)