Skip to content

Commit 3a7cb2e

Browse files
committed
fix: use JSON API types to create a vehicle inspection
1 parent c3bc772 commit 3a7cb2e

File tree

9 files changed

+138
-145
lines changed

9 files changed

+138
-145
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies {
2525
implementation(libs.jetbrains.kotlinx.serialization.json)
2626
implementation(libs.jsonapi.converter)
2727
implementation(libs.jackson.datatype.jsr)
28+
implementation(libs.jackson.kotlin)
2829

2930
testImplementation(kotlin("test"))
3031
testImplementation(libs.mockk)

gradle/libs.versions.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ ktor = "3.0.3"
55
kotlinSerialization = "1.7.3"
66
mockk = "1.9.3"
77
jsonapi = "0.14"
8+
jacksonKotlin = "2.18.2"
89

910
[libraries]
1011
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
@@ -17,4 +18,5 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref =
1718
jetbrains-kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" }
1819
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
1920
jsonapi-converter = { module = "com.github.jasminb:jsonapi-converter", version.ref = "jsonapi" }
20-
jackson-datatype-jsr = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "datatypeJsr" }
21+
jackson-datatype-jsr = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "datatypeJsr" }
22+
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jacksonKotlin" }

src/main/kotlin/com/ctrlhub/core/assets/vehicles/VehicleInspectionsRouter.kt

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
package com.ctrlhub.core.assets.vehicles
22

3-
import com.ctrlhub.core.api.ApiException
4-
import com.ctrlhub.core.api.ApiResourcePayload
5-
import com.ctrlhub.core.assets.vehicles.payload.VehicleInspectionPayload
6-
import com.ctrlhub.core.assets.vehicles.response.VehicleInspection
3+
import com.ctrlhub.core.assets.vehicles.resource.VehicleInspection
74
import com.ctrlhub.core.router.Router
85
import com.ctrlhub.core.router.request.RequestParameters
96
import io.ktor.client.*
10-
import io.ktor.http.*
117

