From a8fc1f520ffaa2c9d66bdeafdbab17c58f312103 Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Tue, 10 Jun 2025 09:48:33 -0400 Subject: [PATCH 1/2] feat: Add support for single_level query parameter in ListFoldersQueryParams - Add single_level parameter to ListFoldersQueryParams for Microsoft accounts - Parameter controls folder hierarchy traversal (single-level vs multi-level) - Defaults to false for backwards compatibility - Add comprehensive test coverage for the new parameter - Add Java and Kotlin examples demonstrating single_level usage - Update documentation and build configurations Changes: - Modified ListFoldersQueryParams to include singleLevel Boolean parameter - Added Builder method for setting single_level parameter - Added extensive tests covering true/false/null scenarios - Created FoldersExample.java and KotlinFoldersExample.kt - Updated CHANGELOG.md, examples README, Makefile, and build config - Maintains full backwards compatibility --- CHANGELOG.md | 2 + examples/Makefile | 10 ++- examples/README.md | 23 +++++ examples/build.gradle.kts | 2 + .../com/nylas/examples/FoldersExample.java | 70 ++++++++++++++++ .../nylas/examples/KotlinFoldersExample.kt | 66 +++++++++++++++ .../nylas/models/ListFoldersQueryParams.kt | 17 ++++ .../com/nylas/resources/FoldersTests.kt | 83 +++++++++++++++++++ 8 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 examples/src/main/java/com/nylas/examples/FoldersExample.java create mode 100644 examples/src/main/kotlin/com/nylas/examples/KotlinFoldersExample.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb8515c..0cce430e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ * Support for `raw_mime` field in `Message` model to access Base64url-encoded message data * Support for query parameters in `Messages.find()` method to specify fields like `include_tracking_options` and `raw_mime` * Added `Builder` pattern to `FindMessageQueryParams` for consistency with other query parameter classes +* Support for `single_level` query parameter in `ListFoldersQueryParams` for Microsoft accounts to control folder hierarchy traversal +* Added folder examples demonstrating the new `single_level` parameter usage in both Java and Kotlin ### Fixed * Fixed `ListThreadsQueryParams.inFolder` parameter to properly handle single folder ID filtering as expected by the Nylas API. The API only supports filtering by a single folder ID, but the SDK was incorrectly accepting a list and only using the last item. Now the SDK uses the first item from a list if provided and includes overloaded `inFolder(String)` method in the Builder for new code. The list-based method is deprecated and will be changed to String in the next major version for backwards compatibility. diff --git a/examples/Makefile b/examples/Makefile index 850da837..23dfbe1c 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -9,7 +9,9 @@ help: @echo " list - List available examples" @echo " java-notetaker - Run the Java Notetaker example" @echo " java-events - Run the Java Events example" + @echo " java-folders - Run the Java Folders example" @echo " kotlin-notetaker - Run the Kotlin Notetaker example" + @echo " kotlin-folders - Run the Kotlin Folders example" # List available examples list: @@ -22,6 +24,12 @@ java-notetaker: java-events: @cd .. && ./gradlew :examples:run -PmainClass=com.nylas.examples.EventsExample +java-folders: + @cd .. && ./gradlew :examples:run -PmainClass=com.nylas.examples.FoldersExample + # Run the Kotlin example kotlin-notetaker: - @cd .. && ./gradlew :examples:run -PmainClass=com.nylas.examples.KotlinNotetakerExampleKt \ No newline at end of file + @cd .. && ./gradlew :examples:run -PmainClass=com.nylas.examples.KotlinNotetakerExampleKt + +kotlin-folders: + @cd .. && ./gradlew :examples:run -PmainClass=com.nylas.examples.KotlinFoldersExampleKt \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 84567b6b..1d80ea85 100644 --- a/examples/README.md +++ b/examples/README.md @@ -31,6 +31,15 @@ The `NotetakerExample` demonstrates how to use the Nylas Java/Kotlin SDK to inte - Get media from a Notetaker (if available) - Leave a Notetaker session +### Folders Example + +The `FoldersExample` and `KotlinFoldersExample` demonstrate how to use the Nylas Java/Kotlin SDK to interact with the Folders API: + +- List all folders with default multi-level hierarchy +- Use the new `single_level` parameter (Microsoft only) to retrieve folders from a single-level hierarchy +- Demonstrate the builder pattern with various query parameters +- Show folder details including parent relationships and unread counts + ## Setup ### 1. Environment Setup @@ -82,6 +91,16 @@ Run Kotlin Notetaker example: ./gradlew :examples:run -PmainClass=com.nylas.examples.KotlinNotetakerExampleKt ``` +Run Java Folders example: +```bash +./gradlew :examples:run -PmainClass=com.nylas.examples.FoldersExample +``` + +Run Kotlin Folders example: +```bash +./gradlew :examples:run -PmainClass=com.nylas.examples.KotlinFoldersExampleKt +``` + #### Option 2: Using the Makefile List available examples: @@ -107,8 +126,10 @@ make kotlin-way - `MessagesExample.java` (Java - demonstrates new message features) - `KotlinMessagesExample.kt` (Kotlin - demonstrates new message features) - `EventsExample.java` (Java - demonstrates events) + - `FoldersExample.java` (Java - demonstrates folders and single_level parameter) - `NotetakerExample.java` (Java - demonstrates notetakers) - `KotlinNotetakerExample.kt` (Kotlin - demonstrates notetakers) + - `KotlinFoldersExample.kt` (Kotlin - demonstrates folders and single_level parameter) ## Project Structure @@ -124,10 +145,12 @@ examples/ │ └── com/nylas/examples/ │ ├── MessagesExample.java # NEW: Message features demo │ ├── EventsExample.java # Events API demo + │ ├── FoldersExample.java # NEW: Folders API demo with single_level parameter │ └── NotetakerExample.java # Notetaker API demo └── kotlin/ # Kotlin examples └── com/nylas/examples/ ├── KotlinMessagesExample.kt # NEW: Message features demo + ├── KotlinFoldersExample.kt # NEW: Folders API demo with single_level parameter └── KotlinNotetakerExample.kt # Notetaker API demo ``` diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index efd81f99..27e6d52b 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -54,8 +54,10 @@ tasks.register("listExamples") { println("- Java-Notetaker: com.nylas.examples.NotetakerExample") println("- Java-Events: com.nylas.examples.EventsExample") println("- Java-Messages: com.nylas.examples.MessagesExample") + println("- Java-Folders: com.nylas.examples.FoldersExample") println("- Kotlin-Notetaker: com.nylas.examples.KotlinNotetakerExampleKt") println("- Kotlin-Messages: com.nylas.examples.KotlinMessagesExampleKt") + println("- Kotlin-Folders: com.nylas.examples.KotlinFoldersExampleKt") println("\nRun an example with: ./gradlew :examples:run -PmainClass=") } } diff --git a/examples/src/main/java/com/nylas/examples/FoldersExample.java b/examples/src/main/java/com/nylas/examples/FoldersExample.java new file mode 100644 index 00000000..0a41ba05 --- /dev/null +++ b/examples/src/main/java/com/nylas/examples/FoldersExample.java @@ -0,0 +1,70 @@ +package com.nylas.examples; + +import com.nylas.NylasClient; +import com.nylas.models.ListFoldersQueryParams; +import com.nylas.models.ListResponse; +import com.nylas.models.Folder; + +/** + * This example demonstrates how to list folders and use the new single_level parameter + * for Microsoft accounts to control folder hierarchy traversal. + */ +public class FoldersExample { + public static void main(String[] args) { + NylasClient nylasClient = new NylasClient.Builder("").build(); + String grantId = ""; // Replace with your grant ID + + try { + System.out.println("📁 Listing all folders with multi-level hierarchy (default behavior)..."); + + // List all folders with default behavior (multi-level hierarchy) + ListResponse allFolders = nylasClient.folders().list(grantId); + System.out.println("Found " + allFolders.getData().size() + " folders:"); + for (Folder folder : allFolders.getData()) { + System.out.println(" - " + folder.getName() + " (ID: " + folder.getId() + ")"); + if (folder.getParentId() != null) { + System.out.println(" └─ Parent ID: " + folder.getParentId()); + } + } + + System.out.println("\n📁 Listing folders with single-level hierarchy (Microsoft only)..."); + + // List folders using single-level hierarchy parameter (Microsoft only) + ListFoldersQueryParams queryParams = new ListFoldersQueryParams.Builder() + .singleLevel(true) // This is the new parameter - Microsoft only + .limit(50) + .build(); + + ListResponse singleLevelFolders = nylasClient.folders().list(grantId, queryParams); + System.out.println("Found " + singleLevelFolders.getData().size() + " folders in single-level hierarchy:"); + for (Folder folder : singleLevelFolders.getData()) { + System.out.println(" - " + folder.getName() + " (ID: " + folder.getId() + ")"); + } + + System.out.println("\n📁 Demonstrating builder pattern with other parameters..."); + + // Example with multiple parameters including the new single_level + ListFoldersQueryParams detailedQueryParams = new ListFoldersQueryParams.Builder() + .singleLevel(false) // Explicitly set to false for multi-level + .limit(10) + .select("id,name,parent_id,unread_count") + .build(); + + ListResponse detailedFolders = nylasClient.folders().list(grantId, detailedQueryParams); + System.out.println("Found " + detailedFolders.getData().size() + " folders with detailed info:"); + for (Folder folder : detailedFolders.getData()) { + System.out.println(" - " + folder.getName()); + System.out.println(" ID: " + folder.getId()); + System.out.println(" Unread Count: " + (folder.getUnreadCount() != null ? folder.getUnreadCount() : 0)); + if (folder.getParentId() != null) { + System.out.println(" Parent ID: " + folder.getParentId()); + } + System.out.println(); + } + + } catch (Exception exception) { + System.out.println("❌ Error listing folders: " + exception.getMessage()); + System.out.println("Note: The single_level parameter is Microsoft-specific and may not work with other providers."); + } + } +} \ No newline at end of file diff --git a/examples/src/main/kotlin/com/nylas/examples/KotlinFoldersExample.kt b/examples/src/main/kotlin/com/nylas/examples/KotlinFoldersExample.kt new file mode 100644 index 00000000..fac44cab --- /dev/null +++ b/examples/src/main/kotlin/com/nylas/examples/KotlinFoldersExample.kt @@ -0,0 +1,66 @@ +package com.nylas.examples + +import com.nylas.NylasClient +import com.nylas.models.ListFoldersQueryParams + +/** + * This example demonstrates how to list folders and use the new single_level parameter + * for Microsoft accounts to control folder hierarchy traversal. + */ +fun main() { + val nylasClient = NylasClient.Builder("").build() + val grantId = "" // Replace with your grant ID + + try { + println("📁 Listing all folders with multi-level hierarchy (default behavior)...") + + // List all folders with default behavior (multi-level hierarchy) + val allFolders = nylasClient.folders().list(grantId) + println("Found ${allFolders.data.size} folders:") + allFolders.data.forEach { folder -> + println(" - ${folder.name} (ID: ${folder.id})") + if (folder.parentId != null) { + println(" └─ Parent ID: ${folder.parentId}") + } + } + + println("\n📁 Listing folders with single-level hierarchy (Microsoft only)...") + + // List folders using single-level hierarchy parameter (Microsoft only) + val queryParams = ListFoldersQueryParams.Builder() + .singleLevel(true) // This is the new parameter - Microsoft only + .limit(50) + .build() + + val singleLevelFolders = nylasClient.folders().list(grantId, queryParams) + println("Found ${singleLevelFolders.data.size} folders in single-level hierarchy:") + singleLevelFolders.data.forEach { folder -> + println(" - ${folder.name} (ID: ${folder.id})") + } + + println("\n📁 Demonstrating builder pattern with other parameters...") + + // Example with multiple parameters including the new single_level + val detailedQueryParams = ListFoldersQueryParams.Builder() + .singleLevel(false) // Explicitly set to false for multi-level + .limit(10) + .select("id,name,parent_id,unread_count") + .build() + + val detailedFolders = nylasClient.folders().list(grantId, detailedQueryParams) + println("Found ${detailedFolders.data.size} folders with detailed info:") + detailedFolders.data.forEach { folder -> + println(" - ${folder.name}") + println(" ID: ${folder.id}") + println(" Unread Count: ${folder.unreadCount ?: 0}") + if (folder.parentId != null) { + println(" Parent ID: ${folder.parentId}") + } + println() + } + + } catch (exception: Exception) { + println("❌ Error listing folders: ${exception.message}") + println("Note: The single_level parameter is Microsoft-specific and may not work with other providers.") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt b/src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt index 05b6017a..09a6d5cb 100644 --- a/src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt +++ b/src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt @@ -30,12 +30,20 @@ data class ListFoldersQueryParams( */ @Json(name = "select") var select: String? = null, + /** + * (Microsoft only) If true, retrieves folders from a single-level hierarchy only. + * If false, retrieves folders across a multi-level hierarchy. + * Defaults to false. + */ + @Json(name = "single_level") + val singleLevel: Boolean? = null, ) : IQueryParams { class Builder { private var limit: Int? = null private var pageToken: String? = null private var parentId: String? = null private var select: String? = null + private var singleLevel: Boolean? = null /** * Sets the maximum number of objects to return. @@ -67,6 +75,14 @@ data class ListFoldersQueryParams( */ fun select(select: String?) = apply { this.select = select } + /** + * Sets whether to retrieve folders from a single-level hierarchy only. (Microsoft only) + * @param singleLevel If true, retrieves folders from a single-level hierarchy only. + * If false, retrieves folders across a multi-level hierarchy. + * @return The builder. + */ + fun singleLevel(singleLevel: Boolean?) = apply { this.singleLevel = singleLevel } + /** * Builds the [ListFoldersQueryParams] object. * @return The [ListFoldersQueryParams] object. @@ -76,6 +92,7 @@ data class ListFoldersQueryParams( pageToken = pageToken, parentId = parentId, select = select, + singleLevel = singleLevel, ) } } diff --git a/src/test/kotlin/com/nylas/resources/FoldersTests.kt b/src/test/kotlin/com/nylas/resources/FoldersTests.kt index fe0823fb..56fcaf90 100644 --- a/src/test/kotlin/com/nylas/resources/FoldersTests.kt +++ b/src/test/kotlin/com/nylas/resources/FoldersTests.kt @@ -117,6 +117,89 @@ class FoldersTests { assertEquals(queryParams, queryParamCaptor.firstValue) } + @Test + fun `listing folders with single_level parameter calls requests with the correct params`() { + val queryParams = + ListFoldersQueryParams( + limit = 10, + pageToken = "abc-123", + select = "id,updated_at", + singleLevel = true, + ) + + 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(Types.newParameterizedType(ListResponse::class.java, Folder::class.java), typeCaptor.firstValue) + assertEquals(queryParams, queryParamCaptor.firstValue) + } + + @Test + fun `listing folders with single_level false calls requests with the correct params`() { + val queryParams = + ListFoldersQueryParams( + limit = 10, + singleLevel = false, + ) + + 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(Types.newParameterizedType(ListResponse::class.java, Folder::class.java), typeCaptor.firstValue) + assertEquals(queryParams, queryParamCaptor.firstValue) + } + + @Test + fun `builder singleLevel parameter works correctly`() { + val queryParams = ListFoldersQueryParams.Builder() + .limit(10) + .singleLevel(true) + .build() + + assertEquals(true, queryParams.singleLevel) + } + + @Test + fun `builder singleLevel false parameter works correctly`() { + val queryParams = ListFoldersQueryParams.Builder() + .limit(10) + .singleLevel(false) + .build() + + assertEquals(false, queryParams.singleLevel) + } + + @Test + fun `builder singleLevel null parameter works correctly`() { + val queryParams = ListFoldersQueryParams.Builder() + .limit(10) + .build() + + assertEquals(null, queryParams.singleLevel) + } + @Test fun `finding a folder calls requests with the correct params`() { val folderId = "folder-123" From d157ac17d8d9d31d2de579c5d51b1dd5dc4f26f6 Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Tue, 10 Jun 2025 10:00:08 -0400 Subject: [PATCH 2/2] fix: Remove trailing spaces and fix documentation in ListFoldersQueryParams - Remove trailing spaces on lines 34 and 80 to fix linting errors - Fix class documentation comment (was incorrectly referring to messages instead of folders) --- src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt b/src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt index 09a6d5cb..bc0c39eb 100644 --- a/src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt +++ b/src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt @@ -3,7 +3,7 @@ package com.nylas.models import com.squareup.moshi.Json /** - * Class representing the query parameters for listing messages. + * Class representing the query parameters for listing folders. */ data class ListFoldersQueryParams( /** @@ -31,7 +31,7 @@ data class ListFoldersQueryParams( @Json(name = "select") var select: String? = null, /** - * (Microsoft only) If true, retrieves folders from a single-level hierarchy only. + * (Microsoft only) If true, retrieves folders from a single-level hierarchy only. * If false, retrieves folders across a multi-level hierarchy. * Defaults to false. */ @@ -77,7 +77,7 @@ data class ListFoldersQueryParams( /** * Sets whether to retrieve folders from a single-level hierarchy only. (Microsoft only) - * @param singleLevel If true, retrieves folders from a single-level hierarchy only. + * @param singleLevel If true, retrieves folders from a single-level hierarchy only. * If false, retrieves folders across a multi-level hierarchy. * @return The builder. */