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:03 UTC

[james-project] branch master updated (715dd58 -> 5725467)

This is an automated email from the ASF dual-hosted git repository.

rcordier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from 715dd58  JAMES-3413 Revert changes to JMAPApiRoutes
     new 4417b41  JAMES-3414 MailboxIds partial updates
     new 5725467  JAMES-3414 MailboxIds partial updates patch validation

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../rfc8621/contract/EmailSetMethodContract.scala  | 265 +++++++++++++++++++++
 .../james/jmap/json/EmailSetSerializer.scala       |  14 +-
 .../scala/org/apache/james/jmap/json/package.scala |   4 +-
 .../org/apache/james/jmap/mail/EmailSet.scala      |  18 +-
 4 files changed, 294 insertions(+), 7 deletions(-)


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 01/02: JAMES-3414 MailboxIds partial updates

Posted by rc...@apache.org.
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


[james-project] 02/02: JAMES-3414 MailboxIds partial updates patch validation

Posted by rc...@apache.org.
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 572546736945e5ea3ecc45c15302ae2ed2aab0c2
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Oct 15 14:41:26 2020 +0700

    JAMES-3414 MailboxIds partial updates patch validation
---
 .../rfc8621/contract/EmailSetMethodContract.scala  | 199 +++++++++++++++++++++
 .../james/jmap/json/EmailSetSerializer.scala       |   1 +
 .../scala/org/apache/james/jmap/json/package.scala |   4 +-
 .../org/apache/james/jmap/mail/EmailSet.scala      |  36 ++--
 4 files changed, 219 insertions(+), 21 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 1bbfc8f..758d6ac 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
@@ -514,6 +514,205 @@ trait EmailSetMethodContract {
   }
 
   @Test
+  def invalidPatchPropertyShouldFail(server: GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId1: MailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+
+    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}": {
+         |          "invalid": "value"
+         |        }
+         |      }
+         |    }, "c1"]
+         |  ]
+         |}""".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[0][1].notUpdated")
+      .isEqualTo(
+      s"""{
+         |  "1": {
+         |    "type": "invalidPatch",
+         |    "description": "Message 1 update is invalid: List((,List(JsonValidationError(List(invalid is an invalid entry in an Email/set update patch),ArraySeq()))))"
+         |  }
+         |}""".stripMargin)
+  }
+
+  @Test
+  def invalidMailboxPartialUpdatePropertyShouldFail(server: GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId1: MailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+
+    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/invalid": "value"
+         |        }
+         |      }
+         |    }, "c1"]
+         |  ]
+         |}""".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[0][1].notUpdated")
+      .isEqualTo(
+      s"""{
+         |  "1": {
+         |    "type": "invalidPatch",
+         |    "description": "Message 1 update is invalid: List((,List(JsonValidationError(List(mailboxIds/invalid is an invalid entry in an Email/set update patch: For input string: \\"invalid\\"),ArraySeq()))))"
+         |  }
+         |}""".stripMargin)
+  }
+
+  @Test
+  def invalidMailboxPartialUpdateValueShouldFail(server: GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId1: MailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+
+    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}": false
+         |        }
+         |      }
+         |    }, "c1"]
+         |  ]
+         |}""".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[0][1].notUpdated")
+      .isEqualTo(
+      s"""{
+         |  "1": {
+         |    "type": "invalidPatch",
+         |    "description": "Message 1 update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds/1 is invalid: MailboxId partial updates requires a JsBoolean(true) (set) or a JsNull (unset)),ArraySeq()))))"
+         |  }
+         |}""".stripMargin)
+  }
+
+  @Test
+  def mixingResetAndPartialUpdatesShouldFail(server: GuiceJamesServer): Unit = {
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId1: MailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+
+    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,
+         |          "mailboxIds" : {
+         |            "${mailboxId1.serialize}": true
+         |          }
+         |        }
+         |      }
+         |    }, "c1"]
+         |  ]
+         |}""".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[0][1].notUpdated")
+      .isEqualTo(
+      s"""{
+         |  "1": {
+         |    "type": "invalidPatch",
+         |    "description": "Message 1 update is invalid: Partial update and reset specified"
+         |  }
+         |}""".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 34d2f07..556f194 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
@@ -82,6 +82,7 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
               case JsNull => MailboxRemoval(id)
               case _ => InvalidPatchEntryValue(property, "MailboxId partial updates requires a JsBoolean(true) (set) or a JsNull (unset)")
             })
+        case _ => InvalidPatchEntryName(property)
       }
     }
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/package.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/package.scala
index c3dd3d8..05eaeab 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/package.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/package.scala
@@ -52,8 +52,8 @@ package object json {
                 refinedKey match {
                   case Left(error) => Left(JsError(error))
                   case scala.util.Right(unparsedK) =>
-                    val transformValue: JsResult[V] = valueReads.reads(keyValue._2)
-                    transformValue.fold(
+                    val transformedValue: JsResult[V] = valueReads.reads(keyValue._2)
+                    transformedValue.fold(
                       error => Left(JsError(error)),
                       v => scala.util.Right(validatedAcc + (unparsedK -> v)))
                 }
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 29d1275..ab692e5 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
@@ -59,25 +59,23 @@ case class EmailSetResponse(accountId: AccountId,
 case class EmailSetUpdate(mailboxIds: Option[MailboxIds],
                           mailboxIdsToAdd: Option[MailboxIds],
                           mailboxIdsToRemove: Option[MailboxIds]) {
-  def validate: Either[IllegalArgumentException, ValidatedEmailSetUpdate] = {
-    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))
-    }
+  def validate: Either[IllegalArgumentException, ValidatedEmailSetUpdate] = if (mailboxIds.isDefined && (mailboxIdsToAdd.isDefined || mailboxIdsToRemove.isDefined)) {
+    Left(new IllegalArgumentException("Partial update and reset specified"))
+  } 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