Skip to content

Commit b5600ff

Browse files
committed
fix #179: centralize and fix HttpResponse parsing and error handling
Before this change, only HttpException were thrown on errors. However it is possible to receive errors but formatted as OCPI messages, and in this case we want to throw an OcpiException and not an HttpException
1 parent 4758fc2 commit b5600ff

File tree

2 files changed

+51
-49
lines changed

2 files changed

+51
-49
lines changed

ocpi-toolkit-2.2.1/src/main/kotlin/com/izivia/ocpi/toolkit/common/HttpResponse.kt

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,17 @@ inline fun <reified T, reified P : Partial<T>> HttpResponse.parseSearchResultIgn
4141
* information. Any error information contained in HTTP request or OCPI body gets converted into Exceptions.
4242
* @param offset
4343
*/
44-
inline fun <reified T> HttpResponse.parseSearchResult(offset: Int): SearchResult<T> {
45-
if (!status.success()) throw HttpException(status, status.name)
46-
if (body.isNullOrBlank()) throw OcpiToolkitResponseParsingException("missing obligatory body in response")
47-
48-
val list = runCatching { mapper.deserializeOcpiResponseList<T>(body) }
49-
.onFailure { e ->
50-
throw OcpiToolkitResponseParsingException("Response cannot be parsed: $body", e)
51-
}
52-
.getOrNull()
53-
?.also { it.maybeThrowOcpiException(status) }
54-
?.data ?: emptyList()
55-
56-
return list.toSearchResult(
57-
totalCount = getHeader(Header.X_TOTAL_COUNT)?.toInt()
58-
?: throw OcpiToolkitMissingRequiredResponseHeaderException(Header.X_TOTAL_COUNT),
59-
limit = getHeader(Header.X_LIMIT)?.toInt()
60-
?: throw OcpiToolkitMissingRequiredResponseHeaderException(Header.X_LIMIT),
61-
offset = offset,
62-
nextPageUrl = getHeader(Header.LINK)?.split("<")?.elementAtOrNull(1)?.split(">")?.first(),
63-
)
64-
}
44+
inline fun <reified T> HttpResponse.parseSearchResult(offset: Int): SearchResult<T> =
45+
parseOcpiResponseBodyAndHandleErrors { mapper.deserializeOcpiResponseList<T>(body) }
46+
.orEmpty()
47+
.toSearchResult(
48+
totalCount = getHeader(Header.X_TOTAL_COUNT)?.toInt()
49+
?: throw OcpiToolkitMissingRequiredResponseHeaderException(Header.X_TOTAL_COUNT),
50+
limit = getHeader(Header.X_LIMIT)?.toInt()
51+
?: throw OcpiToolkitMissingRequiredResponseHeaderException(Header.X_LIMIT),
52+
offset = offset,
53+
nextPageUrl = getHeader(Header.LINK)?.split("<")?.elementAtOrNull(1)?.split(">")?.first(),
54+
)
6555

