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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 9 additions & 1 deletion examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
@cd .. && ./gradlew :examples:run -PmainClass=com.nylas.examples.KotlinNotetakerExampleKt

kotlin-folders:
@cd .. && ./gradlew :examples:run -PmainClass=com.nylas.examples.KotlinFoldersExampleKt
23 changes: 23 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand All @@ -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
```

Expand Down
2 changes: 2 additions & 0 deletions examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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=<example class name>")
}
}
Expand Down
70 changes: 70 additions & 0 deletions examples/src/main/java/com/nylas/examples/FoldersExample.java
Original file line number Diff line number Diff line change
@@ -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("<NYLAS_API_KEY>").build();
String grantId = "<GRANT_ID>"; // 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<Folder> 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<Folder> 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<Folder> 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.");
}
}
}
Original file line number Diff line number Diff line change
@@ -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("<NYLAS_API_KEY>").build()
val grantId = "<GRANT_ID>" // 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.")
}
}
19 changes: 18 additions & 1 deletion src/main/kotlin/com/nylas/models/ListFoldersQueryParams.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
/**
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -76,6 +92,7 @@ data class ListFoldersQueryParams(
pageToken = pageToken,
parentId = parentId,
select = select,
singleLevel = singleLevel,
)
}
}
83 changes: 83 additions & 0 deletions src/test/kotlin/com/nylas/resources/FoldersTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>()
val typeCaptor = argumentCaptor<Type>()
val queryParamCaptor = argumentCaptor<IQueryParams>()
val overrideParamCaptor = argumentCaptor<RequestOverrides>()
verify(mockNylasClient).executeGet<ListResponse<Folder>>(
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<String>()
val typeCaptor = argumentCaptor<Type>()
val queryParamCaptor = argumentCaptor<IQueryParams>()
val overrideParamCaptor = argumentCaptor<RequestOverrides>()
verify(mockNylasClient).executeGet<ListResponse<Folder>>(
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"
Expand Down
Loading