You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2020/10/19 03:59:04 UTC
[james-project] 01/02: JAMES-3414 MailboxIds partial updates
This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 4417b41ffd510ff58e080031ae18a8fcdcc50077
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Oct 15 14:07:30 2020 +0700
JAMES-3414 MailboxIds partial updates
Add or remove specific mailbox ids. This can be use to copy messages.
---
.../rfc8621/contract/EmailSetMethodContract.scala | 66 ++++++++++++++++++++++
.../james/jmap/json/EmailSetSerializer.scala | 15 ++++-
.../org/apache/james/jmap/mail/EmailSet.scala | 28 ++++++---
3 files changed, 99 insertions(+), 10 deletions(-)
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index 799e907..1bbfc8f 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -448,6 +448,72 @@ trait EmailSetMethodContract {
}
@Test
+ def mailboxIdsShouldSupportPartialUpdates(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ val mailboxId1: MailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+ val mailboxId2: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "other"))
+ val mailboxId3: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "yet-another"))
+
+ val messageId: MessageId = mailboxProbe
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+ AppendCommand.from(
+ buildTestMessage))
+ .getMessageId
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core","urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${messageId.serialize}": {
+ | "mailboxIds": {
+ | "${mailboxId1.serialize}": true,
+ | "${mailboxId2.serialize}": true
+ | }
+ | }
+ | }
+ | }, "c1"],
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "update": {
+ | "${messageId.serialize}": {
+ | "mailboxIds/${mailboxId1.serialize}": null,
+ | "mailboxIds/${mailboxId3.serialize}": true
+ | }
+ | }
+ | }, "c2"],
+ | ["Email/get", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize}"],
+ | "properties": ["mailboxIds"]
+ | }, "c3"]
+ | ]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[2][1].list[0]")
+ .isEqualTo(
+ s"""{
+ | "id": "${messageId.serialize}",
+ | "mailboxIds": {"${mailboxId2.serialize}":true, "${mailboxId3.serialize}":true}
+ |}""".stripMargin)
+ }
+
+ @Test
def emailSetDestroySuccessAndFailureCanBeMixed(server: GuiceJamesServer): Unit = {
val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
index 2e33585..34d2f07 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
@@ -30,7 +30,6 @@ import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsResult, JsStr
import scala.util.Try
class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxIdFactory: MailboxId.Factory) {
-
object EmailSetUpdateReads {
def reads(jsObject: JsObject): JsResult[EmailSetUpdate] =
asEmailSetUpdate(jsObject.value.map {
@@ -63,16 +62,26 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
.filter(_.nonEmpty)
.map(MailboxIds)
- JsSuccess(EmailSetUpdate(mailboxIds = mailboxReset))
+ JsSuccess(EmailSetUpdate(mailboxIds = mailboxReset,
+ mailboxIdsToAdd = mailboxesToAdd,
+ mailboxIdsToRemove = mailboxesToRemove))
})
object EntryValidation {
+ private val mailboxIdPrefix: String = "mailboxIds/"
+
def from(property: String, value: JsValue): EntryValidation = property match {
case "mailboxIds" => mailboxIdsReads.reads(value)
.fold(
e => InvalidPatchEntryValue(property, e.toString()),
MailboxReset)
- case _ => InvalidPatchEntryName(property)
+ case name if name.startsWith(mailboxIdPrefix) => Try(mailboxIdFactory.fromString(name.substring(mailboxIdPrefix.length)))
+ .fold(e => InvalidPatchEntryNameWithDetails(property, e.getMessage),
+ id => value match {
+ case JsBoolean(true) => MailboxAddition(id)
+ case JsNull => MailboxRemoval(id)
+ case _ => InvalidPatchEntryValue(property, "MailboxId partial updates requires a JsBoolean(true) (set) or a JsNull (unset)")
+ })
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
index ecab680..29d1275 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
@@ -56,14 +56,28 @@ case class EmailSetResponse(accountId: AccountId,
destroyed: Option[DestroyIds],
notDestroyed: Option[Map[UnparsedMessageId, SetError]])
-case class EmailSetUpdate(mailboxIds: Option[MailboxIds]) {
+case class EmailSetUpdate(mailboxIds: Option[MailboxIds],
+ mailboxIdsToAdd: Option[MailboxIds],
+ mailboxIdsToRemove: Option[MailboxIds]) {
def validate: Either[IllegalArgumentException, ValidatedEmailSetUpdate] = {
- val identity: Function[MailboxIds, MailboxIds] = ids => ids
- val mailboxIdsReset: Function[MailboxIds, MailboxIds] = mailboxIds
- .map(toReset => (_: MailboxIds) => toReset)
- .getOrElse(identity)
- val mailboxIdsTransformation: Function[MailboxIds, MailboxIds] = mailboxIdsReset
- scala.Right(ValidatedEmailSetUpdate(mailboxIdsTransformation))
+ if (mailboxIds.isDefined && (mailboxIdsToAdd.isDefined || mailboxIdsToRemove.isDefined)) {
+ Left(new IllegalArgumentException("Partial update for mailboxIds"))
+ } else {
+ val identity: Function[MailboxIds, MailboxIds] = ids => ids
+ val mailboxIdsAddition: Function[MailboxIds, MailboxIds] = mailboxIdsToAdd
+ .map(toBeAdded => (ids: MailboxIds) => ids ++ toBeAdded)
+ .getOrElse(identity)
+ val mailboxIdsRemoval: Function[MailboxIds, MailboxIds] = mailboxIdsToRemove
+ .map(toBeRemoved => (ids: MailboxIds) => ids -- toBeRemoved)
+ .getOrElse(identity)
+ val mailboxIdsReset: Function[MailboxIds, MailboxIds] = mailboxIds
+ .map(toReset => (_: MailboxIds) => toReset)
+ .getOrElse(identity)
+ val mailboxIdsTransformation: Function[MailboxIds, MailboxIds] = mailboxIdsAddition
+ .compose(mailboxIdsRemoval)
+ .compose(mailboxIdsReset)
+ scala.Right(ValidatedEmailSetUpdate(mailboxIdsTransformation))
+ }
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org