From 8cc4df6c8ea1df605b79e1b29c21b00adce540b9 Mon Sep 17 00:00:00 2001 From: Gordan Ovcaric Date: Wed, 6 May 2026 15:51:02 +0200 Subject: [PATCH 1/3] TT-4464: fix parsing of events exception error --- src/main/kotlin/com/nylas/NylasClient.kt | 3 +- .../nylas/models/NylasSdkRemoteClosedError.kt | 4 +- .../com/nylas/models/NylasSdkTimeoutError.kt | 4 +- .../util/CreateAttachmentRequestAdapter.kt | 3 +- .../nylas/util/CreateConferencingAdapter.kt | 3 +- .../com/nylas/util/CreateWhenAdapter.kt | 3 +- src/main/kotlin/com/nylas/util/JsonHelper.kt | 11 +++- .../nylas/util/UpdateConferencingAdapter.kt | 3 +- .../com/nylas/util/UpdateConnectorAdapter.kt | 3 +- .../com/nylas/util/UpdateWhenAdapter.kt | 3 +- .../kotlin/com/nylas/resources/EventsTests.kt | 51 +++++++++++++++++++ 11 files changed, 79 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/nylas/NylasClient.kt b/src/main/kotlin/com/nylas/NylasClient.kt index dd6130b8..b8ae7d39 100644 --- a/src/main/kotlin/com/nylas/NylasClient.kt +++ b/src/main/kotlin/com/nylas/NylasClient.kt @@ -9,6 +9,7 @@ import com.nylas.util.JsonHelper import com.squareup.moshi.JsonDataException import okhttp3.* import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import java.io.IOException import java.lang.Exception @@ -250,7 +251,7 @@ open class NylasClient( overrides: RequestOverrides? = null, ): T { val url = buildUrl(path, queryParams, overrides) - var jsonBody = RequestBody.create(null, ByteArray(0)) + var jsonBody = ByteArray(0).toRequestBody(null) if (requestBody != null) { jsonBody = JsonHelper.jsonRequestBody(requestBody) } diff --git a/src/main/kotlin/com/nylas/models/NylasSdkRemoteClosedError.kt b/src/main/kotlin/com/nylas/models/NylasSdkRemoteClosedError.kt index 044febf0..06347307 100644 --- a/src/main/kotlin/com/nylas/models/NylasSdkRemoteClosedError.kt +++ b/src/main/kotlin/com/nylas/models/NylasSdkRemoteClosedError.kt @@ -6,6 +6,6 @@ package com.nylas.models * @param originalErrorMessage The error message from the library that closed the connection. */ class NylasSdkRemoteClosedError( - url: String, - originalErrorMessage: String, + val url: String, + val originalErrorMessage: String, ) : AbstractNylasSdkError("Nylas API closed the connection before the Nylas SDK received a response.") diff --git a/src/main/kotlin/com/nylas/models/NylasSdkTimeoutError.kt b/src/main/kotlin/com/nylas/models/NylasSdkTimeoutError.kt index 1c830c04..4848fff3 100644 --- a/src/main/kotlin/com/nylas/models/NylasSdkTimeoutError.kt +++ b/src/main/kotlin/com/nylas/models/NylasSdkTimeoutError.kt @@ -6,6 +6,6 @@ package com.nylas.models * @param timeout The timeout value set in the Nylas SDK, in seconds. */ class NylasSdkTimeoutError( - url: String, - timeout: Int, + val url: String, + val timeout: Int, ) : AbstractNylasSdkError("Nylas SDK timed out before receiving a response from the server.") diff --git a/src/main/kotlin/com/nylas/util/CreateAttachmentRequestAdapter.kt b/src/main/kotlin/com/nylas/util/CreateAttachmentRequestAdapter.kt index 03be7eb8..35ae6853 100644 --- a/src/main/kotlin/com/nylas/util/CreateAttachmentRequestAdapter.kt +++ b/src/main/kotlin/com/nylas/util/CreateAttachmentRequestAdapter.kt @@ -34,7 +34,8 @@ class CreateAttachmentRequestAdapter { @FromJson @Throws(UnsupportedOperationException::class) - fun fromJson(reader: com.squareup.moshi.JsonReader): CreateAttachmentRequest? { + @Suppress("UNUSED_PARAMETER") + fun fromJson(_reader: com.squareup.moshi.JsonReader): CreateAttachmentRequest? { throw UnsupportedOperationException("Deserialization not supported") } } diff --git a/src/main/kotlin/com/nylas/util/CreateConferencingAdapter.kt b/src/main/kotlin/com/nylas/util/CreateConferencingAdapter.kt index 5b76c83f..5d02bc1c 100644 --- a/src/main/kotlin/com/nylas/util/CreateConferencingAdapter.kt +++ b/src/main/kotlin/com/nylas/util/CreateConferencingAdapter.kt @@ -10,7 +10,8 @@ import com.squareup.moshi.* class CreateConferencingAdapter { @FromJson @Throws(UnsupportedOperationException::class) - fun fromJson(reader: JsonReader): CreateEventRequest.Conferencing? { + @Suppress("UNUSED_PARAMETER") + fun fromJson(_reader: JsonReader): CreateEventRequest.Conferencing? { throw UnsupportedOperationException("CreateConferencingAdapter is only used for serialization") } diff --git a/src/main/kotlin/com/nylas/util/CreateWhenAdapter.kt b/src/main/kotlin/com/nylas/util/CreateWhenAdapter.kt index 0ce666ea..23b00e80 100644 --- a/src/main/kotlin/com/nylas/util/CreateWhenAdapter.kt +++ b/src/main/kotlin/com/nylas/util/CreateWhenAdapter.kt @@ -10,7 +10,8 @@ import com.squareup.moshi.* class CreateWhenAdapter { @FromJson @Throws(UnsupportedOperationException::class) - fun fromJson(reader: JsonReader): CreateEventRequest.When? { + @Suppress("UNUSED_PARAMETER") + fun fromJson(_reader: JsonReader): CreateEventRequest.When? { throw UnsupportedOperationException("CreateWhenAdapter is only used for serialization") } diff --git a/src/main/kotlin/com/nylas/util/JsonHelper.kt b/src/main/kotlin/com/nylas/util/JsonHelper.kt index c2b80a4e..73a06be2 100644 --- a/src/main/kotlin/com/nylas/util/JsonHelper.kt +++ b/src/main/kotlin/com/nylas/util/JsonHelper.kt @@ -1,6 +1,9 @@ package com.nylas.util import com.nylas.models.Connector.Companion.CONNECTOR_JSON_ADAPTER_FACTORY +import com.nylas.models.ConferencingProvider +import com.nylas.models.CreateEventAutoConferencingProvider +import com.nylas.models.CreateEventManualConferencingProvider import com.nylas.models.CreateConnectorRequest.Companion.CREATE_CONNECTOR_JSON_ADAPTER_FACTORY import com.nylas.models.CreateCredentialRequest.Companion.CREATE_CREDENTIAL_JSON_ADAPTER_FACTORY import com.nylas.models.GetFreeBusyResponse.Companion.FREE_BUSY_JSON_FACTORY @@ -9,11 +12,13 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonReader import com.squareup.moshi.Moshi import com.squareup.moshi.Types +import com.squareup.moshi.adapters.EnumJsonAdapter import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody import java.io.IOException import java.lang.reflect.Type import java.util.* @@ -36,6 +41,9 @@ class JsonHelper { .add(IMessageAdapter()) .add(UpdateConnectorAdapter()) .add(CredentialDataAdapter()) + .add(ConferencingProvider::class.java, EnumJsonAdapter.create(ConferencingProvider::class.java).withUnknownFallback(null)) + .add(CreateEventAutoConferencingProvider::class.java, EnumJsonAdapter.create(CreateEventAutoConferencingProvider::class.java).withUnknownFallback(null)) + .add(CreateEventManualConferencingProvider::class.java, EnumJsonAdapter.create(CreateEventManualConferencingProvider::class.java).withUnknownFallback(null)) .add(EventStatusJsonAdapter()) .add(CreateAttachmentRequestAdapter()) .add(MicrosoftAdminConsentCredentialDataAdapter()) @@ -120,6 +128,7 @@ class JsonHelper { * @suppress Not for public use. */ @JvmStatic + @Suppress("UNCHECKED_CAST") fun adapter(type: Type): JsonAdapter { return moshi.adapter(type).indent(" ") as JsonAdapter } @@ -234,7 +243,7 @@ class JsonHelper { */ @JvmStatic fun jsonRequestBody(json: String): RequestBody { - return RequestBody.create(jsonType(), json.toByteArray()) + return json.toByteArray().toRequestBody(jsonType()) } } } diff --git a/src/main/kotlin/com/nylas/util/UpdateConferencingAdapter.kt b/src/main/kotlin/com/nylas/util/UpdateConferencingAdapter.kt index d2bc9367..390dcfb1 100644 --- a/src/main/kotlin/com/nylas/util/UpdateConferencingAdapter.kt +++ b/src/main/kotlin/com/nylas/util/UpdateConferencingAdapter.kt @@ -10,7 +10,8 @@ import com.squareup.moshi.* class UpdateConferencingAdapter { @FromJson @Throws(UnsupportedOperationException::class) - fun fromJson(reader: JsonReader): UpdateEventRequest.Conferencing? { + @Suppress("UNUSED_PARAMETER") + fun fromJson(_reader: JsonReader): UpdateEventRequest.Conferencing? { throw UnsupportedOperationException("UpdateConferencingAdapter is only used for serialization") } diff --git a/src/main/kotlin/com/nylas/util/UpdateConnectorAdapter.kt b/src/main/kotlin/com/nylas/util/UpdateConnectorAdapter.kt index 7c967899..f80c88be 100644 --- a/src/main/kotlin/com/nylas/util/UpdateConnectorAdapter.kt +++ b/src/main/kotlin/com/nylas/util/UpdateConnectorAdapter.kt @@ -10,7 +10,8 @@ import com.squareup.moshi.* class UpdateConnectorAdapter { @FromJson @Throws(UnsupportedOperationException::class) - fun fromJson(reader: JsonReader): UpdateConnectorRequest? { + @Suppress("UNUSED_PARAMETER") + fun fromJson(_reader: JsonReader): UpdateConnectorRequest? { throw UnsupportedOperationException("UpdateConnectorRequest is only used for serialization") } diff --git a/src/main/kotlin/com/nylas/util/UpdateWhenAdapter.kt b/src/main/kotlin/com/nylas/util/UpdateWhenAdapter.kt index be29c890..ffae2407 100644 --- a/src/main/kotlin/com/nylas/util/UpdateWhenAdapter.kt +++ b/src/main/kotlin/com/nylas/util/UpdateWhenAdapter.kt @@ -10,7 +10,8 @@ import com.squareup.moshi.* class UpdateWhenAdapter { @FromJson @Throws(UnsupportedOperationException::class) - fun fromJson(reader: JsonReader): UpdateEventRequest.When? { + @Suppress("UNUSED_PARAMETER") + fun fromJson(_reader: JsonReader): UpdateEventRequest.When? { throw UnsupportedOperationException("UpdateWhenAdapter is only used for serialization") } diff --git a/src/test/kotlin/com/nylas/resources/EventsTests.kt b/src/test/kotlin/com/nylas/resources/EventsTests.kt index a3aa2087..bfb499af 100644 --- a/src/test/kotlin/com/nylas/resources/EventsTests.kt +++ b/src/test/kotlin/com/nylas/resources/EventsTests.kt @@ -353,6 +353,57 @@ class EventsTests { assertEquals(CreateEventManualConferencingProvider.SKYPE_FOR_CONSUMER, adapter.fromJson("\"Skype for Consumer\"")) } + @Test + fun `ConferencingProvider deserializes known values properly`() { + val adapter = JsonHelper.moshi().adapter(ConferencingProvider::class.java) + + assertEquals(ConferencingProvider.ZOOM_MEETING, adapter.fromJson("\"Zoom Meeting\"")) + assertEquals(ConferencingProvider.GOOGLE_MEET, adapter.fromJson("\"Google Meet\"")) + assertEquals(ConferencingProvider.MICROSOFT_TEAMS, adapter.fromJson("\"Microsoft Teams\"")) + assertEquals(ConferencingProvider.WEBEX, adapter.fromJson("\"WebEx\"")) + assertEquals(ConferencingProvider.GOTOMEETING, adapter.fromJson("\"GoToMeeting\"")) + assertEquals(ConferencingProvider.SKYPE_FOR_CONSUMER, adapter.fromJson("\"Skype for Consumer\"")) + assertEquals(ConferencingProvider.SKYPE_FOR_BUSINESS, adapter.fromJson("\"Skype for Business\"")) + } + + @Test + fun `ConferencingProvider deserializes unknown value as null`() { + val adapter = JsonHelper.moshi().adapter(ConferencingProvider::class.java).nullSafe() + + assertEquals(null, adapter.fromJson("\"Hangouts\"")) + assertEquals(null, adapter.fromJson("\"some_future_provider\"")) + } + + @Test + fun `Event with unknown conferencing provider deserializes without throwing`() { + val adapter = JsonHelper.moshi().adapter(Event::class.java) + val jsonBuffer = Buffer().writeUtf8( + """ + { + "id": "5d3qmne77v32r8l4phyuksl2x", + "grant_id": "41009df5-bf11-4c97-aa18-b285b5f2e386", + "calendar_id": "7d93zl2palhxqdy6e5qinsakt", + "object": "event", + "conferencing": { + "provider": "Hangouts", + "details": { + "url": "https://hangouts.google.com/call/abc123" + } + }, + "when": { + "date": "2024-06-18", + "object": "date" + } + } + """.trimIndent(), + ) + + val event = adapter.fromJson(jsonBuffer)!! + assertIs(event.conferencing) + val conferencingDetails = event.conferencing as Conferencing.Details + assertEquals(null, conferencingDetails.provider) + } + @Test fun `CreateEventRequest with Autocreate conferencing serializes properly`() { val adapter = JsonHelper.moshi().adapter(CreateEventRequest::class.java) From 66929896696bede0279c83fb8e80ea26345b7614 Mon Sep 17 00:00:00 2001 From: Gordan Ovcaric Date: Wed, 6 May 2026 15:53:55 +0200 Subject: [PATCH 2/3] test coverage --- .../kotlin/com/nylas/resources/EventsTests.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/kotlin/com/nylas/resources/EventsTests.kt b/src/test/kotlin/com/nylas/resources/EventsTests.kt index bfb499af..43618326 100644 --- a/src/test/kotlin/com/nylas/resources/EventsTests.kt +++ b/src/test/kotlin/com/nylas/resources/EventsTests.kt @@ -329,6 +329,14 @@ class EventsTests { assertEquals(CreateEventAutoConferencingProvider.MICROSOFT_TEAMS, adapter.fromJson("\"Microsoft Teams\"")) } + @Test + fun `CreateEventAutoConferencingProvider deserializes unknown value as null`() { + val adapter = JsonHelper.moshi().adapter(CreateEventAutoConferencingProvider::class.java).nullSafe() + + assertEquals(null, adapter.fromJson("\"Hangouts\"")) + assertEquals(null, adapter.fromJson("\"some_future_provider\"")) + } + @Test fun `CreateEventManualConferencingProvider serializes properly`() { val adapter = JsonHelper.moshi().adapter(CreateEventManualConferencingProvider::class.java) @@ -353,6 +361,14 @@ class EventsTests { assertEquals(CreateEventManualConferencingProvider.SKYPE_FOR_CONSUMER, adapter.fromJson("\"Skype for Consumer\"")) } + @Test + fun `CreateEventManualConferencingProvider deserializes unknown value as null`() { + val adapter = JsonHelper.moshi().adapter(CreateEventManualConferencingProvider::class.java).nullSafe() + + assertEquals(null, adapter.fromJson("\"Hangouts\"")) + assertEquals(null, adapter.fromJson("\"some_future_provider\"")) + } + @Test fun `ConferencingProvider deserializes known values properly`() { val adapter = JsonHelper.moshi().adapter(ConferencingProvider::class.java) From d953a36be35454b3c3d38c26a9590b7bb9dfc702 Mon Sep 17 00:00:00 2001 From: Gordan Ovcaric Date: Wed, 6 May 2026 16:53:42 +0200 Subject: [PATCH 3/3] fix lint --- CHANGELOG.md | 4 ++++ src/main/kotlin/com/nylas/util/JsonHelper.kt | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6ba6826..da5d5f4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,12 @@ * `colorId` field on `Event`, `CreateEventRequest`, and `UpdateEventRequest` for Google Calendar event colors, mapped to the `color_id` JSON property. Valid values are strings `"1"` through `"11"`. See [Google Calendar Colors](https://developers.google.com/calendar/api/v3/reference/colors). +* `metadataPair` field on `ListMessagesQueryParams` (and its `Builder`) for filtering messages by metadata key-value pairs via the `metadata_pair` query parameter. (#319) ### Fixed +* `ConferencingProvider`, `CreateEventAutoConferencingProvider`, and `CreateEventManualConferencingProvider` now deserialize to `null` instead of throwing a `JsonDataException` when the API returns an unrecognized provider name (e.g. `"Hangouts"`). (#320) +* `url`, `originalErrorMessage`, and `timeout` are now exposed as readable properties on `NylasSdkRemoteClosedError` and `NylasSdkTimeoutError`. (#320) +* Thread IDs containing `/` are now URL-encoded before being interpolated into request paths, preventing malformed URLs when using `Threads.find()`, `update()`, or `destroy()`. (#318) * `Configuration.participants` now defaults to an empty list when the field is absent from the API response (e.g. group-event configurations), preventing a `JsonDataException` from being thrown during deserialization. ## [v2.15.1] - Release 2026-03-30 diff --git a/src/main/kotlin/com/nylas/util/JsonHelper.kt b/src/main/kotlin/com/nylas/util/JsonHelper.kt index 73a06be2..8db7f701 100644 --- a/src/main/kotlin/com/nylas/util/JsonHelper.kt +++ b/src/main/kotlin/com/nylas/util/JsonHelper.kt @@ -1,11 +1,11 @@ package com.nylas.util -import com.nylas.models.Connector.Companion.CONNECTOR_JSON_ADAPTER_FACTORY import com.nylas.models.ConferencingProvider -import com.nylas.models.CreateEventAutoConferencingProvider -import com.nylas.models.CreateEventManualConferencingProvider +import com.nylas.models.Connector.Companion.CONNECTOR_JSON_ADAPTER_FACTORY import com.nylas.models.CreateConnectorRequest.Companion.CREATE_CONNECTOR_JSON_ADAPTER_FACTORY import com.nylas.models.CreateCredentialRequest.Companion.CREATE_CREDENTIAL_JSON_ADAPTER_FACTORY +import com.nylas.models.CreateEventAutoConferencingProvider +import com.nylas.models.CreateEventManualConferencingProvider import com.nylas.models.GetFreeBusyResponse.Companion.FREE_BUSY_JSON_FACTORY import com.nylas.models.When.Companion.WHEN_JSON_FACTORY import com.squareup.moshi.JsonAdapter