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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Nylas Java SDK Changelog

## [unreleased]

### Fixed
* Prevent double-encoding of pre-encoded path IDs when building request URLs, fixing Gmail attachment download 404s for attachment IDs containing reserved characters such as `:` and `=`

## [v2.17.0] - Release 2026-06-15

### Added
Expand Down
131 changes: 121 additions & 10 deletions src/main/kotlin/com/nylas/NylasClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,18 @@ open class NylasClient(
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildUrl(path, queryParams, overrides)
val url = buildRawUrl(path, queryParams, overrides)
return executeRequest(url, HttpMethod.GET, null, resultType, overrides)
}

@Throws(AbstractNylasApiError::class, NylasSdkTimeoutError::class)
internal open fun <T> executeGetEncoded(
path: String,
resultType: Type,
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildEncodedUrl(path, queryParams, overrides)
return executeRequest(url, HttpMethod.GET, null, resultType, overrides)
}

Expand All @@ -236,7 +247,20 @@ open class NylasClient(
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildUrl(path, queryParams, overrides)
val url = buildRawUrl(path, queryParams, overrides)
val jsonBody = if (requestBody != null) JsonHelper.jsonRequestBody(requestBody) else null
return executeRequest(url, HttpMethod.PUT, jsonBody, resultType, overrides)
}

@Throws(AbstractNylasApiError::class, NylasSdkTimeoutError::class)
internal open fun <T> executePutEncoded(
path: String,
resultType: Type,
requestBody: String? = null,
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildEncodedUrl(path, queryParams, overrides)
val jsonBody = if (requestBody != null) JsonHelper.jsonRequestBody(requestBody) else null
return executeRequest(url, HttpMethod.PUT, jsonBody, resultType, overrides)
}
Expand All @@ -258,7 +282,20 @@ open class NylasClient(
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildUrl(path, queryParams, overrides)
val url = buildRawUrl(path, queryParams, overrides)
val jsonBody = if (requestBody != null) JsonHelper.jsonRequestBody(requestBody) else null
return executeRequest(url, HttpMethod.PATCH, jsonBody, resultType, overrides)
}

@Throws(AbstractNylasApiError::class, NylasSdkTimeoutError::class)
internal open fun <T> executePatchEncoded(
path: String,
resultType: Type,
requestBody: String? = null,
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildEncodedUrl(path, queryParams, overrides)
val jsonBody = if (requestBody != null) JsonHelper.jsonRequestBody(requestBody) else null
return executeRequest(url, HttpMethod.PATCH, jsonBody, resultType, overrides)
}
Expand All @@ -280,7 +317,23 @@ open class NylasClient(
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildUrl(path, queryParams, overrides)
val url = buildRawUrl(path, queryParams, overrides)
var jsonBody = ByteArray(0).toRequestBody(null)
if (requestBody != null) {
jsonBody = JsonHelper.jsonRequestBody(requestBody)
}
return executeRequest(url, HttpMethod.POST, jsonBody, resultType, overrides)
}

