@@ -3,8 +3,15 @@ package com.extole.android.sdk.impl.http
33import com.extole.android.sdk.RestException
44import com.extole.android.sdk.impl.ResponseEntity
55import com.extole.android.sdk.impl.http.HttpRequest.HttpRequestException
6+ import org.json.JSONException
67import org.json.JSONObject
78
9+ private const val HTTP_STATUS_MIN = 100
10+ private const val HTTP_STATUS_MAX = 599
11+ private const val ERROR_BODY_PREVIEW_LENGTH = 512
12+ private const val FALLBACK_TRANSPORT_ERROR_STATUS = " 502"
13+ private const val FALLBACK_APPLICATION_ERROR_STATUS = " 400"
14+
815class Endpoints (
916 val accessToken : String? ,
1017 val headers : Map <String , String > = emptyMap()
@@ -15,26 +22,26 @@ class Endpoints(
1522 body : JSONObject ? = null
1623 ): ResponseEntity <JSONObject > {
1724 try {
18- var resultBody: String?
19- if (body != null ) {
20- resultBody = httpRequest.send(body.toString()).body()
21- } else {
22- resultBody = httpRequest.body()
23- }
24- if (httpRequest.ok() || httpRequest.created() || httpRequest.noContent()) {
25- return ResponseEntity (
26- JSONObject (resultBody.ifBlank { " {}" }),
27- httpRequest.headers(),
28- httpRequest.code()
29- )
30- } else {
31- throw handleException(httpRequest)
25+ val responseBody =
26+ if (body != null ) httpRequest.send(body.toString()).body()
27+ else httpRequest.body()
28+
29+ when {
30+ httpRequest.ok() || httpRequest.created() || httpRequest.noContent() ->
31+ return ResponseEntity (
32+ JSONObject (responseBody.ifBlank { " {}" }),
33+ httpRequest.headers(),
34+ httpRequest.code()
35+ )
36+
37+ else ->
38+ throw restExceptionFromHttpErrorResponse(
39+ resolveHttpStatusCode(httpRequest),
40+ responseBody
41+ )
3242 }
3343 } catch (e: HttpRequestException ) {
34- throw RestException (
35- " http_request_exception" , " 500" , " http_request_exception" , e.message
36- ? : " HttpRequestException" , emptyMap(), e
37- )
44+ throw restExceptionFromTransportFailure(resolveHttpStatusCode(httpRequest), e)
3845 }
3946 }
4047
@@ -46,14 +53,89 @@ class Endpoints(
4653 .headers(headers)
4754 }
4855
49- private fun handleException (httpRequest : HttpRequest ): RestException {
50- val responseBody = JSONObject (httpRequest.body())
56+ private fun resolveHttpStatusCode (httpRequest : HttpRequest ): String? =
57+ try {
58+ val code = httpRequest.code()
59+ if (code in HTTP_STATUS_MIN .. HTTP_STATUS_MAX ) code.toString() else null
60+ } catch (_: HttpRequestException ) {
61+ null
62+ }
63+ }
64+
65+ internal fun restExceptionFromHttpErrorResponse (
66+ statusFromWire : String? ,
67+ rawBody : String
68+ ): RestException {
69+ val trimmedBody = rawBody.trim()
70+ if (trimmedBody.isEmpty()) {
5171 return RestException (
52- responseBody.getString(" unique_id" ),
53- responseBody.getString(" http_status_code" ),
54- responseBody.getString(" code" ),
55- responseBody.getString(" message" ),
56- toMap(responseBody.getJSONObject(" parameters" ))
72+ uniqueId = " empty_error_body" ,
73+ httpStatusCode = statusFromWire ? : FALLBACK_APPLICATION_ERROR_STATUS ,
74+ errorCode = " empty_error_body" ,
75+ message =
76+ if (statusFromWire != null ) {
77+ " Empty response body for HTTP $statusFromWire "
78+ } else {
79+ " Empty error response body"
80+ },
81+ parameters = emptyMap()
82+ )
83+ }
84+
85+ return try {
86+ val json = JSONObject (trimmedBody)
87+ if (isStructuredRestError(json)) {
88+ val statusFromPayload = json.optString(" http_status_code" ).trim()
89+ RestException (
90+ json.getString(" unique_id" ),
91+ statusFromPayload.ifBlank { statusFromWire ? : FALLBACK_APPLICATION_ERROR_STATUS },
92+ json.getString(" code" ),
93+ json.getString(" message" ),
94+ toMap(json.optJSONObject(" parameters" ) ? : JSONObject ())
95+ )
96+ } else {
97+ RestException (
98+ uniqueId = " unexpected_error_payload" ,
99+ httpStatusCode = statusFromWire ? : FALLBACK_APPLICATION_ERROR_STATUS ,
100+ errorCode = " unexpected_error_payload" ,
101+ message = trimmedBody.take(ERROR_BODY_PREVIEW_LENGTH ),
102+ parameters = emptyMap()
103+ )
104+ }
105+ } catch (_: JSONException ) {
106+ RestException (
107+ uniqueId = " invalid_error_payload" ,
108+ httpStatusCode = statusFromWire ? : FALLBACK_APPLICATION_ERROR_STATUS ,
109+ errorCode = " invalid_error_payload" ,
110+ message = trimmedBody.take(ERROR_BODY_PREVIEW_LENGTH ),
111+ parameters = emptyMap()
57112 )
58113 }
59114}
115+
116+ internal fun restExceptionFromTransportFailure (
117+ resolvedHttpStatusToken : String? ,
118+ cause : HttpRequestException
119+ ): RestException =
120+ RestException (
121+ uniqueId = " http_request_exception" ,
122+ httpStatusCode = resolvedHttpStatusToken ? : FALLBACK_TRANSPORT_ERROR_STATUS ,
123+ errorCode = " http_request_exception" ,
124+ message = readableTransportMessage(cause),
125+ parameters = emptyMap()
126+ )
127+
128+ internal fun readableTransportMessage (e : HttpRequestException ): String {
129+ val cause = e.cause
130+ val detail =
131+ cause?.message?.takeIf { it.isNotBlank() }
132+ ? : e.message?.takeIf { it.isNotBlank() }
133+ ? : " HttpRequestException"
134+ if (cause == null ) return detail
135+ val type = cause.javaClass.simpleName
136+ val fqcnPrefix = " ${cause.javaClass.name} :"
137+ return if (detail.startsWith(type) || detail.startsWith(fqcnPrefix)) detail else " $type : $detail "
138+ }
139+
140+ private fun isStructuredRestError (body : JSONObject ): Boolean =
141+ body.has(" unique_id" ) && body.has(" code" ) && body.has(" message" )
0 commit comments