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

## [Unreleased]

### Added
* Contact metadata request/response models and `metadata_pair` filtering.
* `contact.updated` and `contact.deleted` webhook trigger constants with iCloud and Yahoo compatibility documentation.

## [v2.18.1] - Release 2026-08-18

### Added
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/com/nylas/models/Contact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ data class Contact(
val jobTitle: String? = null,
@Json(name = "manager_name")
val managerName: String? = null,
/**
* Nylas-owned metadata associated with the contact. Metadata is not written
* to the provider and does not follow a contact if its public ID changes.
*/
@Json(name = "metadata")
val metadata: Map<String, String>? = null,
@Json(name = "middle_name")
val middleName: String? = null,
@Json(name = "nickname")
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/com/nylas/models/CreateContactRequest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ data class CreateContactRequest(
val jobTitle: String? = null,
@Json(name = "manager_name")
val managerName: String? = null,
/** Nylas-owned metadata to associate with the contact. */
@Json(name = "metadata")
val metadata: Map<String, String>? = null,
@Json(name = "middle_name")
val middleName: String? = null,
@Json(name = "nickname")
Expand Down Expand Up @@ -53,6 +56,7 @@ data class CreateContactRequest(
private var imAddresses: List<InstantMessagingAddress>? = null
private var jobTitle: String? = null
private var managerName: String? = null
private var metadata: Map<String, String>? = null
private var middleName: String? = null
private var nickname: String? = null
private var notes: String? = null
Expand All @@ -74,6 +78,7 @@ data class CreateContactRequest(
fun imAddresses(imAddresses: List<InstantMessagingAddress>?) = apply { this.imAddresses = imAddresses }
fun jobTitle(jobTitle: String?) = apply { this.jobTitle = jobTitle }
fun managerName(managerName: String?) = apply { this.managerName = managerName }
fun metadata(metadata: Map<String, String>?) = apply { this.metadata = metadata }
fun middleName(middleName: String?) = apply { this.middleName = middleName }
fun nickname(nickname: String?) = apply { this.nickname = nickname }
fun notes(notes: String?) = apply { this.notes = notes }
Expand All @@ -96,6 +101,7 @@ data class CreateContactRequest(
imAddresses = imAddresses,
jobTitle = jobTitle,
managerName = managerName,
metadata = metadata,
middleName = middleName,
nickname = nickname,
notes = notes,
Expand Down
15 changes: 15 additions & 0 deletions src/main/kotlin/com/nylas/models/ListContactsQueryParams.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ data class ListContactsQueryParams(
*/
@Json(name = "recurse")
val recurse: Boolean? = null,
/**
* Filters contacts by one indexed metadata entry. Use one of key1 through
* key5. This filter cannot be combined with provider-side contact filters.
*/
@Json(name = "metadata_pair")
val metadataPair: Map<String, String>? = null,
) : IQueryParams {
class Builder {
private var limit: Int? = null
Expand All @@ -53,6 +59,7 @@ data class ListContactsQueryParams(
private var source: SourceType? = null
private var group: String? = null
private var recurse: Boolean? = null
private var metadataPair: Map<String, String>? = null

/**
* Sets the maximum number of objects to return.
Expand Down Expand Up @@ -106,6 +113,13 @@ data class ListContactsQueryParams(
*/
fun recurse(recurse: Boolean?) = apply { this.recurse = recurse }

/**
* Sets the indexed metadata key/value pair used to filter contacts.
* @param metadataPair A single key/value pair using key1 through key5.
* @return The builder.
*/
fun metadataPair(metadataPair: Map<String, String>?) = apply { this.metadataPair = metadataPair }

/**
* Builds a [ListContactsQueryParams] instance.
* @return The [ListContactsQueryParams] instance.
Expand All @@ -118,6 +132,7 @@ data class ListContactsQueryParams(
source = source,
group = group,
recurse = recurse,
metadataPair = metadataPair,
)
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/com/nylas/models/UpdateContactRequest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ data class UpdateContactRequest(
val jobTitle: String? = null,
@Json(name = "manager_name")
val managerName: String? = null,
/**
* Nylas-owned metadata for the contact. Null preserves existing metadata, a
* map replaces it, and an empty map clears it.
*/
@Json(name = "metadata")
val metadata: Map<String, String>? = null,
@Json(name = "middle_name")
val middleName: String? = null,
@Json(name = "nickname")
Expand Down Expand Up @@ -53,6 +59,7 @@ data class UpdateContactRequest(
private var imAddresses: List<InstantMessagingAddress>? = null
private var jobTitle: String? = null
private var managerName: String? = null
private var metadata: Map<String, String>? = null
private var middleName: String? = null
private var nickname: String? = null
private var notes: String? = null
Expand All @@ -74,6 +81,7 @@ data class UpdateContactRequest(
fun imAddresses(imAddresses: List<InstantMessagingAddress>?) = apply { this.imAddresses = imAddresses }
fun jobTitle(jobTitle: String?) = apply { this.jobTitle = jobTitle }
fun managerName(managerName: String?) = apply { this.managerName = managerName }
fun metadata(metadata: Map<String, String>?) = apply { this.metadata = metadata }
fun middleName(middleName: String?) = apply { this.middleName = middleName }
fun nickname(nickname: String?) = apply { this.nickname = nickname }
fun notes(notes: String?) = apply { this.notes = notes }
Expand All @@ -96,6 +104,7 @@ data class UpdateContactRequest(
imAddresses = imAddresses,
jobTitle = jobTitle,
managerName = managerName,
metadata = metadata,
middleName = middleName,
nickname = nickname,
notes = notes,
Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/com/nylas/models/WebhookTriggers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,12 @@ enum class WebhookTriggers {

@Json(name = "thread.replied")
THREAD_REPLIED,

/** Native iCloud supports this contact trigger; Yahoo does not. */
@Json(name = "contact.updated")
CONTACT_UPDATED,

/** Native iCloud supports this contact trigger; Yahoo does not. */
@Json(name = "contact.deleted")
CONTACT_DELETED,
}
7 changes: 7 additions & 0 deletions src/test/kotlin/com/nylas/NylasClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ class NylasClientTest {
assertEquals(listOf("key1:value1"), url.queryParameterValues("metadata_pair"))
}

@Test
fun `should serialize a contact metadata pair`() {
val url = requestUrlFor(ListContactsQueryParams(metadataPair = mapOf("key1" to "sync_eligible")))

assertEquals(listOf("key1:sync_eligible"), url.queryParameterValues("metadata_pair"))
}

@Test
fun `should omit metadata_pair given an empty map`() {
val url = requestUrlFor(ListMessagesQueryParams(metadataPair = emptyMap()))
Expand Down
48 changes: 48 additions & 0 deletions src/test/kotlin/com/nylas/resources/ContactsTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class ContactsTests {
],
"job_title": "Software Engineer",
"manager_name": "Bill",
"metadata": {
"key1": "sync_eligible"
},
"middle_name": "Jacob",
"nickname": "JD",
"notes": "Loves ramen",
Expand Down Expand Up @@ -111,6 +114,7 @@ class ContactsTests {
assertEquals("5d3qmne77v32r8l4phyuksl2x", contact.id)
assertEquals("Software Engineer", contact.jobTitle)
assertEquals("Bill", contact.managerName)
assertEquals(mapOf("key1" to "sync_eligible"), contact.metadata)
assertEquals("Jacob", contact.middleName)
assertEquals("JD", contact.nickname)
assertEquals("Loves ramen", contact.notes)
Expand Down Expand Up @@ -177,6 +181,29 @@ class ContactsTests {
contact.physicalAddresses,
)
}

@Test
fun `contact requests serialize metadata using the public wire name`() {
val createAdapter = JsonHelper.moshi().adapter(CreateContactRequest::class.java)
val updateAdapter = JsonHelper.moshi().adapter(UpdateContactRequest::class.java)

assertEquals(
"""{"metadata":{"key1":"sync_eligible"}}""",
createAdapter.toJson(
CreateContactRequest.Builder()
.metadata(mapOf("key1" to "sync_eligible"))
.build(),
),
)
assertEquals(
"""{"metadata":{}}""",
updateAdapter.toJson(
UpdateContactRequest.Builder()
.metadata(emptyMap())
.build(),
),
)
}
}

@Nested
Expand Down Expand Up @@ -217,6 +244,25 @@ class ContactsTests {
assertEquals(queryParams, queryParamCaptor.firstValue)
}

@Test
fun `listing contacts accepts a metadata pair query param`() {
val queryParams = ListContactsQueryParams(
metadataPair = mapOf("key1" to "sync_eligible"),
)

contacts.list(grantId, queryParams)

val queryParamCaptor = argumentCaptor<ListContactsQueryParams>()
verify(mockNylasClient).executeGetEncoded<ListResponse<Contact>>(
any(),
any(),
queryParamCaptor.capture(),
anyOrNull(),
)

assertEquals(queryParams, queryParamCaptor.firstValue)
}

@Test
fun `listing contacts without query params calls requests with the correct params`() {
contacts.list(grantId)
Expand Down Expand Up @@ -310,6 +356,7 @@ class ContactsTests {
),
jobTitle = "Software Engineer",
managerName = "Bill",
metadata = mapOf("key1" to "sync_eligible"),
middleName = "Jacob",
nickname = "JD",
notes = "Loves ramen",
Expand Down Expand Up @@ -386,6 +433,7 @@ class ContactsTests {
),
jobTitle = "Software Engineer",
managerName = "Bill",
metadata = emptyMap(),
middleName = "Jacob",
nickname = "JD",
notes = "Loves ramen",
Expand Down
8 changes: 8 additions & 0 deletions src/test/kotlin/com/nylas/resources/WebhooksTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ class WebhooksTests {
assertEquals(1234567890, webhook.createdAt)
assertEquals(1234567890, webhook.updatedAt)
}

@Test
fun `contact webhook triggers serialize using the supported wire names`() {
val adapter = JsonHelper.moshi().adapter(WebhookTriggers::class.java)

assertEquals("\"contact.updated\"", adapter.toJson(WebhookTriggers.CONTACT_UPDATED))
assertEquals("\"contact.deleted\"", adapter.toJson(WebhookTriggers.CONTACT_DELETED))
}
}

@Nested
Expand Down
Loading