From 303373e9bd5bd9b3efd0df8837be0ee654a58e45 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 13 Nov 2023 18:25:49 -0300 Subject: [PATCH 1/8] Convert ApiResponse into a seal class. --- .../config/detekt/baseline.xml | 5 +- .../embracesdk/comms/api/ApiClientImpl.kt | 32 +++++----- .../embracesdk/comms/api/ApiResponse.kt | 24 ++++++-- .../embracesdk/comms/api/EmbraceApiService.kt | 54 ++++++++++------ .../embracesdk/comms/api/ApiClientImplTest.kt | 61 +++++++++++++------ .../comms/api/EmbraceApiServiceTest.kt | 42 ++++++------- 6 files changed, 137 insertions(+), 81 deletions(-) diff --git a/embrace-android-sdk/config/detekt/baseline.xml b/embrace-android-sdk/config/detekt/baseline.xml index 5703cad343..a7f28b94d6 100644 --- a/embrace-android-sdk/config/detekt/baseline.xml +++ b/embrace-android-sdk/config/detekt/baseline.xml @@ -1,6 +1,7 @@ - + - + + UseCheckOrError:EmbraceApiService.kt$EmbraceApiService$throw IllegalStateException("Failed to retrieve from Embrace server.") diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt index 36bf1b0bb9..5ec66dc20d 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt @@ -27,13 +27,13 @@ internal class ApiClientImpl( override fun executeGet(request: ApiRequest): ApiResponse { var connection: EmbraceConnection? = null - try { + return try { connection = request.toConnection() setTimeouts(connection) connection.connect() - return executeHttpRequest(connection) + executeHttpRequest(connection) } catch (ex: Throwable) { - throw IllegalStateException(ex.localizedMessage ?: "", ex) + ApiResponse.Error(IllegalStateException(ex.localizedMessage ?: "", ex)) } finally { runCatching { connection?.inputStream?.close() @@ -60,13 +60,10 @@ internal class ApiClientImpl( connection.outputStream?.write(payload) connection.connect() } - val response = executeHttpRequest(connection) - // pre-existing behavior. handle this better in future. - check(response.statusCode == HttpURLConnection.HTTP_OK) { "Failed to retrieve from Embrace server." } response } catch (ex: Throwable) { - throw IllegalStateException(ex.localizedMessage ?: "", ex) + ApiResponse.Error(IllegalStateException(ex.localizedMessage ?: "", ex)) } finally { runCatching { connection?.inputStream?.close() @@ -84,16 +81,21 @@ internal class ApiClientImpl( * server as a string. */ private fun executeHttpRequest(connection: EmbraceConnection): ApiResponse { - try { + return try { val responseCode = readHttpResponseCode(connection) - val headers = readHttpResponseHeaders(connection) - return ApiResponse( - responseCode, - headers, - readResponseBodyAsString(connection.inputStream) - ) + + val responseHeaders = readHttpResponseHeaders(connection) + return if (responseCode == HttpURLConnection.HTTP_OK) { + val responseBody = readResponseBodyAsString(connection.inputStream) + ApiResponse.Success(responseBody, responseHeaders) + } else { + val errorMessage = connection.errorStream?.let { + readResponseBodyAsString(it) + } + ApiResponse.Failure(errorMessage, responseCode, responseHeaders) + } } catch (exc: Throwable) { - throw IllegalStateException("Error occurred during HTTP request execution", exc) + ApiResponse.Error(IllegalStateException("Error occurred during HTTP request execution", exc)) } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt index 6bf734ce97..16f2385558 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt @@ -1,7 +1,21 @@ package io.embrace.android.embracesdk.comms.api -internal data class ApiResponse( - val statusCode: Int?, - val headers: Map, - val body: T? -) +/** + * ApiResponse is a sealed class that represents the result of an API call. + */ +internal sealed class ApiResponse { + /** + * Represents a successful API call (status code 200-299) + */ + data class Success(val body: T?, val headers: Map?) : ApiResponse() + + /** + * Represents a failed API call. (status code 400-499 or 500-599) + */ + data class Failure(val errorMessage: String?, val code: Int, val headers: Map?) : ApiResponse() + + /** + * Represents an exception thrown while making the API call. + */ + data class Error(val exception: Throwable) : ApiResponse() +} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt index 3e1b4d1ea7..00cb181f0c 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt @@ -60,27 +60,32 @@ internal class EmbraceApiService( if (cachedResponse.isValid()) { // only bother if we have a useful response. request = request.copy(eTag = cachedResponse.eTag) } - val response = apiClient.executeGet(request) - return when (response.statusCode) { - HttpURLConnection.HTTP_OK -> { + return when (val response = apiClient.executeGet(request)) { + is ApiResponse.Success -> { logger.logInfo("Fetched new config successfully.") val jsonReader = JsonReader(StringReader(response.body)) serializer.loadObject(jsonReader, RemoteConfig::class.java) } - - HttpURLConnection.HTTP_NOT_MODIFIED -> { - logger.logInfo("Confirmed config has not been modified.") - cachedResponse.remoteConfig - } - - ApiClient.NO_HTTP_RESPONSE -> { - logger.logInfo("Failed to fetch config (no response).") - null + is ApiResponse.Failure -> { + when (response.code) { + HttpURLConnection.HTTP_NOT_MODIFIED -> { + logger.logInfo("Confirmed config has not been modified.") + cachedResponse.remoteConfig + } + + ApiClient.NO_HTTP_RESPONSE -> { + logger.logInfo("Failed to fetch config (no response).") + null + } + else -> { + logger.logWarning("Unexpected status code when fetching config: ${response.code}") + null + } + } } - - else -> { - logger.logWarning("Unexpected status code when fetching config: ${response.statusCode}") - null + is ApiResponse.Error -> { + logger.logWarning("Failed to fetch config.", response.exception) + throw response.exception } } } @@ -322,8 +327,23 @@ internal class EmbraceApiService( return "$abbreviation:$stories" } + @Throws(IllegalStateException::class) private fun executePost(request: ApiRequest, payload: ByteArray) { - apiClient.executePost(request, payload) + when (val response = apiClient.executePost(request, payload)) { + is ApiResponse.Success -> { + logger.logInfo("Post successful") + } + is ApiResponse.Failure -> { + // Temporarily, we just throw an exception. In the future, we will handle this. + logger.logWarning("Post failed with code: ${response.code}") + throw IllegalStateException("Failed to retrieve from Embrace server.") + } + is ApiResponse.Error -> { + // Temporarily, we just throw an exception. In the future, we will handle this. + logger.logError("Post failed with exception: ${response.exception.message}") + throw IllegalStateException("Failed to retrieve from Embrace server.") + } + } } companion object { diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt index 803e7c01a3..684a38c067 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt @@ -12,12 +12,14 @@ import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import java.net.SocketException import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.util.zip.GZIPInputStream +import kotlin.IllegalStateException /** * Runs a [MockWebServer] and asserts against our network code to ensure that it @@ -42,65 +44,81 @@ internal class ApiClientImplTest { server.shutdown() } - @Test(expected = IllegalStateException::class) + @Test fun testUnreachableHost() { // attempt some unreachable port val request = ApiRequest(url = EmbraceUrl.create("http://localhost:1565")) - apiClient.executePost(request, "Hello world".toByteArray()) + val response = apiClient.executePost(request, "Hello world".toByteArray()) + check(response is ApiResponse.Error) + assertTrue(response.exception is IllegalStateException) } @Test fun testGet200Response() { server.enqueue(response200) - val result = runGetRequest() + val response = runGetRequest() assertGetRequest(server.takeRequest()) - assertEquals(DEFAULT_RESPONSE_BODY, result.body) + check(response is ApiResponse.Success) + assertEquals(DEFAULT_RESPONSE_BODY, response.body) } @Test fun testPost200ResponseCompressed() { server.enqueue(response200) - val result = runPostRequest() - assertEquals(DEFAULT_RESPONSE_BODY, result.body) + val response = runPostRequest() + check(response is ApiResponse.Success) + assertEquals(DEFAULT_RESPONSE_BODY, response.body) val delivered = server.takeRequest() assertPostRequest(delivered) assertEquals(DEFAULT_REQUEST_BODY, delivered.readCompressedRequestBody()) } - @Test(expected = IllegalStateException::class) + @Test fun testGet400Response() { server.enqueue(response400) - runGetRequest() + val response = runGetRequest() + check(response is ApiResponse.Failure) + assertEquals(response.code, 400) } - @Test(expected = IllegalStateException::class) + @Test fun testPost400Response() { server.enqueue(response400) - runPostRequest() + val response = runPostRequest() + check(response is ApiResponse.Failure) + assertEquals(response.code, 400) } - @Test(expected = IllegalStateException::class) + @Test fun testGet500Response() { server.enqueue(response500) - runGetRequest() + val response = runGetRequest() + check(response is ApiResponse.Failure) + assertEquals(response.code, 500) } - @Test(expected = IllegalStateException::class) + @Test fun testPost500Response() { server.enqueue(response500) - runPostRequest() + val response = runPostRequest() + check(response is ApiResponse.Failure) + assertEquals(response.code, 500) } - @Test(expected = IllegalStateException::class) + @Test fun testGetConnectionThrows() { - apiClient.executeGet(createThrowingRequest()) + val response = apiClient.executeGet(createThrowingRequest()) + check(response is ApiResponse.Error) + assertTrue(response.exception is java.lang.IllegalStateException) } - @Test(expected = IllegalStateException::class) + @Test fun testPostConnectionThrows() { - apiClient.executePost(createThrowingRequest(), DEFAULT_REQUEST_BODY.toByteArray()) + val response = apiClient.executePost(createThrowingRequest(), DEFAULT_REQUEST_BODY.toByteArray()) + check(response is ApiResponse.Error) + assertTrue(response.exception is java.lang.IllegalStateException) } /** @@ -114,6 +132,7 @@ internal class ApiClientImplTest { val payload = createLargeSessionPayload() val result = runPostRequest(payload = payload.toByteArray()) + check(result is ApiResponse.Success) // assert on result parsed by ApiClient assertEquals(DEFAULT_RESPONSE_BODY, result.body) @@ -126,7 +145,7 @@ internal class ApiClientImplTest { /** * Simulates an I/O exception midway through a request. */ - @Test(expected = IllegalStateException::class) + @Test fun testIoExceptionMidRequest() { server.enqueue(MockResponse().throttleBody(1, 1000, TimeUnit.MILLISECONDS)) @@ -136,7 +155,9 @@ internal class ApiClientImplTest { }, 25, TimeUnit.MILLISECONDS) // fire off the api request - runPostRequest() + val response = runPostRequest() + check(response is ApiResponse.Failure) + assertEquals(-1, response.code) } @Test diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/EmbraceApiServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/EmbraceApiServiceTest.kt index 96acc181e1..23c86768c7 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/EmbraceApiServiceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/EmbraceApiServiceTest.kt @@ -64,8 +64,7 @@ internal class EmbraceApiServiceTest { @Test fun `test getConfig returns correct values in Response`() { fakeApiClient.queueResponse( - ApiResponse( - statusCode = 200, + ApiResponse.Success( headers = emptyMap(), body = defaultConfigResponseBody ) @@ -82,8 +81,8 @@ internal class EmbraceApiServiceTest { @Test(expected = SocketException::class) fun `getConfig rethrows an exception thrown by apiClient`() { - val killerResponse: ApiResponse = mockk(relaxed = true) - every { killerResponse.statusCode } answers { throw SocketException() } + val killerResponse: ApiResponse.Failure = mockk(relaxed = true) + every { killerResponse.code } answers { throw SocketException() } fakeApiClient.queueResponse(killerResponse) apiService.getConfig() } @@ -91,10 +90,10 @@ internal class EmbraceApiServiceTest { @Test fun `cached remote config returned when 304 received`() { fakeApiClient.queueResponse( - ApiResponse( - statusCode = 304, - headers = emptyMap(), - body = null, + ApiResponse.Failure( + errorMessage = "Not modified", + code = 304, + headers = emptyMap() ) ) assertEquals(cachedConfig.remoteConfig, apiService.getConfig()) @@ -103,10 +102,10 @@ internal class EmbraceApiServiceTest { @Test fun `getConfig did not complete returns a null config`() { fakeApiClient.queueResponse( - ApiResponse( - statusCode = NO_HTTP_RESPONSE, - headers = emptyMap(), - body = null + ApiResponse.Failure( + errorMessage = "No response code", + code = NO_HTTP_RESPONSE, + headers = emptyMap() ) ) assertNull(apiService.getConfig()) @@ -115,10 +114,10 @@ internal class EmbraceApiServiceTest { @Test fun `getConfig results in unexpected response code returns a null config`() { fakeApiClient.queueResponse( - ApiResponse( - statusCode = 400, - headers = emptyMap(), - body = null + ApiResponse.Failure( + errorMessage = "Bad Request", + code = 400, + headers = emptyMap() ) ) assertNull(apiService.getConfig()) @@ -129,10 +128,10 @@ internal class EmbraceApiServiceTest { val cfg = RemoteConfig() cachedConfig = CachedConfig(cfg, "my_etag") fakeApiClient.queueResponse( - ApiResponse( - statusCode = 304, - headers = emptyMap(), - body = null + ApiResponse.Failure( + errorMessage = "Not modified", + code = 304, + headers = emptyMap() ) ) val remoteConfig = apiService.getConfig() @@ -291,8 +290,7 @@ internal class EmbraceApiServiceTest { private const val fakeDeviceId = "ajflkadsflkadslkfjds" private const val fakeAppVersionName = "6.1.0" private val defaultConfigResponseBody = ResourceReader.readResourceAsText("remote_config_response.json") - private val successfulPostResponse = ApiResponse( - statusCode = 200, + private val successfulPostResponse = ApiResponse.Success( headers = emptyMap(), body = "" ) From 956ddeaaa188835f04812e6773a7099fce960ec5 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 13 Nov 2023 18:29:43 -0300 Subject: [PATCH 2/8] Add variable to make clear the return statement. --- .../io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt index 5ec66dc20d..2957ffc76f 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt @@ -31,7 +31,8 @@ internal class ApiClientImpl( connection = request.toConnection() setTimeouts(connection) connection.connect() - executeHttpRequest(connection) + val response = executeHttpRequest(connection) + response } catch (ex: Throwable) { ApiResponse.Error(IllegalStateException(ex.localizedMessage ?: "", ex)) } finally { From ee8ba11ccde3f07033bdf44e21504fd5ed5643f1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 14 Nov 2023 10:34:17 -0300 Subject: [PATCH 3/8] Rename ApiResponse.Error to ApiResponse.Incomplete --- .../embrace/android/embracesdk/comms/api/ApiClientImpl.kt | 6 +++--- .../io/embrace/android/embracesdk/comms/api/ApiResponse.kt | 2 +- .../android/embracesdk/comms/api/EmbraceApiService.kt | 4 ++-- .../android/embracesdk/comms/api/ApiClientImplTest.kt | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt index 2957ffc76f..0fbd606667 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt @@ -34,7 +34,7 @@ internal class ApiClientImpl( val response = executeHttpRequest(connection) response } catch (ex: Throwable) { - ApiResponse.Error(IllegalStateException(ex.localizedMessage ?: "", ex)) + ApiResponse.Incomplete(IllegalStateException(ex.localizedMessage ?: "", ex)) } finally { runCatching { connection?.inputStream?.close() @@ -64,7 +64,7 @@ internal class ApiClientImpl( val response = executeHttpRequest(connection) response } catch (ex: Throwable) { - ApiResponse.Error(IllegalStateException(ex.localizedMessage ?: "", ex)) + ApiResponse.Incomplete(IllegalStateException(ex.localizedMessage ?: "", ex)) } finally { runCatching { connection?.inputStream?.close() @@ -96,7 +96,7 @@ internal class ApiClientImpl( ApiResponse.Failure(errorMessage, responseCode, responseHeaders) } } catch (exc: Throwable) { - ApiResponse.Error(IllegalStateException("Error occurred during HTTP request execution", exc)) + ApiResponse.Incomplete(IllegalStateException("Error occurred during HTTP request execution", exc)) } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt index 16f2385558..fdd5020cf6 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt @@ -17,5 +17,5 @@ internal sealed class ApiResponse { /** * Represents an exception thrown while making the API call. */ - data class Error(val exception: Throwable) : ApiResponse() + data class Incomplete(val exception: Throwable) : ApiResponse() } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt index a7480acb21..547ca30cc0 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt @@ -83,7 +83,7 @@ internal class EmbraceApiService( } } } - is ApiResponse.Error -> { + is ApiResponse.Incomplete -> { logger.logWarning("Failed to fetch config.", response.exception) throw response.exception } @@ -224,7 +224,7 @@ internal class EmbraceApiService( logger.logWarning("Post failed with code: ${response.code}") throw IllegalStateException("Failed to retrieve from Embrace server.") } - is ApiResponse.Error -> { + is ApiResponse.Incomplete -> { // Temporarily, we just throw an exception. In the future, we will handle this. logger.logError("Post failed with exception: ${response.exception.message}") throw IllegalStateException("Failed to retrieve from Embrace server.") diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt index 684a38c067..0d506ab8c1 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt @@ -49,7 +49,7 @@ internal class ApiClientImplTest { // attempt some unreachable port val request = ApiRequest(url = EmbraceUrl.create("http://localhost:1565")) val response = apiClient.executePost(request, "Hello world".toByteArray()) - check(response is ApiResponse.Error) + check(response is ApiResponse.Incomplete) assertTrue(response.exception is IllegalStateException) } @@ -110,14 +110,14 @@ internal class ApiClientImplTest { @Test fun testGetConnectionThrows() { val response = apiClient.executeGet(createThrowingRequest()) - check(response is ApiResponse.Error) + check(response is ApiResponse.Incomplete) assertTrue(response.exception is java.lang.IllegalStateException) } @Test fun testPostConnectionThrows() { val response = apiClient.executePost(createThrowingRequest(), DEFAULT_REQUEST_BODY.toByteArray()) - check(response is ApiResponse.Error) + check(response is ApiResponse.Incomplete) assertTrue(response.exception is java.lang.IllegalStateException) } From 43416bafbc7da76d358fecb4ce84aeb5c0f32b35 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 14 Nov 2023 10:43:29 -0300 Subject: [PATCH 4/8] Remove Type from ApiResponse as it's always a String. --- .../embrace/android/embracesdk/comms/api/ApiClient.kt | 4 ++-- .../android/embracesdk/comms/api/ApiClientImpl.kt | 8 ++++---- .../android/embracesdk/comms/api/ApiResponse.kt | 8 ++++---- .../android/embracesdk/comms/api/EmbraceApiService.kt | 2 +- .../android/embracesdk/comms/api/ApiClientImplTest.kt | 6 +++--- .../embrace/android/embracesdk/fakes/FakeApiClient.kt | 10 +++++----- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClient.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClient.kt index 982d5862dc..f03cccd7d8 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClient.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClient.kt @@ -7,13 +7,13 @@ internal interface ApiClient { /** * Executes [ApiRequest] as a GET, returning the response as a [ApiResponse] */ - fun executeGet(request: ApiRequest): ApiResponse + fun executeGet(request: ApiRequest): ApiResponse /** * Executes [ApiRequest] as a POST with the given body defined by [payloadToCompress], returning the response as a [ApiResponse]. * The body will be gzip compressed. */ - fun executePost(request: ApiRequest, payloadToCompress: ByteArray): ApiResponse + fun executePost(request: ApiRequest, payloadToCompress: ByteArray): ApiResponse companion object { /** diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt index 0fbd606667..d164a2d321 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt @@ -24,7 +24,7 @@ internal class ApiClientImpl( private val logger: InternalEmbraceLogger ) : ApiClient { - override fun executeGet(request: ApiRequest): ApiResponse { + override fun executeGet(request: ApiRequest): ApiResponse { var connection: EmbraceConnection? = null return try { @@ -42,13 +42,13 @@ internal class ApiClientImpl( } } - override fun executePost(request: ApiRequest, payloadToCompress: ByteArray): ApiResponse = + override fun executePost(request: ApiRequest, payloadToCompress: ByteArray): ApiResponse = executeRawPost(request, gzip(payloadToCompress)) /** * Posts a payload according to the ApiRequest parameter. The payload will not be gzip compressed. */ - private fun executeRawPost(request: ApiRequest, payload: ByteArray?): ApiResponse { + private fun executeRawPost(request: ApiRequest, payload: ByteArray?): ApiResponse { logger.logDeveloper("ApiClient", request.httpMethod.toString() + " " + request.url) logger.logDeveloper("ApiClient", "Request details: $request") @@ -81,7 +81,7 @@ internal class ApiClientImpl( * Executes a HTTP call using the specified connection, returning the response from the * server as a string. */ - private fun executeHttpRequest(connection: EmbraceConnection): ApiResponse { + private fun executeHttpRequest(connection: EmbraceConnection): ApiResponse { return try { val responseCode = readHttpResponseCode(connection) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt index fdd5020cf6..f26e47edbc 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt @@ -3,19 +3,19 @@ package io.embrace.android.embracesdk.comms.api /** * ApiResponse is a sealed class that represents the result of an API call. */ -internal sealed class ApiResponse { +internal sealed class ApiResponse { /** * Represents a successful API call (status code 200-299) */ - data class Success(val body: T?, val headers: Map?) : ApiResponse() + data class Success(val body: String?, val headers: Map?) : ApiResponse() /** * Represents a failed API call. (status code 400-499 or 500-599) */ - data class Failure(val errorMessage: String?, val code: Int, val headers: Map?) : ApiResponse() + data class Failure(val errorMessage: String?, val code: Int, val headers: Map?) : ApiResponse() /** * Represents an exception thrown while making the API call. */ - data class Incomplete(val exception: Throwable) : ApiResponse() + data class Incomplete(val exception: Throwable) : ApiResponse() } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt index 547ca30cc0..a6f72deea3 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt @@ -61,7 +61,7 @@ internal class EmbraceApiService( } return when (val response = apiClient.executeGet(request)) { - is ApiResponse.Success -> { + is ApiResponse.Success -> { logger.logInfo("Fetched new config successfully.") val jsonReader = JsonReader(StringReader(response.body)) serializer.loadObject(jsonReader, RemoteConfig::class.java) diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt index 0d506ab8c1..48beda0a67 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt @@ -67,7 +67,7 @@ internal class ApiClientImplTest { fun testPost200ResponseCompressed() { server.enqueue(response200) val response = runPostRequest() - check(response is ApiResponse.Success) + check(response is ApiResponse.Success) assertEquals(DEFAULT_RESPONSE_BODY, response.body) val delivered = server.takeRequest() @@ -199,7 +199,7 @@ internal class ApiClientImplTest { ) } - private fun runGetRequest(): ApiResponse = + private fun runGetRequest(): ApiResponse = apiClient.executeGet( ApiRequest( url = EmbraceUrl.create(baseUrl), @@ -209,7 +209,7 @@ internal class ApiClientImplTest { private fun runPostRequest( payload: ByteArray = DEFAULT_REQUEST_BODY.toByteArray() - ): ApiResponse = + ): ApiResponse = apiClient.executePost( ApiRequest( url = EmbraceUrl.create(baseUrl), diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeApiClient.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeApiClient.kt index 4ddfab6cf7..8396a7778b 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeApiClient.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeApiClient.kt @@ -8,17 +8,17 @@ import java.util.Queue internal class FakeApiClient : ApiClient { val sentRequests: MutableList> = mutableListOf() - private val queuedResponses: Queue> = LinkedList() + private val queuedResponses: Queue = LinkedList() - override fun executeGet(request: ApiRequest): ApiResponse = getNext(request, null) + override fun executeGet(request: ApiRequest): ApiResponse = getNext(request, null) - override fun executePost(request: ApiRequest, payloadToCompress: ByteArray): ApiResponse = getNext(request, payloadToCompress) + override fun executePost(request: ApiRequest, payloadToCompress: ByteArray): ApiResponse = getNext(request, payloadToCompress) - fun queueResponse(response: ApiResponse) { + fun queueResponse(response: ApiResponse) { queuedResponses.add(response) } - private fun getNext(request: ApiRequest, bytes: ByteArray?): ApiResponse { + private fun getNext(request: ApiRequest, bytes: ByteArray?): ApiResponse { sentRequests.add(Pair(request, bytes)) return checkNotNull(queuedResponses.poll()) { "No response" } } From 2fbb9c482dc167b21dd06fb37d9bb0ba6bbd948e Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 14 Nov 2023 10:53:30 -0300 Subject: [PATCH 5/8] Revert baseline towards Suppress annotation. --- embrace-android-sdk/config/detekt/baseline.xml | 5 ++--- .../android/embracesdk/comms/api/EmbraceApiService.kt | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/embrace-android-sdk/config/detekt/baseline.xml b/embrace-android-sdk/config/detekt/baseline.xml index a7f28b94d6..5703cad343 100644 --- a/embrace-android-sdk/config/detekt/baseline.xml +++ b/embrace-android-sdk/config/detekt/baseline.xml @@ -1,7 +1,6 @@ - + - + - UseCheckOrError:EmbraceApiService.kt$EmbraceApiService$throw IllegalStateException("Failed to retrieve from Embrace server.") diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt index a6f72deea3..43c824c60d 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt @@ -214,6 +214,7 @@ internal class EmbraceApiService( } } + @Suppress("UseCheckOrError") private fun executePost(request: ApiRequest, payload: ByteArray) { when (val response = apiClient.executePost(request, payload)) { is ApiResponse.Success -> { From cb7bb0d65ff1592939d7e6ff475e1f8ac91b8054 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 14 Nov 2023 17:56:30 -0300 Subject: [PATCH 6/8] Add more specific response types to ApiResponse. --- .../android/embracesdk/comms/api/ApiClient.kt | 2 + .../embracesdk/comms/api/ApiClientImpl.kt | 34 +++++++++--- .../embracesdk/comms/api/ApiResponse.kt | 19 ++++++- .../embracesdk/comms/api/EmbraceApiService.kt | 55 +++++++++++-------- .../embracesdk/comms/api/ApiClientImplTest.kt | 4 +- .../comms/api/EmbraceApiServiceTest.kt | 26 +++------ 6 files changed, 86 insertions(+), 54 deletions(-) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClient.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClient.kt index f03cccd7d8..8df73e8fb8 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClient.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClient.kt @@ -23,6 +23,8 @@ internal interface ApiClient { const val NO_HTTP_RESPONSE = -1 + const val TOO_MANY_REQUESTS = 429 + const val defaultTimeoutMs = 60 * 1000 } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt index d164a2d321..4a3760c8b6 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiClientImpl.kt @@ -1,13 +1,16 @@ package io.embrace.android.embracesdk.comms.api import io.embrace.android.embracesdk.comms.api.ApiClient.Companion.NO_HTTP_RESPONSE +import io.embrace.android.embracesdk.comms.api.ApiClient.Companion.TOO_MANY_REQUESTS import io.embrace.android.embracesdk.comms.api.ApiClient.Companion.defaultTimeoutMs import io.embrace.android.embracesdk.logging.InternalEmbraceLogger import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InputStream import java.io.InputStreamReader -import java.net.HttpURLConnection +import java.net.HttpURLConnection.HTTP_ENTITY_TOO_LARGE +import java.net.HttpURLConnection.HTTP_NOT_MODIFIED +import java.net.HttpURLConnection.HTTP_OK import java.util.zip.GZIPOutputStream /** @@ -84,16 +87,29 @@ internal class ApiClientImpl( private fun executeHttpRequest(connection: EmbraceConnection): ApiResponse { return try { val responseCode = readHttpResponseCode(connection) - val responseHeaders = readHttpResponseHeaders(connection) - return if (responseCode == HttpURLConnection.HTTP_OK) { - val responseBody = readResponseBodyAsString(connection.inputStream) - ApiResponse.Success(responseBody, responseHeaders) - } else { - val errorMessage = connection.errorStream?.let { - readResponseBodyAsString(it) + + return when (responseCode) { + HTTP_OK -> { + val responseBody = readResponseBodyAsString(connection.inputStream) + ApiResponse.Success(responseBody, responseHeaders) + } + HTTP_NOT_MODIFIED -> { + ApiResponse.NotModified + } + HTTP_ENTITY_TOO_LARGE -> { + ApiResponse.PayloadTooLarge + } + TOO_MANY_REQUESTS -> { + val retryAfter = responseHeaders["Retry-After"]?.toLongOrNull() + ApiResponse.TooManyRequests(retryAfter) + } + NO_HTTP_RESPONSE -> { + ApiResponse.Incomplete(IllegalStateException("Connection failed or unexpected response code")) + } + else -> { + ApiResponse.Failure(responseCode, responseHeaders) } - ApiResponse.Failure(errorMessage, responseCode, responseHeaders) } } catch (exc: Throwable) { ApiResponse.Incomplete(IllegalStateException("Error occurred during HTTP request execution", exc)) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt index f26e47edbc..9a434505d2 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt @@ -5,14 +5,29 @@ package io.embrace.android.embracesdk.comms.api */ internal sealed class ApiResponse { /** - * Represents a successful API call (status code 200-299) + * Represents an API call that returned a 200 OK status code. */ data class Success(val body: String?, val headers: Map?) : ApiResponse() + /** + * Represents an API call that returned a 304 Not Modified status code. + */ + object NotModified : ApiResponse() + + /** + * Represents an API call that returned a 413 Payload Too Large status code. + */ + object PayloadTooLarge : ApiResponse() + + /** + * Represents an API call that returned a 429 Too Many Requests status code. + */ + data class TooManyRequests(val retryAfter: Long?) : ApiResponse() + /** * Represents a failed API call. (status code 400-499 or 500-599) */ - data class Failure(val errorMessage: String?, val code: Int, val headers: Map?) : ApiResponse() + data class Failure(val code: Int, val headers: Map?) : ApiResponse() /** * Represents an exception thrown while making the API call. diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt index 43c824c60d..cbf78c6e13 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt @@ -15,7 +15,6 @@ import io.embrace.android.embracesdk.payload.BlobMessage import io.embrace.android.embracesdk.payload.EventMessage import io.embrace.android.embracesdk.payload.NetworkEvent import java.io.StringReader -import java.net.HttpURLConnection import java.util.concurrent.Future import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit @@ -53,6 +52,7 @@ internal class EmbraceApiService( * @return a future containing the configuration. */ @Throws(IllegalStateException::class) + @Suppress("UseCheckOrError") override fun getConfig(): RemoteConfig? { var request = prepareConfigRequest(configUrl) val cachedResponse = cachedConfigProvider(configUrl, request) @@ -66,27 +66,27 @@ internal class EmbraceApiService( val jsonReader = JsonReader(StringReader(response.body)) serializer.loadObject(jsonReader, RemoteConfig::class.java) } + is ApiResponse.NotModified -> { + logger.logInfo("Confirmed config has not been modified.") + cachedResponse.remoteConfig + } + is ApiResponse.TooManyRequests -> { + // TODO: We should retry after the retryAfter time or 3 seconds and apply exponential backoff. + logger.logWarning("Too many requests. ") + throw IllegalStateException("Too many requests.") + } is ApiResponse.Failure -> { - when (response.code) { - HttpURLConnection.HTTP_NOT_MODIFIED -> { - logger.logInfo("Confirmed config has not been modified.") - cachedResponse.remoteConfig - } - - ApiClient.NO_HTTP_RESPONSE -> { - logger.logInfo("Failed to fetch config (no response).") - null - } - else -> { - logger.logWarning("Unexpected status code when fetching config: ${response.code}") - null - } - } + logger.logInfo("Failed to fetch config (no response).") + null } is ApiResponse.Incomplete -> { logger.logWarning("Failed to fetch config.", response.exception) throw response.exception } + ApiResponse.PayloadTooLarge -> { + // Not expected to receive a 413 response for a GET request. + null + } } } @@ -217,19 +217,28 @@ internal class EmbraceApiService( @Suppress("UseCheckOrError") private fun executePost(request: ApiRequest, payload: ByteArray) { when (val response = apiClient.executePost(request, payload)) { - is ApiResponse.Success -> { - logger.logInfo("Post successful") + is ApiResponse.Success -> {} + is ApiResponse.TooManyRequests -> { + // Temporarily, we just throw an exception. In the future, + // we will use the retryAfter to schedule a retry. + val retryAfter = response.retryAfter ?: 3 + throw IllegalStateException("Too many requests. Will retry after $retryAfter seconds.") + } + is ApiResponse.PayloadTooLarge -> { + // We don't want to retry on PayloadTooLarge error, so we just log the error. + logger.logError("Payload too large. Dropping event.") } is ApiResponse.Failure -> { - // Temporarily, we just throw an exception. In the future, we will handle this. - logger.logWarning("Post failed with code: ${response.code}") - throw IllegalStateException("Failed to retrieve from Embrace server.") + // We don't want to retry on 4xx or 5xx errors, so we just log the error. + logger.logError("Failed to retrieve from Embrace server. Status code: ${response.code}") } is ApiResponse.Incomplete -> { - // Temporarily, we just throw an exception. In the future, we will handle this. - logger.logError("Post failed with exception: ${response.exception.message}") + // Retry on incomplete response. throw IllegalStateException("Failed to retrieve from Embrace server.") } + is ApiResponse.NotModified -> { + // Not expected to receive a 304 response for a POST request. + } } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt index 48beda0a67..664c540beb 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/ApiClientImplTest.kt @@ -156,8 +156,8 @@ internal class ApiClientImplTest { // fire off the api request val response = runPostRequest() - check(response is ApiResponse.Failure) - assertEquals(-1, response.code) + check(response is ApiResponse.Incomplete) + assertTrue(response.exception is IllegalStateException) } @Test diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/EmbraceApiServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/EmbraceApiServiceTest.kt index 968a257f46..99e12beac4 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/EmbraceApiServiceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/comms/api/EmbraceApiServiceTest.kt @@ -28,7 +28,6 @@ import org.junit.Assert.assertSame import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -import java.net.SocketException import java.util.concurrent.ScheduledExecutorService internal class EmbraceApiServiceTest { @@ -79,22 +78,19 @@ internal class EmbraceApiServiceTest { assertEquals(100, remoteConfig.threshold) } - @Test(expected = SocketException::class) - fun `getConfig rethrows an exception thrown by apiClient`() { - val killerResponse: ApiResponse.Failure = mockk(relaxed = true) - every { killerResponse.code } answers { throw SocketException() } - fakeApiClient.queueResponse(killerResponse) + @Test(expected = IllegalStateException::class) + fun `getConfig throws an exception when receiving ApiResponse_Incomplete`() { + val incompleteResponse: ApiResponse.Incomplete = ApiResponse.Incomplete( + IllegalStateException("Connection failed") + ) + fakeApiClient.queueResponse(incompleteResponse) apiService.getConfig() } @Test fun `cached remote config returned when 304 received`() { fakeApiClient.queueResponse( - ApiResponse.Failure( - errorMessage = "Not modified", - code = 304, - headers = emptyMap() - ) + ApiResponse.NotModified ) assertEquals(cachedConfig.remoteConfig, apiService.getConfig()) } @@ -103,7 +99,6 @@ internal class EmbraceApiServiceTest { fun `getConfig did not complete returns a null config`() { fakeApiClient.queueResponse( ApiResponse.Failure( - errorMessage = "No response code", code = NO_HTTP_RESPONSE, headers = emptyMap() ) @@ -115,7 +110,6 @@ internal class EmbraceApiServiceTest { fun `getConfig results in unexpected response code returns a null config`() { fakeApiClient.queueResponse( ApiResponse.Failure( - errorMessage = "Bad Request", code = 400, headers = emptyMap() ) @@ -128,11 +122,7 @@ internal class EmbraceApiServiceTest { val cfg = RemoteConfig() cachedConfig = CachedConfig(cfg, "my_etag") fakeApiClient.queueResponse( - ApiResponse.Failure( - errorMessage = "Not modified", - code = 304, - headers = emptyMap() - ) + ApiResponse.NotModified ) val remoteConfig = apiService.getConfig() assertSame(cfg, remoteConfig) From d090b2a5b12577fdce5652148217d17cea1201a9 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 15 Nov 2023 10:08:11 -0300 Subject: [PATCH 7/8] Update comment --- .../java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt index 9a434505d2..60bec72148 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/ApiResponse.kt @@ -25,7 +25,7 @@ internal sealed class ApiResponse { data class TooManyRequests(val retryAfter: Long?) : ApiResponse() /** - * Represents a failed API call. (status code 400-499 or 500-599) + * Represents a failed API call. (status code 400-499 or 500-599 except 413 and 429) */ data class Failure(val code: Int, val headers: Map?) : ApiResponse() From 5935e614861f873716e49c04f0e63fe57bd0eeef Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 15 Nov 2023 10:22:07 -0300 Subject: [PATCH 8/8] Return null on 429 --- .../embrace/android/embracesdk/comms/api/EmbraceApiService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt index cbf78c6e13..afd48aebc4 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt @@ -73,7 +73,7 @@ internal class EmbraceApiService( is ApiResponse.TooManyRequests -> { // TODO: We should retry after the retryAfter time or 3 seconds and apply exponential backoff. logger.logWarning("Too many requests. ") - throw IllegalStateException("Too many requests.") + null } is ApiResponse.Failure -> { logger.logInfo("Failed to fetch config (no response).")