diff --git a/CHANGELOG.md b/CHANGELOG.md
index c25166fb..e16510af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,8 +2,15 @@
## [Unreleased]
+### Added
+* `select` query parameter support for list and find operations on contacts, messages, events, threads, folders, calendars, and attachments
+* `FindFolderQueryParams`, `FindThreadQueryParams`, and `FindCalendarQueryParams` classes for find operations with select support
+* Comprehensive test coverage for select parameter functionality across all supported endpoints
+* Extensive examples demonstrating proper usage of select parameter with all endpoints
+
### Changed
* `SendMessageRequest.sendAt` field changed from `Int?` to `Long?` to support Unix timestamps beyond 2038. Maintains backward compatibility through method overloading - existing `Int` parameters are automatically converted to `Long`. **Note:** Kotlin users passing integer literals may need to specify type explicitly (e.g., `1620000000L` or `1620000000 as Int`).
+* Model properties (`grantId`, `id`, and other required fields) are now optional in `Message`, `Contact`, `Event`, `Thread`, `Folder`, and `Calendar` to support select query parameter functionality
## [2.13.1]
diff --git a/examples/src/main/kotlin/com/nylas/examples/KotlinFoldersExample.kt b/examples/src/main/kotlin/com/nylas/examples/KotlinFoldersExample.kt
index 631bdc49..a4238bcb 100644
--- a/examples/src/main/kotlin/com/nylas/examples/KotlinFoldersExample.kt
+++ b/examples/src/main/kotlin/com/nylas/examples/KotlinFoldersExample.kt
@@ -86,12 +86,14 @@ fun main() {
val comprehensiveFolders = nylasClient.folders().list(grantId, comprehensiveParams)
println("Found ${comprehensiveFolders.data.size} folders with comprehensive options:")
comprehensiveFolders.data.forEach { folder ->
- println(" - ${folder.name}")
- println(" ID: ${folder.id}")
+ println(" - ${folder.name ?: "N/A"}")
+ println(" ID: ${folder.id ?: "N/A"}")
println(" Unread Count: ${folder.unreadCount ?: 0}")
if (folder.parentId != null) {
println(" Parent ID: ${folder.parentId}")
}
+ // Note: When using select parameter, only selected fields are returned
+ // grantId and other fields may be null if not included in select
println()
}
diff --git a/examples/src/main/kotlin/com/nylas/examples/SelectParameterExample.kt b/examples/src/main/kotlin/com/nylas/examples/SelectParameterExample.kt
new file mode 100644
index 00000000..92ae7478
--- /dev/null
+++ b/examples/src/main/kotlin/com/nylas/examples/SelectParameterExample.kt
@@ -0,0 +1,361 @@
+package com.nylas.examples
+
+import com.nylas.NylasClient
+import com.nylas.models.*
+
+/**
+ * Example demonstrating how to use the select parameter with various Nylas endpoints.
+ *
+ * The select parameter allows you to specify which fields you want returned in the response,
+ * which can help optimize response size and reduce latency by limiting queries to only
+ * the information that you need.
+ *
+ * When using select, fields not included in the select parameter will be null in the response objects.
+ */
+class SelectParameterExample {
+
+ private val client = NylasClient(
+ apiKey = "YOUR_API_KEY"
+ )
+
+ /**
+ * Example: List folders with select parameter
+ * Only returns id and name fields, grant_id will be null
+ */
+ fun listFoldersWithSelect() {
+ try {
+ val queryParams = ListFoldersQueryParams.Builder()
+ .limit(10)
+ .select("id,name") // Only return id and name
+ .build()
+
+ val response = client.folders().list("grant-id", queryParams)
+
+ response.data.forEach { folder ->
+ println("Folder ID: ${folder.id}")
+ println("Folder Name: ${folder.name}")
+ println("Grant ID: ${folder.grantId}") // This will be null because not in select
+ println("---")
+ }
+ } catch (e: Exception) {
+ println("Error listing folders: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: Find folder with select parameter
+ * Only returns id, name, and grant_id fields
+ */
+ fun findFolderWithSelect() {
+ try {
+ val queryParams = FindFolderQueryParams.Builder()
+ .select("id,name,grant_id")
+ .build()
+
+ val response = client.folders().find("grant-id", "folder-id", queryParams)
+ val folder = response.data
+
+ println("Folder ID: ${folder.id}")
+ println("Folder Name: ${folder.name}")
+ println("Grant ID: ${folder.grantId}")
+ println("Parent ID: ${folder.parentId}") // This will be null because not in select
+ } catch (e: Exception) {
+ println("Error finding folder: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: List contacts with select parameter
+ * Only returns id, display_name, and emails
+ */
+ fun listContactsWithSelect() {
+ try {
+ val queryParams = ListContactsQueryParams.Builder()
+ .limit(5)
+ .select("id,display_name,emails")
+ .build()
+
+ val response = client.contacts().list("grant-id", queryParams)
+
+ response.data.forEach { contact ->
+ println("Contact ID: ${contact.id}")
+ println("Display Name: ${contact.displayName}")
+ println("Emails: ${contact.emails?.map { it.email }}")
+ println("Grant ID: ${contact.grantId}") // This will be null because not in select
+ println("---")
+ }
+ } catch (e: Exception) {
+ println("Error listing contacts: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: Find contact with select parameter
+ */
+ fun findContactWithSelect() {
+ try {
+ val queryParams = FindContactQueryParams.Builder()
+ .select("id,display_name,grant_id")
+ .profilePicture(true)
+ .build()
+
+ val response = client.contacts().find("grant-id", "contact-id", queryParams)
+ val contact = response.data
+
+ println("Contact ID: ${contact.id}")
+ println("Display Name: ${contact.displayName}")
+ println("Grant ID: ${contact.grantId}")
+ println("Phone Numbers: ${contact.phoneNumbers}") // This will be null because not in select
+ } catch (e: Exception) {
+ println("Error finding contact: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: List messages with select parameter
+ * Only returns id, subject, and from fields
+ */
+ fun listMessagesWithSelect() {
+ try {
+ val queryParams = ListMessagesQueryParams.Builder()
+ .limit(10)
+ .select("id,subject,from")
+ .build()
+
+ val response = client.messages().list("grant-id", queryParams)
+
+ response.data.forEach { message ->
+ println("Message ID: ${message.id}")
+ println("Subject: ${message.subject}")
+ println("From: ${message.from?.map { "${it.name} <${it.email}>" }}")
+ println("Grant ID: ${message.grantId}") // This will be null because not in select
+ println("---")
+ }
+ } catch (e: Exception) {
+ println("Error listing messages: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: Find message with select parameter
+ */
+ fun findMessageWithSelect() {
+ try {
+ val queryParams = FindMessageQueryParams.Builder()
+ .select("id,subject,body,grant_id")
+ .fields(MessageFields.STANDARD)
+ .build()
+
+ val response = client.messages().find("grant-id", "message-id", queryParams)
+ val message = response.data
+
+ println("Message ID: ${message.id}")
+ println("Subject: ${message.subject}")
+ println("Body: ${message.body}")
+ println("Grant ID: ${message.grantId}")
+ println("Attachments: ${message.attachments}") // This will be null because not in select
+ } catch (e: Exception) {
+ println("Error finding message: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: List threads with select parameter
+ */
+ fun listThreadsWithSelect() {
+ try {
+ val queryParams = ListThreadsQueryParams.Builder()
+ .limit(10)
+ .select("id,subject,latest_draft_or_message")
+ .build()
+
+ val response = client.threads().list("grant-id", queryParams)
+
+ response.data.forEach { thread ->
+ println("Thread ID: ${thread.id}")
+ println("Subject: ${thread.subject}")
+ println("Latest Message: ${thread.latestDraftOrMessage}")
+ println("Grant ID: ${thread.grantId}") // This will be null because not in select
+ println("---")
+ }
+ } catch (e: Exception) {
+ println("Error listing threads: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: Find thread with select parameter
+ */
+ fun findThreadWithSelect() {
+ try {
+ val queryParams = FindThreadQueryParams.Builder()
+ .select("id,subject,grant_id")
+ .build()
+
+ val response = client.threads().find("grant-id", "thread-id", queryParams)
+ val thread = response.data
+
+ println("Thread ID: ${thread.id}")
+ println("Subject: ${thread.subject}")
+ println("Grant ID: ${thread.grantId}")
+ println("Message IDs: ${thread.messageIds}") // This will be null because not in select
+ } catch (e: Exception) {
+ println("Error finding thread: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: List events with select parameter
+ */
+ fun listEventsWithSelect() {
+ try {
+ val queryParams = ListEventQueryParams.Builder("primary")
+ .limit(10)
+ .select("id,title,when")
+ .build()
+
+ val response = client.events().list("grant-id", queryParams)
+
+ response.data.forEach { event ->
+ println("Event ID: ${event.id}")
+ println("Title: ${event.title}")
+ println("When: ${event.`when`}")
+ println("Grant ID: ${event.grantId}") // This will be null because not in select
+ println("---")
+ }
+ } catch (e: Exception) {
+ println("Error listing events: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: Find event with select parameter
+ */
+ fun findEventWithSelect() {
+ try {
+ val queryParams = FindEventQueryParams.Builder("primary")
+ .select("id,title,description,grant_id")
+ .build()
+
+ val response = client.events().find("grant-id", "event-id", queryParams)
+ val event = response.data
+
+ println("Event ID: ${event.id}")
+ println("Title: ${event.title}")
+ println("Description: ${event.description}")
+ println("Grant ID: ${event.grantId}")
+ println("Participants: ${event.participants}") // This will be null because not in select
+ } catch (e: Exception) {
+ println("Error finding event: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: List calendars with select parameter
+ */
+ fun listCalendarsWithSelect() {
+ try {
+ val queryParams = ListCalendersQueryParams.Builder()
+ .limit(10)
+ .select("id,name,description")
+ .build()
+
+ val response = client.calendars().list("grant-id", queryParams)
+
+ response.data.forEach { calendar ->
+ println("Calendar ID: ${calendar.id}")
+ println("Name: ${calendar.name}")
+ println("Description: ${calendar.description}")
+ println("Grant ID: ${calendar.grantId}") // This will be null because not in select
+ println("---")
+ }
+ } catch (e: Exception) {
+ println("Error listing calendars: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: Find calendar with select parameter
+ */
+ fun findCalendarWithSelect() {
+ try {
+ val queryParams = FindCalendarQueryParams.Builder()
+ .select("id,name,grant_id")
+ .build()
+
+ val response = client.calendars().find("grant-id", "calendar-id", queryParams)
+ val calendar = response.data
+
+ println("Calendar ID: ${calendar.id}")
+ println("Name: ${calendar.name}")
+ println("Grant ID: ${calendar.grantId}")
+ println("Timezone: ${calendar.timezone}") // This will be null because not in select
+ } catch (e: Exception) {
+ println("Error finding calendar: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: Find attachment with select parameter
+ */
+ fun findAttachmentWithSelect() {
+ try {
+ val queryParams = FindAttachmentQueryParams.Builder("message-id")
+ .select("id,filename,content_type")
+ .build()
+
+ val response = client.attachments().find("grant-id", "attachment-id", queryParams)
+ val attachment = response.data
+
+ println("Attachment ID: ${attachment.id}")
+ println("Filename: ${attachment.filename}")
+ println("Content Type: ${attachment.contentType}")
+ println("Grant ID: ${attachment.grantId}") // This will be null because not in select
+ } catch (e: Exception) {
+ println("Error finding attachment: ${e.message}")
+ }
+ }
+
+ /**
+ * Example: Download attachment with select parameter
+ * Note: The download itself returns the binary data, but you can still use select
+ * to limit the metadata returned in other operations
+ */
+ fun downloadAttachmentWithSelect() {
+ try {
+ val queryParams = FindAttachmentQueryParams.Builder("message-id")
+ .select("id,filename,content_type,size")
+ .build()
+
+ // Download the attachment data
+ val responseBody = client.attachments().download("grant-id", "attachment-id", queryParams)
+ val data = responseBody.bytes()
+ responseBody.close()
+
+ println("Downloaded ${data.size} bytes")
+ } catch (e: Exception) {
+ println("Error downloading attachment: ${e.message}")
+ }
+ }
+}
+
+fun main() {
+ val example = SelectParameterExample()
+
+ println("=== Select Parameter Examples ===")
+ println()
+
+ println("1. List folders with select:")
+ example.listFoldersWithSelect()
+ println()
+
+ println("2. Find folder with select:")
+ example.findFolderWithSelect()
+ println()
+
+ println("3. List contacts with select:")
+ example.listContactsWithSelect()
+ println()
+
+ // Add more examples as needed...
+}
diff --git a/src/main/kotlin/com/nylas/models/Calendar.kt b/src/main/kotlin/com/nylas/models/Calendar.kt
index a72784b1..ab14e45a 100644
--- a/src/main/kotlin/com/nylas/models/Calendar.kt
+++ b/src/main/kotlin/com/nylas/models/Calendar.kt
@@ -10,17 +10,17 @@ data class Calendar(
* Globally unique object identifier.
*/
@Json(name = "id")
- val id: String = "",
+ val id: String? = null,
/**
* Grant ID of the Nylas account.
*/
@Json(name = "grant_id")
- val grantId: String = "",
+ val grantId: String? = null,
/**
* Name of the Calendar.
*/
@Json(name = "name")
- val name: String = "",
+ val name: String? = null,
/**
* The type of object.
*/
@@ -31,22 +31,22 @@ data class Calendar(
* @see List of tz database time zones
*/
@Json(name = "timezone")
- val timezone: String = "",
+ val timezone: String? = null,
/**
* If the event participants are able to edit the event.
*/
@Json(name = "read_only")
- val readOnly: Boolean = false,
+ val readOnly: Boolean? = null,
/**
* If the calendar is owned by the user account.
*/
@Json(name = "is_owned_by_user")
- val isOwnedByUser: Boolean = false,
+ val isOwnedByUser: Boolean? = null,
/**
* If the calendar is the primary calendar.
*/
@Json(name = "is_primary")
- val isPrimary: Boolean = false,
+ val isPrimary: Boolean? = null,
/**
* Description of the calendar.
*/
diff --git a/src/main/kotlin/com/nylas/models/Contact.kt b/src/main/kotlin/com/nylas/models/Contact.kt
index 5a80c4ab..f0dc9d1c 100644
--- a/src/main/kotlin/com/nylas/models/Contact.kt
+++ b/src/main/kotlin/com/nylas/models/Contact.kt
@@ -7,9 +7,9 @@ import com.squareup.moshi.Json
*/
data class Contact(
@Json(name = "id")
- val id: String = "",
+ val id: String? = null,
@Json(name = "grant_id")
- val grantId: String = "",
+ val grantId: String? = null,
@Json(name = "object")
private val obj: String = "contact",
@Json(name = "birthday")
@@ -17,7 +17,7 @@ data class Contact(
@Json(name = "company_name")
val companyName: String? = null,
@Json(name = "display_name")
- val displayName: String = "",
+ val displayName: String? = null,
@Json(name = "emails")
val emails: List? = null,
@Json(name = "im_addresses")
diff --git a/src/main/kotlin/com/nylas/models/Event.kt b/src/main/kotlin/com/nylas/models/Event.kt
index bd476a54..8f486a0d 100644
--- a/src/main/kotlin/com/nylas/models/Event.kt
+++ b/src/main/kotlin/com/nylas/models/Event.kt
@@ -10,12 +10,12 @@ data class Event(
* Globally unique object identifier.
*/
@Json(name = "id")
- val id: String = "",
+ val id: String? = null,
/**
* Grant ID of the Nylas account.
*/
@Json(name = "grant_id")
- val grantId: String = "",
+ val grantId: String? = null,
/**
* Representation of time and duration for events. When object can be in one of four formats (sub-objects):
* - [When.Date]
@@ -24,22 +24,22 @@ data class Event(
* - [When.Timespan]
*/
@Json(name = "when")
- private val whenObj: When = When.Time(0),
+ private val whenObj: When? = null,
/**
* This value determines whether to show this event's time block as available on shared or public calendars.
*/
@Json(name = "busy")
- val busy: Boolean = false,
+ val busy: Boolean? = null,
/**
* Calendar ID of the event.
*/
@Json(name = "calendar_id")
- val calendarId: String = "",
+ val calendarId: String? = null,
/**
* Whether participants of the event should be hidden.
*/
@Json(name = "hide_participants")
- val hideParticipants: Boolean = false,
+ val hideParticipants: Boolean? = null,
/**
* The type of object.
*/
@@ -49,17 +49,17 @@ data class Event(
* List of participants invited to the event. Participants may also be rooms or resources.
*/
@Json(name = "participants")
- val participants: List = emptyList(),
+ val participants: List? = null,
/**
* If the event participants are able to edit the event.
*/
@Json(name = "read_only")
- val readOnly: Boolean = false,
+ val readOnly: Boolean? = null,
/**
* Visibility of the event, if the event is private or public.
*/
@Json(name = "visibility")
- val visibility: EventVisibility = EventVisibility.DEFAULT,
+ val visibility: EventVisibility? = null,
/**
* Representation of conferencing details for events. Conferencing object can be in one of two formats (sub-objects):
* - [Conferencing.Autocreate]
@@ -164,7 +164,7 @@ data class Event(
/**
* Get the representation of time and duration for events.
- * @return The representation of time and duration for events.
+ * @return The representation of time and duration for events, or null if not set.
*/
- fun getWhen(): When = whenObj
+ fun getWhen(): When? = whenObj
}
diff --git a/src/main/kotlin/com/nylas/models/FindAttachmentQueryParams.kt b/src/main/kotlin/com/nylas/models/FindAttachmentQueryParams.kt
index e8d8286f..db882795 100644
--- a/src/main/kotlin/com/nylas/models/FindAttachmentQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/FindAttachmentQueryParams.kt
@@ -11,6 +11,13 @@ data class FindAttachmentQueryParams(
*/
@Json(name = "message_id")
val messageId: String,
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
) : IQueryParams {
/**
* Builder for [FindAttachmentQueryParams].
@@ -19,12 +26,22 @@ data class FindAttachmentQueryParams(
data class Builder(
private val messageId: String,
) {
+ private var select: String? = null
+
+ /**
+ * Sets the fields to select.
+ * @param select The fields to select.
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
/**
* Builds a new [FindAttachmentQueryParams] instance.
* @return [FindAttachmentQueryParams]
*/
fun build() = FindAttachmentQueryParams(
messageId,
+ select,
)
}
}
diff --git a/src/main/kotlin/com/nylas/models/FindCalendarQueryParams.kt b/src/main/kotlin/com/nylas/models/FindCalendarQueryParams.kt
new file mode 100644
index 00000000..c7302c62
--- /dev/null
+++ b/src/main/kotlin/com/nylas/models/FindCalendarQueryParams.kt
@@ -0,0 +1,38 @@
+package com.nylas.models
+
+import com.squareup.moshi.Json
+
+/**
+ * Class representing the query parameters for finding a calendar.
+ */
+data class FindCalendarQueryParams(
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
+) : IQueryParams {
+ /**
+ * Builder for [FindCalendarQueryParams].
+ */
+ class Builder {
+ private var select: String? = null
+
+ /**
+ * Sets the fields to select.
+ * @param select The fields to select.
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
+ /**
+ * Builds the [FindCalendarQueryParams] object.
+ * @return The [FindCalendarQueryParams] object.
+ */
+ fun build() = FindCalendarQueryParams(
+ select = select,
+ )
+ }
+}
diff --git a/src/main/kotlin/com/nylas/models/FindContactQueryParams.kt b/src/main/kotlin/com/nylas/models/FindContactQueryParams.kt
index 9e5970fd..226b7db8 100644
--- a/src/main/kotlin/com/nylas/models/FindContactQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/FindContactQueryParams.kt
@@ -5,4 +5,42 @@ import com.squareup.moshi.Json
data class FindContactQueryParams(
@Json(name = "profile_picture")
val profilePicture: Boolean? = null,
-) : IQueryParams
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
+) : IQueryParams {
+ /**
+ * Builder for [FindContactQueryParams].
+ */
+ class Builder {
+ private var profilePicture: Boolean? = null
+ private var select: String? = null
+
+ /**
+ * Sets whether to include profile picture.
+ * @param profilePicture Whether to include profile picture.
+ * @return The builder.
+ */
+ fun profilePicture(profilePicture: Boolean?) = apply { this.profilePicture = profilePicture }
+
+ /**
+ * Sets the fields to select.
+ * @param select The fields to select.
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
+ /**
+ * Builds the [FindContactQueryParams] object.
+ * @return The [FindContactQueryParams] object.
+ */
+ fun build() = FindContactQueryParams(
+ profilePicture = profilePicture,
+ select = select,
+ )
+ }
+}
diff --git a/src/main/kotlin/com/nylas/models/FindEventQueryParams.kt b/src/main/kotlin/com/nylas/models/FindEventQueryParams.kt
index 4e739f9f..da862fb6 100644
--- a/src/main/kotlin/com/nylas/models/FindEventQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/FindEventQueryParams.kt
@@ -11,6 +11,13 @@ data class FindEventQueryParams(
*/
@Json(name = "calendar_id")
val calendarId: String,
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
) : IQueryParams {
/**
* Builder for [FindEventQueryParams].
@@ -19,12 +26,22 @@ data class FindEventQueryParams(
data class Builder(
private val calendarId: String,
) {
+ private var select: String? = null
+
+ /**
+ * Sets the fields to select.
+ * @param select The fields to select.
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
/**
* Builds a new [FindEventQueryParams] instance.
* @return [FindEventQueryParams]
*/
fun build() = FindEventQueryParams(
calendarId,
+ select,
)
}
}
diff --git a/src/main/kotlin/com/nylas/models/FindFolderQueryParams.kt b/src/main/kotlin/com/nylas/models/FindFolderQueryParams.kt
new file mode 100644
index 00000000..6ed83371
--- /dev/null
+++ b/src/main/kotlin/com/nylas/models/FindFolderQueryParams.kt
@@ -0,0 +1,38 @@
+package com.nylas.models
+
+import com.squareup.moshi.Json
+
+/**
+ * Class representing the query parameters for finding a folder.
+ */
+data class FindFolderQueryParams(
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
+) : IQueryParams {
+ /**
+ * Builder for [FindFolderQueryParams].
+ */
+ class Builder {
+ private var select: String? = null
+
+ /**
+ * Sets the fields to select.
+ * @param select The fields to select.
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
+ /**
+ * Builds the [FindFolderQueryParams] object.
+ * @return The [FindFolderQueryParams] object.
+ */
+ fun build() = FindFolderQueryParams(
+ select = select,
+ )
+ }
+}
diff --git a/src/main/kotlin/com/nylas/models/FindMessageQueryParams.kt b/src/main/kotlin/com/nylas/models/FindMessageQueryParams.kt
index a2e5936e..68b5683a 100644
--- a/src/main/kotlin/com/nylas/models/FindMessageQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/FindMessageQueryParams.kt
@@ -11,12 +11,20 @@ data class FindMessageQueryParams(
*/
@Json(name = "fields")
val fields: MessageFields? = null,
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
) : IQueryParams {
/**
* Builder for [FindMessageQueryParams].
*/
class Builder {
private var fields: MessageFields? = null
+ private var select: String? = null
/**
* Set the fields to include in the response.
@@ -25,12 +33,20 @@ data class FindMessageQueryParams(
*/
fun fields(fields: MessageFields?) = apply { this.fields = fields }
+ /**
+ * Set the fields to return in the response.
+ * @param select List of field names to return (e.g. "id,grant_id,subject")
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
/**
* Builds the [FindMessageQueryParams] object.
* @return The [FindMessageQueryParams] object.
*/
fun build() = FindMessageQueryParams(
fields = fields,
+ select = select,
)
}
}
diff --git a/src/main/kotlin/com/nylas/models/FindThreadQueryParams.kt b/src/main/kotlin/com/nylas/models/FindThreadQueryParams.kt
new file mode 100644
index 00000000..61d8a741
--- /dev/null
+++ b/src/main/kotlin/com/nylas/models/FindThreadQueryParams.kt
@@ -0,0 +1,38 @@
+package com.nylas.models
+
+import com.squareup.moshi.Json
+
+/**
+ * Class representing the query parameters for finding a thread.
+ */
+data class FindThreadQueryParams(
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
+) : IQueryParams {
+ /**
+ * Builder for [FindThreadQueryParams].
+ */
+ class Builder {
+ private var select: String? = null
+
+ /**
+ * Sets the fields to select.
+ * @param select The fields to select.
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
+ /**
+ * Builds the [FindThreadQueryParams] object.
+ * @return The [FindThreadQueryParams] object.
+ */
+ fun build() = FindThreadQueryParams(
+ select = select,
+ )
+ }
+}
diff --git a/src/main/kotlin/com/nylas/models/Folder.kt b/src/main/kotlin/com/nylas/models/Folder.kt
index bcac524d..41b70b03 100644
--- a/src/main/kotlin/com/nylas/models/Folder.kt
+++ b/src/main/kotlin/com/nylas/models/Folder.kt
@@ -10,12 +10,12 @@ data class Folder(
* A globally unique object identifier.
*/
@Json(name = "id")
- val id: String,
+ val id: String? = null,
/**
* A Grant ID of the Nylas account.
*/
@Json(name = "grant_id")
- val grantId: String,
+ val grantId: String? = null,
/**
* Folder name
*/
diff --git a/src/main/kotlin/com/nylas/models/ListCalendersQueryParams.kt b/src/main/kotlin/com/nylas/models/ListCalendersQueryParams.kt
index 9adaf03c..b6a2f857 100644
--- a/src/main/kotlin/com/nylas/models/ListCalendersQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/ListCalendersQueryParams.kt
@@ -23,6 +23,13 @@ data class ListCalendersQueryParams(
*/
@Json(name = "metadata_pair")
val metadataPair: Map? = null,
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
) : IQueryParams {
/**
* Builder for [ListCalendersQueryParams].
@@ -31,6 +38,7 @@ data class ListCalendersQueryParams(
private var limit: Int? = null
private var pageToken: String? = null
private var metadataPair: Map? = null
+ private var select: String? = null
/**
* Sets the maximum number of objects to return.
@@ -55,6 +63,13 @@ data class ListCalendersQueryParams(
*/
fun metadataPair(metadataPair: Map?) = apply { this.metadataPair = metadataPair }
+ /**
+ * Set the fields to return in the response.
+ * @param select List of field names to return (e.g. "id,grant_id,name")
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
/**
* Builds a [ListCalendersQueryParams] instance.
* @return The [ListCalendersQueryParams] instance.
@@ -63,6 +78,7 @@ data class ListCalendersQueryParams(
limit,
pageToken,
metadataPair,
+ select,
)
}
}
diff --git a/src/main/kotlin/com/nylas/models/ListContactsQueryParams.kt b/src/main/kotlin/com/nylas/models/ListContactsQueryParams.kt
index ed832d44..b00fca27 100644
--- a/src/main/kotlin/com/nylas/models/ListContactsQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/ListContactsQueryParams.kt
@@ -44,6 +44,13 @@ data class ListContactsQueryParams(
*/
@Json(name = "recurse")
val recurse: Boolean? = null,
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
) : IQueryParams {
class Builder {
private var limit: Int? = null
@@ -53,6 +60,7 @@ data class ListContactsQueryParams(
private var source: SourceType? = null
private var group: String? = null
private var recurse: Boolean? = null
+ private var select: String? = null
/**
* Sets the maximum number of objects to return.
@@ -106,6 +114,13 @@ data class ListContactsQueryParams(
*/
fun recurse(recurse: Boolean?) = apply { this.recurse = recurse }
+ /**
+ * Set the fields to return in the response.
+ * @param select List of field names to return (e.g. "id,grant_id,display_name")
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
/**
* Builds a [ListContactsQueryParams] instance.
* @return The [ListContactsQueryParams] instance.
@@ -118,6 +133,7 @@ data class ListContactsQueryParams(
source = source,
group = group,
recurse = recurse,
+ select = select,
)
}
}
diff --git a/src/main/kotlin/com/nylas/models/ListEventQueryParams.kt b/src/main/kotlin/com/nylas/models/ListEventQueryParams.kt
index 3b7f81ec..a663aa21 100644
--- a/src/main/kotlin/com/nylas/models/ListEventQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/ListEventQueryParams.kt
@@ -124,6 +124,13 @@ data class ListEventQueryParams(
*/
@Json(name = "event_type")
val eventType: EventType? = null,
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
) : IQueryParams {
/**
* Builder for [ListEventQueryParams].
@@ -152,6 +159,7 @@ data class ListEventQueryParams(
private var updatedAfter: Long? = null
private var attendees: List? = null
private var eventType: EventType? = null
+ private var select: String? = null
/**
* Sets the maximum number of objects to return.
@@ -299,6 +307,13 @@ data class ListEventQueryParams(
*/
fun eventType(eventType: EventType?) = apply { this.eventType = eventType }
+ /**
+ * Set the fields to return in the response.
+ * @param select List of field names to return (e.g. "id,grant_id,title")
+ * @return The builder.
+ */
+ fun select(select: String?) = apply { this.select = select }
+
/**
* Builds a [ListEventQueryParams] instance.
* @return The [ListEventQueryParams] instance.
@@ -323,6 +338,7 @@ data class ListEventQueryParams(
updatedAfter,
attendees,
eventType,
+ select,
)
}
}
diff --git a/src/main/kotlin/com/nylas/models/ListMessagesQueryParams.kt b/src/main/kotlin/com/nylas/models/ListMessagesQueryParams.kt
index 2f1f75df..0d92f94e 100644
--- a/src/main/kotlin/com/nylas/models/ListMessagesQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/ListMessagesQueryParams.kt
@@ -94,6 +94,13 @@ data class ListMessagesQueryParams(
*/
@Json(name = "search_query_native")
val searchQueryNative: String? = null,
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
) : IQueryParams {
class Builder {
private var limit: Int? = null
@@ -113,6 +120,7 @@ data class ListMessagesQueryParams(
private var hasAttachment: Boolean? = null
private var fields: MessageFields? = null
private var searchQueryNative: String? = null
+ private var select: String? = null
/**
* Sets the maximum number of objects to return.
@@ -236,6 +244,13 @@ data class ListMessagesQueryParams(
*/
fun searchQueryNative(searchQueryNative: String?) = apply { this.searchQueryNative = searchQueryNative }
+ /**
+ * Set the fields to return in the response.
+ * @param select List of field names to return (e.g. "id,grant_id,subject")
+ * @return The builder
+ */
+ fun select(select: String?) = apply { this.select = select }
+
/**
* Builds the [ListMessagesQueryParams] object.
* @return The [ListMessagesQueryParams] object.
@@ -258,6 +273,7 @@ data class ListMessagesQueryParams(
hasAttachment = hasAttachment,
fields = fields,
searchQueryNative = searchQueryNative,
+ select = select,
)
}
}
diff --git a/src/main/kotlin/com/nylas/models/ListThreadsQueryParams.kt b/src/main/kotlin/com/nylas/models/ListThreadsQueryParams.kt
index 60f5b93b..2da2719e 100644
--- a/src/main/kotlin/com/nylas/models/ListThreadsQueryParams.kt
+++ b/src/main/kotlin/com/nylas/models/ListThreadsQueryParams.kt
@@ -88,6 +88,13 @@ data class ListThreadsQueryParams(
*/
@Json(name = "search_query_native")
val searchQueryNative: String? = null,
+ /**
+ * Specify fields that you want Nylas to return, as a comma-separated list (for example, select=id,updated_at).
+ * This allows you to receive only the portion of object data that you're interested in.
+ * You can use select to optimize response size and reduce latency by limiting queries to only the information that you need
+ */
+ @Json(name = "select")
+ var select: String? = null,
) : IQueryParams {
/**
@@ -129,6 +136,7 @@ data class ListThreadsQueryParams(
private var hasAttachment: Boolean? = null
private var fields: MessageFields? = null
private var searchQueryNative: String? = null
+ private var select: String? = null
/**
* Sets the maximum number of objects to return.
@@ -270,6 +278,13 @@ data class ListThreadsQueryParams(
*/
fun searchQueryNative(searchQueryNative: String?) = apply { this.searchQueryNative = searchQueryNative }
+ /**
+ * Set the fields to return in the response.
+ * @param select List of field names to return (e.g. "id,grant_id,subject")
+ * @return The builder
+ */
+ fun select(select: String?) = apply { this.select = select }
+
/**
* Builds the [ListThreadsQueryParams] object.
* @return The [ListThreadsQueryParams] object.
@@ -290,6 +305,7 @@ data class ListThreadsQueryParams(
latestMessageAfter = latestMessageAfter,
hasAttachment = hasAttachment,
searchQueryNative = searchQueryNative,
+ select = select,
)
}
}
diff --git a/src/main/kotlin/com/nylas/models/Message.kt b/src/main/kotlin/com/nylas/models/Message.kt
index cf794a48..ec1099ce 100644
--- a/src/main/kotlin/com/nylas/models/Message.kt
+++ b/src/main/kotlin/com/nylas/models/Message.kt
@@ -10,7 +10,7 @@ data class Message(
* Grant ID of the Nylas account.
*/
@Json(name = "grant_id")
- val grantId: String,
+ val grantId: String? = null,
/**
* The type of object.
*/
diff --git a/src/main/kotlin/com/nylas/models/Thread.kt b/src/main/kotlin/com/nylas/models/Thread.kt
index f6add67d..b77e483a 100644
--- a/src/main/kotlin/com/nylas/models/Thread.kt
+++ b/src/main/kotlin/com/nylas/models/Thread.kt
@@ -7,12 +7,12 @@ data class Thread(
* The unique identifier for the thread.
*/
@Json(name = "id")
- val id: String,
+ val id: String? = null,
/**
* Grant ID of the Nylas account.
*/
@Json(name = "grant_id")
- val grantId: String,
+ val grantId: String? = null,
/**
* The type of object.
*/
diff --git a/src/main/kotlin/com/nylas/resources/Calendars.kt b/src/main/kotlin/com/nylas/resources/Calendars.kt
index 6572e261..c474d96f 100644
--- a/src/main/kotlin/com/nylas/resources/Calendars.kt
+++ b/src/main/kotlin/com/nylas/resources/Calendars.kt
@@ -33,14 +33,15 @@ class Calendars(client: NylasClient) : Resource(client, Calendar::clas
* Return a Calendar
* @param identifier Grant ID or email account to query.
* @param calendarId The id of the Calendar to retrieve. Use "primary" to refer to the primary calendar associated with grant.
+ * @param queryParams The query parameters to include in the request
* @param overrides Optional request overrides to apply
* @return The calendar
*/
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
@JvmOverloads
- fun find(identifier: String, calendarId: String, overrides: RequestOverrides? = null): Response {
+ fun find(identifier: String, calendarId: String, queryParams: FindCalendarQueryParams? = null, overrides: RequestOverrides? = null): Response {
val path = String.format("v3/grants/%s/calendars/%s", identifier, calendarId)
- return findResource(path, overrides = overrides)
+ return findResource(path, queryParams, overrides = overrides)
}
/**
diff --git a/src/main/kotlin/com/nylas/resources/Folders.kt b/src/main/kotlin/com/nylas/resources/Folders.kt
index 31bfae3b..93c44151 100644
--- a/src/main/kotlin/com/nylas/resources/Folders.kt
+++ b/src/main/kotlin/com/nylas/resources/Folders.kt
@@ -23,14 +23,15 @@ class Folders(client: NylasClient) : Resource(client, Folder::class.java
* Return a Folder
* @param identifier Grant ID or email account to query.
* @param folderId The id of the folder to retrieve.
+ * @param queryParams The query parameters to include in the request
* @param overrides Optional request overrides to apply
* @return The folder
*/
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
@JvmOverloads
- fun find(identifier: String, folderId: String, overrides: RequestOverrides? = null): Response {
+ fun find(identifier: String, folderId: String, queryParams: FindFolderQueryParams? = null, overrides: RequestOverrides? = null): Response {
val path = String.format("v3/grants/%s/folders/%s", identifier, folderId)
- return findResource(path, overrides = overrides)
+ return findResource(path, queryParams, overrides = overrides)
}
/**
diff --git a/src/main/kotlin/com/nylas/resources/Threads.kt b/src/main/kotlin/com/nylas/resources/Threads.kt
index 020fc208..574a191f 100644
--- a/src/main/kotlin/com/nylas/resources/Threads.kt
+++ b/src/main/kotlin/com/nylas/resources/Threads.kt
@@ -23,14 +23,15 @@ class Threads(client: NylasClient) : Resource(client, Thread::class.java
* Return a Thread
* @param identifier The identifier of the grant to act upon
* @param threadId The id of the Thread to retrieve.
+ * @param queryParams The query parameters to include in the request
* @param overrides Optional request overrides to apply
* @return The Thread
*/
@Throws(NylasApiError::class, NylasSdkTimeoutError::class)
@JvmOverloads
- fun find(identifier: String, threadId: String, overrides: RequestOverrides? = null): Response {
+ fun find(identifier: String, threadId: String, queryParams: FindThreadQueryParams? = null, overrides: RequestOverrides? = null): Response {
val path = String.format("v3/grants/%s/threads/%s", identifier, threadId)
- return findResource(path, overrides = overrides)
+ return findResource(path, queryParams, overrides = overrides)
}
/**
diff --git a/src/test/kotlin/com/nylas/models/SelectParameterDeserializationTests.kt b/src/test/kotlin/com/nylas/models/SelectParameterDeserializationTests.kt
new file mode 100644
index 00000000..dbff0a0e
--- /dev/null
+++ b/src/test/kotlin/com/nylas/models/SelectParameterDeserializationTests.kt
@@ -0,0 +1,212 @@
+package com.nylas.models
+
+import com.nylas.util.JsonHelper
+import okio.Buffer
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertNull
+
+/**
+ * Tests to verify that models can be deserialized properly when using the select parameter,
+ * which means some fields may be missing from the response JSON.
+ */
+class SelectParameterDeserializationTests {
+
+ @Test
+ fun `Folder deserializes properly with minimal select fields (id only)`() {
+ val adapter = JsonHelper.moshi().adapter(Folder::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "folder-123",
+ "object": "folder"
+ }
+ """.trimIndent(),
+ )
+
+ val folder = adapter.fromJson(jsonBuffer)!!
+ assertIs(folder)
+ assertEquals("folder-123", folder.id)
+ assertEquals("folder", folder.obj)
+ assertNull(folder.grantId) // Should be null when not included in select
+ assertNull(folder.name)
+ }
+
+ @Test
+ fun `Folder deserializes properly with select id,name (no grant_id)`() {
+ val adapter = JsonHelper.moshi().adapter(Folder::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "folder-123",
+ "name": "Inbox",
+ "object": "folder"
+ }
+ """.trimIndent(),
+ )
+
+ val folder = adapter.fromJson(jsonBuffer)!!
+ assertIs(folder)
+ assertEquals("folder-123", folder.id)
+ assertEquals("Inbox", folder.name)
+ assertEquals("folder", folder.obj)
+ assertNull(folder.grantId) // Should be null when not included in select
+ }
+
+ @Test
+ fun `Contact deserializes properly with minimal select fields (id only)`() {
+ val adapter = JsonHelper.moshi().adapter(Contact::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "contact-123",
+ "object": "contact"
+ }
+ """.trimIndent(),
+ )
+
+ val contact = adapter.fromJson(jsonBuffer)!!
+ assertIs(contact)
+ assertEquals("contact-123", contact.id)
+ assertNull(contact.grantId) // Should be null when not included in select
+ assertNull(contact.displayName)
+ }
+
+ @Test
+ fun `Message deserializes properly with minimal select fields (id only)`() {
+ val adapter = JsonHelper.moshi().adapter(Message::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "message-123",
+ "object": "message"
+ }
+ """.trimIndent(),
+ )
+
+ val message = adapter.fromJson(jsonBuffer)!!
+ assertIs(message)
+ assertEquals("message-123", message.id)
+ assertNull(message.grantId) // Should be null when not included in select
+ assertNull(message.subject)
+ }
+
+ @Test
+ fun `Thread deserializes properly with minimal select fields (id only)`() {
+ val adapter = JsonHelper.moshi().adapter(Thread::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "thread-123",
+ "object": "thread"
+ }
+ """.trimIndent(),
+ )
+
+ val thread = adapter.fromJson(jsonBuffer)!!
+ assertIs(thread)
+ assertEquals("thread-123", thread.id)
+ assertNull(thread.grantId) // Should be null when not included in select
+ assertNull(thread.subject)
+ }
+
+ @Test
+ fun `Event deserializes properly with minimal select fields (id only)`() {
+ val adapter = JsonHelper.moshi().adapter(Event::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "event-123",
+ "object": "event"
+ }
+ """.trimIndent(),
+ )
+
+ val event = adapter.fromJson(jsonBuffer)!!
+ assertIs(event)
+ assertEquals("event-123", event.id)
+ assertNull(event.grantId) // Should be null when not included in select
+ assertNull(event.title)
+ }
+
+ @Test
+ fun `Calendar deserializes properly with minimal select fields (id only)`() {
+ val adapter = JsonHelper.moshi().adapter(Calendar::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "calendar-123",
+ "object": "calendar"
+ }
+ """.trimIndent(),
+ )
+
+ val calendar = adapter.fromJson(jsonBuffer)!!
+ assertIs(calendar)
+ assertEquals("calendar-123", calendar.id)
+ assertNull(calendar.grantId) // Should be null when not included in select
+ assertNull(calendar.name)
+ }
+
+ @Test
+ fun `Attachment deserializes properly with minimal select fields (id only)`() {
+ val adapter = JsonHelper.moshi().adapter(Attachment::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "attachment-123",
+ "object": "attachment"
+ }
+ """.trimIndent(),
+ )
+
+ val attachment = adapter.fromJson(jsonBuffer)!!
+ assertIs(attachment)
+ assertEquals("attachment-123", attachment.id)
+ assertNull(attachment.grantId) // Should be null when not included in select
+ assertNull(attachment.filename)
+ }
+
+ @Test
+ fun `ListResponse with Folder array deserializes properly with select fields`() {
+ val listResponseType = com.squareup.moshi.Types.newParameterizedType(
+ ListResponse::class.java,
+ Folder::class.java,
+ )
+ val adapter = JsonHelper.moshi().adapter>(listResponseType)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "data": [
+ {
+ "id": "folder-1",
+ "name": "Inbox",
+ "object": "folder"
+ },
+ {
+ "id": "folder-2",
+ "name": "Sent",
+ "object": "folder"
+ }
+ ],
+ "next_cursor": "abc123"
+ }
+ """.trimIndent(),
+ )
+
+ val response = adapter.fromJson(jsonBuffer)!!
+ assertIs>(response)
+ assertEquals(2, response.data.size)
+
+ val folder1 = response.data[0]
+ assertEquals("folder-1", folder1.id)
+ assertEquals("Inbox", folder1.name)
+ assertNull(folder1.grantId) // Should be null when not in select
+
+ val folder2 = response.data[1]
+ assertEquals("folder-2", folder2.id)
+ assertEquals("Sent", folder2.name)
+ assertNull(folder2.grantId) // Should be null when not in select
+ }
+}
diff --git a/src/test/kotlin/com/nylas/resources/EventsTests.kt b/src/test/kotlin/com/nylas/resources/EventsTests.kt
index d343fed6..11789378 100644
--- a/src/test/kotlin/com/nylas/resources/EventsTests.kt
+++ b/src/test/kotlin/com/nylas/resources/EventsTests.kt
@@ -140,12 +140,12 @@ class EventsTests {
assertEquals("event", event.getObject())
assertEquals("organizer@example.com", event.organizer?.email)
assertEquals("", event.organizer?.name)
- assertEquals(1, event.participants.size)
- assertEquals("Aristotle", event.participants[0].comment)
- assertEquals("aristotle@example.com", event.participants[0].email)
- assertEquals("Aristotle", event.participants[0].name)
- assertEquals("+1 23456778", event.participants[0].phoneNumber)
- assertEquals(ParticipantStatus.MAYBE, event.participants[0].status)
+ assertEquals(1, event.participants?.size)
+ assertEquals("Aristotle", event.participants?.get(0)?.comment)
+ assertEquals("aristotle@example.com", event.participants?.get(0)?.email)
+ assertEquals("Aristotle", event.participants?.get(0)?.name)
+ assertEquals("+1 23456778", event.participants?.get(0)?.phoneNumber)
+ assertEquals(ParticipantStatus.MAYBE, event.participants?.get(0)?.status)
assertEquals(false, event.readOnly)
assertEquals(false, event.reminders?.useDefault)
assertEquals(1, event.reminders?.overrides?.size)
diff --git a/src/test/kotlin/com/nylas/resources/FoldersTests.kt b/src/test/kotlin/com/nylas/resources/FoldersTests.kt
index 3c12f845..b6557331 100644
--- a/src/test/kotlin/com/nylas/resources/FoldersTests.kt
+++ b/src/test/kotlin/com/nylas/resources/FoldersTests.kt
@@ -75,6 +75,49 @@ class FoldersTests {
assertEquals(0, folder.totalCount)
assertEquals(listOf("\\SENT"), folder.attributes)
}
+
+ @Test
+ fun `Folder with select parameter serializes properly with only selected fields`() {
+ val adapter = JsonHelper.moshi().adapter(Folder::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "SENT",
+ "name": "SENT"
+ }
+ """.trimIndent(),
+ )
+
+ val folder = adapter.fromJson(jsonBuffer)!!
+ assertIs(folder)
+ assertEquals("SENT", folder.id)
+ assertEquals("SENT", folder.name)
+ // When select is used, grantId and other fields may not be present
+ assertNull(folder.grantId)
+ assertEquals("folder", folder.getObject()) // default value
+ }
+
+ @Test
+ fun `Folder backwards compatibility - full object still works`() {
+ val adapter = JsonHelper.moshi().adapter(Folder::class.java)
+ val jsonBuffer = Buffer().writeUtf8(
+ """
+ {
+ "id": "SENT",
+ "grant_id": "41009df5-bf11-4c97-aa18-b285b5f2e386",
+ "name": "SENT",
+ "object": "folder"
+ }
+ """.trimIndent(),
+ )
+
+ val folder = adapter.fromJson(jsonBuffer)!!
+ assertIs(folder)
+ assertEquals("SENT", folder.id)
+ assertEquals("41009df5-bf11-4c97-aa18-b285b5f2e386", folder.grantId)
+ assertEquals("SENT", folder.name)
+ assertEquals("folder", folder.getObject())
+ }
}
@Nested
@@ -305,6 +348,29 @@ class FoldersTests {
assertNull(queryParamCaptor.firstValue)
}
+ @Test
+ fun `finding a folder with select parameter calls requests with the correct params`() {
+ val folderId = "folder-123"
+ val queryParams = FindFolderQueryParams(select = "id,name")
+
+ folders.find(grantId, folderId, queryParams)
+
+ val pathCaptor = argumentCaptor()
+ val typeCaptor = argumentCaptor()
+ val queryParamCaptor = argumentCaptor()
+ val overrideParamCaptor = argumentCaptor()
+ verify(mockNylasClient).executeGet>(
+ pathCaptor.capture(),
+ typeCaptor.capture(),
+ queryParamCaptor.capture(),
+ overrideParamCaptor.capture(),
+ )
+
+ assertEquals("v3/grants/$grantId/folders/$folderId", pathCaptor.firstValue)
+ assertEquals(Types.newParameterizedType(Response::class.java, Folder::class.java), typeCaptor.firstValue)
+ assertEquals(queryParams, queryParamCaptor.firstValue)
+ }
+
@Test
fun `creating a folder calls requests with the correct params`() {
val adapter = JsonHelper.moshi().adapter(CreateFolderRequest::class.java)
diff --git a/src/test/kotlin/com/nylas/resources/SelectParameterIntegrationTests.kt b/src/test/kotlin/com/nylas/resources/SelectParameterIntegrationTests.kt
new file mode 100644
index 00000000..c369c033
--- /dev/null
+++ b/src/test/kotlin/com/nylas/resources/SelectParameterIntegrationTests.kt
@@ -0,0 +1,124 @@
+package com.nylas.resources
+
+import com.nylas.NylasClient
+import com.nylas.models.*
+import okhttp3.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.mockito.kotlin.*
+import kotlin.test.*
+
+/**
+ * Integration tests to verify that select parameter functionality works end-to-end,
+ * focusing on the response parsing and URL construction.
+ */
+class SelectParameterIntegrationTests {
+
+ private lateinit var grantId: String
+ private lateinit var mockNylasClient: NylasClient
+
+ @BeforeEach
+ fun setup() {
+ grantId = "abc-123-grant-id"
+ mockNylasClient = mock()
+ }
+
+ @Test
+ fun `list folders with select parameter includes correct query params`() {
+ val folders = Folders(mockNylasClient)
+ val queryParams = ListFoldersQueryParams(
+ limit = 10,
+ select = "id,name",
+ )
+
+ folders.list(grantId, queryParams)
+
+ val pathCaptor = argumentCaptor()
+ val typeCaptor = argumentCaptor()
+ val queryParamCaptor = argumentCaptor()
+ val overrideParamCaptor = argumentCaptor()
+
+ verify(mockNylasClient).executeGet>(
+ pathCaptor.capture(),
+ typeCaptor.capture(),
+ queryParamCaptor.capture(),
+ overrideParamCaptor.capture(),
+ )
+
+ assertEquals("v3/grants/$grantId/folders", pathCaptor.firstValue)
+ assertEquals(queryParams, queryParamCaptor.firstValue)
+ assertEquals("id,name", (queryParamCaptor.firstValue as ListFoldersQueryParams).select)
+ }
+
+ @Test
+ fun `find folder with select parameter includes correct query params`() {
+ val folders = Folders(mockNylasClient)
+ val queryParams = FindFolderQueryParams(select = "id,name")
+
+ folders.find(grantId, "folder-123", queryParams)
+
+ val pathCaptor = argumentCaptor()
+ val typeCaptor = argumentCaptor()
+ val queryParamCaptor = argumentCaptor()
+ val overrideParamCaptor = argumentCaptor()
+
+ verify(mockNylasClient).executeGet>(
+ pathCaptor.capture(),
+ typeCaptor.capture(),
+ queryParamCaptor.capture(),
+ overrideParamCaptor.capture(),
+ )
+
+ assertEquals("v3/grants/$grantId/folders/folder-123", pathCaptor.firstValue)
+ assertEquals(queryParams, queryParamCaptor.firstValue)
+ assertEquals("id,name", (queryParamCaptor.firstValue as FindFolderQueryParams).select)
+ }
+
+ @Test
+ fun `list contacts with select parameter includes correct query params`() {
+ val contacts = Contacts(mockNylasClient)
+ val queryParams = ListContactsQueryParams(
+ limit = 10,
+ select = "id,display_name",
+ )
+
+ contacts.list(grantId, queryParams)
+
+ val pathCaptor = argumentCaptor()
+ val queryParamCaptor = argumentCaptor()
+
+ verify(mockNylasClient).executeGet>(
+ pathCaptor.capture(),
+ any(),
+ queryParamCaptor.capture(),
+ any(),
+ )
+
+ assertEquals("v3/grants/$grantId/contacts", pathCaptor.firstValue)
+ assertEquals("id,display_name", (queryParamCaptor.firstValue as ListContactsQueryParams).select)
+ }
+
+ @Test
+ fun `list messages with select parameter includes correct query params`() {
+ val messages = Messages(mockNylasClient)
+ val queryParams = ListMessagesQueryParams(
+ limit = 10,
+ select = "id,subject",
+ )
+
+ messages.list(grantId, queryParams)
+
+ val pathCaptor = argumentCaptor()
+ val queryParamCaptor = argumentCaptor()
+
+ verify(mockNylasClient).executeGet>(
+ pathCaptor.capture(),
+ any(),
+ queryParamCaptor.capture(),
+ any(),
+ )
+
+ assertEquals("v3/grants/$grantId/messages", pathCaptor.firstValue)
+ assertEquals("id,subject", (queryParamCaptor.firstValue as ListMessagesQueryParams).select)
+ }
+}