diff --git a/client/src/test/java/com/sinch/sdk/e2e/domains/mailgun/v1/EmailsSteps.java b/client/src/test/java/com/sinch/sdk/e2e/domains/mailgun/v1/EmailsSteps.java
index 558af2dcb..028ad8fe7 100644
--- a/client/src/test/java/com/sinch/sdk/e2e/domains/mailgun/v1/EmailsSteps.java
+++ b/client/src/test/java/com/sinch/sdk/e2e/domains/mailgun/v1/EmailsSteps.java
@@ -162,13 +162,13 @@ public void getSendingQueuesStatusResult() {
ExceededQueueQuota.builder()
.setIsDisabled(false)
.setDetails(
- QueueStatusDisabledDetails.builder().setUntil("").setReason("").build())
+ QueueStatusDisabledDetails.builder().setUntil(null).setReason("").build())
.build())
.setScheduled(
ExceededQueueQuota.builder()
.setIsDisabled(false)
.setDetails(
- QueueStatusDisabledDetails.builder().setUntil("").setReason("").build())
+ QueueStatusDisabledDetails.builder().setUntil(null).setReason("").build())
.build())
.build();
diff --git a/core/src/main/com/sinch/sdk/core/utils/DateUtil.java b/core/src/main/com/sinch/sdk/core/utils/DateUtil.java
index d63a77e77..3cc01b2ba 100644
--- a/core/src/main/com/sinch/sdk/core/utils/DateUtil.java
+++ b/core/src/main/com/sinch/sdk/core/utils/DateUtil.java
@@ -3,12 +3,24 @@
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
+import java.time.ZoneId;
import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
+import java.util.logging.Logger;
/** Utility class for Date */
public class DateUtil {
+ private static final DateTimeFormatter RFC822_ZONED_FORMAT =
+ DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss z");
+
+ private static final DateTimeFormatter RFC822_GMT_FORMAT =
+ DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss").withZone(ZoneId.of("GMT"));
+
+ private static final Logger LOGGER = Logger.getLogger(DateUtil.class.getName());
+
private DateUtil() {}
/**
@@ -41,20 +53,116 @@ public static Instant toInstant(OffsetDateTime value) {
*/
public static Instant failSafeTimeStampToInstant(String value) {
- if (null == value) {
+ String trimmed = null == value ? "" : value.trim();
+
+ if (trimmed.isEmpty()) {
+ return null;
+ }
+
+ Instant parsed = parseISO8601(trimmed);
+ if (null != parsed) {
+ return parsed;
+ }
+
+ parsed = parseISO8601WithOffset(trimmed);
+ if (null != parsed) {
+ return parsed;
+ }
+
+ // fallback: this is not an ISO8601 compliant format: give a chance and assume it is GMT zone
+ parsed = parseISO8601WithoutOffset(trimmed);
+ if (null != parsed) {
+ return parsed;
+ }
+
+ // do not break deserialization: fallback to empty value
+ LOGGER.severe(String.format("Unable to parse '%s' date string", value));
+
+ return null;
+ }
+
+ private static Instant parseISO8601(String trimmed) {
+ try {
+ return Instant.parse(trimmed);
+ } catch (DateTimeParseException _unused) {
+ return null;
+ }
+ }
+
+ private static Instant parseISO8601WithOffset(String trimmed) {
+ try {
+ return OffsetDateTime.parse(trimmed).toInstant();
+ } catch (DateTimeParseException _unused) {
return null;
}
+ }
- Instant timestamp;
+ private static Instant parseISO8601WithoutOffset(String trimmed) {
try {
- timestamp = Instant.parse(value);
- } catch (DateTimeParseException e) {
- try {
- timestamp = OffsetDateTime.parse(value).toInstant();
- } catch (DateTimeParseException dte) {
- timestamp = LocalDateTime.parse(value).toInstant(ZoneOffset.UTC);
- }
- }
- return timestamp;
+ return LocalDateTime.parse(trimmed).toInstant(ZoneOffset.UTC);
+ } catch (DateTimeParseException _unused) {
+ return null;
+ }
+ }
+
+ /**
+ * Convert String to Instant
+ *
+ *
Consume a date time in form of RFC-822 format
+ *
+ * @param value An RFC-822 compliant string
+ * @return Extracted Instant value
+ * @since __TO_BE_DEFINED__
+ */
+ public static Instant RFC822StringToInstant(String value) {
+
+ String trimmed = null == value ? "" : value.trim();
+
+ if (trimmed.isEmpty()) {
+ return null;
+ }
+
+ Instant parsed = parseRFC822(trimmed);
+ if (null != parsed) {
+ return parsed;
+ }
+
+ parsed = parseRFC822WithZone(trimmed);
+ if (null != parsed) {
+ return parsed;
+ }
+
+ // fallback: this is not an RFC compliant format: give a chance and assume it is GMT zone
+ parsed = parseRFC822WithoutZoneNorOffset(trimmed);
+ if (null != parsed) {
+ return parsed;
+ }
+
+ LOGGER.severe(String.format("Unable to parse '%s' date string", value));
+ return null;
+ }
+
+ private static Instant parseRFC822(String trimmed) {
+ try {
+ return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(trimmed));
+ } catch (DateTimeParseException _unused) {
+ return null;
+ }
+ }
+
+ private static Instant parseRFC822WithZone(String trimmed) {
+ try {
+ return ZonedDateTime.parse(trimmed, RFC822_ZONED_FORMAT).toInstant();
+ } catch (DateTimeParseException _unused) {
+ return null;
+ }
+ }
+
+ private static Instant parseRFC822WithoutZoneNorOffset(String trimmed) {
+ try {
+ return ZonedDateTime.parse(trimmed, RFC822_GMT_FORMAT).toInstant();
+ } catch (DateTimeParseException _unused) {
+ return null;
+ }
}
}
diff --git a/core/src/main/com/sinch/sdk/core/utils/databind/Mapper.java b/core/src/main/com/sinch/sdk/core/utils/databind/Mapper.java
index 287c10f18..25b48d047 100644
--- a/core/src/main/com/sinch/sdk/core/utils/databind/Mapper.java
+++ b/core/src/main/com/sinch/sdk/core/utils/databind/Mapper.java
@@ -34,7 +34,7 @@
public class Mapper {
- public static final PropertyFilter uninitializedFilter =
+ private static final PropertyFilter uninitializedFilter =
new SimpleBeanPropertyFilter() {
@Override
public void serializeAsField(
@@ -95,19 +95,19 @@ public static ObjectMapper getInstance() {
private static class LazyHolder {
- public static final SimpleModule module =
+ static final SimpleModule module =
new JavaTimeModule()
.addDeserializer(OffsetDateTime.class, new OffsetDateTimeCustomDeserializer())
.addDeserializer(Instant.class, new InstantCustomDeserializer());
- public static final SimpleModule optionalValueModule =
+ static final SimpleModule optionalValueModule =
new SimpleModule("optionalValueModule")
.addSerializer(OptionalValue.class, new OptionalValueSerializer());
- public static final SimpleModule dynamicEnumModule =
+ static final SimpleModule dynamicEnumModule =
new SimpleModule("dynamicEnumModule")
.addSerializer(EnumDynamic.class, new EnumDynamicSerializer());
- public static final SimpleModule validationDeserializationModule =
+ static final SimpleModule validationDeserializationModule =
new SimpleModule("deserializationModule")
.setDeserializerModifier(new BuilderDeserializerWithValidation());
@@ -128,7 +128,7 @@ private static class LazyHolder {
.registerModule(validationDeserializationModule);
}
- public static final class OffsetDateTimeCustomDeserializer
+ private static final class OffsetDateTimeCustomDeserializer
extends JsonDeserializer {
@Override
@@ -148,18 +148,33 @@ public OffsetDateTime deserialize(JsonParser parser, DeserializationContext cont
}
}
- public static final class InstantCustomDeserializer extends JsonDeserializer {
+ private static final class InstantCustomDeserializer extends JsonDeserializer {
@Override
public Instant deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
String text = parser.getText();
- return DateUtil.failSafeTimeStampToInstant(text);
+
+ if (null == text) {
+ return null;
+ }
+
+ String trimmed = text.trim();
+ if (trimmed.isEmpty()) {
+ return null;
+ }
+
+ // RFC Date are starting with character not a digit
+ if (Character.isDigit(text.charAt(0))) {
+ return DateUtil.failSafeTimeStampToInstant(text);
+ }
+
+ return DateUtil.RFC822StringToInstant(text);
}
}
- public static class EnumDynamicSerializer extends JsonSerializer {
+ private static class EnumDynamicSerializer extends JsonSerializer {
@Override
public void serialize(EnumDynamic value, JsonGenerator jgen, SerializerProvider provider)
@@ -168,7 +183,7 @@ public void serialize(EnumDynamic value, JsonGenerator jgen, SerializerProvider
}
}
- public static class OptionalValueSerializer extends JsonSerializer {
+ private static class OptionalValueSerializer extends JsonSerializer {
@Override
public void serialize(OptionalValue value, JsonGenerator jgen, SerializerProvider provider)
diff --git a/core/src/test/java/com/sinch/sdk/core/utils/DateUtilTest.java b/core/src/test/java/com/sinch/sdk/core/utils/DateUtilTest.java
index 85a298886..4ad30d67b 100644
--- a/core/src/test/java/com/sinch/sdk/core/utils/DateUtilTest.java
+++ b/core/src/test/java/com/sinch/sdk/core/utils/DateUtilTest.java
@@ -32,6 +32,18 @@ void failSafeTimeStampNullGuard() {
assertNull(instant);
}
+ @Test
+ void failSafeTimeStampFromBlankString() {
+ Instant instant = DateUtil.failSafeTimeStampToInstant(" ");
+ assertNull(instant);
+ }
+
+ @Test
+ void failSafeTimeStampFromStringWithBlanks() {
+ Instant instant = DateUtil.failSafeTimeStampToInstant(" 2024-05-04T10:00:00.1234 ");
+ assertEquals("2024-05-04T10:00:00.123400Z", instant.toString());
+ }
+
@Test
void failSafeTimeStampNoTZ() {
Instant instant = DateUtil.failSafeTimeStampToInstant("2024-05-04T10:00:00.1234");
@@ -49,4 +61,71 @@ void failSafeTimeStampFromUTC() {
Instant instant = DateUtil.failSafeTimeStampToInstant("2024-05-04T10:00:00.1234Z");
assertEquals("2024-05-04T10:00:00.123400Z", instant.toString());
}
+
+ @Test
+ void failSafeTimeStampInvalid() {
+ Instant instant = DateUtil.failSafeTimeStampToInstant("2024 05 04 10 00 00 1234 ");
+ assertNull(instant);
+ }
+
+ @Test
+ void RFC822NullGuard() {
+ Instant instant = DateUtil.RFC822StringToInstant(null);
+ assertNull(instant);
+ }
+
+ @Test
+ void RFC822FromBlankString() {
+ Instant instant = DateUtil.RFC822StringToInstant(" ");
+ assertNull(instant);
+ }
+
+ @Test
+ void RFC822FromStringWithBlanks() {
+ Instant instant = DateUtil.RFC822StringToInstant(" Mon, 2 Jan 2006 15:04:05 MST ");
+ assertEquals("2006-01-02T22:04:05Z", instant.toString());
+ }
+
+ @Test
+ void RFC822WithUnsupportedMST() {
+ Instant instant = DateUtil.RFC822StringToInstant("Mon, 2 Jan 2006 15:04:05 MST");
+ assertEquals("2006-01-02T22:04:05Z", instant.toString());
+ }
+
+ @Test
+ void RFC822WithGMT() {
+ Instant instant = DateUtil.RFC822StringToInstant("Mon, 2 Jan 2006 15:04:05 GMT");
+ assertEquals("2006-01-02T15:04:05Z", instant.toString());
+ }
+
+ @Test
+ void RFC822WithUnsupportedUT() {
+ Instant instant = DateUtil.RFC822StringToInstant("Mon, 2 Jan 2006 15:04:05 UT");
+ assertEquals("2006-01-02T15:04:05Z", instant.toString());
+ }
+
+ @Test
+ void RFC822WithUnsupportedEST() {
+ Instant instant = DateUtil.RFC822StringToInstant("Mon, 2 Jan 2006 15:04:05 EST");
+ assertEquals("2006-01-02T20:04:05Z", instant.toString());
+ }
+
+ @Test
+ void RFC822WithOffset() {
+ Instant instant = DateUtil.RFC822StringToInstant("Mon, 2 Jan 2006 15:04:05 +0100");
+ assertEquals("2006-01-02T14:04:05Z", instant.toString());
+ }
+
+ @Test
+ void RFC822NoOffset() {
+ Instant instant = DateUtil.RFC822StringToInstant("Mon, 2 Jan 2006 15:04:05");
+ assertEquals("2006-01-02T15:04:05Z", instant.toString());
+ }
+
+ @Test
+ void RFC822Invalid() {
+ // 12th of January 2006 is not a Monday (it was a Thursday)
+ Instant instant = DateUtil.RFC822StringToInstant("Mon, 12 Jan 2006 15:04:05 +0100");
+ assertNull(instant);
+ }
}
diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/mailgun/models/v1/emails/response/QueueStatusDisabledDetails.java b/openapi-contracts/src/main/com/sinch/sdk/domains/mailgun/models/v1/emails/response/QueueStatusDisabledDetails.java
index b74218998..33bfa283b 100644
--- a/openapi-contracts/src/main/com/sinch/sdk/domains/mailgun/models/v1/emails/response/QueueStatusDisabledDetails.java
+++ b/openapi-contracts/src/main/com/sinch/sdk/domains/mailgun/models/v1/emails/response/QueueStatusDisabledDetails.java
@@ -11,6 +11,7 @@
package com.sinch.sdk.domains.mailgun.models.v1.emails.response;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import java.time.Instant;
/** QueueStatusDisabledDetails */
@JsonDeserialize(builder = QueueStatusDisabledDetailsImpl.Builder.class)
@@ -21,7 +22,7 @@ public interface QueueStatusDisabledDetails {
*
* @return until
*/
- String getUntil();
+ Instant getUntil();
/**
* Get reason
@@ -49,7 +50,7 @@ interface Builder {
* @return Current builder
* @see #getUntil
*/
- Builder setUntil(String until);
+ Builder setUntil(Instant until);
/**
* see getter
diff --git a/openapi-contracts/src/main/com/sinch/sdk/domains/mailgun/models/v1/emails/response/QueueStatusDisabledDetailsImpl.java b/openapi-contracts/src/main/com/sinch/sdk/domains/mailgun/models/v1/emails/response/QueueStatusDisabledDetailsImpl.java
index 9cbf9124c..ebe4bbb66 100644
--- a/openapi-contracts/src/main/com/sinch/sdk/domains/mailgun/models/v1/emails/response/QueueStatusDisabledDetailsImpl.java
+++ b/openapi-contracts/src/main/com/sinch/sdk/domains/mailgun/models/v1/emails/response/QueueStatusDisabledDetailsImpl.java
@@ -7,6 +7,7 @@
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.sinch.sdk.core.models.OptionalValue;
+import java.time.Instant;
import java.util.Objects;
@JsonPropertyOrder({
@@ -20,7 +21,7 @@ public class QueueStatusDisabledDetailsImpl implements QueueStatusDisabledDetail
public static final String JSON_PROPERTY_UNTIL = "until";
- private OptionalValue until;
+ private OptionalValue until;
public static final String JSON_PROPERTY_REASON = "reason";
@@ -29,19 +30,19 @@ public class QueueStatusDisabledDetailsImpl implements QueueStatusDisabledDetail
public QueueStatusDisabledDetailsImpl() {}
protected QueueStatusDisabledDetailsImpl(
- OptionalValue until, OptionalValue reason) {
+ OptionalValue until, OptionalValue reason) {
this.until = until;
this.reason = reason;
}
@JsonIgnore
- public String getUntil() {
+ public Instant getUntil() {
return until.orElse(null);
}
@JsonProperty(JSON_PROPERTY_UNTIL)
@JsonInclude(value = JsonInclude.Include.ALWAYS)
- public OptionalValue until() {
+ public OptionalValue until() {
return until;
}
@@ -103,11 +104,11 @@ private String toIndentedString(Object o) {
@JsonPOJOBuilder(withPrefix = "set")
static class Builder implements QueueStatusDisabledDetails.Builder {
- OptionalValue until = OptionalValue.empty();
+ OptionalValue until = OptionalValue.empty();
OptionalValue reason = OptionalValue.empty();
@JsonProperty(JSON_PROPERTY_UNTIL)
- public Builder setUntil(String until) {
+ public Builder setUntil(Instant until) {
this.until = OptionalValue.of(until);
return this;
}
diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/mailgun/models/v1/emails/response/SendingQueuesStatusResponseTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/mailgun/models/v1/emails/response/SendingQueuesStatusResponseTest.java
index 0205f89cc..005d9afcb 100644
--- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/mailgun/models/v1/emails/response/SendingQueuesStatusResponseTest.java
+++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/mailgun/models/v1/emails/response/SendingQueuesStatusResponseTest.java
@@ -4,6 +4,7 @@
import com.adelean.inject.resources.junit.jupiter.TestWithResources;
import com.sinch.sdk.BaseTest;
import com.sinch.sdk.core.TestHelpers;
+import java.time.Instant;
import org.junit.jupiter.api.Test;
@TestWithResources
@@ -16,7 +17,7 @@ public class SendingQueuesStatusResponseTest extends BaseTest {
.setIsDisabled(true)
.setDetails(
QueueStatusDisabledDetails.builder()
- .setUntil("Mon, 24 Jan 2006 16:00:00 MST")
+ .setUntil(Instant.parse("2025-01-30T04:14:04Z"))
.setReason("You have too many messages in regular queue")
.build())
.build())
@@ -25,7 +26,7 @@ public class SendingQueuesStatusResponseTest extends BaseTest {
.setIsDisabled(true)
.setDetails(
QueueStatusDisabledDetails.builder()
- .setUntil("Mon, 12 Jan 2006 15:04:05 MST")
+ .setUntil(Instant.parse("2025-01-30T15:14:04Z"))
.setReason("You have too many messages in scheduled queue")
.build())
.build())
diff --git a/openapi-contracts/src/test/resources/domains/mailgun/v1/emails/response/SendingQueuesStatusResponseDto.json b/openapi-contracts/src/test/resources/domains/mailgun/v1/emails/response/SendingQueuesStatusResponseDto.json
index e7cf05986..b4110ce54 100644
--- a/openapi-contracts/src/test/resources/domains/mailgun/v1/emails/response/SendingQueuesStatusResponseDto.json
+++ b/openapi-contracts/src/test/resources/domains/mailgun/v1/emails/response/SendingQueuesStatusResponseDto.json
@@ -2,14 +2,14 @@
"scheduled": {
"is_disabled": true,
"disabled": {
- "until": "Mon, 12 Jan 2006 15:04:05 MST",
+ "until": "Thu, 30 Jan 2025 16:14:04 +0100",
"reason": "You have too many messages in scheduled queue"
}
},
"regular": {
"is_disabled": true,
"disabled": {
- "until": "Mon, 24 Jan 2006 16:00:00 MST",
+ "until": "Thu, 30 Jan 2025 06:14:04 +0200",
"reason": "You have too many messages in regular queue"
}
}