Skip to content

Commit cf2d524

Browse files
erlendnils1erlend
andauthored
Never return null when mapping to protobuf format objects (#498)
* Always return default instance instead of null when mapping to proto * More tests --------- Co-authored-by: erlend <erlend@scelto.no>
1 parent a7caa25 commit cf2d524

5 files changed

Lines changed: 158 additions & 121 deletions

File tree

support-core/src/main/java/no/entur/abt/mapstruct/common/ProtobufStandardMappings.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ default byte[] mapByteString(ByteString in) {
6262
}
6363

6464
default ByteString mapByteStringToString(String string) {
65-
return ByteString.copyFromUtf8(string);
65+
return ByteString.copyFromUtf8(string != null ? string : "");
6666
}
6767

6868
default String mapStringToByteString(ByteString in) {
@@ -74,15 +74,18 @@ default String mapStringToByteString(ByteString in) {
7474
}
7575

7676
default com.google.type.Date mapLocalDate(LocalDate t) {
77-
return com.google.type.Date.newBuilder().setYear(t.getYear()).setMonth(t.getMonthValue()).setDay(t.getDayOfMonth()).build();
77+
return t != null ? com.google.type.Date.newBuilder().setYear(t.getYear()).setMonth(t.getMonthValue()).setDay(t.getDayOfMonth()).build()
78+
: com.google.type.Date.getDefaultInstance();
7879
}
7980

8081
default LocalDate mapDate(com.google.type.Date t) {
8182
return LocalDate.of(t.getYear(), t.getMonth(), t.getDay());
8283
}
8384

8485
default com.google.type.TimeOfDay mapLocalTime(LocalTime t) {
85-
return com.google.type.TimeOfDay.newBuilder().setHours(t.getHour()).setMinutes(t.getMinute()).setSeconds(t.getSecond()).setNanos(t.getNano()).build();
86+
return t != null
87+
? com.google.type.TimeOfDay.newBuilder().setHours(t.getHour()).setMinutes(t.getMinute()).setSeconds(t.getSecond()).setNanos(t.getNano()).build()
88+
: com.google.type.TimeOfDay.getDefaultInstance();
8689
}
8790

8891
default LocalTime mapTimeOfDay(com.google.type.TimeOfDay t) {
@@ -91,7 +94,7 @@ default LocalTime mapTimeOfDay(com.google.type.TimeOfDay t) {
9194

9295
default Timestamp map(LocalDateTime i) {
9396
if (i == null) {
94-
return null;
97+
return Timestamp.getDefaultInstance();
9598
}
9699

97100
TimeZone systemDefault = TimeZone.getDefault();
@@ -103,7 +106,7 @@ default Timestamp map(LocalDateTime i) {
103106
}
104107

105108
default Timestamp map(OffsetDateTime in) {
106-
return Timestamp.newBuilder().setSeconds(in.toEpochSecond()).setNanos(0).build();
109+
return in != null ? Timestamp.newBuilder().setSeconds(in.toEpochSecond()).setNanos(0).build() : Timestamp.getDefaultInstance();
107110
}
108111

109112
default float map(FloatValue f) {
@@ -143,22 +146,16 @@ default ByteString map(BytesValue f) {
143146
}
144147

145148
default Instant mapToInstant(Timestamp t) {
146-
if (t == null) {
149+
if (t == null || Timestamp.getDefaultInstance().equals(t)) {
147150
return null;
148151
}
149-
150152
Timestamp sanitized = Timestamps.sanitize(t);
151-
152-
if (sanitized != null) {
153-
return Instant.ofEpochSecond(sanitized.getSeconds(), sanitized.getNanos());
154-
} else {
155-
return null;
156-
}
153+
return Instant.ofEpochSecond(sanitized.getSeconds(), sanitized.getNanos());
157154
}
158155

159156
default Timestamp mapToTimestamp(Instant i) {
160157
if (i == null) {
161-
return null;
158+
return Timestamp.getDefaultInstance();
162159
}
163160

164161
Timestamp t = Timestamp.newBuilder().setSeconds(i.getEpochSecond()).setNanos(i.getNano()).build();

support-core/src/main/java/no/entur/abt/mapstruct/common/Timestamps.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ public class Timestamps {
4242
* Sanitize Timestamps outside legal range where possible.
4343
*/
4444
public static Timestamp sanitize(Timestamp t) {
45-
if (t.getSeconds() == 0 && t.getNanos() == 0) {
46-
return null; // Assuming null for epoch, cannot differentiate
47-
}
4845
if (t.getNanos() < 0 || t.getNanos() >= NANOS_PER_SECOND) {
4946
throw new IllegalArgumentException(String.format(
5047
"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].",

support-lite/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,8 @@ default Long toEpochMilliseconds(Timestamp instance) {
4848
return instant == null ? null : instant.toEpochMilli();
4949
}
5050

51-
default Timestamp fromEpochMilliseconds(Long instance) {
52-
if (instance == null) {
53-
return null;
54-
}
55-
Instant instant = Instant.ofEpochMilli(instance);
51+
default Timestamp fromEpochMilliseconds(Long millis) {
52+
Instant instant = Instant.ofEpochMilli(millis != null ? millis : 0L);
5653
return mapToTimestamp(instant);
5754
}
5855

@@ -61,6 +58,9 @@ default Duration mapDuration(com.google.protobuf.Duration t) {
6158
}
6259

6360
default com.google.protobuf.Duration mapDuration(Duration t) {
61+
if (t == null) {
62+
return com.google.protobuf.Duration.getDefaultInstance();
63+
}
6464
long seconds = t.getSeconds();
6565
int nanos = t.getNano();
6666

support-standard/src/main/java/no/entur/abt/mapstruct/ProtobufStandardMappings.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ default com.google.protobuf.Duration mapDuration(Duration t) {
6767
if (t != null) {
6868
return Durations.fromNanos(t.toNanos());
6969
} else {
70-
return null;
70+
return com.google.protobuf.Duration.getDefaultInstance();
7171
}
7272
}
7373

support-standard/src/test/java/no/entur/abt/mapstruct/ProtobufStandardMappingsTest.java

Lines changed: 141 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -23,120 +23,163 @@
2323
* #L%
2424
*/
2525

26-
import static org.junit.jupiter.api.Assertions.assertEquals;
27-
import static org.junit.jupiter.api.Assertions.assertNull;
26+
import com.google.protobuf.ByteString;
27+
import com.google.protobuf.Timestamp;
28+
import com.google.protobuf.util.Durations;
29+
import com.google.type.TimeOfDay;
30+
import no.entur.abt.mapstruct.common.Timestamps;
31+
import org.junit.jupiter.api.Test;
2832

2933
import java.time.Duration;
3034
import java.time.Instant;
3135
import java.time.LocalDateTime;
36+
import java.time.OffsetDateTime;
3237
import java.time.ZoneId;
3338
import java.time.temporal.ChronoUnit;
3439
import java.util.concurrent.TimeUnit;
3540

36-
import org.junit.jupiter.api.Test;
37-
38-
import com.google.protobuf.Timestamp;
39-
import com.google.protobuf.util.Durations;
40-
41-
import no.entur.abt.mapstruct.common.Timestamps;
41+
import static org.junit.jupiter.api.Assertions.assertEquals;
42+
import static org.junit.jupiter.api.Assertions.assertNull;
4243

4344
public class ProtobufStandardMappingsTest {
4445

45-
no.entur.abt.mapstruct.ProtobufStandardMappings MAPPER = no.entur.abt.mapstruct.ProtobufStandardMappings.INSTANCE;
46-
47-
@Test
48-
public void testMapLocalDateToTimestampSummertime() {
49-
LocalDateTime l = LocalDateTime.of(2000, 6, 1, 12, 0);
50-
51-
Timestamp timestamp = MAPPER.map(l);
52-
Instant instant = MAPPER.mapToInstant(timestamp);
53-
54-
LocalDateTime back = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
55-
56-
assertEquals(l, back);
57-
}
58-
59-
@Test
60-
public void testMapLocalDateToTimestampWintertime() {
61-
LocalDateTime l = LocalDateTime.of(2000, 2, 1, 12, 0);
62-
63-
Timestamp timestamp = MAPPER.map(l);
64-
Instant instant = MAPPER.mapToInstant(timestamp);
65-
66-
LocalDateTime back = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
67-
68-
assertEquals(l, back);
69-
}
70-
71-
@Test
72-
public void mapToInstant_whenSecondsAndNanosIs0_thenMapToNull() {
73-
assertNull(MAPPER.mapToInstant(Timestamp.newBuilder().build()));
74-
}
75-
76-
@Test
77-
public void mapToInstant_whenSecondsAndNanosIsNull_thenMapToNull() {
78-
assertNull(MAPPER.mapToInstant(null));
79-
}
80-
81-
@Test
82-
public void mapToInstant_whenNanosIsSet_thenMapToInstant() {
83-
assertEquals(3000, MAPPER.mapToInstant(Timestamp.newBuilder().setNanos(3000).build()).getNano());
84-
}
85-
86-
@Test
87-
public void mapToInstant_whenValueIsTooLargeForRangeForTimestamp_thenMapFromMaxValidTimestamp() {
88-
assertEquals(MAPPER.mapToInstant(Timestamps.MAX_VALUE), MAPPER.mapToInstant(Timestamp.newBuilder().setSeconds(Long.MAX_VALUE).build()));
89-
}
90-
91-
@Test
92-
public void mapToInstant_whenValueIsTooSmallForRangeForTimestamp_thenMapFromMinValidTimestamp() {
93-
assertEquals(MAPPER.mapToInstant(Timestamps.MIN_VALUE), MAPPER.mapToInstant(Timestamp.newBuilder().setSeconds(-Long.MAX_VALUE).build()));
94-
}
95-
96-
@Test
97-
public void mapInstantToTimestamp_whenValueIsTooLargeForRangeForTimestamp_thenMapToMaxValidTimestamp() {
98-
assertEquals(Timestamps.MAX_VALUE, MAPPER.mapToTimestamp(Instant.now().plus(Integer.MAX_VALUE, ChronoUnit.DAYS)));
99-
}
100-
101-
@Test
102-
public void mapInstantToTimestamp_whenValueIsTooSmallForRangeForTimestamp_thenMapToMinValidTimestamp() {
103-
assertEquals(Timestamps.MIN_VALUE, MAPPER.mapToTimestamp(Instant.now().minus(Integer.MAX_VALUE, ChronoUnit.DAYS)));
104-
}
105-
106-
@Test
107-
public void mapPositiveDuration() {
108-
Duration duration = Duration.of(3, ChronoUnit.NANOS);
46+
no.entur.abt.mapstruct.ProtobufStandardMappings MAPPER = no.entur.abt.mapstruct.ProtobufStandardMappings.INSTANCE;
10947

110-
com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration);
111-
Durations.checkValid(pbDuration);
112-
assertEquals(duration, MAPPER.mapDuration(pbDuration));
113-
}
48+
@Test
49+
public void testMapLocalDateToTimestampSummertime() {
50+
LocalDateTime l = LocalDateTime.of(2000, 6, 1, 12, 0);
11451

115-
@Test
116-
public void mapNegativeDurationToProto_whenSecondsAreNegativeAndNanoPositive() {
117-
Duration duration = Duration.ofSeconds(-3, 2);
52+
Timestamp timestamp = MAPPER.map(l);
53+
Instant instant = MAPPER.mapToInstant(timestamp);
11854

119-
com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration);
120-
Durations.checkValid(pbDuration);
121-
assertEquals(duration, MAPPER.mapDuration(pbDuration));
122-
}
55+
LocalDateTime back = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
56+
57+
assertEquals(l, back);
58+
}
12359

124-
@Test
125-
public void mapNegativeDurationToProto_whenSecondsArePositiveAndNanoNegative() {
126-
// Duration.ofSeconds accepts negative values. Will still be stored as positive values in Duration
127-
Duration duration = Duration.ofSeconds(3, -(TimeUnit.SECONDS.toNanos(1) - 2));
60+
@Test
61+
public void testMapLocalDateToTimestampWintertime() {
62+
LocalDateTime l = LocalDateTime.of(2000, 2, 1, 12, 0);
12863

129-
com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration);
130-
Durations.checkValid(pbDuration);
131-
assertEquals(duration, MAPPER.mapDuration(pbDuration));
132-
}
64+
Timestamp timestamp = MAPPER.map(l);
65+
Instant instant = MAPPER.mapToInstant(timestamp);
66+
67+
LocalDateTime back = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
68+
69+
assertEquals(l, back);
70+
}
71+
72+
@Test
73+
public void mapToInstant_whenSecondsAndNanosIs0_thenMapToNull() {
74+
assertNull(MAPPER.mapToInstant(Timestamp.newBuilder().build()));
75+
}
76+
77+
@Test
78+
public void mapToInstant_whenSecondsAndNanosIsNull_thenMapToNull() {
79+
assertNull(MAPPER.mapToInstant(null));
80+
}
81+
82+
@Test
83+
public void mapToInstant_whenNanosIsSet_thenMapToInstant() {
84+
assertEquals(3000, MAPPER.mapToInstant(Timestamp.newBuilder().setNanos(3000).build()).getNano());
85+
}
86+
87+
@Test
88+
public void mapToInstant_whenValueIsTooLargeForRangeForTimestamp_thenMapFromMaxValidTimestamp() {
89+
assertEquals(MAPPER.mapToInstant(Timestamps.MAX_VALUE), MAPPER.mapToInstant(Timestamp.newBuilder().setSeconds(Long.MAX_VALUE).build()));
90+
}
91+
92+
@Test
93+
public void mapToInstant_whenValueIsTooSmallForRangeForTimestamp_thenMapFromMinValidTimestamp() {
94+
assertEquals(MAPPER.mapToInstant(Timestamps.MIN_VALUE), MAPPER.mapToInstant(Timestamp.newBuilder().setSeconds(-Long.MAX_VALUE).build()));
95+
}
96+
97+
@Test
98+
public void mapInstantToTimestamp_whenValueIsTooLargeForRangeForTimestamp_thenMapToMaxValidTimestamp() {
99+
assertEquals(Timestamps.MAX_VALUE, MAPPER.mapToTimestamp(Instant.now().plus(Integer.MAX_VALUE, ChronoUnit.DAYS)));
100+
}
101+
102+
@Test
103+
public void mapInstantToTimestamp_whenValueIsTooSmallForRangeForTimestamp_thenMapToMinValidTimestamp() {
104+
assertEquals(Timestamps.MIN_VALUE, MAPPER.mapToTimestamp(Instant.now().minus(Integer.MAX_VALUE, ChronoUnit.DAYS)));
105+
}
106+
107+
@Test
108+
public void mapToInstant_whenEpoch_thenReturnDefaultTimestamp() {
109+
assertEquals(Timestamp.getDefaultInstance(), MAPPER.mapToTimestamp(Instant.ofEpochSecond(0)));
110+
}
111+
112+
@Test
113+
public void mapPositiveDuration() {
114+
Duration duration = Duration.of(3, ChronoUnit.NANOS);
115+
116+
com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration);
117+
Durations.checkValid(pbDuration);
118+
assertEquals(duration, MAPPER.mapDuration(pbDuration));
119+
}
120+
121+
@Test
122+
public void mapNegativeDurationToProto_whenSecondsAreNegativeAndNanoPositive() {
123+
Duration duration = Duration.ofSeconds(-3, 2);
124+
125+
com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration);
126+
Durations.checkValid(pbDuration);
127+
assertEquals(duration, MAPPER.mapDuration(pbDuration));
128+
}
129+
130+
@Test
131+
public void mapNegativeDurationToProto_whenSecondsArePositiveAndNanoNegative() {
132+
// Duration.ofSeconds accepts negative values. Will still be stored as positive values in Duration
133+
Duration duration = Duration.ofSeconds(3, -(TimeUnit.SECONDS.toNanos(1) - 2));
134+
135+
com.google.protobuf.Duration pbDuration = MAPPER.mapDuration(duration);
136+
Durations.checkValid(pbDuration);
137+
assertEquals(duration, MAPPER.mapDuration(pbDuration));
138+
}
139+
140+
@Test
141+
public void mapNegativeDuration_fromProto() {
142+
com.google.protobuf.Duration pbDuration = com.google.protobuf.Duration.newBuilder().setSeconds(-10).setNanos(-5).build();
143+
144+
Duration duration = MAPPER.mapDuration(pbDuration);
145+
Durations.checkValid(pbDuration);
146+
assertEquals(pbDuration, MAPPER.mapDuration(duration));
147+
}
148+
149+
@Test
150+
public void mapDurationToProto_whenNull_thenReturnDefaultInstance() {
151+
assertEquals(com.google.protobuf.Duration.getDefaultInstance(), MAPPER.mapDuration((Duration) null));
152+
}
153+
154+
@Test
155+
public void mapLocalDateToProto_whenNull_thenReturnDefaultInstance() {
156+
assertEquals(com.google.type.Date.getDefaultInstance(), MAPPER.mapLocalDate(null));
157+
}
158+
159+
@Test
160+
public void mapLocalDateTimeToProto_whenNull_thenReturnDefaultInstance() {
161+
assertEquals(Timestamp.getDefaultInstance(), MAPPER.map((LocalDateTime) null));
162+
}
163+
164+
@Test
165+
public void mapOffsetDateTimeToProto_whenNull_thenReturnDefaultInstance() {
166+
assertEquals(Timestamp.getDefaultInstance(), MAPPER.map((OffsetDateTime) null));
167+
}
168+
169+
@Test
170+
public void mapInstantToProto_whenNull_thenReturnDefaultInstance() {
171+
assertEquals(Timestamp.getDefaultInstance(), MAPPER.mapToTimestamp(null));
172+
}
173+
174+
@Test
175+
public void mapLocalTimeToProto_whenNull_thenReturnDefaultInstance() {
176+
assertEquals(TimeOfDay.getDefaultInstance(), MAPPER.mapLocalTime(null));
177+
}
178+
179+
@Test
180+
public void mapByteArrayToProto_whenNull_thenReturnEmpty() {
181+
assertEquals(ByteString.empty(), MAPPER.mapByteString((byte[]) null));
182+
}
133183

134-
@Test
135-
public void mapNegativeDuration_fromProto() {
136-
com.google.protobuf.Duration pbDuration = com.google.protobuf.Duration.newBuilder().setSeconds(-10).setNanos(-5).build();
137184

138-
Duration duration = MAPPER.mapDuration(pbDuration);
139-
Durations.checkValid(pbDuration);
140-
assertEquals(pbDuration, MAPPER.mapDuration(duration));
141-
}
142185
}

0 commit comments

Comments
 (0)