Skip to content

Commit 10e422f

Browse files
authored
Merge pull request #392 from Automattic/hamorillo/377-gravatar-exception
Throwing GravatarException on noncatching service methods
2 parents f2a573c + 1976a41 commit 10e422f

File tree

15 files changed

+209
-115
lines changed

15 files changed

+209
-115
lines changed

gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/avatarpicker/AvatarPickerViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ internal class AvatarPickerViewModel(
264264
ErrorType.NotFound,
265265
ErrorType.RateLimitExceeded,
266266
ErrorType.Timeout,
267-
ErrorType.Unknown,
267+
is ErrorType.Unknown,
268268
is ErrorType.InvalidRequest,
269269
-> SectionError.Unknown
270270
}

gravatar-quickeditor/src/test/java/com/gravatar/quickeditor/data/repository/AvatarRepositoryTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,12 @@ class AvatarRepositoryTest {
9797
coEvery { tokenStorage.getToken(any()) } returns "token"
9898
coEvery {
9999
avatarService.setAvatarCatching(any(), any(), any())
100-
} returns GravatarResult.Failure(ErrorType.Unknown)
100+
} returns GravatarResult.Failure(ErrorType.Unknown())
101101

102102
val result = avatarRepository.selectAvatar(email, "avatarId")
103103

104104
assertEquals(
105-
GravatarResult.Failure<String, QuickEditorError>(QuickEditorError.Request(ErrorType.Unknown)),
105+
GravatarResult.Failure<String, QuickEditorError>(QuickEditorError.Request(ErrorType.Unknown())),
106106
result,
107107
)
108108
}

