generated from pagopa/template-payments-java-repository
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: ErrorHandling and LocalDateTime json serialization (#21)
- Loading branch information
1 parent
7054748
commit 6748d91
Showing
12 changed files
with
514 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
src/main/java/it/gov/pagopa/pu/pagopapayments/config/json/JsonConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package it.gov.pagopa.pu.pagopapayments.config.json; | ||
|
||
import com.fasterxml.jackson.annotation.JsonAutoDetect; | ||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
import com.fasterxml.jackson.annotation.PropertyAccessor; | ||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.SerializationFeature; | ||
import com.fasterxml.jackson.databind.module.SimpleModule; | ||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; | ||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | ||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.TimeZone; | ||
|
||
@Configuration | ||
public class JsonConfig { | ||
|
||
@Bean | ||
public ObjectMapper objectMapper() { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
mapper.registerModule(configureDateTimeModule()); | ||
mapper.registerModule(new Jdk8Module()); | ||
mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.DEFAULT)); | ||
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); | ||
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); | ||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | ||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); | ||
mapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY); | ||
mapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY); | ||
mapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY); | ||
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); | ||
mapper.setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.ANY); | ||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); | ||
mapper.setTimeZone(TimeZone.getDefault()); | ||
return mapper; | ||
} | ||
|
||
/** openApi is documenting LocalDateTime as date-time, which is interpreted as an OffsetDateTime by openApiGenerator */ | ||
private static SimpleModule configureDateTimeModule() { | ||
return new JavaTimeModule() | ||
.addSerializer(LocalDateTime.class, new LocalDateTimeToOffsetDateTimeSerializer()) | ||
.addDeserializer(LocalDateTime.class, new OffsetDateTimeToLocalDateTimeDeserializer()); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
.../it/gov/pagopa/pu/pagopapayments/config/json/LocalDateTimeToOffsetDateTimeSerializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package it.gov.pagopa.pu.pagopapayments.config.json; | ||
|
||
import com.fasterxml.jackson.core.JsonGenerator; | ||
import com.fasterxml.jackson.databind.JsonSerializer; | ||
import com.fasterxml.jackson.databind.SerializerProvider; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
import java.io.IOException; | ||
import java.time.LocalDateTime; | ||
import java.time.OffsetDateTime; | ||
import java.time.ZoneId; | ||
|
||
@Configuration | ||
public class LocalDateTimeToOffsetDateTimeSerializer extends JsonSerializer<LocalDateTime> { | ||
|
||
@Override | ||
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { | ||
if (value != null) { | ||
OffsetDateTime offsetDateTime = value.atZone(ZoneId.systemDefault()).toOffsetDateTime(); | ||
gen.writeString(offsetDateTime.toString()); | ||
} | ||
} | ||
} | ||
|
27 changes: 27 additions & 0 deletions
27
...t/gov/pagopa/pu/pagopapayments/config/json/OffsetDateTimeToLocalDateTimeDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package it.gov.pagopa.pu.pagopapayments.config.json; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JsonDeserializer; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
import java.io.IOException; | ||
import java.time.LocalDateTime; | ||
import java.time.OffsetDateTime; | ||
|
||
|
||
@Configuration | ||
public class OffsetDateTimeToLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> { | ||
|
||
@Override | ||
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { | ||
|
||
String dateString = p.getValueAsString(); | ||
if(dateString.contains("+")){ | ||
return OffsetDateTime.parse(dateString).toLocalDateTime(); | ||
} else { | ||
return LocalDateTime.parse(dateString); | ||
} | ||
} | ||
} | ||
|
33 changes: 0 additions & 33 deletions
33
src/main/java/it/gov/pagopa/pu/pagopapayments/exception/ControllerExceptionHandler.java
This file was deleted.
Oops, something went wrong.
101 changes: 101 additions & 0 deletions
101
src/main/java/it/gov/pagopa/pu/pagopapayments/exception/PagopaPaymentsExceptionHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package it.gov.pagopa.pu.pagopapayments.exception; | ||
|
||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import it.gov.pagopa.pu.pagopapayments.dto.generated.PagoPaPaymentsErrorDTO; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.validation.ValidationException; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.HttpStatusCode; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.http.converter.HttpMessageNotReadableException; | ||
import org.springframework.validation.FieldError; | ||
import org.springframework.web.ErrorResponse; | ||
import org.springframework.web.bind.MethodArgumentNotValidException; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
||
import java.util.stream.Collectors; | ||
|
||
@RestControllerAdvice | ||
@Slf4j | ||
public class PagopaPaymentsExceptionHandler { | ||
|
||
@ExceptionHandler(NotFoundException.class) | ||
@ResponseStatus(value = HttpStatus.NOT_FOUND) | ||
public ResponseEntity<PagoPaPaymentsErrorDTO> handleResourceNotFoundException(NotFoundException ex, HttpServletRequest request) { | ||
return handleException(ex, request, HttpStatus.NOT_FOUND, PagoPaPaymentsErrorDTO.CodeEnum.NOT_FOUND); | ||
} | ||
|
||
@ExceptionHandler({ValidationException.class, HttpMessageNotReadableException.class, MethodArgumentNotValidException.class}) | ||
public ResponseEntity<PagoPaPaymentsErrorDTO> handleViolationException(Exception ex, HttpServletRequest request) { | ||
return handleException(ex, request, HttpStatus.BAD_REQUEST, PagoPaPaymentsErrorDTO.CodeEnum.BAD_REQUEST); | ||
} | ||
|
||
@ExceptionHandler({ServletException.class}) | ||
public ResponseEntity<PagoPaPaymentsErrorDTO> handleServletException(ServletException ex, HttpServletRequest request) { | ||
HttpStatusCode httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; | ||
PagoPaPaymentsErrorDTO.CodeEnum errorCode = PagoPaPaymentsErrorDTO.CodeEnum.GENERIC_ERROR; | ||
if (ex instanceof ErrorResponse errorResponse) { | ||
httpStatus = errorResponse.getStatusCode(); | ||
if (httpStatus.is4xxClientError()) { | ||
errorCode = PagoPaPaymentsErrorDTO.CodeEnum.BAD_REQUEST; | ||
} | ||
} | ||
return handleException(ex, request, httpStatus, errorCode); | ||
} | ||
|
||
@ExceptionHandler({RuntimeException.class}) | ||
public ResponseEntity<PagoPaPaymentsErrorDTO> handleRuntimeException(RuntimeException ex, HttpServletRequest request) { | ||
return handleException(ex, request, HttpStatus.INTERNAL_SERVER_ERROR, PagoPaPaymentsErrorDTO.CodeEnum.GENERIC_ERROR); | ||
} | ||
|
||
static ResponseEntity<PagoPaPaymentsErrorDTO> handleException(Exception ex, HttpServletRequest request, HttpStatusCode httpStatus, PagoPaPaymentsErrorDTO.CodeEnum errorEnum) { | ||
logException(ex, request, httpStatus); | ||
|
||
String message = buildReturnedMessage(ex); | ||
|
||
return ResponseEntity | ||
.status(httpStatus) | ||
.body(new PagoPaPaymentsErrorDTO(errorEnum, message)); | ||
} | ||
|
||
private static void logException(Exception ex, HttpServletRequest request, HttpStatusCode httpStatus) { | ||
log.info("A {} occurred handling request {}: HttpStatus {} - {}", | ||
ex.getClass(), | ||
getRequestDetails(request), | ||
httpStatus.value(), | ||
ex.getMessage()); | ||
} | ||
|
||
private static String buildReturnedMessage(Exception ex) { | ||
if (ex instanceof HttpMessageNotReadableException) { | ||
if(ex.getCause() instanceof JsonMappingException jsonMappingException){ | ||
return "Cannot parse body: " + | ||
jsonMappingException.getPath().stream() | ||
.map(JsonMappingException.Reference::getFieldName) | ||
.collect(Collectors.joining(".")) + | ||
": " + jsonMappingException.getOriginalMessage(); | ||
} | ||
return "Required request body is missing"; | ||
} else if (ex instanceof MethodArgumentNotValidException methodArgumentNotValidException) { | ||
return "Invalid request content:" + | ||
methodArgumentNotValidException.getBindingResult() | ||
.getAllErrors().stream() | ||
.map(e -> " " + | ||
(e instanceof FieldError fieldError? fieldError.getField(): e.getObjectName()) + | ||
": " + e.getDefaultMessage()) | ||
.sorted() | ||
.collect(Collectors.joining(";")); | ||
} else { | ||
return ex.getMessage(); | ||
} | ||
} | ||
|
||
static String getRequestDetails(HttpServletRequest request) { | ||
return "%s %s".formatted(request.getMethod(), request.getRequestURI()); | ||
} | ||
|
||
} |
51 changes: 51 additions & 0 deletions
51
...gov/pagopa/pu/pagopapayments/config/json/LocalDateTimeToOffsetDateTimeSerializerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package it.gov.pagopa.pu.pagopapayments.config.json; | ||
|
||
import com.fasterxml.jackson.core.JsonGenerator; | ||
import com.fasterxml.jackson.databind.SerializerProvider; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
|
||
import java.io.IOException; | ||
import java.time.LocalDateTime; | ||
import java.util.TimeZone; | ||
|
||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.verifyNoInteractions; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
class LocalDateTimeToOffsetDateTimeSerializerTest { | ||
|
||
@Mock | ||
private JsonGenerator jsonGenerator; | ||
|
||
@Mock | ||
private SerializerProvider serializerProvider; | ||
|
||
private LocalDateTimeToOffsetDateTimeSerializer dateTimeSerializer; | ||
|
||
@BeforeEach | ||
public void setUp() { | ||
dateTimeSerializer = new LocalDateTimeToOffsetDateTimeSerializer(); | ||
} | ||
|
||
@Test | ||
void testDateSerializer() throws IOException { | ||
LocalDateTime localDateTime = LocalDateTime.of(2025, 1, 16, 9, 15, 20); | ||
|
||
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Rome")); | ||
|
||
dateTimeSerializer.serialize(localDateTime, jsonGenerator, serializerProvider); | ||
|
||
verify(jsonGenerator).writeString("2025-01-16T09:15:20+01:00"); | ||
} | ||
|
||
@Test | ||
void testNullDateSerializer() throws IOException { | ||
dateTimeSerializer.serialize(null, jsonGenerator, serializerProvider); | ||
|
||
verifyNoInteractions(jsonGenerator); | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
...v/pagopa/pu/pagopapayments/config/json/OffsetDateTimeToLocalDateTimeDeserializerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package it.gov.pagopa.pu.pagopapayments.config.json; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.mockito.Mockito; | ||
|
||
import java.io.IOException; | ||
import java.time.LocalDateTime; | ||
import java.time.OffsetDateTime; | ||
|
||
class OffsetDateTimeToLocalDateTimeDeserializerTest { | ||
|
||
private final OffsetDateTimeToLocalDateTimeDeserializer deserializer = new OffsetDateTimeToLocalDateTimeDeserializer(); | ||
|
||
@Test | ||
void givenOffsetDateTimeWhenThenOk() throws IOException { | ||
// Given | ||
OffsetDateTime offsetDateTime = OffsetDateTime.now(); | ||
JsonParser parser = Mockito.mock(JsonParser.class); | ||
Mockito.when(parser.getValueAsString()) | ||
.thenReturn(offsetDateTime.toString()); | ||
|
||
// When | ||
LocalDateTime result = deserializer.deserialize(parser, null); | ||
|
||
// Then | ||
Assertions.assertEquals(offsetDateTime.toLocalDateTime(), result); | ||
} | ||
|
||
@Test | ||
void givenLocalDateTimeWhenThenOk() throws IOException { | ||
// Given | ||
LocalDateTime localDateTime = LocalDateTime.now(); | ||
JsonParser parser = Mockito.mock(JsonParser.class); | ||
Mockito.when(parser.getValueAsString()) | ||
.thenReturn(localDateTime.toString()); | ||
|
||
// When | ||
LocalDateTime result = deserializer.deserialize(parser, null); | ||
|
||
// Then | ||
Assertions.assertEquals(localDateTime, result); | ||
} | ||
} |
Oops, something went wrong.