diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt index 17bddcca..e99bcce6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt @@ -15,7 +15,9 @@ package com.keylesspalace.tusky.entity +import com.google.gson.annotations.JsonAdapter import com.google.gson.annotations.SerializedName +import com.keylesspalace.tusky.json.GuardedBooleanAdapter data class Relationship( val id: String, @@ -26,7 +28,11 @@ data class Relationship( @SerializedName("muting_notifications") val mutingNotifications: Boolean, val requested: Boolean, @SerializedName("showing_reblogs") val showingReblogs: Boolean, - val subscribing: Boolean? = null, // Pleroma extension + /* Pleroma extension, same as 'notifying' on Mastodon. + * Some instances like qoto.org have a custom subscription feature where 'subscribing' is a json object, + * so we use the custom GuardedBooleanAdapter to ignore the field if it is not a boolean. + */ + @JsonAdapter(GuardedBooleanAdapter::class) val subscribing: Boolean? = null, @SerializedName("domain_blocking") val blockingDomain: Boolean, val note: String?, // nullable for backward compatibility / feature detection val notifying: Boolean? // since 3.3.0rc diff --git a/app/src/main/java/com/keylesspalace/tusky/json/GuardedBooleanAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/json/GuardedBooleanAdapter.kt new file mode 100644 index 00000000..8ed702a8 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/json/GuardedBooleanAdapter.kt @@ -0,0 +1,33 @@ +/* Copyright 2022 Tusky Contributors + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.json + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import java.lang.reflect.Type + +class GuardedBooleanAdapter : JsonDeserializer { + @Throws(JsonParseException::class) + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Boolean? { + return if (json.isJsonObject) { + null + } else { + json.asBoolean + } + } +} diff --git a/app/src/test/java/com/keylesspalace/tusky/json/GuardedBooleanAdapterTest.kt b/app/src/test/java/com/keylesspalace/tusky/json/GuardedBooleanAdapterTest.kt new file mode 100644 index 00000000..15e05d53 --- /dev/null +++ b/app/src/test/java/com/keylesspalace/tusky/json/GuardedBooleanAdapterTest.kt @@ -0,0 +1,130 @@ +package com.keylesspalace.tusky.json + +import com.google.gson.Gson +import com.keylesspalace.tusky.entity.Relationship +import org.junit.Assert.assertEquals +import org.junit.Test + +class GuardedBooleanAdapterTest { + + private val gson = Gson() + + @Test + fun `should deserialize Relationship when attribute 'subscribing' is a boolean`() { + val jsonInput = """ + { + "id": "1", + "following": true, + "showing_reblogs": true, + "notifying": false, + "followed_by": true, + "blocking": false, + "blocked_by": false, + "muting": false, + "muting_notifications": false, + "requested": false, + "domain_blocking": false, + "endorsed": false, + "note": "Hi", + "subscribing": true + } + """.trimIndent() + + assertEquals( + Relationship( + id = "1", + following = true, + followedBy = true, + blocking = false, + muting = false, + mutingNotifications = false, + requested = false, + showingReblogs = true, + subscribing = true, + blockingDomain = false, + note = "Hi", + notifying = false + ), + gson.fromJson(jsonInput, Relationship::class.java) + ) + } + + @Test + fun `should deserialize Relationship when attribute 'subscribing' is an object`() { + val jsonInput = """ + { + "id": "2", + "following": true, + "showing_reblogs": true, + "notifying": false, + "followed_by": true, + "blocking": false, + "blocked_by": false, + "muting": false, + "muting_notifications": false, + "requested": false, + "domain_blocking": false, + "endorsed": false, + "note": "Hi", + "subscribing": { } + } + """.trimIndent() + + assertEquals( + Relationship( + id = "2", + following = true, + followedBy = true, + blocking = false, + muting = false, + mutingNotifications = false, + requested = false, + showingReblogs = true, + subscribing = null, + blockingDomain = false, + note = "Hi", + notifying = false + ), + gson.fromJson(jsonInput, Relationship::class.java) + ) + } + + @Test + fun `should deserialize Relationship when attribute 'subscribing' does not exist`() { + val jsonInput = """ + { + "id": "3", + "following": true, + "showing_reblogs": true, + "notifying": false, + "followed_by": true, + "blocking": false, + "blocked_by": false, + "muting": false, + "muting_notifications": false, + "requested": false, + "domain_blocking": false, + "endorsed": false, + "note": "Hi" + } + """.trimIndent() + + assertEquals( + Relationship( + id = "3", + following = true, + followedBy = true, + blocking = false, + muting = false, + mutingNotifications = false, + requested = false, + showingReblogs = true, + subscribing = null, + blockingDomain = false, + note = "Hi", + notifying = false + ), + gson.fromJson(jsonInput, Relationship::class.java) + ) + } +}