gravatar-quickeditor/src/test/java/com/gravatar/quickeditor/ui/avatarpicker/AvatarPickerViewModelTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class AvatarPickerViewModelTest {
6262

6363
@Before
6464
fun setup() {
65-
coEvery { profileService.retrieveCatching(email) } returns GravatarResult.Failure(ErrorType.Unknown)
65+
coEvery { profileService.retrieveCatching(email) } returns GravatarResult.Failure(ErrorType.Unknown())
6666
coEvery { avatarRepository.getAvatars(email) } returns GravatarResult.Success(emailAvatars)
6767
}
6868

gravatar-quickeditor/src/test/java/com/gravatar/quickeditor/ui/oauth/OAuthViewModelTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class OAuthViewModelTest {
9191
fun `given token when association check failed then UiState_Status updated`() = runTest {
9292
coEvery {
9393
profileService.checkAssociatedEmailCatching(token, email)
94-
} returns GravatarResult.Failure(ErrorType.Unknown)
94+
} returns GravatarResult.Failure(ErrorType.Unknown())
9595

9696
viewModel.uiState.test {
9797
expectMostRecentItem()
@@ -108,7 +108,7 @@ class OAuthViewModelTest {
108108
fun `given token when association check failed then token stored`() = runTest {
109109
coEvery {
110110
profileService.checkAssociatedEmailCatching(token, email)
111-
} returns GravatarResult.Failure(ErrorType.Unknown)
111+
} returns GravatarResult.Failure(ErrorType.Unknown())
112112

113113
viewModel.tokenReceived(
114114
email,

gravatar/api/gravatar.api

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,12 +705,20 @@ public final class com/gravatar/services/ErrorType$Unauthorized : com/gravatar/s
705705
}
706706

707707
public final class com/gravatar/services/ErrorType$Unknown : com/gravatar/services/ErrorType {
708-
public static final field INSTANCE Lcom/gravatar/services/ErrorType$Unknown;
708+
public fun <init> ()V
709+
public fun <init> (Ljava/lang/String;)V
710+
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
709711
public fun equals (Ljava/lang/Object;)Z
712+
public final fun getErrorMsg ()Ljava/lang/String;
710713
public fun hashCode ()I
711714
public fun toString ()Ljava/lang/String;
712715
}
713716

717+
public final class com/gravatar/services/GravatarException : java/lang/RuntimeException {
718+
public final fun getErrorType ()Lcom/gravatar/services/ErrorType;
719+
public final fun getOriginalException ()Ljava/lang/Exception;
720+
}
721+
714722
public abstract interface class com/gravatar/services/GravatarListener {
715723
public abstract fun onError (Ljava/lang/Object;)V
716724
public abstract fun onSuccess (Ljava/lang/Object;)V

gravatar/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ dependencies {
8484
testImplementation(libs.mockk.android)
8585
testImplementation(libs.mockk.agent)
8686
testImplementation(libs.kotlinx.coroutines.test)
87+
testImplementation(kotlin("test"))
8788
}
8889

8990
project.afterEvaluate {

gravatar/src/main/java/com/gravatar/services/AvatarService.kt

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,25 @@ public class AvatarService(private val okHttpClient: OkHttpClient? = null) {
2929
* @param file The image file to upload
3030
* @param oauthToken The OAuth token to use for authentication
3131
*/
32-
public suspend fun upload(file: File, oauthToken: String): Avatar = withContext(GravatarSdkDI.dispatcherIO) {
33-
val service = instance.getGravatarV3Service(okHttpClient, oauthToken)
32+
public suspend fun upload(file: File, oauthToken: String): Avatar = runThrowingExceptionRequest {
33+
withContext(GravatarSdkDI.dispatcherIO) {
34+
val service = instance.getGravatarV3Service(okHttpClient, oauthToken)
3435

35-
val filePart =
36-
MultipartBody.Part.createFormData("image", file.name, file.asRequestBody())
36+
val filePart =
37+
MultipartBody.Part.createFormData("image", file.name, file.asRequestBody())
3738

38-
val response = service.uploadAvatar(filePart)
39+
val response = service.uploadAvatar(filePart)
3940

40-
if (response.isSuccessful && response.body() != null) {
41-
response.body()!!
42-
} else {
43-
// Log the response body for debugging purposes if the response is not successful
44-
Logger.w(
45-
LOG_TAG,
46-
"Network call unsuccessful trying to upload Gravatar: $response.body",
47-
)
48-
throw HttpException(response)
41+
if (response.isSuccessful && response.body() != null) {
42+
response.body()!!
43+
} else {
44+
// Log the response body for debugging purposes if the response is not successful
45+
Logger.w(
46+
LOG_TAG,
47+
"Network call unsuccessful trying to upload Gravatar: $response.body",
48+
)
49+
throw HttpException(response)
50+
}
4951
}
5052
}
5153

@@ -70,7 +72,7 @@ public class AvatarService(private val okHttpClient: OkHttpClient? = null) {
7072
* @param hash The hash of the email to associate the avatars with
7173
* @return The list of avatars
7274
*/
73-
public suspend fun retrieve(oauthToken: String, hash: Hash): List<Avatar> =
75+
public suspend fun retrieve(oauthToken: String, hash: Hash): List<Avatar> = runThrowingExceptionRequest {
7476
withContext(GravatarSdkDI.dispatcherIO) {
7577
val service = instance.getGravatarV3Service(okHttpClient, oauthToken)
7678

@@ -87,6 +89,7 @@ public class AvatarService(private val okHttpClient: OkHttpClient? = null) {
8789
throw HttpException(response)
8890
}
8991
}
92+
}
9093

9194
/**
9295
* Retrieves a list of available avatars for the authenticated user.
@@ -110,20 +113,22 @@ public class AvatarService(private val okHttpClient: OkHttpClient? = null) {
110113
* @param oauthToken The OAuth token to use for authentication
111114
*/
112115
public suspend fun setAvatar(hash: String, avatarId: String, oauthToken: String): Unit =
113-
withContext(GravatarSdkDI.dispatcherIO) {
114-
val service = GravatarSdkDI.getGravatarV3Service(okHttpClient, oauthToken)
116+
runThrowingExceptionRequest {
117+
withContext(GravatarSdkDI.dispatcherIO) {
118+
val service = GravatarSdkDI.getGravatarV3Service(okHttpClient, oauthToken)
115119

116-
val response = service.setEmailAvatar(avatarId, SetEmailAvatarRequest { emailHash = hash })
120+
val response = service.setEmailAvatar(avatarId, SetEmailAvatarRequest { emailHash = hash })
117121

118-
if (response.isSuccessful) {
119-
Unit
120-
} else {
121-
// Log the response body for debugging purposes if the response is not successful
122-
Logger.w(
123-
LOG_TAG,
124-
"Network call unsuccessful trying to set Gravatar avatar: $response.body",
125-
)
126-
throw HttpException(response)
122+
if (response.isSuccessful) {
123+
Unit
124+
} else {
125+
// Log the response body for debugging purposes if the response is not successful
126+
Logger.w(
127+
LOG_TAG,
128+
"Network call unsuccessful trying to set Gravatar avatar: $response.body",
129+
)
130+
throw HttpException(response)
131+
}
127132
}
128133
}
129134

gravatar/src/main/java/com/gravatar/services/ErrorType.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ internal fun HttpException.errorTypeFromHttpCode(moshi: Moshi): ErrorType = when
2020
}
2121

2222
in HttpResponseCode.SERVER_ERRORS -> ErrorType.Server
23-
else -> ErrorType.Unknown
23+
else -> ErrorType.Unknown("HTTP Code $code - ErrorBody $rawErrorBody")
2424
}
2525

2626
internal fun Throwable.errorType(moshi: Moshi): ErrorType {
2727
return when (this) {
2828
is SocketTimeoutException -> ErrorType.Timeout
2929
is UnknownHostException -> ErrorType.Network
3030
is HttpException -> this.errorTypeFromHttpCode(moshi)
31-
else -> ErrorType.Unknown
31+
else -> ErrorType.Unknown(message)
3232
}
3333
}
3434

@@ -54,8 +54,18 @@ public sealed class ErrorType {
5454
/** User not authorized to perform given action **/
5555
public data object Unauthorized : ErrorType()
5656

57-
/** An unknown error occurred */
58-
public data object Unknown : ErrorType()
57+
/**
58+
* An unknown error occurred
59+
*
60+
* @property errorMsg The error message, if available.
61+
*/
62+
public class Unknown(public val errorMsg: String? = null) : ErrorType() {
63+
override fun toString(): String = "Unknown(errorMsg=$errorMsg)"
64+
65+
override fun equals(other: Any?): Boolean = other is Unknown && errorMsg == other.errorMsg
66+
67+
override fun hashCode(): Int = Objects.hash(errorMsg)
68+
}
5969

6070
/**
6171
* An error occurred while processing the request.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.gravatar.services
2+
3+
/**
4+
* Exception that will be thrown when an error occurs during Gravatar operations.
5+
*
6+
* @property errorType The type of error that occurred.
7+
* @property originalException The original exception that caused this exception.
8+
*/
9+
public class GravatarException internal constructor(
10+
public val errorType: ErrorType,
11+
public val originalException: Exception? = null,
12+
) : RuntimeException()

gravatar/src/main/java/com/gravatar/services/ProfileService.kt

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.gravatar.services
22

33
import com.gravatar.HttpResponseCode
4+
import com.gravatar.di.container.GravatarSdkContainer
45
import com.gravatar.logger.Logger
56
import com.gravatar.restapi.models.Profile
67
import com.gravatar.types.Email
@@ -29,23 +30,29 @@ public class ProfileService(private val okHttpClient: OkHttpClient? = null) {
2930
* @param hashOrUsername The hash or username to fetch the profile for
3031
* @return The fetched profile or null if profile not found
3132
*/
32-
public suspend fun retrieve(hashOrUsername: String): Profile? = withContext(GravatarSdkDI.dispatcherIO) {
33-
val response = service.getProfileById(hashOrUsername)
34-
if (response.isSuccessful) {
35-
response.body() ?: error("Response body is null")
36-
} else {
37-
// Log the response body for debugging purposes if the response is not successful
38-
Logger.w(
39-
LOG_TAG,
40-
"Network call unsuccessful trying to get Gravatar profile: ${response.code()}",
41-
)
42-
if (response.code() == HttpResponseCode.HTTP_NOT_FOUND) {
43-
return@withContext null
44-
} else {
45-
throw HttpException(response)
33+
public suspend fun retrieve(hashOrUsername: String): Profile? =
34+
@Suppress("TooGenericExceptionCaught")
35+
try {
36+
withContext(GravatarSdkDI.dispatcherIO) {
37+
val response = service.getProfileById(hashOrUsername)
38+
if (response.isSuccessful) {
39+
response.body() ?: error("Response body is null")
40+
} else {
41+
// Log the response body for debugging purposes if the response is not successful
42+
Logger.w(
43+
LOG_TAG,
44+
"Network call unsuccessful trying to get Gravatar profile: ${response.code()}",
45+
)
46+
if (response.code() == HttpResponseCode.HTTP_NOT_FOUND) {
47+
return@withContext null
48+
} else {
49+
throw HttpException(response)
50+
}
51+
}
4652
}
53+
} catch (ex: Exception) {
54+
throw GravatarException(ex.errorType(GravatarSdkContainer.instance.moshi), ex)
4755
}
48-
}
4956

5057
/**
5158
* Fetches a Gravatar profile for the given hash or username.
@@ -138,7 +145,7 @@ public class ProfileService(private val okHttpClient: OkHttpClient? = null) {
138145
* @param email The email address to check
139146
* @return True if the email is associated with the account, false otherwise
140147
*/
141-
public suspend fun checkAssociatedEmail(oauthToken: String, email: Email): Boolean =
148+
public suspend fun checkAssociatedEmail(oauthToken: String, email: Email): Boolean = runThrowingExceptionRequest {
142149
withContext(GravatarSdkDI.dispatcherIO) {
143150
val service = GravatarSdkDI.getGravatarV3Service(okHttpClient, oauthToken)
144151

@@ -154,6 +161,7 @@ public class ProfileService(private val okHttpClient: OkHttpClient? = null) {
154161
throw HttpException(response)
155162
}
156163
}
164+
}
157165

158166
/**
159167
* Checks if the given email address is associated with the already authorized Gravatar account.

0 commit comments

Comments
 (0)