128
/**
139
* A router that interacts with the vehicle inspections realm of the Ctrl Hub API
@@ -64,21 +60,13 @@ class VehicleInspectionsRouter(httpClient: HttpClient) : Router(httpClient) {
6460
*
6561
* @return A result representing the outcome of this operation
6662
*/
67-
suspend fun create(organisationId: String, vehicleId: String, payload: VehicleInspectionPayload): VehicleInspection {
63+
suspend fun create(organisationId: String, vehicleId: String, payload: VehicleInspection): VehicleInspection {
6864
val endpoint = "/v3/orgs/$organisationId/assets/vehicles/$vehicleId/inspections"
6965

70-
val response = performPost(
71-
endpoint, body = ApiResourcePayload(
72-
type = resourceType,
73-
data = payload
74-
)
66+
return postJsonApiResource(
67+
endpoint = endpoint,
68+
requestBody = payload
7569
)
76-
77-
if (response.status !== HttpStatusCode.Created) {
78-
throw ApiException("Unable to create vehicle inspection")
79-
}
80-
81-
return fetchJsonApiResource(response)
8270
}
8371
}
8472

src/main/kotlin/com/ctrlhub/core/assets/vehicles/payload/VehicleInspectionPayload.kt

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.ctrlhub.core.assets.vehicles.resource
2+
3+
import com.ctrlhub.core.serializer.JacksonLocalDateTimeSerializer
4+
import com.fasterxml.jackson.annotation.JsonCreator
5+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
6+
import com.fasterxml.jackson.annotation.JsonProperty
7+
import com.fasterxml.jackson.databind.annotation.JsonSerialize
8+
import com.github.jasminb.jsonapi.StringIdHandler
9+
import com.github.jasminb.jsonapi.annotations.Id
10+
import com.github.jasminb.jsonapi.annotations.Type
11+
import kotlinx.serialization.SerialName
12+
import java.time.LocalDateTime
13+
14+
@Type("vehicle-inspections")
15+
@JsonIgnoreProperties(ignoreUnknown = true)
16+
class VehicleInspection @JsonCreator constructor(
17+
@JsonProperty("id") @Id(StringIdHandler::class) var id: String = "",
18+
@JsonProperty("checks") var checks: VehicleInspectionChecks? = null,
19+
@SerialName("inspected_at") @JsonProperty("inspected_at") var inspectedAt: LocalDateTime? = null
20+
) {
21+
constructor() : this(
22+
id = "",
23+
checks = null,
24+
inspectedAt = null
25+
)
26+
}
27+
28+
@JsonIgnoreProperties(ignoreUnknown = true)
29+
class VehicleInspectionChecks @JsonCreator constructor(
30+
@JsonProperty("visible_damage") var visibleDamage: Boolean? = null,
31+
@JsonProperty("tyres") var tyres: Boolean? = null,
32+
@JsonProperty("washers_and_wipers") var washersAndWipers: Boolean? = null,
33+
@JsonProperty("windscreen") var windscreen: Boolean? = null,
34+
@JsonProperty("number_plate") var numberPlate: Boolean? = null,
35+
@JsonProperty("security") var security: Boolean? = null,
36+
@JsonProperty("accessories") var accessories: Boolean? = null,
37+
@JsonProperty("spare_number_plate") var spareNumberPlate: Boolean? = null,
38+
@JsonProperty("safe_access") var safeAccess: Boolean? = null,
39+
@JsonProperty("reversing_alarm") var reversingAlarm: Boolean? = null,
40+
@JsonProperty("beacons") var beacons: Boolean? = null,
41+
@JsonProperty("chemicals_and_fuel") var chemicalsAndFuel: Boolean? = null,
42+
@JsonProperty("storage") var storage: Boolean? = null,
43+
@JsonProperty("lights_and_indicators") var lightsAndIndicators: Boolean? = null,
44+
@JsonProperty("engine_warning_lights") var engineWarningLights: Boolean? = null,
45+
@JsonProperty("servicing") var servicing: Boolean? = null,
46+
@JsonProperty("levels") var levels: Boolean? = null,
47+
@JsonProperty("cleanliness") var cleanliness: Boolean? = null,
48+
@JsonProperty("driver_checks") var driverChecks: Boolean? = null
49+
) {
50+
constructor() : this(
51+
visibleDamage = null,
52+
tyres = null,
53+
washersAndWipers = null,
54+
windscreen = null,
55+
numberPlate = null,
56+
security = null,
57+
accessories = null,
58+
spareNumberPlate = null,
59+
safeAccess = null,
60+
reversingAlarm = null,
61+
beacons = null,
62+
chemicalsAndFuel = null,
63+
storage = null,
64+
lightsAndIndicators = null,
65+
engineWarningLights = null,
66+
servicing = null,
67+
levels = null,
68+
cleanliness = null,
69+
driverChecks = null
70+
)
71+
}

src/main/kotlin/com/ctrlhub/core/assets/vehicles/response/VehicleInspection.kt

Lines changed: 0 additions & 82 deletions
This file was deleted.

src/main/kotlin/com/ctrlhub/core/assets/vehicles/response/VehiclesResponse.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.ctrlhub.core.assets.vehicles.response
22

33
import com.ctrlhub.core.api.Assignable
4-
import com.ctrlhub.core.iam.response.User
54
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
65
import com.fasterxml.jackson.annotation.JsonProperty
76
import com.github.jasminb.jsonapi.StringIdHandler

src/main/kotlin/com/ctrlhub/core/router/Router.kt

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ package com.ctrlhub.core.router
33
import com.ctrlhub.core.api.ApiClientException
44
import com.ctrlhub.core.api.ApiException
55
import com.ctrlhub.core.api.UnauthorizedException
6+
import com.ctrlhub.core.serializer.JacksonLocalDateTimeDeserializer
7+
import com.ctrlhub.core.serializer.JacksonLocalDateTimeSerializer
8+
import com.fasterxml.jackson.module.kotlin.kotlinModule
69
import com.fasterxml.jackson.databind.ObjectMapper
10+
import com.fasterxml.jackson.databind.module.SimpleModule
711
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
12+
import com.github.jasminb.jsonapi.JSONAPIDocument
813
import com.github.jasminb.jsonapi.ResourceConverter
914
import com.github.jasminb.jsonapi.SerializationFeature
1015
import io.ktor.client.*
@@ -13,6 +18,7 @@ import io.ktor.client.plugins.ClientRequestException
1318
import io.ktor.client.request.*
1419
import io.ktor.client.statement.*
1520
import io.ktor.http.*
21+
import java.time.LocalDateTime
1622

1723
abstract class Router(val httpClient: HttpClient) {
1824
protected suspend fun performGet(endpoint: String, queryString: Map<String, String> = emptyMap()): HttpResponse {
@@ -23,11 +29,15 @@ abstract class Router(val httpClient: HttpClient) {
2329
}
2430
}
2531

26-
protected suspend inline fun <reified T> performPost(endpoint: String, body: T, queryString: Map<String, String> = emptyMap()): HttpResponse {
32+
protected suspend inline fun <reified T> performPost(
33+
endpoint: String,
34+
body: T,
35+
queryParameters: Map<String, String> = emptyMap()
36+
): HttpResponse {
2737
return httpClient.post(endpoint) {
2838
contentType(ContentType.Application.Json)
2939
url {
30-
queryString.forEach { key, value -> parameters.append(key, value) }
40+
queryParameters.forEach { key, value -> parameters.append(key, value) }
3141
}
3242
setBody(body)
3343
}
@@ -125,8 +135,50 @@ abstract class Router(val httpClient: HttpClient) {
125135
}
126136

127137
fun getObjectMapper(): ObjectMapper {
138+
val module = SimpleModule().apply {
139+
addSerializer(LocalDateTime::class.java, JacksonLocalDateTimeSerializer())
140+
addDeserializer(LocalDateTime::class.java, JacksonLocalDateTimeDeserializer())
141+
}
142+
128143
return ObjectMapper().apply {
129144
registerModule(JavaTimeModule())
145+
registerModule(module)
146+
registerModule(kotlinModule())
147+
}
148+
}
149+
150+
protected suspend inline fun <reified T> postJsonApiResource(
151+
endpoint: String,
152+
requestBody: T,
153+
queryParameters: Map<String, String> = emptyMap(),
154+
vararg includedClasses: Class<*>
155+
): T {
156+
return try {
157+
val resourceConverter = ResourceConverter(getObjectMapper(), T::class.java, *includedClasses).apply {
158+
enableSerializationOption(SerializationFeature.INCLUDE_RELATIONSHIP_ATTRIBUTES)
159+
}
160+
161+
val jsonApiRequestDocument = JSONAPIDocument(requestBody)
162+
val jsonApiRequest = resourceConverter.writeDocument(jsonApiRequestDocument)
163+
164+
val response: HttpResponse = performPost(
165+
endpoint = endpoint,
166+
body = jsonApiRequest,
167+
queryParameters = queryParameters
168+
)
169+
170+
val jsonApiResponse = resourceConverter.readDocument<T>(
171+
response.body<ByteArray>(), T::class.java
172+
)
173+
174+
jsonApiResponse.get() ?: throw ApiException("Failed to parse JSON:API response for $endpoint", Exception())
175+
} catch (e: ClientRequestException) {
176+
if (e.response.status == HttpStatusCode.Unauthorized) {
177+
throw UnauthorizedException("Unauthorized action: $endpoint", e.response, e)
178+
}
179+
throw ApiClientException("Request failed: $endpoint", e.response, e)
180+
} catch (e: Exception) {
181+
throw ApiException("Request failed: $endpoint", e)
130182
}
131183
}
132184
}

src/test/kotlin/com/ctrlhub/core/assets/vehicles/VehicleInspectionsRouterTest.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package com.ctrlhub.core.assets.vehicles
22

3-
import com.ctrlhub.core.assets.vehicles.payload.VehicleInspectionChecksPayload
4-
import com.ctrlhub.core.assets.vehicles.payload.VehicleInspectionPayload
5-
import com.ctrlhub.core.assets.vehicles.response.VehicleInspection
3+
import com.ctrlhub.core.assets.vehicles.resource.VehicleInspection
4+
import com.ctrlhub.core.assets.vehicles.resource.VehicleInspectionChecks
65
import com.ctrlhub.core.configureForTest
76
import io.ktor.client.*
87
import io.ktor.client.engine.mock.*
@@ -14,7 +13,6 @@ import java.nio.file.Paths
1413
import java.time.LocalDateTime
1514
import kotlin.test.assertIs
1615
import kotlin.test.assertNotNull
17-
import kotlin.test.assertTrue
1816

1917
class VehicleInspectionsRouterTest {
2018
@Test
@@ -89,8 +87,8 @@ class VehicleInspectionsRouterTest {
8987
val result = vehicleInspectionsRouter.create(
9088
organisationId = "org-123",
9189
vehicleId = "vehicle-123",
92-
payload = VehicleInspectionPayload(
93-
checks = VehicleInspectionChecksPayload(
90+
payload = VehicleInspection(
91+
checks = VehicleInspectionChecks(
9492
accessories = listOf(true, false, null).random(),
9593
beacons = listOf(true, false, null).random(),
9694
chemicalsAndFuel = listOf(true, false, null).random(),

0 commit comments

Comments
 (0)