Skip to content

Commit

Permalink
Merge pull request #1084 from microsoft/fix/error-handling
Browse files Browse the repository at this point in the history
Add default UTC offset when deserializing to OffsetDateTime fails due to a missing time offset value.
  • Loading branch information
Ndiritu authored Feb 13, 2024
2 parents 0847294 + 9f27a5c commit 9124573
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 13 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

## [1.0.2] - 2024-02-13

### Changed

- Add default UTC offset when deserializing to OffsetDateTime fails due to a missing time offset value.

## [1.0.1] - 2024-02-09

### Changed
Expand Down
1 change: 1 addition & 0 deletions components/serialization/form/gradle/dependencies.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dependencies {
// Use JUnit Jupiter API for testing.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.2'

// Use JUnit Jupiter Engine for testing.
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
Expand Down Expand Up @@ -167,7 +170,17 @@ private String sanitizeKey(@Nonnull final String key) {
@Nullable public OffsetDateTime getOffsetDateTimeValue() {
final String stringValue = getStringValue();
if (stringValue == null) return null;
return OffsetDateTime.parse(stringValue);
try {
return OffsetDateTime.parse(stringValue);
} catch (DateTimeParseException ex) {
// Append UTC offset if it's missing
try {
LocalDateTime localDateTime = LocalDateTime.parse(stringValue);
return localDateTime.atOffset(ZoneOffset.UTC);
} catch (DateTimeParseException ex2) {
throw ex;
}
}
}

@Nullable public LocalDate getLocalDateValue() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.microsoft.kiota.serialization;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -10,8 +11,13 @@
import com.microsoft.kiota.serialization.mocks.TestEntity;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalTime;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.UUID;

Expand Down Expand Up @@ -99,4 +105,34 @@ public void getCollectionOfGuidPrimitiveValuesFromForm() {
assertEquals(
UUID.fromString("48d31887-5fad-4d73-a9f5-3c356e68a038"), numberCollection.get(0));
}

@Test
void testParsesDateTimeOffset() {
final var dateTimeOffsetString = "2024-02-12T19:47:39+02:00";
final var result =
new FormParseNode(URLEncoder.encode(dateTimeOffsetString, StandardCharsets.UTF_8))
.getOffsetDateTimeValue();
assertEquals(dateTimeOffsetString, result.toString());
}

@Test
void testParsesDateTimeStringWithoutOffsetToDateTimeOffset() {
final var dateTimeString = "2024-02-12T19:47:39";
final var result =
new FormParseNode(URLEncoder.encode(dateTimeString, StandardCharsets.UTF_8))
.getOffsetDateTimeValue();
assertEquals(dateTimeString + "Z", result.toString());
}

@ParameterizedTest
@ValueSource(strings = {"2024-02-12T19:47:39 Europe/Paris", "19:47:39"})
void testInvalidOffsetDateTimeStringThrowsException(final String dateTimeString) {
try {
new FormParseNode(URLEncoder.encode(dateTimeString, StandardCharsets.UTF_8))
.getOffsetDateTimeValue();
} catch (final Exception ex) {
assertInstanceOf(DateTimeParseException.class, ex);
assertTrue(ex.getMessage().contains(dateTimeString));
}
}
}
1 change: 1 addition & 0 deletions components/serialization/json/gradle/dependencies.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dependencies {
// Use JUnit Jupiter API for testing.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.2'

// Use JUnit Jupiter Engine for testing.
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.EnumSet;
Expand Down Expand Up @@ -95,7 +98,17 @@ public JsonParseNode(@Nonnull final JsonElement node) {
@Nullable public OffsetDateTime getOffsetDateTimeValue() {
final String stringValue = currentNode.getAsString();
if (stringValue == null) return null;
return OffsetDateTime.parse(stringValue);
try {
return OffsetDateTime.parse(stringValue);
} catch (DateTimeParseException ex) {
// Append UTC offset if it's missing
try {
LocalDateTime localDateTime = LocalDateTime.parse(stringValue);
return localDateTime.atOffset(ZoneOffset.UTC);
} catch (DateTimeParseException ex2) {
throw ex;
}
}
}

@Nullable public LocalDate getLocalDateValue() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package com.microsoft.kiota.serialization;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.google.gson.JsonParser;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.time.format.DateTimeParseException;

class JsonParseNodeTests {
private static final JsonParseNodeFactory _parseNodeFactory = new JsonParseNodeFactory();
Expand All @@ -19,4 +27,32 @@ void itDDoesNotFailForGetChildElementOnMissingKey() throws UnsupportedEncodingEx
final var result = parseNode.getChildNode("@odata.type");
assertNull(result);
}

@Test
void testParsesDateTimeOffset() {
final var dateTimeOffsetString = "2024-02-12T19:47:39+02:00";
final var jsonElement = JsonParser.parseString("\"" + dateTimeOffsetString + "\"");
final var result = new JsonParseNode(jsonElement).getOffsetDateTimeValue();
assertEquals(dateTimeOffsetString, result.toString());
}

@Test
void testParsesDateTimeStringWithoutOffsetToDateTimeOffset() {
final var dateTimeString = "2024-02-12T19:47:39";
final var jsonElement = JsonParser.parseString("\"" + dateTimeString + "\"");
final var result = new JsonParseNode(jsonElement).getOffsetDateTimeValue();
assertEquals(dateTimeString + "Z", result.toString());
}

@ParameterizedTest
@ValueSource(strings = {"2024-02-12T19:47:39 Europe/Paris", "19:47:39"})
void testInvalidOffsetDateTimeStringThrowsException(final String dateTimeString) {
final var jsonElement = JsonParser.parseString("\"" + dateTimeString + "\"");
try {
new JsonParseNode(jsonElement).getOffsetDateTimeValue();
} catch (final Exception ex) {
assertInstanceOf(DateTimeParseException.class, ex);
assertTrue(ex.getMessage().contains(dateTimeString));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class UnionWrapperParseTests {
private static final String contentType = "application/json";

@Test
void ParsesUnionTypeComplexProperty1() throws UnsupportedEncodingException {
void parsesUnionTypeComplexProperty1() throws UnsupportedEncodingException {
final var initialString =
"{\"@odata.type\":\"#microsoft.graph.testEntity\",\"officeLocation\":\"Montreal\","
+ " \"id\": \"opaque\"}";
Expand All @@ -38,7 +38,7 @@ void ParsesUnionTypeComplexProperty1() throws UnsupportedEncodingException {
}

@Test
void ParsesUnionTypeComplexProperty2() throws UnsupportedEncodingException {
void parsesUnionTypeComplexProperty2() throws UnsupportedEncodingException {
final var initialString =
"{\"@odata.type\":\"#microsoft.graph.secondTestEntity\",\"officeLocation\":\"Montreal\","
+ " \"id\": 10}";
Expand All @@ -54,7 +54,7 @@ void ParsesUnionTypeComplexProperty2() throws UnsupportedEncodingException {
}

@Test
void ParsesUnionTypeComplexProperty3() throws UnsupportedEncodingException {
void parsesUnionTypeComplexProperty3() throws UnsupportedEncodingException {
final var initialString =
"[{\"@odata.type\":\"#microsoft.graph.TestEntity\",\"officeLocation\":\"Ottawa\","
+ " \"id\": \"11\"},"
Expand All @@ -73,7 +73,7 @@ void ParsesUnionTypeComplexProperty3() throws UnsupportedEncodingException {
}

@Test
void ParsesUnionTypeStringValue() throws UnsupportedEncodingException {
void parsesUnionTypeStringValue() throws UnsupportedEncodingException {
final var initialString = "\"officeLocation\"";
final var rawResponse = new ByteArrayInputStream(initialString.getBytes("UTF-8"));
final var parseNode = _parseNodeFactory.getParseNode(contentType, rawResponse);
Expand All @@ -87,7 +87,7 @@ void ParsesUnionTypeStringValue() throws UnsupportedEncodingException {
}

@Test
void SerializesUnionTypeStringValue() throws IOException {
void serializesUnionTypeStringValue() throws IOException {
try (final var writer = _serializationWriterFactory.getSerializationWriter(contentType)) {
var model =
new UnionTypeMock() {
Expand All @@ -105,7 +105,7 @@ void SerializesUnionTypeStringValue() throws IOException {
}

@Test
void SerializesUnionTypeComplexProperty1() throws IOException {
void serializesUnionTypeComplexProperty1() throws IOException {
try (final var writer = _serializationWriterFactory.getSerializationWriter(contentType)) {
var model =
new UnionTypeMock() {
Expand Down Expand Up @@ -135,7 +135,7 @@ void SerializesUnionTypeComplexProperty1() throws IOException {
}

@Test
void SerializesUnionTypeComplexProperty2() throws IOException {
void serializesUnionTypeComplexProperty2() throws IOException {
try (final var writer = _serializationWriterFactory.getSerializationWriter(contentType)) {
var model =
new UnionTypeMock() {
Expand All @@ -159,7 +159,7 @@ void SerializesUnionTypeComplexProperty2() throws IOException {
}

@Test
void SerializesUnionTypeComplexProperty3() throws IOException {
void serializesUnionTypeComplexProperty3() throws IOException {
try (final var writer = _serializationWriterFactory.getSerializationWriter(contentType)) {
var model =
new UnionTypeMock() {
Expand Down
3 changes: 2 additions & 1 deletion components/serialization/text/gradle/dependencies.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
dependencies {
// Use JUnit Jupiter API for testing.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.2'

// Use JUnit Jupiter Engine for testing.
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'

// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1'

api project(':components:abstractions')
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.util.Base64;
import java.util.EnumSet;
import java.util.List;
Expand Down Expand Up @@ -79,7 +82,17 @@ public TextParseNode(@Nonnull final String rawText) {
}

@Nullable public OffsetDateTime getOffsetDateTimeValue() {
return OffsetDateTime.parse(this.getStringValue());
try {
return OffsetDateTime.parse(this.getStringValue());
} catch (DateTimeParseException ex) {
// Append UTC offset if it's missing
try {
LocalDateTime localDateTime = LocalDateTime.parse(this.getStringValue());
return localDateTime.atOffset(ZoneOffset.UTC);
} catch (DateTimeParseException ex2) {
throw ex;
}
}
}

@Nullable public LocalDate getLocalDateValue() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.microsoft.kiota.serialization;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.time.format.DateTimeParseException;

public class TextParseNodeTest {

@Test
void testParsesDateTimeOffset() {
final var dateTimeOffsetString = "2024-02-12T19:47:39+02:00";
final var result = new TextParseNode(dateTimeOffsetString).getOffsetDateTimeValue();
assertEquals(dateTimeOffsetString, result.toString());
}

@Test
void testParsesDateTimeStringWithoutOffsetToDateTimeOffset() {
final var dateTimeString = "2024-02-12T19:47:39";
final var result = new TextParseNode(dateTimeString).getOffsetDateTimeValue();
assertEquals(dateTimeString + "Z", result.toString());
}

@ParameterizedTest
@ValueSource(strings = {"2024-02-12T19:47:39 Europe/Paris", "19:47:39"})
void testInvalidOffsetDateTimeStringThrowsException(final String dateTimeString) {
try {
new TextParseNode(dateTimeString).getOffsetDateTimeValue();
} catch (final Exception ex) {
assertInstanceOf(DateTimeParseException.class, ex);
assertTrue(ex.getMessage().contains(dateTimeString));
}
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ org.gradle.caching=true
mavenGroupId = com.microsoft.kiota
mavenMajorVersion = 1
mavenMinorVersion = 0
mavenPatchVersion = 1
mavenPatchVersion = 2
mavenArtifactSuffix =

#These values are used to run functional tests
Expand Down

0 comments on commit 9124573

Please sign in to comment.