mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-16 13:48:09 -07:00
Use Apollo GraphQL for Trackers with GraphQL APIs (#3827)
* Add Apollo for GraphQL interactions * Migrate AniList to use Apollo * Migrate Shikimori to use Apollo Not all Shikimori requests can be served by their GraphQL API (notably they do not offer any mutations), so those remain as-is. * Migrate Suwayomi to use Apollo Includes leveraging some slightly newer Suwayomi features, specifically the Double/Float filter fixes first pre-released in 2.1.1985 on 2025-10-26 and first fully released in 2.1.2100 on 2026-05-08. Considering the tachiyomix 1.6 extension lib changes were added in Suwayomi 2.3.2223 on 2026-06-30, I think it is fair to depend on such sorta-recent behaviour. * Fix temp migration names and leftovers * Create ApolloResponse.dataOrElse extension Because Apollo doesn't throw exceptions and instead just provides them as response attributes, the current ApolloResponse.awaitSuccess() is useless and will be removed shortly. This new extension function should wrap the basic handling of an ApolloResponse object neatly. Co-authored-by: Mitchell Syer <6090655+Syer10@users.noreply.github.com> * Use dataOrElse in AnilistApi.kt * Use dataOrElse in ShikimoriApi.kt * Use dataOrElse in SuwayomiApi.kt * Delete obsolete ApolloCall.awaitSuccess * Migrate Kitsu to use Apollo Kitsu has some unfortunate edges in its GraphQL API (such as returning nonstandard bodies which don't play well with Apollo) but this should be an equivalent implementation. * Extract a MangaFragment for AniList This simplifies the queries and the converters (which can now delegate to the MangaFragment.toTrackSearch() method internally and adjust the track as needed). Also move the toALUser converter to AnilistUtils.kt to keep all converters together. Added a scalar-mapper for AL's CountryCode type. * Minor consistency cleanup of the queries * Store list entry ID not title ID for Shikimori * Extract common MangaFragment for Shikimori Also map the ISO8601Date to a Kotlin String in the service config. * Map Kitsu's ISO8601DateTime to kotlin.time.Instant Utilises Apollo's own adapter-core library which provides the mapper. Requires opting in to ExperimentalTime until the library resolves the outdated annotation. Co-authored-by: Mitchell Syer <6090655+Syer10@users.noreply.github.com> * Replace Kitsu TODO comment So much for STOPSHIP preventing me shipping this... * Bump Apollo version to 5.1.0 * Bump apollo-adapters-core to 0.8.0 Freshly released just earlier. This resolves having to opt in to ExperimentalTime. * Make new utils internal to align with #3908 * Fix leftover issues from merged #3828 This PR restructured the AniList and Kitsu user objects. * Bump apollo to 5.2.0 * Remove unused attributes from queries * Allow custom exception handler in dataOrElse Enables callers to manually handle specific non-200 responses. Callers MUST close the `ApolloHttpException.body` attached to the passed exception to avoid resource and socket leaks. Defaults to current behaviour of (re)throwing the status code with our HttpException. Utilized in KitsuApi.kt to specially handle Kitsu sending a 500 error response when deleting library IDs not actually in a user's library and falling back to the "throw as HttpException" if it's any other error response. * Rethrow networking/IO exceptions This helps to avoid masking networking problems as "This isn't in your list" problems. Currently, the app shows "Failed to connect to ..." when the remote server is unreachable (e.g. downtime, DNS block, etc) because that's the message provided by the ConnectException thrown by Android's network stack. This change restores that behaviour, approximately. Not the most elegant solution but it _should_ avoid false-positive toasts. --------- Co-authored-by: Mitchell Syer <6090655+Syer10@users.noreply.github.com>
This commit is contained in:
co-authored by
Mitchell Syer
parent
59fdf7e7fa
commit
ec614bb4c3
@@ -33,6 +33,8 @@ dependencies {
|
||||
api(libs.okhttp.dnsOverHttps)
|
||||
api(libs.okio)
|
||||
|
||||
api(libs.apollo)
|
||||
|
||||
implementation(libs.image.decoder)
|
||||
|
||||
implementation(libs.unifile)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package eu.kanade.tachiyomi.network
|
||||
|
||||
import com.apollographql.apollo.api.ApolloResponse
|
||||
import com.apollographql.apollo.api.Operation
|
||||
import com.apollographql.apollo.exception.ApolloHttpException
|
||||
import com.apollographql.apollo.exception.ApolloNetworkException
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
|
||||
/**
|
||||
* Returns the result of calling [transform] with [data] if the GraphQL response had data present without errors,
|
||||
* otherwise logs the failure under [errorLog] and falls back to [default].
|
||||
*
|
||||
* Any [ApolloHttpException] is re-thrown as [HttpException] after logging the response body.
|
||||
* This behavior can be replaced with a custom handler by providing a non-null value to [onException]. Such a lambda
|
||||
* MUST close the [ApolloHttpException.body] properly to avoid sockets and other resources leaking.
|
||||
*
|
||||
* Other failures are logged and the result of calling [default] is returned.
|
||||
*
|
||||
* @param errorLog A log message which will be included with any exceptions or errors being logged.
|
||||
* @param default A lambda which provides a default value to be returned in place of the expected value when an
|
||||
* exception or error is found.
|
||||
* @param onException A handler for [ApolloHttpException]s. It MUST handle closing the response body to avoid resource
|
||||
* leaks. Defaults to throwing a [HttpException] with the same status code.
|
||||
* @param transform A transforming lambda for the GraphQL data received.
|
||||
*/
|
||||
fun <D : Operation.Data, R> ApolloResponse<D>.dataOrElse(
|
||||
errorLog: String,
|
||||
default: () -> R,
|
||||
onException: ((ApolloHttpException) -> Unit)? = null,
|
||||
transform: (D) -> R,
|
||||
): R {
|
||||
if (exception != null) {
|
||||
val e = exception!!
|
||||
if (e is ApolloHttpException) {
|
||||
val body = e.body?.use { it.readUtf8() }
|
||||
logcat(LogPriority.ERROR, throwable = e) { "$errorLog: $body" }
|
||||
if (onException != null) {
|
||||
onException(e)
|
||||
} else {
|
||||
throw HttpException(e.statusCode).apply { stackTrace = e.stackTrace }
|
||||
}
|
||||
}
|
||||
logcat(LogPriority.ERROR, throwable = e) { errorLog }
|
||||
// Don't mask connection/IO-related issues
|
||||
if (e is ApolloNetworkException && e.cause != null) {
|
||||
throw e.cause!!
|
||||
}
|
||||
} else if (!errors.isNullOrEmpty()) {
|
||||
val errorMessages = errors!!.joinToString(separator = "\n ", prefix = "\n ") { it.message }
|
||||
logcat(LogPriority.ERROR) { "$errorLog: $errorMessages" }
|
||||
} else if (data != null) {
|
||||
return transform(data!!)
|
||||
}
|
||||
return default()
|
||||
}
|
||||
Reference in New Issue
Block a user