Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/com/nylas/NylasClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/nylas/models/NylasSdkRemoteClosedError.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
4 changes: 2 additions & 2 deletions src/main/kotlin/com/nylas/models/NylasSdkTimeoutError.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
3 changes: 2 additions & 1 deletion src/main/kotlin/com/nylas/util/CreateConferencingAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/com/nylas/util/CreateWhenAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
11 changes: 10 additions & 1 deletion src/main/kotlin/com/nylas/util/JsonHelper.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package com.nylas.util

import com.nylas.models.ConferencingProvider
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
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.*
Expand All @@ -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())
Expand Down Expand Up @@ -120,6 +128,7 @@ class JsonHelper {
* @suppress Not for public use.
*/
@JvmStatic
@Suppress("UNCHECKED_CAST")
fun <T> adapter(type: Type): JsonAdapter<T> {
return moshi.adapter<Any>(type).indent(" ") as JsonAdapter<T>
}
Expand Down Expand Up @@ -234,7 +243,7 @@ class JsonHelper {
*/
@JvmStatic
fun jsonRequestBody(json: String): RequestBody {
return RequestBody.create(jsonType(), json.toByteArray())
return json.toByteArray().toRequestBody(jsonType())
}
}
}
3 changes: 2 additions & 1 deletion src/main/kotlin/com/nylas/util/UpdateConferencingAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/com/nylas/util/UpdateConnectorAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/com/nylas/util/UpdateWhenAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
67 changes: 67 additions & 0 deletions src/test/kotlin/com/nylas/resources/EventsTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -353,6 +361,65 @@ 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)

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<Conferencing.Details>(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)
Expand Down
Loading