@Throws(AbstractNylasApiError::class, NylasSdkTimeoutError::class)
internal open fun <T> executePostEncoded(
path: String,
resultType: Type,
requestBody: String? = null,
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildEncodedUrl(path, queryParams, overrides)
var jsonBody = ByteArray(0).toRequestBody(null)
if (requestBody != null) {
jsonBody = JsonHelper.jsonRequestBody(requestBody)
Expand All @@ -303,7 +356,18 @@ open class NylasClient(
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildUrl(path, queryParams, overrides)
val url = buildRawUrl(path, queryParams, overrides)
return executeRequest(url, HttpMethod.DELETE, null, resultType, overrides)
}

@Throws(AbstractNylasApiError::class, NylasSdkTimeoutError::class)
internal open fun <T> executeDeleteEncoded(
path: String,
resultType: Type,
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildEncodedUrl(path, queryParams, overrides)
return executeRequest(url, HttpMethod.DELETE, null, resultType, overrides)
}

Expand All @@ -324,7 +388,20 @@ open class NylasClient(
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildUrl(path, queryParams, overrides)
val url = buildRawUrl(path, queryParams, overrides)
val jsonBody = JsonHelper.jsonRequestBody(requestBody)
return executeRequest(url, HttpMethod.DELETE, jsonBody, resultType, overrides)
}

@Throws(AbstractNylasApiError::class, NylasSdkTimeoutError::class)
internal open fun <T> executeDeleteEncoded(
path: String,
resultType: Type,
requestBody: String,
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildEncodedUrl(path, queryParams, overrides)
val jsonBody = JsonHelper.jsonRequestBody(requestBody)
return executeRequest(url, HttpMethod.DELETE, jsonBody, resultType, overrides)
}
Expand All @@ -348,7 +425,20 @@ open class NylasClient(
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildUrl(path, queryParams, overrides)
val url = buildRawUrl(path, queryParams, overrides)
return executeRequest(url, method, requestBody, resultType, overrides)
}

@Throws(AbstractNylasApiError::class, NylasSdkTimeoutError::class)
internal open fun <T> executeFormRequestEncoded(
path: String,
method: HttpMethod,
requestBody: RequestBody,
resultType: Type,
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): T {
val url = buildEncodedUrl(path, queryParams, overrides)
return executeRequest(url, method, requestBody, resultType, overrides)
}

Expand Down Expand Up @@ -406,7 +496,17 @@ open class NylasClient(
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): ResponseBody {
val url = buildUrl(path, queryParams, overrides)
val url = buildRawUrl(path, queryParams, overrides)
return this.executeRequestRawResponse(url, HttpMethod.GET, null, overrides)
}

@Throws(AbstractNylasApiError::class, NylasSdkTimeoutError::class)
internal open fun downloadResponseEncoded(
path: String,
queryParams: IQueryParams? = null,
overrides: RequestOverrides? = null,
): ResponseBody {
val url = buildEncodedUrl(path, queryParams, overrides)
return this.executeRequestRawResponse(url, HttpMethod.GET, null, overrides)
}

Expand Down Expand Up @@ -526,14 +626,25 @@ open class NylasClient(
)
}

private fun buildUrl(path: String, queryParams: IQueryParams?, overrides: RequestOverrides?): HttpUrl.Builder {
// Sets the API URI if it is provided in the overrides.
private fun buildRawUrl(path: String, queryParams: IQueryParams?, overrides: RequestOverrides?): HttpUrl.Builder {
var url = if (overrides?.apiUri != null) {
overrides.apiUri.toHttpUrl().newBuilder().addPathSegments(path)
} else {
newUrlBuilder().addPathSegments(path)
}
if (queryParams != null) {
url = addQueryParams(url, queryParams.convertToMap())
}

return url
}

private fun buildEncodedUrl(path: String, queryParams: IQueryParams?, overrides: RequestOverrides?): HttpUrl.Builder {
Comment thread
googsvg marked this conversation as resolved.
var url = if (overrides?.apiUri != null) {
overrides.apiUri.toHttpUrl().newBuilder().addEncodedPathSegments(path)
} else {
newUrlBuilder().addEncodedPathSegments(path)
}
if (queryParams != null) {
url = addQueryParams(url, queryParams.convertToMap())
}
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/com/nylas/resources/Attachments.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class Attachments(client: NylasClient) : Resource<Attachment>(client, Attachment
@Throws(NylasOAuthError::class, NylasSdkTimeoutError::class)
@JvmOverloads
fun find(identifier: String, attachmentId: String, queryParams: FindAttachmentQueryParams, overrides: RequestOverrides? = null): Response<Attachment> {
val path = String.format("v3/grants/%s/attachments/%s", identifier, PathEncoder.encode(attachmentId))
return findResource(path, queryParams, overrides = overrides)
val path = String.format("v3/grants/%s/attachments/%s", PathEncoder.encode(identifier), PathEncoder.encode(attachmentId))
return findResourceEncoded(path, queryParams, overrides = overrides)
}

/**
Expand All @@ -46,9 +46,9 @@ class Attachments(client: NylasClient) : Resource<Attachment>(client, Attachment
@Throws(NylasOAuthError::class, NylasSdkTimeoutError::class)
@JvmOverloads
fun download(identifier: String, attachmentId: String, queryParams: FindAttachmentQueryParams, overrides: RequestOverrides? = null): ResponseBody {
val path = String.format("v3/grants/%s/attachments/%s/download", identifier, PathEncoder.encode(attachmentId))
val path = String.format("v3/grants/%s/attachments/%s/download", PathEncoder.encode(identifier), PathEncoder.encode(attachmentId))

return client.downloadResponse(path, queryParams, overrides = overrides)
return client.downloadResponseEncoded(path, queryParams, overrides = overrides)
}

/**
Expand Down
12 changes: 6 additions & 6 deletions src/main/kotlin/com/nylas/resources/Bookings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Bookings(client: NylasClient) : Resource<Booking>(client, Booking::class.j
overrides: RequestOverrides? = null,
): Response<Booking> {
val path = String.format("v3/scheduling/bookings/%s", PathEncoder.encode(bookingId))
return findResource(path, queryParams, overrides = overrides)
return findResourceEncoded(path, queryParams, overrides = overrides)
}

/**
Expand All @@ -47,7 +47,7 @@ class Bookings(client: NylasClient) : Resource<Booking>(client, Booking::class.j
val path = "v3/scheduling/bookings"
val adapter = JsonHelper.moshi().adapter(CreateBookingRequest::class.java)
val serializedRequestBody = adapter.toJson(requestBody)
return createResource(path, serializedRequestBody, queryParams, overrides = overrides)
return createResourceEncoded(path, serializedRequestBody, queryParams, overrides = overrides)
}

/**
Expand All @@ -69,7 +69,7 @@ class Bookings(client: NylasClient) : Resource<Booking>(client, Booking::class.j
val path = String.format("v3/scheduling/bookings/%s", PathEncoder.encode(bookingId))
val adapter = JsonHelper.moshi().adapter(ConfirmBookingRequest::class.java)
val serializedRequestBody = adapter.toJson(requestBody)
return updateResource(path, serializedRequestBody, queryParams, overrides = overrides)
return updateResourceEncoded(path, serializedRequestBody, queryParams, overrides = overrides)
}

/**
Expand All @@ -91,7 +91,7 @@ class Bookings(client: NylasClient) : Resource<Booking>(client, Booking::class.j
val path = String.format("v3/scheduling/bookings/%s", PathEncoder.encode(bookingId))
val adapter = JsonHelper.moshi().adapter(RescheduleBookingRequest::class.java)
val serializedRequestBody = adapter.toJson(requestBody)
return patchResource(path, serializedRequestBody, queryParams, overrides = overrides)
return patchResourceEncoded(path, serializedRequestBody, queryParams, overrides = overrides)
}

/**
Expand Down Expand Up @@ -136,7 +136,7 @@ class Bookings(client: NylasClient) : Resource<Booking>(client, Booking::class.j
val path = String.format("v3/scheduling/bookings/%s", PathEncoder.encode(bookingId))
val adapter = JsonHelper.moshi().adapter(DestroyBookingRequest::class.java)
val serializedRequestBody = adapter.toJson(requestBody)
return client.executeDelete(path, DeleteResponse::class.java, serializedRequestBody, queryParams, overrides)
return client.executeDeleteEncoded(path, DeleteResponse::class.java, serializedRequestBody, queryParams, overrides)
}

/**
Expand All @@ -153,6 +153,6 @@ class Bookings(client: NylasClient) : Resource<Booking>(client, Booking::class.j
*/
fun destroy(bookingId: String, queryParams: DestroyBookingQueryParams? = null, overrides: RequestOverrides? = null): DeleteResponse {
val path = String.format("v3/scheduling/bookings/%s", PathEncoder.encode(bookingId))
return destroyResource(path, queryParams, overrides = overrides)
return destroyResourceEncoded(path, queryParams, overrides = overrides)
}
}
26 changes: 13 additions & 13 deletions src/main/kotlin/com/nylas/resources/Calendars.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class Calendars(client: NylasClient) : Resource<Calendar>(client, Calendar::clas
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
@JvmOverloads
fun list(identifier: String, queryParams: ListCalendersQueryParams? = null, overrides: RequestOverrides? = null): ListResponse<Calendar> {
val path = String.format("v3/grants/%s/calendars", identifier)
return listResource(path, queryParams, overrides)
val path = String.format("v3/grants/%s/calendars", PathEncoder.encode(identifier))
return listResourceEncoded(path, queryParams, overrides)
}

/**
Expand All @@ -40,8 +40,8 @@ class Calendars(client: NylasClient) : Resource<Calendar>(client, Calendar::clas
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
@JvmOverloads
fun find(identifier: String, calendarId: String, overrides: RequestOverrides? = null): Response<Calendar> {
val path = String.format("v3/grants/%s/calendars/%s", identifier, PathEncoder.encode(calendarId))
return findResource(path, overrides = overrides)
val path = String.format("v3/grants/%s/calendars/%s", PathEncoder.encode(identifier), PathEncoder.encode(calendarId))
return findResourceEncoded(path, overrides = overrides)
}

/**
Expand All @@ -54,10 +54,10 @@ class Calendars(client: NylasClient) : Resource<Calendar>(client, Calendar::clas
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
@JvmOverloads
fun create(identifier: String, requestBody: CreateCalendarRequest, overrides: RequestOverrides? = null): Response<Calendar> {
val path = String.format("v3/grants/%s/calendars", identifier)
val path = String.format("v3/grants/%s/calendars", PathEncoder.encode(identifier))
val adapter = JsonHelper.moshi().adapter(CreateCalendarRequest::class.java)
val serializedRequestBody = adapter.toJson(requestBody)
return createResource(path, serializedRequestBody, overrides = overrides)
return createResourceEncoded(path, serializedRequestBody, overrides = overrides)
}

/**
Expand All @@ -71,10 +71,10 @@ class Calendars(client: NylasClient) : Resource<Calendar>(client, Calendar::clas
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
@JvmOverloads
fun update(identifier: String, calendarId: String, requestBody: UpdateCalendarRequest, overrides: RequestOverrides? = null): Response<Calendar> {
val path = String.format("v3/grants/%s/calendars/%s", identifier, PathEncoder.encode(calendarId))
val path = String.format("v3/grants/%s/calendars/%s", PathEncoder.encode(identifier), PathEncoder.encode(calendarId))
val adapter = JsonHelper.moshi().adapter(UpdateCalendarRequest::class.java)
val serializedRequestBody = adapter.toJson(requestBody)
return updateResource(path, serializedRequestBody, overrides = overrides)
return updateResourceEncoded(path, serializedRequestBody, overrides = overrides)
}

/**
Expand All @@ -86,8 +86,8 @@ class Calendars(client: NylasClient) : Resource<Calendar>(client, Calendar::clas
*/
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
fun destroy(identifier: String, calendarId: String, overrides: RequestOverrides? = null): DeleteResponse {
val path = String.format("v3/grants/%s/calendars/%s", identifier, PathEncoder.encode(calendarId))
return destroyResource(path, overrides = overrides)
val path = String.format("v3/grants/%s/calendars/%s", PathEncoder.encode(identifier), PathEncoder.encode(calendarId))
return destroyResourceEncoded(path, overrides = overrides)
}

/**
Expand All @@ -107,7 +107,7 @@ class Calendars(client: NylasClient) : Resource<Calendar>(client, Calendar::clas

val responseType = Types.newParameterizedType(Response::class.java, GetAvailabilityResponse::class.java)

return client.executePost(path, responseType, serializedRequestBody, overrides = overrides)
return client.executePostEncoded(path, responseType, serializedRequestBody, overrides = overrides)
}

/**
Expand All @@ -120,14 +120,14 @@ class Calendars(client: NylasClient) : Resource<Calendar>(client, Calendar::clas
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
@JvmOverloads
fun getFreeBusy(identifier: String, request: GetFreeBusyRequest, overrides: RequestOverrides? = null): Response<List<GetFreeBusyResponse>> {
val path = String.format("v3/grants/%s/calendars/free-busy", identifier)
val path = String.format("v3/grants/%s/calendars/free-busy", PathEncoder.encode(identifier))

val serializedRequestBody = JsonHelper.moshi()
.adapter(GetFreeBusyRequest::class.java)
.toJson(request)

val responseType = Types.newParameterizedType(Response::class.java, GET_FREE_BUSY_RESPONSE_ADAPTER)

return client.executePost(path, responseType, serializedRequestBody, overrides = overrides)
return client.executePostEncoded(path, responseType, serializedRequestBody, overrides = overrides)
}
}
Loading
Loading