Skip to content

Commit

Permalink
Improve error handling for attachments (#244)
Browse files Browse the repository at this point in the history
# Description
This PR introduces a new error in the case that a user uploads a file
that is too large, causing the remote connection to be closed on the API
side.

# License
<!-- Your PR comment must contain the following line for us to merge the
PR. -->
I confirm that this contribution is made under the terms of the MIT
license and that I have the authority necessary to make this
contribution on behalf of its copyright owner.
  • Loading branch information
mrashed-dev authored Sep 25, 2024
1 parent f38cdaf commit c9dd03c
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 56 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Unreleased

### Added
* Added new error class `NylasSdkRemoteClosedError` for when the remote connection is closed
* Added support for new filter fields for listing events

### [2.4.1] - Released 2024-07-26
Expand Down
81 changes: 31 additions & 50 deletions src/main/kotlin/com/nylas/NylasClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import okhttp3.Response
import java.io.IOException
import java.lang.Exception
import java.lang.reflect.Type
import java.net.SocketException
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit

Expand Down Expand Up @@ -76,112 +77,84 @@ class NylasClient(
* Access the Applications API
* @return The Applications API
*/
fun applications(): Applications {
return Applications(this)
}
fun applications(): Applications = Applications(this)

/**
* Access the Attachments API
* @return The Attachments API
*/
fun attachments(): Attachments {
return Attachments(this)
}
fun attachments(): Attachments = Attachments(this)

/**
* Access the Auth API
* @return The Auth API
*/
fun auth(): Auth {
return Auth(this)
}
fun auth(): Auth = Auth(this)

/**
* Access the Calendars API
* @return The Calendars API
*/
fun calendars(): Calendars {
return Calendars(this)
}
fun calendars(): Calendars = Calendars(this)

/**
* Access the Connectors API
* @return The Connectors API
*/
fun connectors(): Connectors {
return Connectors(this)
}
fun connectors(): Connectors = Connectors(this)

/**
* Access the Drafts API
* @return The Drafts API
*/
fun drafts(): Drafts {
return Drafts(this)
}
fun drafts(): Drafts = Drafts(this)

/**
* Access the Events API
* @return The Events API
*/
fun events(): Events {
return Events(this)
}
fun events(): Events = Events(this)

/**
* Access the Folders API
* @return The Folders API
*/
fun folders(): Folders {
return Folders(this)
}
fun folders(): Folders = Folders(this)

/**
* Access the Grants API
* @return The Grants API
*/
fun grants(): Grants {
return Grants(this)
}
fun grants(): Grants = Grants(this)

/**
* Access the Messages API
* @return The Messages API
*/
fun messages(): Messages {
return Messages(this)
}
fun messages(): Messages = Messages(this)

/**
* Access the Threads API
* @return The Threads API
*/
fun threads(): Threads {
return Threads(this)
}
fun threads(): Threads = Threads(this)

/**
* Access the Webhooks API
* @return The Webhooks API
*/
fun webhooks(): Webhooks {
return Webhooks(this)
}
fun webhooks(): Webhooks = Webhooks(this)

/**
* Access the Contacts API
* @return The Contacts API
*/
fun contacts(): Contacts {
return Contacts(this)
}
fun contacts(): Contacts = Contacts(this)

/**
* Get a URL builder instance for the Nylas API.
*/
fun newUrlBuilder(): HttpUrl.Builder {
return apiUri.newBuilder()
}
fun newUrlBuilder(): HttpUrl.Builder = apiUri.newBuilder()

/**
* Execute a GET request to the Nylas API.
Expand Down Expand Up @@ -393,6 +366,16 @@ class NylasClient(
return response.body() ?: throw Exception("Unexpected null response body")
} catch (e: SocketTimeoutException) {
throw NylasSdkTimeoutError(finalUrl.toString(), httpClient.callTimeoutMillis())
} catch (e: SocketException) {
throw NylasSdkRemoteClosedError(finalUrl.toString(), e.message ?: "Unknown error")
} catch (e: AbstractNylasApiError) {
throw e
} catch (e: Exception) {
throw NylasApiError(
type = "unknown",
message = "Unknown error occurred: ${e.message}",
statusCode = 0,
)
}
}

Expand Down Expand Up @@ -542,13 +525,11 @@ class NylasClient(
*/
companion object {
val DEFAULT_BASE_URL = Region.US.nylasApiUrl
private fun defaultHttpClient(): OkHttpClient.Builder {
return OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS)
.writeTimeout(90, TimeUnit.SECONDS)
.protocols(listOf(Protocol.HTTP_1_1))
.addNetworkInterceptor(HttpLoggingInterceptor())
}
private fun defaultHttpClient(): OkHttpClient.Builder = OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS)
.writeTimeout(90, TimeUnit.SECONDS)
.protocols(listOf(Protocol.HTTP_1_1))
.addNetworkInterceptor(HttpLoggingInterceptor())
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/com/nylas/models/NylasSdkRemoteClosedError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.nylas.models

/**
* Error thrown when the Nylas API closes the connection before the Nylas SDK receives a response.
* @param url The URL that timed out.
* @param originalErrorMessage The error message from the library that closed the connection.
*/
class NylasSdkRemoteClosedError(
url: String,
originalErrorMessage: String,
) : AbstractNylasSdkError("Nylas API closed the connection before the Nylas SDK received a response.")
24 changes: 18 additions & 6 deletions src/test/kotlin/com/nylas/NylasClientTest.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.nylas

import com.nylas.models.IQueryParams
import com.nylas.models.NylasApiError
import com.nylas.models.NylasOAuthError
import com.nylas.models.NylasSdkTimeoutError
import com.nylas.models.*
import com.nylas.util.JsonHelper
import okhttp3.*
import okhttp3.Response
import okio.Buffer
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
Expand All @@ -18,6 +16,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
import java.net.SocketException
import java.net.SocketTimeoutException
import java.nio.charset.StandardCharsets
import kotlin.test.assertFailsWith
Expand Down Expand Up @@ -304,16 +303,29 @@ class NylasClientTest {
assertNotNull(exception)
}

@Test
fun `should handle socket exception`() {
val urlBuilder = nylasClient.newUrlBuilder()
whenever(mockCall.execute()).thenThrow(SocketException())

val exception = assertFailsWith<NylasSdkRemoteClosedError> {
nylasClient.executeRequest(urlBuilder, NylasClient.HttpMethod.GET, null, String::class.java)
}

assertNotNull(exception)
}

@Test
fun `should handle unexpected null response body`() {
val urlBuilder = nylasClient.newUrlBuilder()
whenever(mockResponse.body()).thenReturn(null)

val exception = assertFailsWith<Exception> {
val exception = assertFailsWith<NylasApiError> {
nylasClient.executeRequest(urlBuilder, NylasClient.HttpMethod.GET, null, String::class.java)
}

assertEquals("Unexpected null response body", exception.message)
assertEquals("Unknown error occurred: Unexpected null response body", exception.message)
assertEquals("unknown", exception.type)
}

// TODO::Should we handle this case?
Expand Down

0 comments on commit c9dd03c

Please sign in to comment.