diff --git a/support-core/src/main/java/no/entur/abt/mapstruct/common/ProtobufStandardMappings.java b/support-core/src/main/java/no/entur/abt/mapstruct/common/ProtobufStandardMappings.java index a2c630d..b76ac3a 100644 --- a/support-core/src/main/java/no/entur/abt/mapstruct/common/ProtobufStandardMappings.java +++ b/support-core/src/main/java/no/entur/abt/mapstruct/common/ProtobufStandardMappings.java @@ -62,7 +62,7 @@ default byte[] mapByteString(ByteString in) { } default ByteString mapByteStringToString(String string) { - return ByteString.copyFromUtf8(string); + return ByteString.copyFromUtf8(string != null ? string : ""); } default String mapStringToByteString(ByteString in) { @@ -74,7 +74,8 @@ default String mapStringToByteString(ByteString in) { } default com.google.type.Date mapLocalDate(LocalDate t) { - return com.google.type.Date.newBuilder().setYear(t.getYear()).setMonth(t.getMonthValue()).setDay(t.getDayOfMonth()).build(); + return t != null ? com.google.type.Date.newBuilder().setYear(t.getYear()).setMonth(t.getMonthValue()).setDay(t.getDayOfMonth()).build() + : com.google.type.Date.getDefaultInstance(); } default LocalDate mapDate(com.google.type.Date t) { @@ -82,7 +83,9 @@ default LocalDate mapDate(com.google.type.Date t) { } default com.google.type.TimeOfDay mapLocalTime(LocalTime t) { - return com.google.type.TimeOfDay.newBuilder().setHours(t.getHour()).setMinutes(t.getMinute()).setSeconds(t.getSecond()).setNanos(t.getNano()).build(); + return t != null + ? com.google.type.TimeOfDay.newBuilder().setHours(t.getHour()).setMinutes(t.getMinute()).setSeconds(t.getSecond()).setNanos(t.getNano()).build() + : com.google.type.TimeOfDay.getDefaultInstance(); } default LocalTime mapTimeOfDay(com.google.type.TimeOfDay t) { @@ -91,7 +94,7 @@ default LocalTime mapTimeOfDay(com.google.type.TimeOfDay t) { default Timestamp map(LocalDateTime i) { if (i == null) { - return null; + return Timestamp.getDefaultInstance(); } TimeZone systemDefault = TimeZone.getDefault(); @@ -103,7 +106,7 @@ default Timestamp map(LocalDateTime i) { } default Timestamp map(OffsetDateTime in) { - return Timestamp.newBuilder().setSeconds(in.toEpochSecond()).setNanos(0).build(); + return in != null ? Timestamp.newBuilder().setSeconds(in.toEpochSecond()).setNanos(0).build() : Timestamp.getDefaultInstance(); } default float map(FloatValue f) { @@ -143,22 +146,16 @@ default ByteString map(BytesValue f) { } default Instant mapToInstant(Timestamp t) { - if (t == null) { + if (t == null || Timestamp.getDefaultInstance().equals(t)) { return null; } - Timestamp sanitized = Timestamps.sanitize(t); - - if (sanitized != null) { - return Instant.ofEpochSecond(sanitized.getSeconds(), sanitized.getNanos()); - } else { - return null; - } + return Instant.ofEpochSecond(sanitized.getSeconds(), sanitized.getNanos()); } default Timestamp mapToTimestamp(Instant i) { if (i == null) { - return null; + return Timestamp.getDefaultInstance(); } Timestamp t = Timestamp.newBuilder().setSeconds(i.getEpochSecond()).setNanos(i.getNano()).build(); diff --git a/support-core/src/main/java/no/entur/abt/mapstruct/common/Timestamps.java b/support-core/src/main/java/no/entur/abt/mapstruct/common/Timestamps.java index bc5c17f..977f6b8 100644 --- a/support-core/src/main/java/no/entur/abt/mapstruct/common/Timestamps.java +++ b/support-core/src/main/java/no/entur/abt/mapstruct/common/Timestamps.java @@ -42,9 +42,6 @@ public class Timestamps { * Sanitize Timestamps outside legal range where possible. */ public static Timestamp sanitize(Timestamp t) { - if (t.getSeconds() == 0 && t.getNanos() == 0) { - return null; // Assuming null for epoch, cannot differentiate - } if (t.getNanos() < 0 || t.getNanos() >= NANOS_PER_SECOND) { throw new IllegalArgumentException(String.format( "Timestamp is not valid. See proto definition for valid values. Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]. Nanos (%s) must be in range [0, +999,999,999].", diff --git a/support-lite/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java b/support-lite/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java index 29a53f2..63723f7 100644 --- a/support-lite/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java +++ b/support-lite/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java @@ -48,11 +48,8 @@ default Long toEpochMilliseconds(Timestamp instance) { return instant == null ? null : instant.toEpochMilli(); } - default Timestamp fromEpochMilliseconds(Long instance) { - if (instance == null) { - return null; - } - Instant instant = Instant.ofEpochMilli(instance); + default Timestamp fromEpochMilliseconds(Long millis) { + Instant instant = Instant.ofEpochMilli(millis != null ? millis : 0L); return mapToTimestamp(instant); } @@ -61,6 +58,9 @@ default Duration mapDuration(com.google.protobuf.Duration t) { } default com.google.protobuf.Duration mapDuration(Duration t) { + if (t == null) { + return com.google.protobuf.Duration.getDefaultInstance(); + } long seconds = t.getSeconds(); int nanos = t.getNano(); diff --git a/support-standard/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java b/support-standard/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java index 95630c5..c758487 100644 --- a/support-standard/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java +++ b/support-standard/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java @@ -67,7 +67,7 @@ default com.google.protobuf.Duration mapDuration(Duration t) { if (t != null) { return Durations.fromNanos(t.toNanos()); } else { - return null; + return com.google.protobuf.Duration.getDefaultInstance(); } } diff --git a/support-standard/src/test/java/no/entur/abt/mapstruct/ProtobufStandardMappingsTest.java b/support-standard/src/test/java/no/entur/abt/mapstruct/ProtobufStandardMappingsTest.java index 7c725f4..a062009 100644 --- a/support-standard/src/test/java/no/entur/abt/mapstruct/ProtobufStandardMappingsTest.java +++ b/support-standard/src/test/java/no/entur/abt/mapstruct/ProtobufStandardMappingsTest.java @@ -23,120 +23,163 @@ * #L% */ -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import com.google.protobuf.ByteString; +import com.google.protobuf.Timestamp; +import com.google.protobuf.util.Durations; +import com.google.type.TimeOfDay; +import no.entur.abt.mapstruct.common.Timestamps; +import org.junit.jupiter.api.Test; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Test; - -import com.google.protobuf.Timestamp; -import com.google.protobuf.util.Durations; - -import no.entur.abt.mapstruct.common.Timestamps; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; public class ProtobufStandardMappingsTest { - no.entur.abt.mapstruct.ProtobufStandardMappings MAPPER = no.entur.abt.mapstruct.ProtobufStandardMappings.INSTANCE; - - @Test - public void testMapLocalDateToTimestampSummertime() { - LocalDateTime l = LocalDateTime.of(2000, 6, 1, 12, 0); - - Timestamp timestamp = MAPPER.map(l); - Instant instant = MAPPER.mapToInstant(timestamp); - - LocalDateTime back = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); - - assertEquals(l, back); - } - - @Test - public void testMapLocalDateToTimestampWintertime() { - LocalDateTime l = LocalDateTime.of(2000, 2, 1, 12, 0); - - Timestamp timestamp = MAPPER.map(l); - Instant instant = MAPPER.mapToInstant(timestamp); - - LocalDateTime back = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); - - assertEquals(l, back); - } - - @Test - public void mapToInstant_whenSecondsAndNanosIs0_thenMapToNull() { - assertNull(MAPPER.mapToInstant(Timestamp.newBuilder().build())); - } - - @Test - public void mapToInstant_whenSecondsAndNanosIsNull_thenMapToNull() { - assertNull(MAPPER.mapToInstant(null)); - } - - @Test - public void mapToInstant_whenNanosIsSet_thenMapToInstant() { - assertEquals(3000, MAPPER.mapToInstant(Timestamp.newBuilder().setNanos(3000).build()).getNano()); - } - - @Test - public void mapToInstant_whenValueIsTooLargeForRangeForTimestamp_thenMapFromMaxValidTimestamp() { - assertEquals(MAPPER.mapToInstant(Timestamps.MAX_VALUE), MAPPER.mapToInstant(Timestamp.newBuilder().setSeconds(Long.MAX_VALUE).build())); - } - - @Test - public void mapToInstant_whenValueIsTooSmallForRangeForTimestamp_thenMapFromMinValidTimestamp() { - assertEquals(MAPPER.mapToInstant(Timestamps.MIN_VALUE), MAPPER.mapToInstant(Timestamp.newBuilder().setSeconds(-Long.MAX_VALUE).build())); - } - - @Test - public void mapInstantToTimestamp_whenValueIsTooLargeForRangeForTimestamp_thenMapToMaxValidTimestamp() { - assertEquals(Timestamps.MAX_VALUE, MAPPER.mapToTimestamp(Instant.now().plus(Integer.MAX_VALUE, ChronoUnit.DAYS))); - } - - @Test - public void mapInstantToTimestamp_whenValueIsTooSmallForRangeForTimestamp_thenMapToMinValidTimestamp() { - assertEquals(Timestamps.MIN_VALUE, MAPPER.mapToTimestamp(Instant.now().minus(Integer.MAX_VALUE, ChronoUnit.DAYS))); - } - - @Test - public void mapPositiveDuration() { - Duration duration = Duration.of(3, ChronoUnit.NANOS); + no.entur.abt.mapstruct.ProtobufStandardMappings MAPPER = no.entur.abt.mapstruct.ProtobufStandardMappings.INSTANCE; - com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration); - Durations.checkValid(pbDuration); - assertEquals(duration, MAPPER.mapDuration(pbDuration)); - } + @Test + public void testMapLocalDateToTimestampSummertime() { + LocalDateTime l = LocalDateTime.of(2000, 6, 1, 12, 0); - @Test - public void mapNegativeDurationToProto_whenSecondsAreNegativeAndNanoPositive() { - Duration duration = Duration.ofSeconds(-3, 2); + Timestamp timestamp = MAPPER.map(l); + Instant instant = MAPPER.mapToInstant(timestamp); - com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration); - Durations.checkValid(pbDuration); - assertEquals(duration, MAPPER.mapDuration(pbDuration)); - } + LocalDateTime back = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + + assertEquals(l, back); + } - @Test - public void mapNegativeDurationToProto_whenSecondsArePositiveAndNanoNegative() { - // Duration.ofSeconds accepts negative values. Will still be stored as positive values in Duration - Duration duration = Duration.ofSeconds(3, -(TimeUnit.SECONDS.toNanos(1) - 2)); + @Test + public void testMapLocalDateToTimestampWintertime() { + LocalDateTime l = LocalDateTime.of(2000, 2, 1, 12, 0); - com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration); - Durations.checkValid(pbDuration); - assertEquals(duration, MAPPER.mapDuration(pbDuration)); - } + Timestamp timestamp = MAPPER.map(l); + Instant instant = MAPPER.mapToInstant(timestamp); + + LocalDateTime back = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + + assertEquals(l, back); + } + + @Test + public void mapToInstant_whenSecondsAndNanosIs0_thenMapToNull() { + assertNull(MAPPER.mapToInstant(Timestamp.newBuilder().build())); + } + + @Test + public void mapToInstant_whenSecondsAndNanosIsNull_thenMapToNull() { + assertNull(MAPPER.mapToInstant(null)); + } + + @Test + public void mapToInstant_whenNanosIsSet_thenMapToInstant() { + assertEquals(3000, MAPPER.mapToInstant(Timestamp.newBuilder().setNanos(3000).build()).getNano()); + } + + @Test + public void mapToInstant_whenValueIsTooLargeForRangeForTimestamp_thenMapFromMaxValidTimestamp() { + assertEquals(MAPPER.mapToInstant(Timestamps.MAX_VALUE), MAPPER.mapToInstant(Timestamp.newBuilder().setSeconds(Long.MAX_VALUE).build())); + } + + @Test + public void mapToInstant_whenValueIsTooSmallForRangeForTimestamp_thenMapFromMinValidTimestamp() { + assertEquals(MAPPER.mapToInstant(Timestamps.MIN_VALUE), MAPPER.mapToInstant(Timestamp.newBuilder().setSeconds(-Long.MAX_VALUE).build())); + } + + @Test + public void mapInstantToTimestamp_whenValueIsTooLargeForRangeForTimestamp_thenMapToMaxValidTimestamp() { + assertEquals(Timestamps.MAX_VALUE, MAPPER.mapToTimestamp(Instant.now().plus(Integer.MAX_VALUE, ChronoUnit.DAYS))); + } + + @Test + public void mapInstantToTimestamp_whenValueIsTooSmallForRangeForTimestamp_thenMapToMinValidTimestamp() { + assertEquals(Timestamps.MIN_VALUE, MAPPER.mapToTimestamp(Instant.now().minus(Integer.MAX_VALUE, ChronoUnit.DAYS))); + } + + @Test + public void mapToInstant_whenEpoch_thenReturnDefaultTimestamp() { + assertEquals(Timestamp.getDefaultInstance(), MAPPER.mapToTimestamp(Instant.ofEpochSecond(0))); + } + + @Test + public void mapPositiveDuration() { + Duration duration = Duration.of(3, ChronoUnit.NANOS); + + com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration); + Durations.checkValid(pbDuration); + assertEquals(duration, MAPPER.mapDuration(pbDuration)); + } + + @Test + public void mapNegativeDurationToProto_whenSecondsAreNegativeAndNanoPositive() { + Duration duration = Duration.ofSeconds(-3, 2); + + com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration); + Durations.checkValid(pbDuration); + assertEquals(duration, MAPPER.mapDuration(pbDuration)); + } + + @Test + public void mapNegativeDurationToProto_whenSecondsArePositiveAndNanoNegative() { + // Duration.ofSeconds accepts negative values. Will still be stored as positive values in Duration + Duration duration = Duration.ofSeconds(3, -(TimeUnit.SECONDS.toNanos(1) - 2)); + + com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration); + Durations.checkValid(pbDuration); + assertEquals(duration, MAPPER.mapDuration(pbDuration)); + } + + @Test + public void mapNegativeDuration_fromProto() { + com.google.protobuf.Duration pbDuration = com.google.protobuf.Duration.newBuilder().setSeconds(-10).setNanos(-5).build(); + + Duration duration = MAPPER.mapDuration(pbDuration); + Durations.checkValid(pbDuration); + assertEquals(pbDuration, MAPPER.mapDuration(duration)); + } + + @Test + public void mapDurationToProto_whenNull_thenReturnDefaultInstance() { + assertEquals(com.google.protobuf.Duration.getDefaultInstance(), MAPPER.mapDuration((Duration) null)); + } + + @Test + public void mapLocalDateToProto_whenNull_thenReturnDefaultInstance() { + assertEquals(com.google.type.Date.getDefaultInstance(), MAPPER.mapLocalDate(null)); + } + + @Test + public void mapLocalDateTimeToProto_whenNull_thenReturnDefaultInstance() { + assertEquals(Timestamp.getDefaultInstance(), MAPPER.map((LocalDateTime) null)); + } + + @Test + public void mapOffsetDateTimeToProto_whenNull_thenReturnDefaultInstance() { + assertEquals(Timestamp.getDefaultInstance(), MAPPER.map((OffsetDateTime) null)); + } + + @Test + public void mapInstantToProto_whenNull_thenReturnDefaultInstance() { + assertEquals(Timestamp.getDefaultInstance(), MAPPER.mapToTimestamp(null)); + } + + @Test + public void mapLocalTimeToProto_whenNull_thenReturnDefaultInstance() { + assertEquals(TimeOfDay.getDefaultInstance(), MAPPER.mapLocalTime(null)); + } + + @Test + public void mapByteArrayToProto_whenNull_thenReturnEmpty() { + assertEquals(ByteString.empty(), MAPPER.mapByteString((byte[]) null)); + } - @Test - public void mapNegativeDuration_fromProto() { - com.google.protobuf.Duration pbDuration = com.google.protobuf.Duration.newBuilder().setSeconds(-10).setNanos(-5).build(); - Duration duration = MAPPER.mapDuration(pbDuration); - Durations.checkValid(pbDuration); - assertEquals(pbDuration, MAPPER.mapDuration(duration)); - } }