Skip to content

Commit

Permalink
Merge pull request #35 from highmobility/ignore-keys
Browse files Browse the repository at this point in the history
main Ignore keys
  • Loading branch information
tonisives authored Jan 31, 2024
2 parents eaf44a4 + 83f6e57 commit b709a61
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 246 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,5 @@ vehicleAccess.yaml
vehicleAccess.json
private-key.json
credentialsPrivateKey.json
credentialsOAuth.json
credentialsOAuth.json
hmkit-fleet/bin
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

This is the changelog for v2 releases. See v0/v1 releases in appropriate branches.

## [2.2.2] - 2023-12-7
## [2.0.3] - 2024-1-31

- Fix `Json {} ignoreUnknownKeys` error

## [2.0.2] - 2023-12-7

- Update javadoc

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version=2.0.2
version=2.0.3
kotlin.code.style=official
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ package com.highmobility.hmkitfleet.network
import com.highmobility.hmkitfleet.HMKitCredentials
import com.highmobility.hmkitfleet.model.AccessToken
import com.highmobility.hmkitfleet.utils.await
import kotlinx.serialization.json.Json
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
Expand Down Expand Up @@ -70,8 +69,6 @@ internal class AccessTokenRequests(
return request
}

private val json = Json { ignoreUnknownKeys = true }

