Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract logic for constructing ApiRequest from ApiService #61

Merged
merged 1 commit into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.embrace.android.embracesdk.comms.api

import io.embrace.android.embracesdk.BuildConfig
import io.embrace.android.embracesdk.EmbraceEvent
import io.embrace.android.embracesdk.comms.api.EmbraceApiService.Companion.Endpoint
import io.embrace.android.embracesdk.network.http.HttpMethod
import io.embrace.android.embracesdk.payload.BlobMessage
import io.embrace.android.embracesdk.payload.EventMessage
import io.embrace.android.embracesdk.payload.NetworkEvent

internal class ApiRequestMapper(
private val urlBuilder: ApiUrlBuilder,
private val lazyDeviceId: Lazy<String>,
private val appId: String
) {

private val apiUrlBuilders = Endpoint.values().associateWith {
urlBuilder.getEmbraceUrlWithSuffix(it.path)
}

private fun Endpoint.asEmbraceUrl(): EmbraceUrl {
val urlString: String = checkNotNull(apiUrlBuilders[this])
return EmbraceUrl.create(urlString)
}

private fun requestBuilder(url: EmbraceUrl): ApiRequest {
return ApiRequest(
url = url,
httpMethod = HttpMethod.POST,
appId = appId,
deviceId = lazyDeviceId.value,
contentEncoding = "gzip"
)
}

fun configRequest(url: String) = ApiRequest(
contentType = "application/json",
userAgent = "Embrace/a/" + BuildConfig.VERSION_NAME,
accept = "application/json",
url = EmbraceUrl.create(url),
httpMethod = HttpMethod.GET,
)

fun logRequest(
eventMessage: EventMessage
): ApiRequest {
checkNotNull(eventMessage.event) { "event must be set" }
val event = eventMessage.event
val type = checkNotNull(event.type) { "event type must be set" }
checkNotNull(event.eventId) { "event ID must be set" }
val url = Endpoint.LOGGING.asEmbraceUrl()
val abbreviation = type.abbreviation
val logIdentifier = abbreviation + ":" + event.messageId
return requestBuilder(url).copy(logId = logIdentifier)
}

fun sessionRequest(): ApiRequest {
val url = Endpoint.SESSIONS.asEmbraceUrl()
return requestBuilder(url)
}

fun eventMessageRequest(eventMessage: EventMessage): ApiRequest {
checkNotNull(eventMessage.event) { "event must be set" }
val event = eventMessage.event
checkNotNull(event.type) { "event type must be set" }
checkNotNull(event.eventId) { "event ID must be set" }
val url = Endpoint.EVENTS.asEmbraceUrl()
val abbreviation = event.type.abbreviation
val eventIdentifier: String = if (event.type == EmbraceEvent.Type.CRASH) {
createCrashActiveEventsHeader(abbreviation, event.activeEventIds)
} else {
abbreviation + ":" + event.eventId
}
return requestBuilder(url).copy(eventId = eventIdentifier)
}

fun networkEventRequest(networkEvent: NetworkEvent): ApiRequest {
val url = Endpoint.NETWORK.asEmbraceUrl()
val abbreviation = EmbraceEvent.Type.NETWORK_LOG.abbreviation
val networkIdentifier = "$abbreviation:${networkEvent.eventId}"
return requestBuilder(url).copy(logId = networkIdentifier)
}

@Suppress("UNUSED_PARAMETER")
fun aeiBlobRequest(blobMessage: BlobMessage): ApiRequest {
val url = Endpoint.BLOBS.asEmbraceUrl()
return requestBuilder(url)
}

/**
* Crashes are sent with a header containing the list of active stories.
*
* @param abbreviation the abbreviation for the event type
* @param eventIds the list of story IDs
* @return the header
*/
private fun createCrashActiveEventsHeader(
abbreviation: String,
eventIds: List<String>?
): String {
val stories = eventIds?.joinToString(",") ?: ""
return "$abbreviation:$stories"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import java.util.concurrent.Future
internal interface ApiService {
fun getConfig(): RemoteConfig?
fun getCachedConfig(): CachedConfig
fun sendLogs(eventMessage: EventMessage)
fun sendLog(eventMessage: EventMessage)
fun sendNetworkCall(networkEvent: NetworkEvent)
fun sendEvent(eventMessage: EventMessage)
fun sendEventAndWait(eventMessage: EventMessage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.google.gson.stream.JsonReader
import io.embrace.android.embracesdk.BuildConfig
import io.embrace.android.embracesdk.EmbraceEvent
import io.embrace.android.embracesdk.capture.connectivity.NetworkConnectivityListener
import io.embrace.android.embracesdk.capture.connectivity.NetworkConnectivityService
import io.embrace.android.embracesdk.comms.delivery.DeliveryCacheManager
Expand All @@ -29,14 +28,14 @@
private val scheduledExecutorService: ScheduledExecutorService,
private val cacheManager: DeliveryCacheManager,
private val deliveryRetryManager: DeliveryRetryManager,
private val lazyDeviceId: Lazy<String>,
private val appId: String,
lazyDeviceId: Lazy<String>,
appId: String,
urlBuilder: ApiUrlBuilder,
networkConnectivityService: NetworkConnectivityService
) : ApiService, NetworkConnectivityListener {

private val mapper = ApiRequestMapper(urlBuilder, lazyDeviceId, appId)
private val configUrl = urlBuilder.getConfigUrl()
private val apiUrlBuilders = Endpoint.values().associateWith { urlBuilder.getEmbraceUrlWithSuffix(it.path) }
private var lastNetworkStatus: NetworkStatus = NetworkStatus.UNKNOWN

init {
Expand All @@ -61,6 +60,7 @@
request = request.copy(eTag = cachedResponse.eTag)
}
val response = apiClient.executeGet(request)

return when (response.statusCode) {
HttpURLConnection.HTTP_OK -> {
logger.logInfo("Fetched new config successfully.")
Expand Down Expand Up @@ -108,14 +108,8 @@
* @param eventMessage the event message containing the log entry
* @return a future containing the response body from the server
*/
override fun sendLogs(eventMessage: EventMessage) {
apiUrlBuilders[Endpoint.LOGGING]?.let { url ->
val event = eventMessage.event
val abbreviation = event.type.abbreviation
val logIdentifier = abbreviation + ":" + event.messageId
val request: ApiRequest = buildRequest(EmbraceUrl.create(url)).copy(logId = logIdentifier)
postEvent(eventMessage, request)
}
override fun sendLog(eventMessage: EventMessage) {
post(eventMessage, mapper::logRequest)
}

/**
Expand All @@ -125,19 +119,7 @@
* @return a future containing the response body from the server
*/
override fun sendAEIBlob(blobMessage: BlobMessage) {
apiUrlBuilders[Endpoint.BLOBS]?.let { url ->
val embraceUrl = EmbraceUrl.create(url)
// TODO: remove seemingly unnecessary parameters once this is better tested
val request: ApiRequest = buildRequest(embraceUrl).copy(
deviceId = lazyDeviceId.value,
appId = appId,
url = embraceUrl,
httpMethod = HttpMethod.POST,
contentEncoding = "gzip"
)

postAEIBlob(blobMessage, request)
}
post(blobMessage, mapper::aeiBlobRequest)
}

/**
Expand All @@ -146,12 +128,7 @@
* @param networkEvent the event containing the network call information
*/
override fun sendNetworkCall(networkEvent: NetworkEvent) {
apiUrlBuilders[Endpoint.NETWORK]?.let { url ->
val abbreviation = EmbraceEvent.Type.NETWORK_LOG.abbreviation
val networkIdentifier = "$abbreviation:${networkEvent.eventId}"
val request: ApiRequest = buildRequest(EmbraceUrl.create(url)).copy(logId = networkIdentifier)
postNetworkEvent(networkEvent, request)
}
post(networkEvent, mapper::networkEventRequest)
}

/**
Expand All @@ -160,9 +137,7 @@
* @param eventMessage the event message containing the event
*/
override fun sendEvent(eventMessage: EventMessage) {
createRequest(eventMessage)?.let { request ->
postEvent(eventMessage, request)
}
post(eventMessage, mapper::eventMessageRequest)
}

/**
Expand All @@ -171,9 +146,7 @@
* @param eventMessage the event message containing the event
*/
override fun sendEventAndWait(eventMessage: EventMessage) {
createRequest(eventMessage)?.let { request ->
postEvent(eventMessage, request)?.get()
}
post(eventMessage, mapper::eventMessageRequest)?.get()

Check warning on line 149 in embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt#L149

Added line #L149 was not covered by tests
}

/**
Expand All @@ -182,94 +155,34 @@
* @param crash the event message containing the crash
*/
override fun sendCrash(crash: EventMessage) {
createRequest(crash)?.let { request ->
try {
postEvent(crash, request) { cacheManager.deleteCrash() }?.get(
CRASH_TIMEOUT,
TimeUnit.SECONDS
)
} catch (e: Exception) {
logger.logError("The crash report request has timed out.")
}
}
}

override fun sendSession(sessionPayload: ByteArray, onFinish: (() -> Unit)?): Future<*>? {
apiUrlBuilders[Endpoint.SESSIONS]?.let { url ->
val embraceUrl = EmbraceUrl.create(url)
// TODO: remove seemingly unnecessary parameters once this is better tested
val request: ApiRequest = buildRequest(embraceUrl).copy(
deviceId = lazyDeviceId.value,
appId = appId,
url = embraceUrl,
httpMethod = HttpMethod.POST,
contentEncoding = "gzip"
try {
post(crash, mapper::eventMessageRequest) { cacheManager.deleteCrash() }?.get(
CRASH_TIMEOUT,
TimeUnit.SECONDS
)
return postOnExecutor(sessionPayload, request, onFinish)
} catch (e: Exception) {
logger.logError("The crash report request has timed out.")
}

return null
}

private fun createRequest(eventMessage: EventMessage): ApiRequest? {
apiUrlBuilders[Endpoint.EVENTS]?.let { url ->
val event = eventMessage.event
val abbreviation = event.type.abbreviation
val eventIdentifier: String = if (event.type == EmbraceEvent.Type.CRASH) {
createCrashActiveEventsHeader(abbreviation, event.activeEventIds)
} else {
abbreviation + ":" + event.eventId
}
return buildRequest(EmbraceUrl.create(url)).copy(eventId = eventIdentifier)
}

return null
override fun sendSession(sessionPayload: ByteArray, onFinish: (() -> Unit)?): Future<*> {
val request: ApiRequest = mapper.sessionRequest()
return postOnExecutor(sessionPayload, request, onFinish)
}

private fun postEvent(eventMessage: EventMessage, request: ApiRequest): Future<*>? {
return postEvent(eventMessage, request, null)
}

private fun postEvent(
eventMessage: EventMessage,
request: ApiRequest,
onComplete: (() -> Unit)?
private inline fun <reified T> post(
payload: T,
mapper: (T) -> ApiRequest,
noinline onComplete: (() -> Unit)? = null
): Future<*>? {
val bytes = serializer.bytesFromPayload(eventMessage, EventMessage::class.java)
val bytes = serializer.bytesFromPayload(payload, T::class.java)
val request: ApiRequest = mapper(payload)

bytes?.let {
logger.logDeveloper(TAG, "Post event")
return postOnExecutor(it, request, onComplete)
}
logger.logError("Failed to serialize event")
return null
}

private fun postNetworkEvent(
event: NetworkEvent,
request: ApiRequest
): Future<*>? {
val bytes = serializer.bytesFromPayload(event, NetworkEvent::class.java)

bytes?.let {
logger.logDeveloper(TAG, "Post Network Event")
return postOnExecutor(it, request, null)
}
logger.logError("Failed to serialize event")
return null
}

private fun postAEIBlob(
blob: BlobMessage,
request: ApiRequest
): Future<*>? {
val bytes = serializer.bytesFromPayload(blob, BlobMessage::class.java)

bytes?.let {
logger.logDeveloper(TAG, "Post AEI Blob message")
return postOnExecutor(it, request, null)
}
logger.logError("Failed to serialize event")
logger.logError("Failed to post event")

Check warning on line 185 in embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt#L185

Added line #L185 was not covered by tests
return null
}

Expand All @@ -296,32 +209,6 @@
}
}

private fun buildRequest(url: EmbraceUrl): ApiRequest {
return ApiRequest(
url = url,
httpMethod = HttpMethod.POST,
appId = appId,
deviceId = lazyDeviceId.value,
contentEncoding = "gzip"
)
}

/**
* Crashes are sent with a header containing the list of active stories.
*
* @param abbreviation the abbreviation for the event type
* @param eventIds the list of story IDs
* @return the header
*/
private fun createCrashActiveEventsHeader(
abbreviation: String,
eventIds: List<String>?
): String {
logger.logDeveloper(TAG, "createCrashActiveEventsHeader")
val stories = eventIds?.joinToString(",") ?: ""
return "$abbreviation:$stories"
}

private fun executePost(request: ApiRequest, payload: ByteArray) {
apiClient.executePost(request, payload)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
}

override fun sendLogs(eventMessage: EventMessage) {
apiService.sendLogs(eventMessage)
apiService.sendLog(eventMessage)

Check warning on line 162 in embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/delivery/EmbraceDeliveryService.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/delivery/EmbraceDeliveryService.kt#L162

Added line #L162 was not covered by tests
}

override fun sendNetworkCall(networkEvent: NetworkEvent) {
Expand Down
Loading