6656
/**
6757
* Parse body of a request that should contain data, usually POST commands.
@@ -85,43 +75,52 @@ inline fun <reified T> HttpResponse.parseOptionalResult(): T? {
8575
return parseResultOrNull()
8676
}
8777

88-
inline fun <reified T> HttpResponse.parseResultListOrNull(): List<T>? {
89-
if (!status.success()) throw HttpException(status, status.name)
90-
if (body.isNullOrBlank()) throw OcpiToolkitResponseParsingException("missing obligatory body in response")
78+
inline fun <reified T> HttpResponse.parseResultListOrNull(): List<T>? =
79+
parseOcpiResponseBodyAndHandleErrors { mapper.deserializeOcpiResponseList<T>(body) }
9180

92-
return runCatching { mapper.deserializeOcpiResponseList<T>(body) }
93-
.onFailure { e ->
94-
throw OcpiToolkitResponseParsingException("Response cannot be parsed: $body", e)
95-
}
96-
.getOrNull()
97-
?.also { it.maybeThrowOcpiException(status) }
98-
?.data
99-
}
81+
inline fun <reified T> HttpResponse.parseResultOrNull(): T? =
82+
parseOcpiResponseBodyAndHandleErrors { mapper.deserializeOcpiResponse<T>(it) }
10083

10184
/**
10285
* Parse body of a request that might contain data, like PUT/PATCH calls.
10386
* Any error information contained in HTTP request or OcpiResponseBody gets converted into Exceptions.
10487
*/
105-
inline fun <reified T> HttpResponse.parseResultOrNull(): T? {
106-
if (!status.success()) throw HttpException(status, status.name)
107-
if (body.isNullOrBlank()) throw OcpiToolkitResponseParsingException("missing obligatory body in response")
88+
inline fun <reified T> HttpResponse.parseOcpiResponseBodyAndHandleErrors(
89+
deserializeFn: (String?) -> OcpiResponseBody<T>,
90+
): T? {
91+
if (!status.success()) {
92+
// We know there was an error, so an exception must be thrown, we will try to throw an OcpiException if the
93+
// error is formatted as an OCPI error
94+
runCatching { deserializeFn(body) }
95+
.getOrElse {
96+
// If deserialization fails, it means that the response is probably not an OCPI error (if it is, it is
97+
// incorrectly formatted, so we read it as a regular HttpException)
98+
throw HttpException(status, status.name)
99+
}
100+
.throwOcpiException(status)
101+
}
108102

109-
return runCatching { mapper.deserializeOcpiResponse<T>(body) }
110-
.onFailure { e ->
103+
// We know the message is a success, so it must be formatted as an OCPI response, so we can safely throw an
104+
// exception if it is not the case
105+
return runCatching { deserializeFn(body) }
106+
.getOrElse { e ->
111107
throw OcpiToolkitResponseParsingException("Response cannot be parsed: $body", e)
112108
}
113-
.getOrNull()
114-
?.also { it.maybeThrowOcpiException(status) }
115-
?.data
109+
.also { parsedOcpiResponse ->
110+
// who knows, maybe the partner responded with 200 or 201, but the OCPI payload is an error, in that case
111+
// throw the error
112+
if (!parsedOcpiResponse.statusCode.toOcpiStatus().isSuccess()) {
113+
parsedOcpiResponse.throwOcpiException(status)
114+
}
115+
}
116+
.data
116117
}
117118

118-
inline fun <reified T> OcpiResponseBody<T>.maybeThrowOcpiException(httpStatus: HttpStatus = HttpStatus.OK) {
119-
if (statusCode != OcpiStatus.SUCCESS.code) {
120-
throw OcpiException(
121-
httpStatus = httpStatus,
122-
ocpiStatus = statusCode.toOcpiStatus(),
123-
ocpiStatusCode = statusCode,
124-
message = statusMessage ?: "",
125-
)
126-
}
119+
inline fun <reified T> OcpiResponseBody<T>.throwOcpiException(httpStatus: HttpStatus) {
120+
throw OcpiException(
121+
httpStatus = httpStatus,
122+
ocpiStatus = statusCode.toOcpiStatus(),
123+
ocpiStatusCode = statusCode,
124+
message = statusMessage.orEmpty(),
125+
)
127126
}

ocpi-toolkit-2.2.1/src/main/kotlin/com/izivia/ocpi/toolkit/common/OcpiStatus.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ enum class OcpiStatus(val code: Int) {
8585
* Used when status code in response is unknown, most likely a custom code
8686
*/
8787
UNKNOWN(9999),
88+
;
89+
90+
fun isSuccess(): Boolean = code in 1000..1999
8891
}
8992

9093
fun Int.toOcpiStatus(): OcpiStatus = OcpiStatus.values().firstOrNull { it.code == this } ?: OcpiStatus.UNKNOWN

0 commit comments

Comments
 (0)