Skip to content

Commit d675191

Browse files
authored
CDPS-1054: Allow different field types (#9)
* CDPS-1054: Updated the v1 core person record patch endpoint to allow fields with non string data types to be updated. * CDPS-1054: Removed unused protected characteristics code. * CDPS-1054: Added ADR for the decision to use one field per request for v1 patch endpoints. * CDPS-1054: Fixed json subtype reference to name it the enum value. * CDPS-1054: Replaced the field name enum with a companion object to allow compile time constants. * CDPS-1054: Set fieldName type to String. * CDPS-1054: Switched to @parameter instead of @Schema for query parameter.
1 parent e03ceb1 commit d675191

File tree

12 files changed

+129
-64
lines changed

12 files changed

+129
-64
lines changed

architecture-decision-record/0001-structure-packages-by-api-version.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[< Previous](0001-structure-packages-by-api-version.md),
1+
[< Previous](0000-separate-domain-specific-code-by-package.md),
22
[Contents](README.md),
33
[Next >](0002-include-username-in-client-credential-token.md)
44

architecture-decision-record/0002-include-username-in-client-credential-token.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
[< Previous](0002-include-username-in-client-credential-token.md),
1+
[< Previous](0001-structure-packages-by-api-version.md),
22
[Contents](README.md),
3-
[Next >](9999-end.md)
3+
[Next >](0003-limit-v1-patch-endpoints-to-one-field-per-request.md)
44

55

66

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[< Previous](0002-include-username-in-client-credential-token.md),
2+
[Contents](README.md),
3+
[Next >](9999-end.md)
4+
5+
6+
7+
# 3. Limit V1 patch endpoints to updating one field per request
8+
9+
Date: 2024-11-28
10+
11+
## Status
12+
13+
✅ Accepted
14+
15+
## Context
16+
17+
For version 1 endpoints the HMPPS Person Integration API will make calls to the Prison API to update the source data in NOMIS. For simplicity the
18+
endpoints on the Prison API update a single field per request. Therefore, if the Person Integration API were to allow multiple fields to be updated in
19+
a single request this would require multiple calls to the Prison API. If one of these calls were to fail then we would need to roll back the changes
20+
from any successful calls in order to leave the data in a consistent state. This could be handled with retries and a fallback to reverse the updates
21+
however this will add complexity without a valuable user case for doing so.
22+
23+
## Decision
24+
25+
For simplicity the version 1 patch endpoints will allow a single field per-request while the Prison API is being used as the underlying datasource.
26+
27+
## Consequences
28+
N/A
29+

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/annotation/ValidPrisonerNumber.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package uk.gov.justice.digital.hmpps.personintegrationapi.common.annotation
22

3-
import io.swagger.v3.oas.annotations.media.Schema
3+
import io.swagger.v3.oas.annotations.Parameter
44
import jakarta.validation.constraints.Pattern
55
import uk.gov.justice.digital.hmpps.personintegrationapi.common.Constants
66

7-
@Schema(
7+
@Parameter(
88
description = Constants.PRISONER_NUMBER_VALIDATION_MESSAGE,
99
example = "A12345",
10-
pattern = Constants.PRISONER_NUMBER_REGEX,
1110
)
1211
@Pattern(
1312
regexp = Constants.PRISONER_NUMBER_REGEX,
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,95 @@
11
package uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request
22

3+
import com.fasterxml.jackson.annotation.JsonSubTypes
4+
import com.fasterxml.jackson.annotation.JsonTypeInfo
35
import io.swagger.v3.oas.annotations.media.Schema
4-
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.enumeration.CorePersonRecordField
6+
import jakarta.validation.constraints.NotNull
7+
import org.springframework.format.annotation.DateTimeFormat
8+
import java.time.LocalDate
59

6-
@Schema(description = "Core Person Record V1 update request")
7-
data class CorePersonRecordV1UpdateRequestDto(
10+
@JsonTypeInfo(
11+
use = JsonTypeInfo.Id.NAME,
12+
include = JsonTypeInfo.As.EXISTING_PROPERTY,
13+
property = "fieldName",
14+
visible = true,
15+
)
16+
@JsonSubTypes(
17+
JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.BIRTHPLACE, value = BirthplaceUpdateDto::class),
18+
JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.COUNTRY_OF_BIRTH, value = CountryOfBirthUpdateDto::class),
19+
JsonSubTypes.Type(name = CorePersonRecordV1UpdateRequestDto.DATE_OF_BIRTH, value = DateOfBirthUpdateDto::class),
20+
)
21+
@Schema(description = "Core Person Record V1 update request base")
22+
sealed class CorePersonRecordV1UpdateRequestDto {
23+
abstract val fieldName: String
24+
abstract val value: Any?
25+
26+
companion object {
27+
const val BIRTHPLACE = "BIRTHPLACE"
28+
const val COUNTRY_OF_BIRTH = "COUNTRY_OF_BIRTH"
29+
const val DATE_OF_BIRTH = "DATE_OF_BIRTH"
30+
}
31+
}
32+
33+
@Schema(description = "Core Person Record V1 birthplace update request")
34+
data class BirthplaceUpdateDto(
835
@Schema(
36+
description = "The new value for the Birthplace",
37+
example = "London",
38+
required = true,
39+
nullable = false,
40+
)
41+
@field:NotNull()
42+
override val value: String,
43+
) : CorePersonRecordV1UpdateRequestDto() {
44+
@Schema(
45+
type = "String",
946
description = "The field to be updated",
10-
example = "BIRTHPLACE",
47+
allowableValues = [BIRTHPLACE],
1148
required = true,
1249
nullable = false,
1350
)
14-
val fieldName: CorePersonRecordField,
51+
override val fieldName: String = BIRTHPLACE
52+
}
1553

54+
@Schema(description = "Core Person Record V1 date of birth update request")
55+
data class DateOfBirthUpdateDto(
1656
@Schema(
17-
description = "The new value for the field",
18-
example = "London",
57+
description = "The new value for the date of birth field",
58+
example = "01/01/2000",
1959
required = true,
2060
nullable = false,
2161
)
22-
val fieldValue: String,
23-
)
62+
@field:NotNull()
63+
@field:DateTimeFormat(pattern = "dd/mm/yyyy")
64+
override val value: LocalDate,
65+
) : CorePersonRecordV1UpdateRequestDto() {
66+
@Schema(
67+
type = "String",
68+
description = "The field to be updated",
69+
allowableValues = [DATE_OF_BIRTH],
70+
required = true,
71+
nullable = false,
72+
)
73+
override val fieldName: String = DATE_OF_BIRTH
74+
}
75+
76+
@Schema(description = "Core Person Record V1 country of birth update request")
77+
data class CountryOfBirthUpdateDto(
78+
@Schema(
79+
description = "The new value for the country of brith field",
80+
example = "UK",
81+
required = true,
82+
nullable = false,
83+
)
84+
@field:NotNull()
85+
override val value: String,
86+
) : CorePersonRecordV1UpdateRequestDto() {
87+
@Schema(
88+
type = "String",
89+
description = "The field to be updated",
90+
allowableValues = [COUNTRY_OF_BIRTH],
91+
required = true,
92+
nullable = false,
93+
)
94+
override val fieldName: String = COUNTRY_OF_BIRTH
95+
}

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/enumeration/CorePersonRecordField.kt

-6
This file was deleted.

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1Resource.kt

+1-5
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,7 @@ class CorePersonRecordV1Resource(
8787
@RequestParam(required = true) @Valid @ValidPrisonerNumber prisonerNumber: String,
8888
@RequestBody(required = true) @Valid corePersonRecordUpdateRequest: CorePersonRecordV1UpdateRequestDto,
8989
): ResponseEntity<Void> {
90-
corePersonRecordService.updateCorePersonRecordField(
91-
prisonerNumber,
92-
corePersonRecordUpdateRequest.fieldName,
93-
corePersonRecordUpdateRequest.fieldValue,
94-
)
90+
corePersonRecordService.updateCorePersonRecordField(prisonerNumber, corePersonRecordUpdateRequest)
9591
return noContent().build()
9692
}
9793

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordService.kt

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ package uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.servi
33
import org.springframework.stereotype.Service
44
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.PrisonApiClient
55
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateBirthPlace
6-
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.enumeration.CorePersonRecordField
6+
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.BirthplaceUpdateDto
7+
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.CorePersonRecordV1UpdateRequestDto
78
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.exception.UnknownCorePersonFieldException
89

910
@Service
1011
class CorePersonRecordService(
1112
private val prisonApiClient: PrisonApiClient,
1213
) {
1314

14-
fun updateCorePersonRecordField(prisonerNumber: String, field: CorePersonRecordField, value: String) {
15-
when (field) {
16-
CorePersonRecordField.BIRTHPLACE -> prisonApiClient.updateBirthPlaceForWorkingName(prisonerNumber, UpdateBirthPlace(value))
17-
else -> throw UnknownCorePersonFieldException("Field '$field' cannot be updated.")
15+
fun updateCorePersonRecordField(prisonerNumber: String, updateRequestDto: CorePersonRecordV1UpdateRequestDto) {
16+
when (updateRequestDto) {
17+
is BirthplaceUpdateDto -> prisonApiClient.updateBirthPlaceForWorkingName(prisonerNumber, UpdateBirthPlace(updateRequestDto.value))
18+
else -> throw UnknownCorePersonFieldException("Field '${updateRequestDto.fieldName}' cannot be updated.")
1819
}
1920
}
2021
}

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/personprotectercharacteristics/dto/request/PersonProtectedCharacteristicsV1UpdateRequestDto.kt

-23
This file was deleted.

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/personprotectercharacteristics/enumeration/ProtectedCharacteristicsField.kt

-5
This file was deleted.

src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1ResourceIntTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
166166
val VALID_PATCH_REQUEST_BODY =
167167
// language=json
168168
"""
169-
{
169+
{
170170
"fieldName": "BIRTHPLACE",
171-
"fieldValue": "London"
171+
"value": "London"
172172
}
173173
""".trimIndent()
174174

src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordServiceTest.kt

+5-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import org.mockito.kotlin.whenever
1414
import org.springframework.http.ResponseEntity
1515
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.PrisonApiClient
1616
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateBirthPlace
17-
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.enumeration.CorePersonRecordField
17+
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.BirthplaceUpdateDto
18+
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.DateOfBirthUpdateDto
1819
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.exception.UnknownCorePersonFieldException
20+
import java.time.LocalDate
1921

2022
@ExtendWith(MockitoExtension::class)
2123
class CorePersonRecordServiceTest {
@@ -40,13 +42,13 @@ class CorePersonRecordServiceTest {
4042
inner class UpdateCorePersonRecordField {
4143
@Test
4244
fun `can update the birthplace field`() {
43-
underTest.updateCorePersonRecordField(PRISONER_NUMBER, CorePersonRecordField.BIRTHPLACE, TEST_BIRTHPLACE_VALUE)
45+
underTest.updateCorePersonRecordField(PRISONER_NUMBER, BirthplaceUpdateDto(TEST_BIRTHPLACE_VALUE))
4446
}
4547

4648
@Test
4749
fun `throws an exception if the field type is not supported`() {
4850
assertThrows<UnknownCorePersonFieldException> {
49-
underTest.updateCorePersonRecordField(PRISONER_NUMBER, CorePersonRecordField.COUNTRY_OF_BIRTH, "")
51+
underTest.updateCorePersonRecordField(PRISONER_NUMBER, DateOfBirthUpdateDto(LocalDate.now()))
5052
}
5153
}
5254
}

0 commit comments

Comments
 (0)