suspend fun getAccessToken(): Response<AccessToken> {
val cachedToken = cache.accessToken
if (cachedToken != null) return Response(cachedToken)
Expand All @@ -86,7 +83,7 @@ internal class AccessTokenRequests(

return try {
if (response.code == HttpURLConnection.HTTP_CREATED || response.code == HttpURLConnection.HTTP_OK) {
cache.accessToken = json.decodeFromString(responseBody)
cache.accessToken = jsonIg.decodeFromString(responseBody)
Response(cache.accessToken)
} else {
parseError(responseBody)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ internal class ClearanceRequests(
val response = call.await()

return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody ->
val jsonElement = Json.parseToJsonElement(responseBody) as JsonObject
val jsonElement = jsonIg.parseToJsonElement(responseBody) as JsonObject
val statuses = jsonElement["vehicles"] as JsonArray
for (statusElement in statuses) {
val status =
Json.decodeFromJsonElement<RequestClearanceResponse>(statusElement)
jsonIg.decodeFromJsonElement<RequestClearanceResponse>(statusElement)
if (status.vin == vin) {
return@tryParseResponse Response(status, null)
}
Expand All @@ -108,11 +108,11 @@ internal class ClearanceRequests(
val response = call.await()

return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody ->
val statuses = Json.parseToJsonElement(responseBody) as JsonArray
val statuses = jsonIg.parseToJsonElement(responseBody) as JsonArray

val builder = Array(statuses.size) {
val statusElement = statuses[it]
val status = Json.decodeFromJsonElement<ClearanceStatus>(statusElement)
val status = jsonIg.decodeFromJsonElement<ClearanceStatus>(statusElement)
status
}

Expand All @@ -138,7 +138,7 @@ internal class ClearanceRequests(
val response = call.await()

return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody ->
val status = Json.decodeFromString<ClearanceStatus>(responseBody)
val status = jsonIg.decodeFromString<ClearanceStatus>(responseBody)
Response(status)
}
}
Expand All @@ -162,7 +162,7 @@ internal class ClearanceRequests(
val response = call.await()

return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody ->
val status = Json.decodeFromString<RequestClearanceResponse>(responseBody)
val status = jsonIg.decodeFromString<RequestClearanceResponse>(responseBody)
Response(status)
}
}
Expand All @@ -174,14 +174,14 @@ internal class ClearanceRequests(
): RequestBody {
val vehicle = buildJsonObject {
put("vin", vin)
put("brand", Json.encodeToJsonElement(brand))
put("brand", jsonIg.encodeToJsonElement(brand))
if (controlMeasures != null) {
putJsonObject("control_measures") {
for (controlMeasure in controlMeasures) {
// polymorphism adds type key to child controlmeasure classes. remove with filter
val json = Json.encodeToJsonElement(controlMeasure)
val json = jsonIg.encodeToJsonElement(controlMeasure)
val valuesWithoutType = json.jsonObject.filterNot { it.key == "type" }
val jsonTrimmed = Json.encodeToJsonElement(valuesWithoutType)
val jsonTrimmed = jsonIg.encodeToJsonElement(valuesWithoutType)
put("odometer", jsonTrimmed)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ internal open class Requests(
) {
val mediaType = "application/json; charset=utf-8".toMediaType()

protected val jsonIg = Json {
ignoreUnknownKeys = true
}

private val jsonIgPr = Json {
ignoreUnknownKeys = true
prettyPrint = true
}

// note: inline functions are not shown in coverage
@Suppress("TooGenericExceptionCaught")
inline fun <T> tryParseResponse(
Expand All @@ -66,20 +75,18 @@ internal open class Requests(
}

fun printRequest(request: Request) {
val format = Json { prettyPrint = true }

// parse into json, so can log it out with pretty print
val body = request.bodyAsString()
var bodyInPrettyPrint = ""
if (!body.isNullOrBlank()) {
val jsonElement = format.decodeFromString<JsonElement>(body)
bodyInPrettyPrint = format.encodeToString(jsonElement)
val jsonElement = jsonIgPr.decodeFromString<JsonElement>(body)
bodyInPrettyPrint = jsonIgPr.encodeToString(jsonElement)
}

logger.debug(
"sending ${request.method} ${request.url}:" +
"\nheaders: ${request.headers}" +
"body: $bodyInPrettyPrint"
"\nheaders: ${request.headers}" +
"body: $bodyInPrettyPrint"
)
}

Expand All @@ -90,14 +97,14 @@ internal open class Requests(
}

fun <T> parseError(responseBody: String): com.highmobility.hmkitfleet.network.Response<T> {
val json = Json.parseToJsonElement(responseBody)
val json = jsonIg.parseToJsonElement(responseBody)

return if (json is JsonObject) {
// there are 3 error formats
val errors = json["errors"] as? JsonArray
parseErrorsArray(errors, json)
} else if (json is JsonArray && json.size > 0) {
val error = Json.decodeFromJsonElement<Error>(json.first())
val error = jsonIg.decodeFromJsonElement<Error>(json.first())
Response(null, error)
} else {
Response(null, genericError("Unknown server response"))
Expand All @@ -109,7 +116,7 @@ internal open class Requests(
json: JsonObject
): com.highmobility.hmkitfleet.network.Response<T> = if (errors != null && errors.size > 0) {
val error =
Json.decodeFromJsonElement<Error>(errors.first())
jsonIg.decodeFromJsonElement<Error>(errors.first())
Response(null, error)
} else {
val error = Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ internal class UtilityRequests(
val response = call.await()

return tryParseResponse(response, HttpURLConnection.HTTP_OK) { responseBody ->
val eligibilityStatus = Json.decodeFromString<EligibilityStatus>(responseBody)
val eligibilityStatus = jsonIg.decodeFromString<EligibilityStatus>(responseBody)
if (eligibilityStatus.vin != vin) logger.warn("VIN in response does not match VIN in request")
Response(eligibilityStatus, null)
}
Expand All @@ -82,10 +82,10 @@ internal class UtilityRequests(
): RequestBody {
val vehicle = buildJsonObject {
put("vin", vin)
put("brand", Json.encodeToJsonElement(brand))
put("brand", jsonIg.encodeToJsonElement(brand))
}

val body = Json.encodeToString(vehicle).toRequestBody(mediaType)
val body = jsonIg.encodeToString(vehicle).toRequestBody(mediaType)
return body
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,22 @@ internal class AccessTokenRequestsTest : BaseTest() {

@Test
fun downloadsAccessTokenAndWritesToCacheIfDoesNotExistOrExpired() {
val responseAccessToken = notExpiredAccessToken()
// return null from cache at first, then on next call a new one
// if auth token is expired, the cache does not return it also
every { cache getProperty "accessToken" } returnsMany listOf(null, responseAccessToken)
for (i in 0..1) {
val responseAccessToken = notExpiredAccessToken()
// return null from cache at first, then on next call a new one
// if auth token is expired, the cache does not return it also
every { cache getProperty "accessToken" } returnsMany listOf(null, responseAccessToken)

val response = runBlocking {
mockSuccessfulRequest(responseAccessToken, 200).getAccessToken()
}
val response = runBlocking {
mockSuccessfulRequest(responseAccessToken, i == 0, 200).getAccessToken()
}

val recordedRequest: RecordedRequest = mockWebServer.takeRequest()
assertTrue(recordedRequest.path!!.endsWith("/access_tokens"))
val recordedRequest: RecordedRequest = mockWebServer.takeRequest()
assertTrue(recordedRequest.path!!.endsWith("/access_tokens"))

verifyNewAccessTokenReturned(responseAccessToken, response)
verify { cache setProperty "accessToken" value responseAccessToken }
verifyNewAccessTokenReturned(responseAccessToken, response)
verify { cache setProperty "accessToken" value responseAccessToken }
}
}

private fun verifyNewAccessTokenReturned(expected: AccessToken, response: Response<AccessToken>) {
Expand All @@ -93,7 +95,7 @@ internal class AccessTokenRequestsTest : BaseTest() {
val responseAccessToken = notExpiredAccessToken()
// return null from cache at first, then on next call a new one
every { cache getProperty "accessToken" } returns responseAccessToken
val response = runBlocking { mockSuccessfulRequest(responseAccessToken, 201).getAccessToken() }
val response = runBlocking { mockSuccessfulRequest(responseAccessToken, false, 201).getAccessToken() }

// this means request is not made
verify(exactly = 0) { cache setProperty "accessToken" value any<AccessToken>() }
Expand All @@ -109,9 +111,9 @@ internal class AccessTokenRequestsTest : BaseTest() {
.setResponseCode(HttpURLConnection.HTTP_UNAUTHORIZED)
.setBody(
"{\"errors\":" +
"[{\"detail\":\"Missing or invalid assertion. It must be a JWT signed with the service account key.\"," +
"\"source\":\"assertion\"," +
"\"title\":\"Not authorized\"}]}"
"[{\"detail\":\"Missing or invalid assertion. It must be a JWT signed with the service account key.\"," +
"\"source\":\"assertion\"," +
"\"title\":\"Not authorized\"}]}"
)
mockWebServer.enqueue(mockResponse)
val baseUrl: HttpUrl = mockWebServer.url("")
Expand Down Expand Up @@ -163,9 +165,16 @@ internal class AccessTokenRequestsTest : BaseTest() {
assertTrue(status.error!!.title == genericError.title)
}

private fun mockSuccessfulRequest(responseAccessToken: AccessToken, responseCode: Int = 201): AccessTokenRequests {
private fun mockSuccessfulRequest(
responseAccessToken: AccessToken,
addUnknownKey: Boolean = false,
responseCode: Int = 201
): AccessTokenRequests {
// return null from cache at first, then on next call a new one
val json = Json.encodeToString(responseAccessToken)
var json = Json.encodeToString(responseAccessToken)
if (addUnknownKey) {
json = json.replaceFirst("{", "{\"unknownKey\":\"unknownValue\",")
}

val mockResponse = MockResponse()
.setResponseCode(responseCode)
Expand Down
Loading

0 comments on commit b709a61

Please sign in to comment.