You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/09/15 02:27:28 UTC
[james-project] branch master updated (04df43a -> 2648753)
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.
from 04df43a JAMES-3369 Email/get preview & hasAttachment simple implementation
new 451ace3 JAMES-2890 Split JMAP RFC-8621 Serializer
new e669096 JAMES-3372 Email/get specific unparsed headers
new 6d12c65 JAMES-3368 Porting Keywords + Keyword to jmap-rfc-8621
new be569bf JAMES-3368 Port KeywordTest + refactoring make it green
new c93af4f JAMES-3368 port KeywordsCombiner and KeywordsCombinerTest
new 2648753 JAMES-3368 add integration test for email get/ keywords
The 6 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:
.../james/jmap/rfc8621/RFC8621MethodsModule.java | 3 -
.../rfc8621/contract/EmailGetMethodContract.scala | 870 ++++++++++++++++-----
server/protocols/jmap-rfc-8621/pom.xml | 11 +
.../org/apache/james/jmap/http/SessionRoutes.scala | 7 +-
.../jmap/json/BackReferenceDeserializer.scala | 54 +-
.../james/jmap/json/EmailGetSerializer.scala | 113 +++
.../james/jmap/json/EmailQuerySerializer.scala | 51 ++
.../apache/james/jmap/json/MailboxSerializer.scala | 185 +++++
.../james/jmap/json/ResponseSerializer.scala | 185 +++++
.../org/apache/james/jmap/json/Serializer.scala | 520 ------------
.../james/jmap/json/VacationSerializer.scala | 91 +++
.../scala/org/apache/james/jmap/json/package.scala | 53 +-
.../scala/org/apache/james/jmap/mail/Email.scala | 16 +-
.../org/apache/james/jmap/mail/EmailGet.scala | 32 +-
.../org/apache/james/jmap/mail/EmailHeader.scala | 6 +
.../org/apache/james/jmap/mail/MailboxSet.scala | 12 +-
.../apache/james/jmap/method/EmailGetMethod.scala | 40 +-
.../james/jmap/method/EmailQueryMethod.scala | 6 +-
.../james/jmap/method/MailboxGetMethod.scala | 6 +-
.../james/jmap/method/MailboxSetMethod.scala | 6 +-
.../jmap/method/VacationResponseGetMethod.scala | 15 +-
.../jmap/method/VacationResponseSetMethod.scala | 11 +-
.../org/apache/james/jmap/model/Keyword.scala | 80 ++
.../org/apache/james/jmap/model/Keywords.scala | 127 +++
.../james/jmap/model/KeywordsCombiner.scala} | 33 +-
.../org/apache/james/jmap/model/Properties.scala | 3 +-
.../apache/james/jmap/routes/JMAPApiRoutes.scala | 16 +-
.../james/jmap/routes/ProcessingContext.scala | 34 +-
.../apache/james/jmap/http/SessionRoutesTest.scala | 3 -
.../apache/james/jmap/json/BackReferenceTest.scala | 7 +-
.../james/jmap/json/JsErrorSerializationTest.scala | 9 +-
.../jmap/json/MailboxGetSerializationTest.scala | 2 +-
.../james/jmap/json/MailboxSerializationTest.scala | 2 +-
.../jmap/json/RequestObjectSerializationTest.scala | 15 +-
.../json/ResponseObjectSerializationTest.scala | 12 +-
.../james/jmap/json/SessionSerializationTest.scala | 3 +-
.../VacationResponseGetSerializationTest.scala | 19 +-
.../json/VacationResponseSerializationTest.scala | 4 +-
.../org/apache/james/jmap/model/KeywordTest.scala | 146 ++++
.../james/jmap/model/KeywordsCombinerTest.scala | 100 +++
.../org/apache/james/jmap/model/KeywordsTest.scala | 126 +++
.../james/jmap/routes/JMAPApiRoutesTest.scala | 7 +-
42 files changed, 2129 insertions(+), 912 deletions(-)
copy mailet/ai/src/main/java/org/apache/james/ai/classic/TokenCollector.java => server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/BackReferenceDeserializer.scala (58%)
create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxSerializer.scala
create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
delete mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/VacationSerializer.scala
create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
copy server/protocols/jmap-rfc-8621/src/{test/scala/org/apache/james/jmap/model/UTCDateTest.scala => main/scala/org/apache/james/jmap/model/KeywordsCombiner.scala} (58%)
create mode 100644 server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordTest.scala
create mode 100644 server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsCombinerTest.scala
create mode 100644 server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsTest.scala
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 03/06: JAMES-3368 Porting Keywords + Keyword to
jmap-rfc-8621
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 6d12c6569f1411cc43302bf45de4e48243ef760f
Author: duc91 <vd...@linagora.com>
AuthorDate: Fri Sep 4 16:53:30 2020 +0700
JAMES-3368 Porting Keywords + Keyword to jmap-rfc-8621
---
.../org/apache/james/jmap/model/Keyword.scala | 84 +++++++++++++
.../org/apache/james/jmap/model/Keywords.scala | 138 +++++++++++++++++++++
2 files changed, 222 insertions(+)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
new file mode 100644
index 0000000..2cd37e4
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
@@ -0,0 +1,84 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ * **************************************************************/
+package org.apache.james.jmap.model
+
+import java.util.Optional
+
+import com.google.common.base.{MoreObjects, Objects}
+import com.google.common.collect.{ImmutableBiMap, ImmutableList}
+import com.ibm.icu.text.UnicodeSet
+import javax.mail.Flags
+import org.apache.commons.lang3.StringUtils
+
+object Keyword {
+ private val FLAG_NAME_MIN_LENGTH = 1
+ private val FLAG_NAME_MAX_LENGTH = 255
+ private val FLAG_NAME_PATTERN = new UnicodeSet("[[a-z][A-Z][0-9]]").add('$').add('_').add('-').freeze
+ val DRAFT = Keyword.of("$Draft")
+ val SEEN = Keyword.of("$Seen")
+ val FLAGGED = Keyword.of("$Flagged")
+ val ANSWERED = Keyword.of("$Answered")
+ val DELETED = Keyword.of("$Deleted")
+ val RECENT = Keyword.of("$Recent")
+ val FORWARDED = Keyword.of("$Forwarded")
+ val FLAG_VALUE = true
+ private val NON_EXPOSED_IMAP_KEYWORDS = ImmutableList.of(Keyword.RECENT, Keyword.DELETED)
+ private val IMAP_SYSTEM_FLAGS = ImmutableBiMap.builder[Flags.Flag, Keyword]
+ .put(Flags.Flag.DRAFT, DRAFT)
+ .put(Flags.Flag.SEEN, SEEN)
+ .put(Flags.Flag.FLAGGED, FLAGGED)
+ .put(Flags.Flag.ANSWERED, ANSWERED)
+ .put(Flags.Flag.RECENT, RECENT)
+ .put(Flags.Flag.DELETED, DELETED)
+ .build
+
+ private val VALIDATION_MESSAGE = """FlagName must not be null or empty, must have length form 1-255,
+ | must not contain characters with hex from '\\u0000' to '\\u00019'
+ | or {'(' ')' '{' ']' '%' '*' '\"' '\\'} """.stripMargin
+
+ def parse(flagName: String): Either[String, Keyword] = {
+ if (isValid(flagName)) Right(new Keyword(flagName))
+ Left(VALIDATION_MESSAGE)
+ }
+
+ def of(flagName: String): Keyword = parse(flagName) match {
+ case Left(errorMessage: String) => throw new IllegalArgumentException(errorMessage)
+ case Right(keyword: Keyword) => keyword
+ }
+
+ def fromFlag(flag: Flags.Flag): Option[Keyword] = Some(IMAP_SYSTEM_FLAGS.get(flag))
+
+ def isValid(flagName: String): Boolean = flagName match {
+ case _ if StringUtils.isBlank(flagName) => false
+ case _ if flagName.length < FLAG_NAME_MIN_LENGTH || flagName.length > FLAG_NAME_MAX_LENGTH => false
+ case _ => FLAG_NAME_PATTERN.containsAll(flagName)
+ }
+}
+
+class Keyword(val flagName: String) {
+ def getFlagName(): String = flagName
+
+ def isExposedImapKeyword(): Boolean = !Keyword.NON_EXPOSED_IMAP_KEYWORDS.contains(this)
+
+ def isDraft(): Boolean = Keyword.DRAFT == this
+
+ def asSystemFlag(): Optional[Flags.Flag] = Optional.ofNullable(Keyword.IMAP_SYSTEM_FLAGS.inverse.get(this))
+
+ def asFlags(): Flags = asSystemFlag().map(Flag => new Flags(Flag)).orElse(new Flags(flagName))
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
new file mode 100644
index 0000000..7ef0540
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
@@ -0,0 +1,138 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+package org.apache.james.jmap.model
+
+import com.google.common.base.{MoreObjects, Objects}
+import javax.mail.Flags
+import org.apache.james.jmap.model.KeywordsFactory.{LENIENT_KEYWORDS_FACTORY, STRICT_KEYWORDS_FACTORY}
+import org.apache.james.mailbox.FlagsBuilder
+
+import scala.collection.IterableOnce
+import scala.jdk.CollectionConverters._
+
+trait ToKeyword {
+ def toKeyword(value: String): Option[Keyword]
+}
+
+object ToKeyword {
+ val STRICT: ToKeyword = (value: String) => Some(Keyword.of(value))
+ val LENIENT: ToKeyword = (value: String) => Keyword.parse(value) match {
+ case Left(errorMessage: String) => {
+ println(errorMessage)
+ None
+ }
+ case Right(value: Keyword) => Some(value)
+ }
+}
+
+trait KeywordsValidator {
+ def validate(keywords: Set[Keyword]): Boolean
+}
+
+object KeywordsValidator {
+ val THROW_ON_IMAP_NON_EXPOSED_KEYWORDS: KeywordsValidator = (keywords: Set[Keyword]) => {
+ if (keywords.exists(KeywordFilter.FILTER_IMAP_NON_EXPOSED_KEYWORDS)) {
+ throw new IllegalArgumentException("Does not allow to update 'Deleted' or 'Recent' flag")
+ }
+ true
+ }
+
+ val IGNORE_NON_EXPOSED_IMAP_KEYWORDS: KeywordsValidator = (keywords: Set[Keyword]) => {
+ def foo(keywords: Set[Keyword]): Option[Keyword] = ???
+ foo(keywords).isDefined
+ }
+ }
+
+object KeywordFilter {
+ val FILTER_IMAP_NON_EXPOSED_KEYWORDS: Keyword => Boolean = (keyword: Keyword) => keyword.isExposedImapKeyword()
+ val KEEP_ALL: Keyword => Boolean = (_: Keyword) => true
+}
+
+object KeywordsFactory {
+ val STRICT_KEYWORDS_FACTORY = new KeywordsFactory(KeywordsValidator.THROW_ON_IMAP_NON_EXPOSED_KEYWORDS, KeywordFilter.KEEP_ALL, ToKeyword.STRICT)
+ val LENIENT_KEYWORDS_FACTORY = new KeywordsFactory(KeywordsValidator.IGNORE_NON_EXPOSED_IMAP_KEYWORDS, KeywordFilter.FILTER_IMAP_NON_EXPOSED_KEYWORDS, ToKeyword.LENIENT)
+
+ private[model] trait KeywordFilter extends IterableOnce[Keyword] {}
+}
+
+object Keywords {
+ val DEFAULT_VALUE: Set[Keywords] = Set()
+}
+
+case class Keywords(keywords: Set[Keyword]) {
+
+ def combiner(firstBuilder: FlagsBuilder, secondBuilder: FlagsBuilder): FlagsBuilder = firstBuilder.add(secondBuilder.build)
+ def accumulator(accumulator: FlagsBuilder, keyword: Keyword): FlagsBuilder = accumulator.add(keyword.asFlags())
+
+ /**
+ * This Keywords factory will filter out invalid keywords (containing inavlid character, being too long or being non
+ * exposed IMAP flags)
+ */
+ def strictFactory: KeywordsFactory = STRICT_KEYWORDS_FACTORY
+
+ /**
+ * This Keywords factory will throw upon invalid keywords (containing inavlid character, being too long or being non
+ * exposed IMAP flags)
+ *
+ * @throws IllegalArgumentException
+ */
+ def lenientFactory: KeywordsFactory = LENIENT_KEYWORDS_FACTORY
+
+ def asFlags(): Flags = keywords.asJava.stream().reduce(FlagsBuilder.builder, this.accumulator, this.combiner).build
+
+ def asFlagsWithRecentAndDeletedFrom(originFlags: Flags): Flags = {
+ val flags = asFlags
+ if (originFlags.contains(Flags.Flag.DELETED)) flags.add(Flags.Flag.DELETED)
+ if (originFlags.contains(Flags.Flag.RECENT)) flags.add(Flags.Flag.RECENT)
+ flags
+ }
+
+ def asMap: Map[String, Boolean] = keywords zip Iterable(Keyword.FLAG_VALUE) toMap
+
+ def getKeywords: Set[Keyword] = keywords
+
+ def contains(keyword: Keyword): Boolean = keywords.contains(keyword)
+}
+
+class KeywordsFactory(val validator: KeywordsValidator, val filter: Keyword => Boolean, val toKeyword: ToKeyword) {
+ def fromSet(setKeywords: Set[Keyword]): Keywords = {
+ validator.validate(setKeywords)
+ Keywords(setKeywords.filter(filter))
+ }
+
+ def from(keywords: Keyword*): Keywords = {
+ fromSet(keywords.toSet)
+ }
+
+ def fromCollection(keywords: Set[String]): Keywords = fromSet(keywords.flatMap(toKeyword.toKeyword))
+
+ def fromMap(mapKeywords: Map[String, Boolean]): Keywords = {
+ if (mapKeywords.values.toSet.exists(value => value)) {
+ throw new IllegalArgumentException("Keyword must be true")
+ }
+ fromCollection(mapKeywords.keySet)
+ }
+
+ def fromFlags(flags: Flags): Keywords = {
+ val fromUserFlags: Set[Option[Keyword]] = flags.getUserFlags.map[Option[Keyword]](toKeyword.toKeyword).toSet
+ val fromSystemFlags: Set[Option[Keyword]] = flags.getSystemFlags.map[Option[Keyword]](Keyword.fromFlag).toSet
+
+ fromSet((fromUserFlags ++ fromSystemFlags).flatten(option => option.isDefined).map(_.get))
+ }
+ }
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 06/06: JAMES-3368 add integration test for email
get/ keywords
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 2648753721af635df1f3768fe5b4d79266e3a606
Author: duc91 <vd...@linagora.com>
AuthorDate: Fri Sep 11 13:50:35 2020 +0700
JAMES-3368 add integration test for email get/ keywords
---
.../rfc8621/contract/EmailGetMethodContract.scala | 627 ++++++++++++++-------
server/protocols/jmap-rfc-8621/pom.xml | 5 +
.../james/jmap/json/EmailGetSerializer.scala | 4 +
.../scala/org/apache/james/jmap/mail/Email.scala | 9 +-
.../org/apache/james/jmap/mail/EmailGet.scala | 4 +-
.../org/apache/james/jmap/model/Keyword.scala | 6 +-
.../org/apache/james/jmap/model/Keywords.scala | 56 +-
.../apache/james/jmap/model/KeywordsCombiner.scala | 13 +-
.../org/apache/james/jmap/model/KeywordTest.scala | 37 +-
.../james/jmap/model/KeywordsCombinerTest.scala | 46 +-
.../org/apache/james/jmap/model/KeywordsTest.scala | 37 +-
11 files changed, 545 insertions(+), 299 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/EmailGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
index d123fc7..63690d7 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
@@ -26,14 +26,17 @@ import java.util.Date
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import io.restassured.http.ContentType.JSON
+import javax.mail.Flags
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.apache.http.HttpStatus.SC_OK
import org.apache.james.GuiceJamesServer
import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.rfc8621.contract.EmailGetMethodContract.createTestMessage
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ALICE, ANDRE, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.mailbox.MessageManager.AppendCommand
import org.apache.james.mailbox.model.MailboxACL.Right
-import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath, MessageId}
+import org.apache.james.mailbox.model.MailboxId
+import org.apache.james.mailbox.model.{MailboxACL, MailboxPath, MessageId}
import org.apache.james.mime4j.dom.Message
import org.apache.james.mime4j.message.MultipartBuilder
import org.apache.james.mime4j.stream.RawField
@@ -41,6 +44,18 @@ import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
import org.apache.james.utils.DataProbeImpl
import org.junit.jupiter.api.{BeforeEach, Test}
+object EmailGetMethodContract {
+ private def createTestMessage: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setSender(ANDRE.asString())
+ .setFrom(ANDRE.asString())
+ .setSubject("World domination \r\n" +
+ " and this is also part of the header")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+}
+
trait EmailGetMethodContract {
@BeforeEach
def setUp(server: GuiceJamesServer): Unit = {
@@ -189,7 +204,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin
@@ -214,7 +229,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [],
- | "notFound": ["${messageId.serialize()}"]
+ | "notFound": ["${messageId.serialize}"]
| },
| "c1"
| ]]
@@ -242,7 +257,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin
@@ -267,7 +282,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "size": 85
| }],
| "notFound": []
@@ -295,7 +310,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["messageId"]
| },
| "c1"]]
@@ -316,7 +331,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "messageId": ["13d4375e-a4a9-f613-06a1-7e8cb1e0ea93@linagora.com"]
|}""".stripMargin)
}
@@ -337,7 +352,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["inReplyTo"]
| },
| "c1"]]
@@ -358,7 +373,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "inReplyTo": ["d5c6f1d6-96e7-8172-9fe6-41fa6c9bd6ec@linagora.com"]
|}""".stripMargin)
}
@@ -379,7 +394,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["references"]
| },
| "c1"]]
@@ -400,7 +415,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "references": ["9b6a4271-69fb-217a-5c14-c68c68375d96@linagora.com"]
|}""".stripMargin)
}
@@ -426,7 +441,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["to"]
| },
| "c1"]]
@@ -447,7 +462,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "to": [
| {
| "name": "user1",
@@ -483,7 +498,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["to"]
| },
| "c1"]]
@@ -504,7 +519,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "to": [
| {
| "email": "user2@domain.tld"
@@ -534,7 +549,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["to"]
| },
| "c1"]]
@@ -555,7 +570,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "to": [
| {
| "name": "MODALİF",
@@ -586,7 +601,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["from"]
| },
| "c1"]]
@@ -607,7 +622,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "from": [
| {
| "name": "MODALİF",
@@ -638,7 +653,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["cc"]
| },
| "c1"]]
@@ -659,7 +674,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "cc": [
| {
| "name": "MODALİF",
@@ -690,7 +705,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["bcc"]
| },
| "c1"]]
@@ -711,7 +726,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bcc": [
| {
| "name": "MODALİF",
@@ -742,7 +757,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sender"]
| },
| "c1"]]
@@ -763,7 +778,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "sender": [
| {
| "name": "MODALİF",
@@ -794,7 +809,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["replyTo"]
| },
| "c1"]]
@@ -815,7 +830,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "replyTo": [
| {
| "name": "MODALİF",
@@ -845,7 +860,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["subject"]
| },
| "c1"]]
@@ -866,7 +881,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "subject": "MODALİF is the best!"
|}""".stripMargin)
}
@@ -894,7 +909,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["from"]
| },
| "c1"]]
@@ -915,7 +930,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "from": [
| {
| "email": "user2@domain.tld"
@@ -947,7 +962,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["cc"]
| },
| "c1"]]
@@ -968,7 +983,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "cc": [
| {
| "email": "user2@domain.tld"
@@ -1000,7 +1015,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["bcc"]
| },
| "c1"]]
@@ -1021,7 +1036,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bcc": [
| {
| "email": "user2@domain.tld"
@@ -1052,7 +1067,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sender"]
| },
| "c1"]]
@@ -1073,7 +1088,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "sender": [
| {
| "email": "user2@domain.tld"
@@ -1105,7 +1120,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["replyTo"]
| },
| "c1"]]
@@ -1126,7 +1141,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "replyTo": [
| {
| "email": "user2@domain.tld"
@@ -1157,7 +1172,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["subject"]
| },
| "c1"]]
@@ -1178,7 +1193,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "subject": "Zo Meuh"
|}""".stripMargin)
}
@@ -1202,7 +1217,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["to"]
| },
| "c1"]]
@@ -1223,7 +1238,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -1248,7 +1263,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["cc"]
| },
| "c1"]]
@@ -1269,7 +1284,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "cc": [
| {
| "name": "user1",
@@ -1301,7 +1316,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["cc"]
| },
| "c1"]]
@@ -1322,7 +1337,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -1347,7 +1362,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["bcc"]
| },
| "c1"]]
@@ -1368,7 +1383,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bcc": [
| {
| "name": "user1",
@@ -1400,7 +1415,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["bcc"]
| },
| "c1"]]
@@ -1421,7 +1436,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -1446,7 +1461,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["from"]
| },
| "c1"]]
@@ -1467,7 +1482,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "from": [
| {
| "name": "user1",
@@ -1499,7 +1514,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["from"]
| },
| "c1"]]
@@ -1520,7 +1535,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -1545,7 +1560,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["replyTo"]
| },
| "c1"]]
@@ -1566,7 +1581,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "replyTo": [
| {
| "name": "user1",
@@ -1598,7 +1613,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["replyTo"]
| },
| "c1"]]
@@ -1619,7 +1634,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -1642,7 +1657,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["subject"]
| },
| "c1"]]
@@ -1663,7 +1678,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "subject": "test"
|}""".stripMargin)
}
@@ -1686,7 +1701,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["subject"]
| },
| "c1"]]
@@ -1707,7 +1722,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -1732,7 +1747,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sentAt"]
| },
| "c1"]]
@@ -1753,7 +1768,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "sentAt": "2020-09-09T05:00:26Z"
|}""".stripMargin)
}
@@ -1781,7 +1796,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sentAt"]
| },
| "c1"]]
@@ -1802,7 +1817,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "sentAt": "2020-09-09T05:00:26Z"
|}""".stripMargin)
}
@@ -1826,7 +1841,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sentAt"]
| },
| "c1"]]
@@ -1847,7 +1862,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -1872,7 +1887,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sender"]
| },
| "c1"]]
@@ -1893,7 +1908,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "sender": [
| {
| "name": "user1",
@@ -1924,7 +1939,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sender"]
| },
| "c1"]]
@@ -1945,7 +1960,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "sender": [
| {
| "email": "user1@domain.tld"
@@ -1975,7 +1990,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sender"]
| },
| "c1"]]
@@ -1996,7 +2011,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "sender": [
| {
| "name": "user1",
@@ -2025,7 +2040,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["sender"]
| },
| "c1"]]
@@ -2046,7 +2061,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -2068,7 +2083,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["messageId"]
| },
| "c1"]]
@@ -2089,7 +2104,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -2111,7 +2126,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["inReplyTo"]
| },
| "c1"]]
@@ -2132,7 +2147,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -2154,7 +2169,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["references"]
| },
| "c1"]]
@@ -2175,7 +2190,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -2201,7 +2216,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}", "invalid", "${nonExistingMessageId.serialize()}"]
+ | "ids": ["${messageId.serialize}", "invalid", "${nonExistingMessageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin
@@ -2227,10 +2242,10 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [ {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "size": 85
| }],
- | "notFound": ["${nonExistingMessageId.serialize()}", "invalid"]
+ | "notFound": ["${nonExistingMessageId.serialize}", "invalid"]
| },
| "c1"
| ]]
@@ -2323,7 +2338,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}", "${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}", "${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin
@@ -2348,7 +2363,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "size": 85
| }],
| "notFound": []
@@ -2379,7 +2394,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}", "${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}", "${messageId.serialize}"],
| "properties": ["id"]
| },
| "c1"]]
@@ -2405,7 +2420,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
| }],
| "notFound": []
| },
@@ -2435,7 +2450,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}", "${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}", "${messageId.serialize}"],
| "properties": []
| },
| "c1"]]
@@ -2461,7 +2476,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [{
- | "id": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}"
| }],
| "notFound": []
| },
@@ -2491,7 +2506,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}", "${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}", "${messageId.serialize}"],
| "properties": ["size"]
| },
| "c1"]]
@@ -2517,7 +2532,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "size": 85
| }],
| "notFound": []
@@ -2548,7 +2563,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}", "${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}", "${messageId.serialize}"],
| "properties": ["invalid"]
| },
| "c1"]]
@@ -2591,7 +2606,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}", "${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}", "${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin
@@ -2616,7 +2631,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [],
- | "notFound": ["${messageId.serialize()}"]
+ | "notFound": ["${messageId.serialize}"]
| },
| "c1"
| ]]
@@ -2646,7 +2661,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin
@@ -2671,7 +2686,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [],
- | "notFound": ["${messageId.serialize()}"]
+ | "notFound": ["${messageId.serialize}"]
| },
| "c1"
| ]]
@@ -2703,7 +2718,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin
@@ -2728,7 +2743,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "size": 85
| }],
| "notFound": []
@@ -2763,7 +2778,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin
@@ -2788,7 +2803,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [],
- | "notFound": ["${messageId.serialize()}"]
+ | "notFound": ["${messageId.serialize}"]
| },
| "c1"
| ]]
@@ -2806,7 +2821,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin)
@@ -2843,7 +2858,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"]
+ | "ids": ["${messageId.serialize}"]
| },
| "c1"]]
|}""".stripMargin)
@@ -2891,7 +2906,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyStructure"],
| "bodyProperties":["partId", "blobId"]
| },
@@ -2919,10 +2934,10 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyStructure": {
| "partId": "1",
- | "blobId": "${messageId.serialize()}_1"
+ | "blobId": "${messageId.serialize}_1"
| }
| }
| ],
@@ -2953,7 +2968,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["mailboxIds"]
| },
| "c1"]]
@@ -2974,7 +2989,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "mailboxIds": {
| "${mailboxId}": true
| }
@@ -3003,7 +3018,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["receivedAt"]
| },
| "c1"]]
@@ -3024,7 +3039,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "receivedAt": "2014-10-30T14:12:00Z"
|}""".stripMargin)
}
@@ -3049,7 +3064,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["blobId"]
| },
| "c1"]]
@@ -3070,8 +3085,8 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
- | "blobId": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}",
+ | "blobId": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -3095,7 +3110,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["threadId"]
| },
| "c1"]]
@@ -3116,8 +3131,8 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
- | "threadId": "${messageId.serialize()}"
+ | "id": "${messageId.serialize}",
+ | "threadId": "${messageId.serialize}"
|}""".stripMargin)
}
@@ -3143,7 +3158,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyStructure"]
| },
| "c1"]]
@@ -3171,10 +3186,10 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyStructure": {
| "partId": "1",
- | "blobId": "${messageId.serialize()}_1",
+ | "blobId": "${messageId.serialize}_1",
| "size": 85,
| "type": "text/plain",
| "charset": "UTF-8"
@@ -3213,7 +3228,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyStructure"],
| "bodyProperties":[]
| },
@@ -3241,7 +3256,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyStructure": {}
| }
| ],
@@ -3276,7 +3291,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyStructure"],
| "bodyProperties":["invalid"]
| },
@@ -3332,7 +3347,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyStructure"],
| "bodyProperties":["partId", "blobId", "size", "name", "type", "charset", "disposition", "cid", "language", "location", "subParts", "headers"]
| },
@@ -3361,10 +3376,10 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyStructure": {
| "partId": "1",
- | "blobId": "${messageId.serialize()}_1",
+ | "blobId": "${messageId.serialize}_1",
| "headers": [
| {
| "name": "MIME-Version",
@@ -3413,7 +3428,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyStructure"],
| "bodyProperties":["partId", "blobId", "size", "name", "type", "charset", "disposition", "cid", "language", "location", "subParts", "headers"]
| },
@@ -3443,7 +3458,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyStructure": {
| "partId": "1",
| "headers": [
@@ -3490,7 +3505,7 @@ trait EmailGetMethodContract {
| "subParts": [
| {
| "partId": "2",
- | "blobId": "${messageId.serialize()}_2",
+ | "blobId": "${messageId.serialize}_2",
| "headers": [
| {
| "name": "Content-Type",
@@ -3507,7 +3522,7 @@ trait EmailGetMethodContract {
| },
| {
| "partId": "3",
- | "blobId": "${messageId.serialize()}_3",
+ | "blobId": "${messageId.serialize}_3",
| "headers": [
| {
| "name": "Content-Type",
@@ -3530,7 +3545,7 @@ trait EmailGetMethodContract {
| },
| {
| "partId": "4",
- | "blobId": "${messageId.serialize()}_4",
+ | "blobId": "${messageId.serialize}_4",
| "headers": [
| {
| "name": "Content-Type",
@@ -3553,7 +3568,7 @@ trait EmailGetMethodContract {
| },
| {
| "partId": "5",
- | "blobId": "${messageId.serialize()}_5",
+ | "blobId": "${messageId.serialize}_5",
| "headers": [
| {
| "name": "Content-Type",
@@ -3604,7 +3619,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyStructure"],
| "bodyProperties":["partId", "blobId", "size", "name", "type", "charset", "disposition", "cid", "language", "location", "subParts", "headers"]
| },
@@ -3633,7 +3648,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyStructure": {
| "partId": "1",
| "headers": [
@@ -3696,7 +3711,7 @@ trait EmailGetMethodContract {
| "subParts": [
| {
| "partId": "3",
- | "blobId": "${messageId.serialize()}_3",
+ | "blobId": "${messageId.serialize}_3",
| "headers": [
| {
| "name": "Content-Type",
@@ -3713,7 +3728,7 @@ trait EmailGetMethodContract {
| },
| {
| "partId": "4",
- | "blobId": "${messageId.serialize()}_4",
+ | "blobId": "${messageId.serialize}_4",
| "headers": [
| {
| "name": "Content-Type",
@@ -3732,7 +3747,7 @@ trait EmailGetMethodContract {
| },
| {
| "partId": "5",
- | "blobId": "${messageId.serialize()}_5",
+ | "blobId": "${messageId.serialize}_5",
| "headers": [
| {
| "name": "Content-ID",
@@ -3788,7 +3803,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["textBody"]
| },
| "c1"]]
@@ -3815,11 +3830,11 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "textBody": [
| {
| "partId": "2",
- | "blobId": "${messageId.serialize()}_2",
+ | "blobId": "${messageId.serialize}_2",
| "size": 97,
| "type": "text/plain",
| "charset": "utf-8"
@@ -3852,7 +3867,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["textBody"]
| },
| "c1"]]
@@ -3879,11 +3894,11 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "textBody": [
| {
| "partId": "3",
- | "blobId": "${messageId.serialize()}_3",
+ | "blobId": "${messageId.serialize}_3",
| "size": 114,
| "type": "text/plain",
| "charset": "ISO-8859-1"
@@ -3916,7 +3931,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["htmlBody"]
| },
| "c1"]]
@@ -3943,11 +3958,11 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "htmlBody": [
| {
| "partId": "4",
- | "blobId": "${messageId.serialize()}_4",
+ | "blobId": "${messageId.serialize}_4",
| "size": 108,
| "type": "text/html",
| "charset": "ISO-8859-1"
@@ -3980,7 +3995,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"],
| "fetchTextBodyValues": true
| },
@@ -4008,7 +4023,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {
| "2": {
| "value": "Send\\n\\n",
@@ -4043,7 +4058,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"],
| "fetchTextBodyValues": true
| },
@@ -4071,7 +4086,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {
| "3": {
| "value": "/blabla/\\n*bloblo*\\n",
@@ -4106,7 +4121,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"],
| "fetchHTMLBodyValues": true
| },
@@ -4134,7 +4149,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {
| "4": {
| "value": "<i>blabla</i>\\n<b>bloblo</b>\\n",
@@ -4169,7 +4184,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"],
| "fetchTextBodyValues": true,
| "fetchHTMLBodyValues": true
@@ -4199,7 +4214,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {
| "3": {
| "value": "/blabla/\\n*bloblo*\\n",
@@ -4240,7 +4255,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"],
| "fetchTextBodyValues": true,
| "fetchHTMLBodyValues": true
@@ -4269,7 +4284,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {
| "2": {
| "value": "Send\\n\\n",
@@ -4304,7 +4319,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"],
| "fetchAllBodyValues": true
| },
@@ -4333,7 +4348,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {
| "2": {
| "value": "Send\\n\\n",
@@ -4378,7 +4393,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"],
| "fetchAllBodyValues": true,
| "maxBodyValueBytes": 32
@@ -4408,7 +4423,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {
| "2": {
| "value": "Send\\n\\n",
@@ -4453,7 +4468,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"],
| "fetchAllBodyValues": true
| },
@@ -4481,7 +4496,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {
| "3": {
| "value": "/blabla/\\n*bloblo*\\n",
@@ -4526,7 +4541,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["bodyValues"]
| },
| "c1"]]
@@ -4553,7 +4568,7 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "bodyValues": {}
| }
| ],
@@ -4583,7 +4598,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["attachments"]
| },
| "c1"]]
@@ -4610,11 +4625,11 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "attachments": [
| {
| "partId": "3",
- | "blobId": "${messageId.serialize()}_3",
+ | "blobId": "${messageId.serialize}_3",
| "size": 519,
| "name": "text1",
| "type": "text/plain",
@@ -4623,7 +4638,7 @@ trait EmailGetMethodContract {
| },
| {
| "partId": "4",
- | "blobId": "${messageId.serialize()}_4",
+ | "blobId": "${messageId.serialize}_4",
| "size": 694,
| "name": "text2",
| "type": "application/vnd.ms-publisher",
@@ -4632,7 +4647,7 @@ trait EmailGetMethodContract {
| },
| {
| "partId": "5",
- | "blobId": "${messageId.serialize()}_5",
+ | "blobId": "${messageId.serialize}_5",
| "size": 713,
| "name": "text3",
| "type": "text/plain",
@@ -4667,7 +4682,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["attachments"]
| },
| "c1"]]
@@ -4694,11 +4709,11 @@ trait EmailGetMethodContract {
| "state": "000001",
| "list": [
| {
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "attachments": [
| {
| "partId": "5",
- | "blobId": "${messageId.serialize()}_5",
+ | "blobId": "${messageId.serialize}_5",
| "size": 249,
| "name": "avertissement.txt",
| "type": "text/plain",
@@ -4736,7 +4751,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["preview"]
| },
| "c1"]]
@@ -4782,7 +4797,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["preview"]
| },
| "c1"]]
@@ -4828,7 +4843,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["preview"]
| },
| "c1"]]
@@ -4874,7 +4889,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["preview"]
| },
| "c1"]]
@@ -4920,7 +4935,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["hasAttachment"]
| },
| "c1"]]
@@ -4962,7 +4977,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["hasAttachment"]
| },
| "c1"]]
@@ -5010,7 +5025,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties":["hasAttachment"]
| },
| "c1"]]
@@ -5038,9 +5053,9 @@ trait EmailGetMethodContract {
@Test
def emailGetShouldReturnUnparsedHeaders(server: GuiceJamesServer): Unit = {
- val bobPath = MailboxPath.inbox(BOB);
+ val bobPath = MailboxPath.inbox(BOB)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
- val alicePath = MailboxPath.inbox(ALICE);
+ val alicePath = MailboxPath.inbox(ALICE)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
val message: Message = Message.Builder
.of
@@ -5065,7 +5080,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["headers"]
| },
| "c1"]]
@@ -5088,7 +5103,7 @@ trait EmailGetMethodContract {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "000001",
| "list": [{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "headers": [
| {"name":"MIME-Version","value":" 1.0"},
| {"name":"Subject","value":" =?US-ASCII?Q?World_domination_=0D=0A_and_thi?=\\r\\n =?US-ASCII?Q?s_is_also_part_of_the_header?="},
@@ -5108,7 +5123,7 @@ trait EmailGetMethodContract {
def emailGetShouldReturnSpecificUnparsedHeaders(server: GuiceJamesServer): Unit = {
val bobPath = MailboxPath.inbox(BOB);
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
- val alicePath = MailboxPath.inbox(ALICE);
+ val alicePath = MailboxPath.inbox(ALICE)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
val message: Message = Message.Builder
.of
@@ -5132,7 +5147,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["header:Subject", "header:From", "header:Sender"]
| },
| "c1"]]
@@ -5150,7 +5165,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "header:Subject":" =?US-ASCII?Q?World_domination_=0D=0A_and_thi?=\\r\\n =?US-ASCII?Q?s_is_also_part_of_the_header?=",
| "header:From":" andre@domain.tld",
| "header:Sender":" andre@domain.tld"
@@ -5161,7 +5176,7 @@ trait EmailGetMethodContract {
def emailGetShouldReturnSpecificUnparsedHeadersWithInsensitiveCaseMatching(server: GuiceJamesServer): Unit = {
val bobPath = MailboxPath.inbox(BOB);
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
- val alicePath = MailboxPath.inbox(ALICE);
+ val alicePath = MailboxPath.inbox(ALICE)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
val message: Message = Message.Builder
.of
@@ -5185,7 +5200,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["header:subJeCt"]
| },
| "c1"]]
@@ -5203,7 +5218,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "header:subJeCt":" =?US-ASCII?Q?World_domination_=0D=0A_and_thi?=\\r\\n =?US-ASCII?Q?s_is_also_part_of_the_header?="
|}""".stripMargin)
}
@@ -5212,7 +5227,7 @@ trait EmailGetMethodContract {
def emailGetShouldRejectSpecificUnparsedHeadersWithColon(server: GuiceJamesServer): Unit = {
val bobPath = MailboxPath.inbox(BOB);
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
- val alicePath = MailboxPath.inbox(ALICE);
+ val alicePath = MailboxPath.inbox(ALICE)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
val message: Message = Message.Builder
.of
@@ -5236,7 +5251,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["header:From:Subject"]
| },
| "c1"]]
@@ -5268,7 +5283,7 @@ trait EmailGetMethodContract {
def emailGetShouldReturnNullWhenUnknownSpecificUnparsedHeaders(server: GuiceJamesServer): Unit = {
val bobPath = MailboxPath.inbox(BOB);
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
- val alicePath = MailboxPath.inbox(ALICE);
+ val alicePath = MailboxPath.inbox(ALICE)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
val message: Message = Message.Builder
.of
@@ -5292,7 +5307,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["header:blahblah"]
| },
| "c1"]]
@@ -5310,7 +5325,7 @@ trait EmailGetMethodContract {
.inPath("methodResponses[0][1].list[0]")
.isEqualTo(
s"""{
- | "id": "${messageId.serialize()}",
+ | "id": "${messageId.serialize}",
| "header:blahblah": null
|}""".stripMargin)
}
@@ -5319,7 +5334,7 @@ trait EmailGetMethodContract {
def emailGetShouldRejectEmptySpecificUnparsedHeaders(server: GuiceJamesServer): Unit = {
val bobPath = MailboxPath.inbox(BOB);
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
- val alicePath = MailboxPath.inbox(ALICE);
+ val alicePath = MailboxPath.inbox(ALICE)
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
val message: Message = Message.Builder
.of
@@ -5343,7 +5358,7 @@ trait EmailGetMethodContract {
| "Email/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
- | "ids": ["${messageId.serialize()}"],
+ | "ids": ["${messageId.serialize}"],
| "properties": ["header:"]
| },
| "c1"]]
@@ -5370,4 +5385,222 @@ trait EmailGetMethodContract {
| ]]
|}""".stripMargin)
}
+
+ @Test
+ def emailGetShouldReturnKeyword(server: GuiceJamesServer): Unit = {
+ val message: Message = createTestMessage
+
+ val flags: Flags = new Flags(Flags.Flag.ANSWERED)
+
+ val bobPath = MailboxPath.inbox(BOB)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobPath, AppendCommand.builder()
+ .withFlags(flags)
+ .build(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize}"],
+ | "properties": ["keywords"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].list[0]")
+ .isEqualTo(String.format(
+ """
+ | {
+ | "id":"%s",
+ | "keywords": {
+ | "$Answered": true
+ | }
+ | }
+ """.stripMargin, messageId.serialize)
+ )
+ }
+
+ @Test
+ def emailGetShouldReturnSystemKeywords(server: GuiceJamesServer): Unit = {
+ val message: Message = createTestMessage
+
+ val flags: Flags = new Flags(Flags.Flag.ANSWERED)
+ flags.add(Flags.Flag.DRAFT)
+ flags.add(Flags.Flag.FLAGGED)
+ flags.add(Flags.Flag.SEEN)
+
+ val bobPath = MailboxPath.inbox(BOB)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobPath, AppendCommand.builder()
+ .withFlags(flags)
+ .build(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize}"],
+ | "properties": ["keywords"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].list[0]")
+ .isEqualTo(String.format(
+ """
+ | {
+ | "id":"%s",
+ | "keywords": {
+ | "$Answered": true,
+ | "$Seen": true,
+ | "$Draft": true,
+ | "$Flagged": true
+ | }
+ | }
+ """.stripMargin, messageId.serialize)
+ )
+ }
+
+ @Test
+ def emailGetShouldReturnSystemAndUserKeywordsIfExposed(server: GuiceJamesServer): Unit = {
+ val message: Message = createTestMessage
+
+ val flags: Flags = new Flags(Flags.Flag.ANSWERED)
+ flags.add(Flags.Flag.DRAFT)
+ flags.add(Flags.Flag.FLAGGED)
+ flags.add("custom_flag")
+
+ val bobPath = MailboxPath.inbox(BOB)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobPath, AppendCommand.builder()
+ .withFlags(flags)
+ .build(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize}"],
+ | "properties": ["keywords"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].list[0]")
+ .isEqualTo(String.format(
+ """
+ | {
+ | "id":"%s",
+ | "keywords": {
+ | "$Answered": true,
+ | "custom_flag": true,
+ | "$Draft": true,
+ | "$Flagged": true
+ | }
+ | }
+ """.stripMargin, messageId.serialize)
+ )
+ }
+
+ @Test
+ def emailGetShouldNotReturnNonExposedKeywords(server: GuiceJamesServer): Unit = {
+ val message: Message = createTestMessage
+
+ val nonExposedFlags: Flags = new Flags(Flags.Flag.RECENT)
+ nonExposedFlags.add(Flags.Flag.DELETED)
+
+ val bobPath = MailboxPath.inbox(BOB)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobPath, AppendCommand.builder()
+ .withFlags(nonExposedFlags)
+ .build(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize}"],
+ | "properties": ["keywords"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].list[0]")
+ .isEqualTo(
+ s"""
+ | {
+ | "id":"${messageId.serialize}",
+ | "keywords": {}
+ | }
+ """.stripMargin)
+ }
}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index dbcc7bb..520910a 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -96,6 +96,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.github.dpaukov</groupId>
+ <artifactId>combinatoricslib3</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>com.typesafe.play</groupId>
<artifactId>play-json_${scala.base}</artifactId>
</dependency>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
index a26909b..c467eeb 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
@@ -62,6 +62,10 @@ object EmailGetSerializer {
private implicit val subjectWrites: Writes[Subject] = Json.valueWrites[Subject]
private implicit val emailNotFoundWrites: Writes[EmailNotFound] = Json.valueWrites[EmailNotFound]
private implicit val messageIdWrites: Writes[MessageId] = id => JsString(id.serialize())
+ private implicit val keywordWrites: Writes[Keyword] = Json.valueWrites[Keyword]
+ private implicit val keywordsWrites: Writes[Keywords] = keywords => JsObject(keywords.asMap.map {
+ case (keyword, b) => (keyword.flagName, JsBoolean(b))
+ })
private implicit def bodyValueMapWrites(implicit bodyValueWriter: Writes[EmailBodyValue]): Writes[Map[PartId, EmailBodyValue]] =
mapWrites[PartId, EmailBodyValue](_.value.toString(), bodyValueWriter)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
index f8cdfb7..e766233 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
@@ -27,8 +27,10 @@ import eu.timepit.refined.numeric.NonNegative
import eu.timepit.refined.refineV
import org.apache.james.jmap.api.model.Preview
import org.apache.james.jmap.mail.Email.Size
-import org.apache.james.jmap.model.{Properties, UTCDate}
-import org.apache.james.mailbox.model.{MailboxId, MessageId}
+import org.apache.james.jmap.model.UTCDate
+import org.apache.james.mailbox.model.MailboxId
+import org.apache.james.jmap.model.{Keywords, Properties}
+import org.apache.james.mailbox.model.MessageId
import org.slf4j.{Logger, LoggerFactory}
import scala.util.{Failure, Success, Try}
@@ -43,7 +45,7 @@ object Email {
val allowedProperties: Properties = Properties("id", "size", "bodyStructure", "textBody", "htmlBody",
"attachments", "headers", "bodyValues", "messageId", "inReplyTo", "references", "to", "cc", "bcc",
"from", "sender", "replyTo", "subject", "sentAt", "mailboxIds", "blobId", "threadId", "receivedAt",
- "preview", "hasAttachment")
+ "preview", "hasAttachment", "keywords")
val idProperty: Properties = Properties("id")
def asUnparsed(messageId: MessageId): Try[UnparsedEmailId] =
@@ -106,6 +108,7 @@ case class EmailHeaders(headers: List[EmailHeader],
sentAt: Option[UTCDate])
case class EmailBody(bodyStructure: EmailBodyPart,
+ keywords: Keywords,
textBody: List[EmailBodyPart],
htmlBody: List[EmailBodyPart],
attachments: List[EmailBodyPart],
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
index f75eea0..bd2b7c7 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
@@ -32,6 +32,7 @@ import org.apache.james.jmap.api.model.Preview
import org.apache.james.jmap.mail.Email.{UnparsedEmailId, sanitizeSize}
import org.apache.james.jmap.mail.EmailGetRequest.MaxBodyValueBytes
import org.apache.james.jmap.mail.EmailHeaders.SPECIFIC_HEADER_PREFIX
+import org.apache.james.jmap.model.KeywordsFactory.LENIENT_KEYWORDS_FACTORY
import org.apache.james.jmap.model.State.State
import org.apache.james.jmap.model.{AccountId, Properties, UTCDate}
import org.apache.james.mailbox.model.{MessageId, MessageResult}
@@ -41,7 +42,6 @@ import org.apache.james.mime4j.dom.{Header, Message}
import org.apache.james.mime4j.message.DefaultMessageBuilder
import org.apache.james.mime4j.stream.{Field, MimeConfig}
import org.apache.james.mime4j.util.MimeUtil
-import org.apache.james.mime4j.stream.MimeConfig
import scala.jdk.CollectionConverters._
import scala.util.{Failure, Success, Try}
@@ -97,6 +97,7 @@ case class EmailGetRequest(accountId: AccountId,
mime4JMessage <- Try(defaultMessageBuilder.parseMessage(firstMessage.getFullContent.getInputStream))
bodyStructure <- EmailBodyPart.of(messageId, mime4JMessage)
bodyValues <- extractBodyValues(bodyStructure)
+ keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags)
blobId <- BlobId.of(messageId)
preview <- Try(previewFactory.fromMessageResult(firstMessage))
} yield {
@@ -125,6 +126,7 @@ case class EmailGetRequest(accountId: AccountId,
textBody = bodyStructure.textBody,
htmlBody = bodyStructure.htmlBody,
attachments = bodyStructure.attachments,
+ keywords = keywords,
bodyValues = bodyValues,
hasAttachment = HasAttachment(!firstMessage.getLoadedAttachments.isEmpty),
preview = preview),
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
index 8efe100..52bdd44 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
@@ -53,7 +53,7 @@ object Keyword {
def parse(flagName: String): Either[String, Keyword] = Either.cond(isValid(flagName), Keyword(flagName), VALIDATION_MESSAGE)
def of(flagName: String): Try[Keyword] = parse(flagName) match {
- case Left(errorMessage: String) => Failure(throw new IllegalArgumentException(errorMessage))
+ case Left(errorMessage: String) => Failure(new IllegalArgumentException(errorMessage))
case Right(keyword: Keyword) => Success(keyword)
}
@@ -66,13 +66,11 @@ object Keyword {
}
}
-final case class Keyword(flagName: String) {
+final case class Keyword(flagName: String) extends AnyVal {
def getFlagName: String = flagName
def isExposedImapKeyword: Boolean = !Keyword.NON_EXPOSED_IMAP_KEYWORDS.contains(this)
- def isDraft: Boolean = Keyword.DRAFT == this
-
def asSystemFlag: Option[Flags.Flag] = Keyword.IMAP_SYSTEM_FLAGS
.filter(entry => entry._2.equals(this))
.keys
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
index 19101ff..468a899 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
@@ -35,7 +35,11 @@ trait KeywordsValidator {
}
object ToKeyword {
- val STRICT: ToKeyword = (value: String) => Some(Keyword.of(value).get)
+ val STRICT: ToKeyword = (value: String) => Keyword.of(value) match {
+ case Failure(_) => None
+ case Success(value: Keyword) => Some(value)
+ }
+
val LENIENT: ToKeyword = (value: String) => Keyword.parse(value) match {
case Left(_) => None
case Right(value: Keyword) => Some(value)
@@ -44,19 +48,19 @@ object ToKeyword {
object KeywordsValidator {
val THROW_ON_IMAP_NON_EXPOSED_KEYWORDS: KeywordsValidator = (keywords: Set[Keyword]) => {
- val exposed_keywords = keywords.filter(keyword => keyword.isExposedImapKeyword)
- if (exposed_keywords.isEmpty || exposed_keywords.size != keywords.size) {
+ val exposedKeywords = keywords.filter(_.isExposedImapKeyword)
+ if (exposedKeywords.isEmpty || exposedKeywords.size != keywords.size) {
Failure(new IllegalArgumentException("Does not allow to update 'Deleted' or 'Recent' flag"))
} else {
Success()
}
}
- val IGNORE_NON_EXPOSED_IMAP_KEYWORDS: KeywordsValidator = (_: Set[Keyword]) => Success()
+ val IGNORE_NON_EXPOSED_IMAP_KEYWORDS: KeywordsValidator = _ => Success()
}
object KeywordFilter {
- val FILTER_IMAP_NON_EXPOSED_KEYWORDS: Keyword => Boolean = keyword => keyword.isExposedImapKeyword
+ val FILTER_IMAP_NON_EXPOSED_KEYWORDS: Keyword => Boolean = _.isExposedImapKeyword
val KEEP_ALL: Keyword => Boolean = _ => true
}
@@ -93,31 +97,31 @@ final case class Keywords(keywords: Set[Keyword]) {
}
class KeywordsFactory(val validator: KeywordsValidator, val filter: Keyword => Boolean, val toKeyword: ToKeyword) {
- def fromSet(setKeywords: Set[Keyword]): Keywords = {
- validator.validate(setKeywords) match {
- case Success(_) => Keywords(setKeywords.filter(filter))
- case Failure(e: Throwable) => throw e
- }
+ def fromSet(setKeywords: Set[Keyword]): Try[Keywords] = {
+ validator.validate(setKeywords) match {
+ case Success(_) => Success(Keywords(setKeywords.filter(filter)))
+ case Failure(throwable: Throwable) => Failure(throwable)
}
+ }
- def from(keywords: Keyword*): Keywords = {
- fromSet(keywords.toSet)
- }
+ def from(keywords: Keyword*): Try[Keywords] = {
+ fromSet(keywords.toSet)
+ }
- def fromCollection(keywords: Set[String]): Keywords = fromSet(keywords.flatMap(toKeyword.toKeyword))
+ def fromCollection(keywords: Set[String]): Try[Keywords] = fromSet(keywords.flatMap(toKeyword.toKeyword))
- def fromMap(mapKeywords: Map[String, Boolean]): Try[Keywords] = {
- if (mapKeywords.values.exists((value: Boolean) => value.equals(false))) {
- Failure(throw new IllegalArgumentException("Keyword must be true"))
- } else {
- Success(fromCollection(mapKeywords.keySet))
- }
+ def fromMap(mapKeywords: Map[String, Boolean]): Try[Keywords] = {
+ if (mapKeywords.values.exists(!_)) {
+ Failure(new IllegalArgumentException("Keyword must be true"))
+ } else {
+ fromCollection(mapKeywords.keySet)
}
+ }
- def fromFlags(flags: Flags): Keywords = {
- val fromUserFlags: Set[Option[Keyword]] = flags.getUserFlags.map[Option[Keyword]](toKeyword.toKeyword).toSet
- val fromSystemFlags: Set[Option[Keyword]] = flags.getSystemFlags.map[Option[Keyword]](Keyword.fromFlag).toSet
+ def fromFlags(flags: Flags): Try[Keywords] = {
+ val fromUserFlags: Set[Option[Keyword]] = flags.getUserFlags.map[Option[Keyword]](toKeyword.toKeyword).toSet
+ val fromSystemFlags: Set[Option[Keyword]] = flags.getSystemFlags.map[Option[Keyword]](Keyword.fromFlag).toSet
- fromSet((fromUserFlags ++ fromSystemFlags).flatten)
- }
- }
\ No newline at end of file
+ fromSet((fromUserFlags ++ fromSystemFlags).flatten)
+ }
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/KeywordsCombiner.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/KeywordsCombiner.scala
index 95262cd..88257f0 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/KeywordsCombiner.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/KeywordsCombiner.scala
@@ -26,18 +26,13 @@ object KeywordsCombiner {
}
final case class KeywordsCombiner() extends BinaryOperator[Keywords] {
- val keywordsFactory: KeywordsFactory = KeywordsFactory.LENIENT_KEYWORDS_FACTORY
-
override def apply(keywords: Keywords, keywords2: Keywords): Keywords =
- keywordsFactory.fromSet(
- keywords.getKeywords.union(keywords2.getKeywords).union(KeywordsCombiner.KEYWORD_NOT_TO_UNION)
- .union(keywords.getKeywords.intersect(keywords2.getKeywords).intersect(KeywordsCombiner.KEYWORD_TO_INTERSECT)))
+ Keywords(keywords.getKeywords.union(keywords2.getKeywords).filterNot(KeywordsCombiner.KEYWORD_NOT_TO_UNION)
+ .union(keywords.getKeywords.intersect(keywords2.getKeywords).filterNot(KeywordsCombiner.KEYWORD_TO_INTERSECT)))
def union(set1: Set[Keyword], set2: Set[Keyword], exceptKeywords: List[Keyword]): Set[Keyword] =
- set1.union(set2)
- .filter((keyword: Keyword) => !exceptKeywords.contains(keyword))
+ set1.union(set2).filter(!exceptKeywords.contains(_))
def intersect(set1: Set[Keyword], set2: Set[Keyword], forKeywords: List[Keyword]): Set[Keyword] =
- set1.intersect(set2)
- .filter(forKeywords.contains)
+ set1.intersect(set2).filter(forKeywords.contains)
}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordTest.scala
index 9df8608..96d5d93 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordTest.scala
@@ -22,9 +22,10 @@ package org.apache.james.jmap.model
import javax.mail.Flags
import nl.jqno.equalsverifier.EqualsVerifier
import org.apache.commons.lang3.StringUtils
-import org.assertj.core.api.Assertions.{assertThat, assertThatCode, assertThatThrownBy}
+import org.assertj.core.api.Assertions.{assertThat, assertThatCode}
import org.scalatest.matchers.must.Matchers
import org.scalatest.wordspec.AnyWordSpec
+import scala.util.Failure
object KeywordTest {
private val FLAG_NAME_MAX_LENGTH = 255
@@ -34,16 +35,16 @@ object KeywordTest {
class KeywordTest extends AnyWordSpec with Matchers {
"respect bean contract" in {
- EqualsVerifier.forClass(classOf[Keyword]).verify()
+ EqualsVerifier.forClass(classOf[Keyword]).withNonnullFields("flagName").verify()
}
"throw when flagName length less than minlength" in {
- assertThatThrownBy(() => Keyword.of("")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName lengthMore than maxlength" in {
val keywordTooLong: String = StringUtils.repeat("a", KeywordTest.FLAG_NAME_MAX_LENGTH + 1)
- assertThatThrownBy(() => Keyword.of(keywordTooLong)).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of(keywordTooLong)).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"createNewOne when flagName length equals maxlength" in {
@@ -59,43 +60,43 @@ class KeywordTest extends AnyWordSpec with Matchers {
}
"throw when flagName contains percentageCharacter" in {
- assertThatThrownBy(() => Keyword.of("a%")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a%")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName contains leftBracket" in {
- assertThatThrownBy(() => Keyword.of("a[")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a[")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName contains rightBracket" in {
- assertThatThrownBy(() => Keyword.of("a]")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a]")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName contains leftBrace" in {
- assertThatThrownBy(() => Keyword.of("a{")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a{")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName contains slash" in {
- assertThatThrownBy(() => Keyword.of("a\\")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a\\")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName containsStar" in {
- assertThatThrownBy(() => Keyword.of("a*")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a*")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName containsQuote" in {
- assertThatThrownBy(() => Keyword.of("a\"")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a\"")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName contains openingParenthesis" in {
- assertThatThrownBy(() => Keyword.of("a(")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a(")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName contains closingParenthesis" in {
- assertThatThrownBy(() => Keyword.of("a)")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a)")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"throw when flagName contains space character" in {
- assertThatThrownBy(() => Keyword.of("a b")).isInstanceOf(classOf[IllegalArgumentException])
+ assertThat(Keyword.of("a b")).isInstanceOf(classOf[Failure[IllegalArgumentException]])
}
"isNotNonExposedImapKeyword should return false when deleted" in {
@@ -115,14 +116,6 @@ class KeywordTest extends AnyWordSpec with Matchers {
assertThat(keyword.isExposedImapKeyword).isTrue
}
- "isDraft should return true when draft" in {
- assertThat(Keyword.DRAFT.isDraft).isTrue
- }
-
- "isDraft should return false when non draft" in {
- assertThat(Keyword.DELETED.isDraft).isFalse
- }
-
"asSystemFlag should return systemFlag" in {
assertThat(Keyword.of("$Draft").get.asSystemFlag).isEqualTo(Some(Flags.Flag.DRAFT))
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsCombinerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsCombinerTest.scala
index 0fec305..59d3df9 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsCombinerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsCombinerTest.scala
@@ -29,39 +29,48 @@ import scala.jdk.CollectionConverters._
class KeywordsCombinerTest extends AnyWordSpec with Matchers {
"apply should union seen keyword" in {
val keywordsCombiner = KeywordsCombiner()
- assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN)))
- .isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN))
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN).get))
+ .isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN).get)
}
"apply should union answered keyword" in {
val keywordsCombiner = KeywordsCombiner()
- assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED))).isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED))
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED).get))
+ .isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED).get)
}
"apply should union flagged keyword" in {
val keywordsCombiner = KeywordsCombiner()
- assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED))).isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED))
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED).get))
+ .isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED).get)
}
"apply should intersect draft keyword" in {
val keywordsCombiner = KeywordsCombiner()
- assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.DRAFT))).isEqualTo(Keywords.DEFAULT_VALUE)
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.DRAFT).get))
+ .isEqualTo(Keywords.DEFAULT_VALUE)
}
"apply should union custom keyword" in {
val keywordsCombiner = KeywordsCombiner()
val customKeyword = Keyword.of("$Any")
- assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(customKeyword.get))).isEqualTo(LENIENT_KEYWORDS_FACTORY.from(customKeyword.get))
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(customKeyword.get).get))
+ .isEqualTo(LENIENT_KEYWORDS_FACTORY.from(customKeyword.get).get)
}
"apply should accept empty as a zeroValue" in {
val keywordsCombiner = KeywordsCombiner()
- assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, Keywords.DEFAULT_VALUE)).isEqualTo(Keywords.DEFAULT_VALUE)
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, Keywords.DEFAULT_VALUE))
+ .isEqualTo(Keywords.DEFAULT_VALUE)
}
"apply should union different flags" in {
val keywordsCombiner = KeywordsCombiner()
- assertThat(keywordsCombiner.apply(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED), LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED))).isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED, Keyword.ANSWERED))
+ val keywords: Keywords = keywordsCombiner.apply(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED).get,
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED).get)
+
+ assertThat(keywords)
+ .isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED, Keyword.ANSWERED).get)
}
"keywords combiner should be commutative" in {
@@ -72,19 +81,20 @@ class KeywordsCombinerTest extends AnyWordSpec with Matchers {
Keyword.FLAGGED,
Keyword.SEEN,
Keyword.of("$Forwarded").get,
- Keyword.of("$Any").get)
+ Keyword.of("$Any").get).get
val values:Set[Keywords] = Set(
- LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED),
- LENIENT_KEYWORDS_FACTORY.from(Keyword.DELETED),
- LENIENT_KEYWORDS_FACTORY.from(Keyword.DRAFT),
- LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED),
- LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN),
- LENIENT_KEYWORDS_FACTORY.from(),
- LENIENT_KEYWORDS_FACTORY.from(Keyword.of("$Forwarded").get),
- LENIENT_KEYWORDS_FACTORY.from(Keyword.of("$Any").get),
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED).get,
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.DELETED).get,
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.DRAFT).get,
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED).get,
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN).get,
+ Keywords.DEFAULT_VALUE,
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.of("$Forwarded").get).get,
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.of("$Any").get).get,
allKeyword)
- assertThat(new CommutativityChecker[Keywords](values.asJava, KeywordsCombiner()).findNonCommutativeInput).isEmpty()
+ assertThat(new CommutativityChecker(values.asJava, KeywordsCombiner()).findNonCommutativeInput)
+ .isEmpty()
}
}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsTest.scala
index 1ec6bfb..5d28d36 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsTest.scala
@@ -20,7 +20,6 @@
package org.apache.james.jmap.model
import org.assertj.core.api.Assertions.assertThat
-import org.assertj.core.api.Assertions.assertThatThrownBy
import javax.mail.Flags
import javax.mail.Flags.Flag
import org.apache.james.mailbox.FlagsBuilder
@@ -39,8 +38,8 @@ class KeywordsTest extends AnyWordSpec with Matchers {
EqualsVerifier.forClass(classOf[Keywords]).verify()
}
- "fromMap should throw when wrong keyword value" in {
- assertThatThrownBy(() => LENIENT_KEYWORDS_FACTORY.fromMap(Map(KeywordsTest.ANY_KEYWORD -> false)))
+ "fromMap should return empty when wrong keyword value" in {
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromMap(Map(KeywordsTest.ANY_KEYWORD -> false)).failed.get)
.isInstanceOf(classOf[IllegalArgumentException])
}
@@ -50,71 +49,71 @@ class KeywordsTest extends AnyWordSpec with Matchers {
}
"fromFlags should return keywords from allFlag" in {
- val keywords = LENIENT_KEYWORDS_FACTORY.fromFlags(new Flags(Flag.ANSWERED))
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromFlags(new Flags(Flag.ANSWERED)).get
assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED))
}
"fromSet should return keywords from set of keywords" in {
- val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED))
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).get
assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED))
}
"asFlags should build flags from keywords" in {
- assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).asFlags).isEqualTo(new Flags(Flag.ANSWERED))
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).get.asFlags).isEqualTo(new Flags(Flag.ANSWERED))
}
"asFlags with recent and deleted from should build flags from keywords and recent originFlags" in {
val originFlags = FlagsBuilder.builder.add(Flag.RECENT, Flag.DRAFT).build
val expectedFlags = FlagsBuilder.builder.add(Flag.ANSWERED, Flag.RECENT).build
- assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).asFlagsWithRecentAndDeletedFrom(originFlags)).isEqualTo(expectedFlags)
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).get.asFlagsWithRecentAndDeletedFrom(originFlags)).isEqualTo(expectedFlags)
}
"asFlags with recent and deleted from should build flags from keywords with deleted and recent originFlags" in {
val originFlags = FlagsBuilder.builder.add(Flag.RECENT, Flag.DELETED, Flag.DRAFT).build
val expectedFlags = FlagsBuilder.builder.add(Flag.ANSWERED, Flag.RECENT, Flag.DELETED).build
- assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).asFlagsWithRecentAndDeletedFrom(originFlags)).isEqualTo(expectedFlags)
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).get.asFlagsWithRecentAndDeletedFrom(originFlags)).isEqualTo(expectedFlags)
}
"asMap should return empty when empty map of string and boolean" in {
- assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set.empty).keywords.nonEmpty).isFalse
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set.empty).isFailure).isFalse
}
"asMap should return map of string and boolean" in {
val expectedMap = Map(Keyword.of("$Answered").get -> Keyword.FLAG_VALUE)
- assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).asMap).isEqualTo(expectedMap)
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).get.asMap).isEqualTo(expectedMap)
}
- "throwWhenUnsupportedKeyword should throw when have unsupportedKeywords" in {
- assertThatThrownBy(() => STRICT_KEYWORDS_FACTORY.fromSet(Set(Keyword.DRAFT, Keyword.DELETED))).isInstanceOf(classOf[IllegalArgumentException])
+ "throwWhenUnsupportedKeyword should return failure when have unsupportedKeywords" in {
+ assertThat(STRICT_KEYWORDS_FACTORY.fromSet(Set(Keyword.DRAFT, Keyword.DELETED)).isFailure).isTrue
}
"throwWhenUnsupportedKeyword should not throw when have draft" in {
- val keywords = STRICT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED, Keyword.DRAFT))
+ val keywords = STRICT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED, Keyword.DRAFT)).get
assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED, Keyword.DRAFT))
}
"filterUnsupported should filter" in {
- val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED, Keyword.DELETED, Keyword.RECENT, Keyword.DRAFT))
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED, Keyword.DELETED, Keyword.RECENT, Keyword.DRAFT)).get
assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED, Keyword.DRAFT))
}
"contains should return true when keywords contain keyword" in {
- val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.SEEN))
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.SEEN)).get
assertThat(keywords.contains(Keyword.SEEN)).isTrue
}
"contains should return false when keywords do not contain keyword" in {
- val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set())
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set()).get
assertThat(keywords.contains(Keyword.SEEN)).isFalse
}
"fromList should return keywords fromList of strings" in {
- val keywords = LENIENT_KEYWORDS_FACTORY.fromCollection(Set("$Answered", "$Flagged"))
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromCollection(Set("$Answered", "$Flagged")).get
assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED, Keyword.FLAGGED))
}
"fromList should not throw on invalidKeyword for lenientFactory" in {
- assertThat(LENIENT_KEYWORDS_FACTORY.fromCollection(Set("in&valid")).keywords).isEqualTo(Keywords.DEFAULT_VALUE.keywords)
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromCollection(Set("in&valid")).get.keywords).isEqualTo(Keywords.DEFAULT_VALUE.keywords)
}
"fromMap should Not Throw On InvalidKeyword For LenientFactory" in {
@@ -122,6 +121,6 @@ class KeywordsTest extends AnyWordSpec with Matchers {
}
"fromFlags should Not Throw On InvalidKeyword For LenientFactory" in {
- assertThat(LENIENT_KEYWORDS_FACTORY.fromFlags(new Flags("in&valid")).keywords).isEqualTo(Keywords.DEFAULT_VALUE.keywords)
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromFlags(new Flags("in&valid")).get.keywords).isEqualTo(Keywords.DEFAULT_VALUE.keywords)
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 04/06: JAMES-3368 Port KeywordTest + refactoring
make it green
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit be569bfdbd77a3aac62af85e69b10e95ff5ccc72
Author: duc91 <vd...@linagora.com>
AuthorDate: Tue Sep 8 16:11:23 2020 +0700
JAMES-3368 Port KeywordTest + refactoring make it green
---
.../org/apache/james/jmap/model/Keyword.scala | 76 +++++-----
.../org/apache/james/jmap/model/Keywords.scala | 101 ++++++--------
.../org/apache/james/jmap/model/KeywordTest.scala | 153 +++++++++++++++++++++
.../org/apache/james/jmap/model/KeywordsTest.scala | 127 +++++++++++++++++
4 files changed, 360 insertions(+), 97 deletions(-)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
index 2cd37e4..8efe100 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keyword.scala
@@ -18,51 +18,46 @@
* **************************************************************/
package org.apache.james.jmap.model
-import java.util.Optional
-
-import com.google.common.base.{MoreObjects, Objects}
-import com.google.common.collect.{ImmutableBiMap, ImmutableList}
import com.ibm.icu.text.UnicodeSet
import javax.mail.Flags
import org.apache.commons.lang3.StringUtils
+import scala.util.{Failure, Success, Try}
+
object Keyword {
private val FLAG_NAME_MIN_LENGTH = 1
private val FLAG_NAME_MAX_LENGTH = 255
- private val FLAG_NAME_PATTERN = new UnicodeSet("[[a-z][A-Z][0-9]]").add('$').add('_').add('-').freeze
- val DRAFT = Keyword.of("$Draft")
- val SEEN = Keyword.of("$Seen")
- val FLAGGED = Keyword.of("$Flagged")
- val ANSWERED = Keyword.of("$Answered")
- val DELETED = Keyword.of("$Deleted")
- val RECENT = Keyword.of("$Recent")
- val FORWARDED = Keyword.of("$Forwarded")
- val FLAG_VALUE = true
- private val NON_EXPOSED_IMAP_KEYWORDS = ImmutableList.of(Keyword.RECENT, Keyword.DELETED)
- private val IMAP_SYSTEM_FLAGS = ImmutableBiMap.builder[Flags.Flag, Keyword]
- .put(Flags.Flag.DRAFT, DRAFT)
- .put(Flags.Flag.SEEN, SEEN)
- .put(Flags.Flag.FLAGGED, FLAGGED)
- .put(Flags.Flag.ANSWERED, ANSWERED)
- .put(Flags.Flag.RECENT, RECENT)
- .put(Flags.Flag.DELETED, DELETED)
- .build
+ private val VALIDATION_MESSAGE: String = "FlagName must not be null or empty, must have length form 1-255," +
+ "must not contain characters with hex from '\\u0000' to '\\u00019'" +
+ " or {'(' ')' '{' ']' '%' '*' '\"' '\\'} "
- private val VALIDATION_MESSAGE = """FlagName must not be null or empty, must have length form 1-255,
- | must not contain characters with hex from '\\u0000' to '\\u00019'
- | or {'(' ')' '{' ']' '%' '*' '\"' '\\'} """.stripMargin
+ private val FLAG_NAME_PATTERN = new UnicodeSet("[[a-z][A-Z][0-9]$_-]").freeze
+ val DRAFT = Keyword.of("$Draft").get
+ val SEEN = Keyword.of("$Seen").get
+ val FLAGGED = Keyword.of("$Flagged").get
+ val ANSWERED = Keyword.of("$Answered").get
+ val DELETED = Keyword.of("$Deleted").get
+ val RECENT = Keyword.of("$Recent").get
+ val FORWARDED = Keyword.of("$Forwarded").get
+ val FLAG_VALUE: Boolean = true
+ private val NON_EXPOSED_IMAP_KEYWORDS = List(Keyword.RECENT, Keyword.DELETED)
+ private val IMAP_SYSTEM_FLAGS: Map[Flags.Flag, Keyword] =
+ Map(
+ Flags.Flag.DRAFT -> DRAFT,
+ Flags.Flag.SEEN -> SEEN,
+ Flags.Flag.FLAGGED -> FLAGGED,
+ Flags.Flag.ANSWERED -> ANSWERED,
+ Flags.Flag.RECENT -> RECENT,
+ Flags.Flag.DELETED -> DELETED)
- def parse(flagName: String): Either[String, Keyword] = {
- if (isValid(flagName)) Right(new Keyword(flagName))
- Left(VALIDATION_MESSAGE)
- }
+ def parse(flagName: String): Either[String, Keyword] = Either.cond(isValid(flagName), Keyword(flagName), VALIDATION_MESSAGE)
- def of(flagName: String): Keyword = parse(flagName) match {
- case Left(errorMessage: String) => throw new IllegalArgumentException(errorMessage)
- case Right(keyword: Keyword) => keyword
+ def of(flagName: String): Try[Keyword] = parse(flagName) match {
+ case Left(errorMessage: String) => Failure(throw new IllegalArgumentException(errorMessage))
+ case Right(keyword: Keyword) => Success(keyword)
}
- def fromFlag(flag: Flags.Flag): Option[Keyword] = Some(IMAP_SYSTEM_FLAGS.get(flag))
+ def fromFlag(flag: Flags.Flag): Option[Keyword] = IMAP_SYSTEM_FLAGS.get(flag)
def isValid(flagName: String): Boolean = flagName match {
case _ if StringUtils.isBlank(flagName) => false
@@ -71,14 +66,17 @@ object Keyword {
}
}
-class Keyword(val flagName: String) {
- def getFlagName(): String = flagName
+final case class Keyword(flagName: String) {
+ def getFlagName: String = flagName
- def isExposedImapKeyword(): Boolean = !Keyword.NON_EXPOSED_IMAP_KEYWORDS.contains(this)
+ def isExposedImapKeyword: Boolean = !Keyword.NON_EXPOSED_IMAP_KEYWORDS.contains(this)
- def isDraft(): Boolean = Keyword.DRAFT == this
+ def isDraft: Boolean = Keyword.DRAFT == this
- def asSystemFlag(): Optional[Flags.Flag] = Optional.ofNullable(Keyword.IMAP_SYSTEM_FLAGS.inverse.get(this))
+ def asSystemFlag: Option[Flags.Flag] = Keyword.IMAP_SYSTEM_FLAGS
+ .filter(entry => entry._2.equals(this))
+ .keys
+ .collectFirst(flag => flag)
- def asFlags(): Flags = asSystemFlag().map(Flag => new Flags(Flag)).orElse(new Flags(flagName))
+ def asFlags: Flags = asSystemFlag.map(Flag => new Flags(Flag)).getOrElse(new Flags(flagName))
}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
index 7ef0540..19101ff 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Keywords.scala
@@ -18,92 +18,74 @@
****************************************************************/
package org.apache.james.jmap.model
-import com.google.common.base.{MoreObjects, Objects}
import javax.mail.Flags
-import org.apache.james.jmap.model.KeywordsFactory.{LENIENT_KEYWORDS_FACTORY, STRICT_KEYWORDS_FACTORY}
import org.apache.james.mailbox.FlagsBuilder
import scala.collection.IterableOnce
-import scala.jdk.CollectionConverters._
+import scala.util.{Failure, Success, Try}
trait ToKeyword {
- def toKeyword(value: String): Option[Keyword]
+ def toKeyword(value: String): Option[Keyword]
+}
+
+trait KeywordFilter extends IterableOnce[Keyword]
+
+trait KeywordsValidator {
+ def validate(keywords: Set[Keyword]): Try[Unit]
}
object ToKeyword {
- val STRICT: ToKeyword = (value: String) => Some(Keyword.of(value))
+ val STRICT: ToKeyword = (value: String) => Some(Keyword.of(value).get)
val LENIENT: ToKeyword = (value: String) => Keyword.parse(value) match {
- case Left(errorMessage: String) => {
- println(errorMessage)
- None
- }
+ case Left(_) => None
case Right(value: Keyword) => Some(value)
}
}
-trait KeywordsValidator {
- def validate(keywords: Set[Keyword]): Boolean
-}
-
object KeywordsValidator {
- val THROW_ON_IMAP_NON_EXPOSED_KEYWORDS: KeywordsValidator = (keywords: Set[Keyword]) => {
- if (keywords.exists(KeywordFilter.FILTER_IMAP_NON_EXPOSED_KEYWORDS)) {
- throw new IllegalArgumentException("Does not allow to update 'Deleted' or 'Recent' flag")
- }
- true
- }
-
- val IGNORE_NON_EXPOSED_IMAP_KEYWORDS: KeywordsValidator = (keywords: Set[Keyword]) => {
- def foo(keywords: Set[Keyword]): Option[Keyword] = ???
- foo(keywords).isDefined
- }
+ val THROW_ON_IMAP_NON_EXPOSED_KEYWORDS: KeywordsValidator = (keywords: Set[Keyword]) => {
+ val exposed_keywords = keywords.filter(keyword => keyword.isExposedImapKeyword)
+ if (exposed_keywords.isEmpty || exposed_keywords.size != keywords.size) {
+ Failure(new IllegalArgumentException("Does not allow to update 'Deleted' or 'Recent' flag"))
+ } else {
+ Success()
}
+ }
+
+ val IGNORE_NON_EXPOSED_IMAP_KEYWORDS: KeywordsValidator = (_: Set[Keyword]) => Success()
+}
object KeywordFilter {
- val FILTER_IMAP_NON_EXPOSED_KEYWORDS: Keyword => Boolean = (keyword: Keyword) => keyword.isExposedImapKeyword()
- val KEEP_ALL: Keyword => Boolean = (_: Keyword) => true
+ val FILTER_IMAP_NON_EXPOSED_KEYWORDS: Keyword => Boolean = keyword => keyword.isExposedImapKeyword
+ val KEEP_ALL: Keyword => Boolean = _ => true
}
object KeywordsFactory {
val STRICT_KEYWORDS_FACTORY = new KeywordsFactory(KeywordsValidator.THROW_ON_IMAP_NON_EXPOSED_KEYWORDS, KeywordFilter.KEEP_ALL, ToKeyword.STRICT)
val LENIENT_KEYWORDS_FACTORY = new KeywordsFactory(KeywordsValidator.IGNORE_NON_EXPOSED_IMAP_KEYWORDS, KeywordFilter.FILTER_IMAP_NON_EXPOSED_KEYWORDS, ToKeyword.LENIENT)
-
- private[model] trait KeywordFilter extends IterableOnce[Keyword] {}
}
object Keywords {
- val DEFAULT_VALUE: Set[Keywords] = Set()
+ val DEFAULT_VALUE: Keywords = Keywords(Set.empty)
}
-case class Keywords(keywords: Set[Keyword]) {
-
- def combiner(firstBuilder: FlagsBuilder, secondBuilder: FlagsBuilder): FlagsBuilder = firstBuilder.add(secondBuilder.build)
- def accumulator(accumulator: FlagsBuilder, keyword: Keyword): FlagsBuilder = accumulator.add(keyword.asFlags())
+final case class Keywords(keywords: Set[Keyword]) {
- /**
- * This Keywords factory will filter out invalid keywords (containing inavlid character, being too long or being non
- * exposed IMAP flags)
- */
- def strictFactory: KeywordsFactory = STRICT_KEYWORDS_FACTORY
-
- /**
- * This Keywords factory will throw upon invalid keywords (containing inavlid character, being too long or being non
- * exposed IMAP flags)
- *
- * @throws IllegalArgumentException
- */
- def lenientFactory: KeywordsFactory = LENIENT_KEYWORDS_FACTORY
-
- def asFlags(): Flags = keywords.asJava.stream().reduce(FlagsBuilder.builder, this.accumulator, this.combiner).build
+ def asFlags: Flags = keywords.foldLeft(new FlagsBuilder)((flag: FlagsBuilder, keyword: Keyword) => flag.add(keyword.asFlags))
+ .build()
def asFlagsWithRecentAndDeletedFrom(originFlags: Flags): Flags = {
val flags = asFlags
- if (originFlags.contains(Flags.Flag.DELETED)) flags.add(Flags.Flag.DELETED)
- if (originFlags.contains(Flags.Flag.RECENT)) flags.add(Flags.Flag.RECENT)
+ if (originFlags.contains(Flags.Flag.DELETED)) {
+ flags.add(Flags.Flag.DELETED)
+ }
+ if (originFlags.contains(Flags.Flag.RECENT)) {
+ flags.add(Flags.Flag.RECENT)
+ }
flags
}
- def asMap: Map[String, Boolean] = keywords zip Iterable(Keyword.FLAG_VALUE) toMap
+ def asMap: Map[Keyword, Boolean] = keywords.map((_, true)).toMap
def getKeywords: Set[Keyword] = keywords
@@ -112,8 +94,10 @@ case class Keywords(keywords: Set[Keyword]) {
class KeywordsFactory(val validator: KeywordsValidator, val filter: Keyword => Boolean, val toKeyword: ToKeyword) {
def fromSet(setKeywords: Set[Keyword]): Keywords = {
- validator.validate(setKeywords)
- Keywords(setKeywords.filter(filter))
+ validator.validate(setKeywords) match {
+ case Success(_) => Keywords(setKeywords.filter(filter))
+ case Failure(e: Throwable) => throw e
+ }
}
def from(keywords: Keyword*): Keywords = {
@@ -122,17 +106,18 @@ class KeywordsFactory(val validator: KeywordsValidator, val filter: Keyword => B
def fromCollection(keywords: Set[String]): Keywords = fromSet(keywords.flatMap(toKeyword.toKeyword))
- def fromMap(mapKeywords: Map[String, Boolean]): Keywords = {
- if (mapKeywords.values.toSet.exists(value => value)) {
- throw new IllegalArgumentException("Keyword must be true")
+ def fromMap(mapKeywords: Map[String, Boolean]): Try[Keywords] = {
+ if (mapKeywords.values.exists((value: Boolean) => value.equals(false))) {
+ Failure(throw new IllegalArgumentException("Keyword must be true"))
+ } else {
+ Success(fromCollection(mapKeywords.keySet))
}
- fromCollection(mapKeywords.keySet)
}
def fromFlags(flags: Flags): Keywords = {
val fromUserFlags: Set[Option[Keyword]] = flags.getUserFlags.map[Option[Keyword]](toKeyword.toKeyword).toSet
val fromSystemFlags: Set[Option[Keyword]] = flags.getSystemFlags.map[Option[Keyword]](Keyword.fromFlag).toSet
- fromSet((fromUserFlags ++ fromSystemFlags).flatten(option => option.isDefined).map(_.get))
+ fromSet((fromUserFlags ++ fromSystemFlags).flatten)
}
}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordTest.scala
new file mode 100644
index 0000000..9df8608
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordTest.scala
@@ -0,0 +1,153 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ * **************************************************************/
+
+package org.apache.james.jmap.model
+
+import javax.mail.Flags
+import nl.jqno.equalsverifier.EqualsVerifier
+import org.apache.commons.lang3.StringUtils
+import org.assertj.core.api.Assertions.{assertThat, assertThatCode, assertThatThrownBy}
+import org.scalatest.matchers.must.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+object KeywordTest {
+ private val FLAG_NAME_MAX_LENGTH = 255
+ private val ANY_KEYWORD = "AnyKeyword"
+}
+
+class KeywordTest extends AnyWordSpec with Matchers {
+
+ "respect bean contract" in {
+ EqualsVerifier.forClass(classOf[Keyword]).verify()
+ }
+
+ "throw when flagName length less than minlength" in {
+ assertThatThrownBy(() => Keyword.of("")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName lengthMore than maxlength" in {
+ val keywordTooLong: String = StringUtils.repeat("a", KeywordTest.FLAG_NAME_MAX_LENGTH + 1)
+ assertThatThrownBy(() => Keyword.of(keywordTooLong)).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "createNewOne when flagName length equals maxlength" in {
+ val maxlengthFlagName = StringUtils.repeat("a", KeywordTest.FLAG_NAME_MAX_LENGTH)
+ val keyword = Keyword.of(maxlengthFlagName).get
+ assertThat(keyword.getFlagName).hasSameSizeAs(maxlengthFlagName)
+ }
+
+ "createNewOne when flagName length equals minlength" in {
+ val minlengthFlagName = "a"
+ val keyword = Keyword.of(minlengthFlagName).get
+ assertThat(keyword.getFlagName).hasSameSizeAs(minlengthFlagName)
+ }
+
+ "throw when flagName contains percentageCharacter" in {
+ assertThatThrownBy(() => Keyword.of("a%")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName contains leftBracket" in {
+ assertThatThrownBy(() => Keyword.of("a[")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName contains rightBracket" in {
+ assertThatThrownBy(() => Keyword.of("a]")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName contains leftBrace" in {
+ assertThatThrownBy(() => Keyword.of("a{")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName contains slash" in {
+ assertThatThrownBy(() => Keyword.of("a\\")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName containsStar" in {
+ assertThatThrownBy(() => Keyword.of("a*")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName containsQuote" in {
+ assertThatThrownBy(() => Keyword.of("a\"")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName contains openingParenthesis" in {
+ assertThatThrownBy(() => Keyword.of("a(")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName contains closingParenthesis" in {
+ assertThatThrownBy(() => Keyword.of("a)")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throw when flagName contains space character" in {
+ assertThatThrownBy(() => Keyword.of("a b")).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "isNotNonExposedImapKeyword should return false when deleted" in {
+ assertThat(Keyword.DELETED.isExposedImapKeyword).isFalse
+ }
+
+ "isNotNonExposedImapKeyword should return false when recent" in {
+ assertThat(Keyword.RECENT.isExposedImapKeyword).isFalse
+ }
+
+ "isNotNonExposedImapKeyword should return true when other systemFlag" in {
+ assertThat(Keyword.DRAFT.isExposedImapKeyword).isTrue
+ }
+
+ "isNotNonExposedImapKeyword should return true when any userFlag" in {
+ val keyword = Keyword.of(KeywordTest.ANY_KEYWORD).get
+ assertThat(keyword.isExposedImapKeyword).isTrue
+ }
+
+ "isDraft should return true when draft" in {
+ assertThat(Keyword.DRAFT.isDraft).isTrue
+ }
+
+ "isDraft should return false when non draft" in {
+ assertThat(Keyword.DELETED.isDraft).isFalse
+ }
+
+ "asSystemFlag should return systemFlag" in {
+ assertThat(Keyword.of("$Draft").get.asSystemFlag).isEqualTo(Some(Flags.Flag.DRAFT))
+ }
+
+ "asSystemFlag should return empty when non systemFlag" in {
+ assertThat(Keyword.of(KeywordTest.ANY_KEYWORD).get.asSystemFlag.nonEmpty).isFalse
+ }
+
+ "asFlags should return flags when systemFlag" in {
+ assertThat(Keyword.DELETED.asFlags).isEqualTo(new Flags(Flags.Flag.DELETED))
+ }
+
+ "asFlags should return flags when userFlag" in {
+ val keyword = Keyword.of(KeywordTest.ANY_KEYWORD).get
+ assertThat(keyword.asFlags).isEqualTo(new Flags(KeywordTest.ANY_KEYWORD))
+ }
+
+ "asFlags should return flags when userFlag contains underscore" in {
+ val userFlag = "$has_cal"
+ val keyword = Keyword.of(userFlag).get
+ assertThat(keyword.asFlags).isEqualTo(new Flags(userFlag))
+ }
+
+ "hyphenMinus should be allowed inKeyword" in {
+ val userFlag = "aa-bb"
+ assertThatCode(() => Keyword.of(userFlag)).doesNotThrowAnyException()
+ }
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsTest.scala
new file mode 100644
index 0000000..1ec6bfb
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsTest.scala
@@ -0,0 +1,127 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ * **************************************************************/
+
+package org.apache.james.jmap.model
+
+import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import javax.mail.Flags
+import javax.mail.Flags.Flag
+import org.apache.james.mailbox.FlagsBuilder
+import nl.jqno.equalsverifier.EqualsVerifier
+import org.apache.james.jmap.model.KeywordsFactory.{LENIENT_KEYWORDS_FACTORY, STRICT_KEYWORDS_FACTORY}
+import org.scalatest.matchers.must.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+object KeywordsTest {
+ val ANY_KEYWORD = "AnyKeyword"
+}
+
+class KeywordsTest extends AnyWordSpec with Matchers {
+
+ "Respect Bean Contract" in {
+ EqualsVerifier.forClass(classOf[Keywords]).verify()
+ }
+
+ "fromMap should throw when wrong keyword value" in {
+ assertThatThrownBy(() => LENIENT_KEYWORDS_FACTORY.fromMap(Map(KeywordsTest.ANY_KEYWORD -> false)))
+ .isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "fromMap should return keywords fromMap String and boolean" in {
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromMap(Map(KeywordsTest.ANY_KEYWORD -> Keyword.FLAG_VALUE))
+ assertThat(keywords.get.getKeywords).isEqualTo(Set(Keyword.of(KeywordsTest.ANY_KEYWORD).get))
+ }
+
+ "fromFlags should return keywords from allFlag" in {
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromFlags(new Flags(Flag.ANSWERED))
+ assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED))
+ }
+
+ "fromSet should return keywords from set of keywords" in {
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED))
+ assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED))
+ }
+
+ "asFlags should build flags from keywords" in {
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).asFlags).isEqualTo(new Flags(Flag.ANSWERED))
+ }
+
+ "asFlags with recent and deleted from should build flags from keywords and recent originFlags" in {
+ val originFlags = FlagsBuilder.builder.add(Flag.RECENT, Flag.DRAFT).build
+ val expectedFlags = FlagsBuilder.builder.add(Flag.ANSWERED, Flag.RECENT).build
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).asFlagsWithRecentAndDeletedFrom(originFlags)).isEqualTo(expectedFlags)
+ }
+
+ "asFlags with recent and deleted from should build flags from keywords with deleted and recent originFlags" in {
+ val originFlags = FlagsBuilder.builder.add(Flag.RECENT, Flag.DELETED, Flag.DRAFT).build
+ val expectedFlags = FlagsBuilder.builder.add(Flag.ANSWERED, Flag.RECENT, Flag.DELETED).build
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).asFlagsWithRecentAndDeletedFrom(originFlags)).isEqualTo(expectedFlags)
+ }
+
+ "asMap should return empty when empty map of string and boolean" in {
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set.empty).keywords.nonEmpty).isFalse
+ }
+
+ "asMap should return map of string and boolean" in {
+ val expectedMap = Map(Keyword.of("$Answered").get -> Keyword.FLAG_VALUE)
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED)).asMap).isEqualTo(expectedMap)
+ }
+
+ "throwWhenUnsupportedKeyword should throw when have unsupportedKeywords" in {
+ assertThatThrownBy(() => STRICT_KEYWORDS_FACTORY.fromSet(Set(Keyword.DRAFT, Keyword.DELETED))).isInstanceOf(classOf[IllegalArgumentException])
+ }
+
+ "throwWhenUnsupportedKeyword should not throw when have draft" in {
+ val keywords = STRICT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED, Keyword.DRAFT))
+ assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED, Keyword.DRAFT))
+ }
+
+ "filterUnsupported should filter" in {
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.ANSWERED, Keyword.DELETED, Keyword.RECENT, Keyword.DRAFT))
+ assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED, Keyword.DRAFT))
+ }
+
+ "contains should return true when keywords contain keyword" in {
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set(Keyword.SEEN))
+ assertThat(keywords.contains(Keyword.SEEN)).isTrue
+ }
+
+ "contains should return false when keywords do not contain keyword" in {
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromSet(Set())
+ assertThat(keywords.contains(Keyword.SEEN)).isFalse
+ }
+
+ "fromList should return keywords fromList of strings" in {
+ val keywords = LENIENT_KEYWORDS_FACTORY.fromCollection(Set("$Answered", "$Flagged"))
+ assertThat(keywords.getKeywords).isEqualTo(Set(Keyword.ANSWERED, Keyword.FLAGGED))
+ }
+
+ "fromList should not throw on invalidKeyword for lenientFactory" in {
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromCollection(Set("in&valid")).keywords).isEqualTo(Keywords.DEFAULT_VALUE.keywords)
+ }
+
+ "fromMap should Not Throw On InvalidKeyword For LenientFactory" in {
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromMap(Map("in&valid" -> true)).get.keywords).isEqualTo(Keywords.DEFAULT_VALUE.keywords)
+ }
+
+ "fromFlags should Not Throw On InvalidKeyword For LenientFactory" in {
+ assertThat(LENIENT_KEYWORDS_FACTORY.fromFlags(new Flags("in&valid")).keywords).isEqualTo(Keywords.DEFAULT_VALUE.keywords)
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 01/06: JAMES-2890 Split JMAP RFC-8621 Serializer
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 451ace39797d4303e7c2f8336b9c1008de77c467
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Sep 14 15:35:11 2020 +0700
JAMES-2890 Split JMAP RFC-8621 Serializer
Rationals:
- Many git conflicts in that class
- This lengthy class is hard to read with almost no structure
- Too much interleaving implicits...
- Some static JSON deserialization requires a val serializer
This is doable as we perform several parsing passes:
- first the request structure
- then back-reference substitutions
- then method argument resolution
- the */set creation and patches validation
These stages are independant from each other, and Methods are groupable by mostly distinct sets,
allowing a better modularisation.
---
.../james/jmap/rfc8621/RFC8621MethodsModule.java | 3 -
.../org/apache/james/jmap/http/SessionRoutes.scala | 7 +-
.../jmap/json/BackReferenceDeserializer.scala | 37 ++
.../james/jmap/json/EmailGetSerializer.scala | 108 +++++
.../james/jmap/json/EmailQuerySerializer.scala | 51 ++
.../apache/james/jmap/json/MailboxSerializer.scala | 185 ++++++++
.../james/jmap/json/ResponseSerializer.scala | 185 ++++++++
.../org/apache/james/jmap/json/Serializer.scala | 520 ---------------------
.../james/jmap/json/VacationSerializer.scala | 91 ++++
.../scala/org/apache/james/jmap/json/package.scala | 53 ++-
.../org/apache/james/jmap/mail/MailboxSet.scala | 12 +-
.../apache/james/jmap/method/EmailGetMethod.scala | 19 +-
.../james/jmap/method/EmailQueryMethod.scala | 6 +-
.../james/jmap/method/MailboxGetMethod.scala | 6 +-
.../james/jmap/method/MailboxSetMethod.scala | 6 +-
.../jmap/method/VacationResponseGetMethod.scala | 15 +-
.../jmap/method/VacationResponseSetMethod.scala | 11 +-
.../apache/james/jmap/routes/JMAPApiRoutes.scala | 16 +-
.../james/jmap/routes/ProcessingContext.scala | 34 +-
.../apache/james/jmap/http/SessionRoutesTest.scala | 3 -
.../apache/james/jmap/json/BackReferenceTest.scala | 7 +-
.../james/jmap/json/JsErrorSerializationTest.scala | 9 +-
.../jmap/json/MailboxGetSerializationTest.scala | 2 +-
.../james/jmap/json/MailboxSerializationTest.scala | 2 +-
.../jmap/json/RequestObjectSerializationTest.scala | 15 +-
.../json/ResponseObjectSerializationTest.scala | 12 +-
.../james/jmap/json/SessionSerializationTest.scala | 3 +-
.../VacationResponseGetSerializationTest.scala | 19 +-
.../json/VacationResponseSerializationTest.scala | 4 +-
.../james/jmap/routes/JMAPApiRoutesTest.scala | 7 +-
30 files changed, 795 insertions(+), 653 deletions(-)
diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index 4b3cd82..31d6170 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -32,7 +32,6 @@ import org.apache.james.jmap.http.Authenticator;
import org.apache.james.jmap.http.BasicAuthenticationStrategy;
import org.apache.james.jmap.http.SessionRoutes;
import org.apache.james.jmap.http.rfc8621.InjectionKeys;
-import org.apache.james.jmap.json.Serializer;
import org.apache.james.jmap.jwt.JWTAuthenticationStrategy;
import org.apache.james.jmap.method.CoreEchoMethod;
import org.apache.james.jmap.method.EmailGetMethod;
@@ -53,7 +52,6 @@ import org.slf4j.LoggerFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
-import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.multibindings.ProvidesIntoSet;
@@ -64,7 +62,6 @@ public class RFC8621MethodsModule extends AbstractModule {
@Override
protected void configure() {
- bind(Serializer.class).in(Scopes.SINGLETON);
bind(ZoneIdProvider.class).to(SystemZoneIdProvider.class);
Multibinder<Method> methods = Multibinder.newSetBinder(binder(), Method.class);
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
index 8d62973..a15726d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
@@ -31,7 +31,7 @@ import org.apache.james.jmap.JMAPUrls.AUTHENTICATION
import org.apache.james.jmap.exceptions.UnauthorizedException
import org.apache.james.jmap.http.SessionRoutes.{JMAP_SESSION, LOGGER}
import org.apache.james.jmap.http.rfc8621.InjectionKeys
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.ResponseSerializer
import org.apache.james.jmap.model.Session
import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
import org.slf4j.LoggerFactory
@@ -47,8 +47,7 @@ object SessionRoutes {
}
class SessionRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
- val serializer: Serializer,
- val sessionSupplier: SessionSupplier) extends JMAPRoutes {
+ val sessionSupplier: SessionSupplier) extends JMAPRoutes {
private val generateSession: JMAPRoute.Action =
(request, response) => SMono.fromPublisher(authenticator.authenticate(request))
@@ -73,7 +72,7 @@ class SessionRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticator:
private def sendRespond(session: Session, resp: HttpServerResponse) =
SMono.fromPublisher(resp.header(CONTENT_TYPE, JSON_CONTENT_TYPE_UTF8)
.status(OK)
- .sendString(SMono.fromCallable(() => Json.stringify(serializer.serialize(session))))
+ .sendString(SMono.fromCallable(() => Json.stringify(ResponseSerializer.serialize(session))))
.`then`())
def errorHandling(throwable: Throwable, response: HttpServerResponse): Mono[Void] =
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/BackReferenceDeserializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/BackReferenceDeserializer.scala
new file mode 100644
index 0000000..cf3fb3d
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/BackReferenceDeserializer.scala
@@ -0,0 +1,37 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.jmap.json
+
+import org.apache.james.jmap.model.Invocation.{MethodCallId, MethodName}
+import org.apache.james.jmap.routes.{BackReference, JsonPath}
+import play.api.libs.json.{Format, JsError, JsResult, JsString, JsSuccess, JsValue, Json, Reads}
+
+object BackReferenceDeserializer {
+ private implicit val jsonPathReadRead: Reads[JsonPath] = {
+ case JsString(path) => JsSuccess(JsonPath.parse(path))
+ case _ => JsError("JsonPath objects are represented by JsonString")
+ }
+
+ private implicit val methodNameFormat: Format[MethodName] = Json.valueFormat[MethodName]
+ private implicit val methodCallIdFormat: Format[MethodCallId] = Json.valueFormat[MethodCallId]
+ private implicit val backReferenceReads: Reads[BackReference] = Json.reads[BackReference]
+
+ def deserializeBackReference(input: JsValue): JsResult[BackReference] = Json.fromJson[BackReference](input)
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
new file mode 100644
index 0000000..797e8f2
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
@@ -0,0 +1,108 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.jmap.json
+
+import org.apache.james.jmap.api.model.Preview
+import org.apache.james.jmap.mail.{Address, BlobId, Charset, Disposition, Email, EmailAddress, EmailBody, EmailBodyPart, EmailBodyValue, EmailGetRequest, EmailGetResponse, EmailHeader, EmailHeaderName, EmailHeaderValue, EmailHeaders, EmailIds, EmailMetadata, EmailNotFound, EmailerName, FetchAllBodyValues, FetchHTMLBodyValues, FetchTextBodyValues, HasAttachment, HeaderMessageId, IsEncodingProblem, IsTruncated, Language, Location, MailboxIds, Name, PartId, Subject, ThreadId, Type}
+import org.apache.james.jmap.model._
+import org.apache.james.mailbox.model.{Cid, MailboxId, MessageId}
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+import scala.language.implicitConversions
+
+object EmailGetSerializer {
+ private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
+
+ private implicit val fetchTextBodyValuesReads: Reads[FetchTextBodyValues] = Json.valueReads[FetchTextBodyValues]
+ private implicit val fetchHTMLBodyValuesReads: Reads[FetchHTMLBodyValues] = Json.valueReads[FetchHTMLBodyValues]
+ private implicit val fetchAllBodyValuesReads: Reads[FetchAllBodyValues] = Json.valueReads[FetchAllBodyValues]
+ private implicit val blobIdWrites: Writes[BlobId] = Json.valueWrites[BlobId]
+ private implicit val cidWrites: Writes[Cid] = cid => JsString(cid.getValue)
+ private implicit val nameWrites: Writes[Name] = Json.valueWrites[Name]
+ private implicit val threadIdWrites: Writes[ThreadId] = Json.valueWrites[ThreadId]
+ private implicit val mailboxIdsWrites: Writes[MailboxIds] = ids => JsObject(ids.value.map(id => (id.serialize(), JsBoolean(true))))
+ private implicit val typeWrites: Writes[Type] = Json.valueWrites[Type]
+ private implicit val charsetWrites: Writes[Charset] = Json.valueWrites[Charset]
+ private implicit val dispositionWrites: Writes[Disposition] = Json.valueWrites[Disposition]
+ private implicit val languageWrites: Writes[Language] = Json.valueWrites[Language]
+ private implicit val locationWrites: Writes[Location] = Json.valueWrites[Location]
+ private implicit val emailerNameWrites: Writes[EmailerName] = Json.valueWrites[EmailerName]
+ private implicit val addressWrites: Writes[Address] = Json.valueWrites[Address]
+ private implicit val emailAddressWrites: Writes[EmailAddress] = Json.writes[EmailAddress]
+ private implicit val headerMessageIdWrites: Writes[HeaderMessageId] = Json.valueWrites[HeaderMessageId]
+ private implicit val isEncodingProblemWrites: Writes[IsEncodingProblem] = Json.valueWrites[IsEncodingProblem]
+ private implicit val isTruncatedWrites: Writes[IsTruncated] = Json.valueWrites[IsTruncated]
+ private implicit val partIdWrites: Writes[PartId] = partId => JsString(partId.serialize)
+ private implicit val previewWrites: Writes[Preview] = preview => JsString(preview.getValue)
+ private implicit val hasAttachmentWrites: Writes[HasAttachment] = Json.valueWrites[HasAttachment]
+ private implicit val headerNameWrites: Writes[EmailHeaderName] = Json.valueWrites[EmailHeaderName]
+ private implicit val headerValueWrites: Writes[EmailHeaderValue] = Json.valueWrites[EmailHeaderValue]
+ private implicit val headersWrites: Writes[EmailHeader] = Json.writes[EmailHeader]
+ private implicit val bodyValueWrites: Writes[EmailBodyValue] = Json.writes[EmailBodyValue]
+ private implicit val emailIdsReads: Reads[EmailIds] = Json.valueReads[EmailIds]
+ private implicit val emailGetRequestReads: Reads[EmailGetRequest] = Json.reads[EmailGetRequest]
+ private implicit val subjectWrites: Writes[Subject] = Json.valueWrites[Subject]
+ private implicit val emailNotFoundWrites: Writes[EmailNotFound] = Json.valueWrites[EmailNotFound]
+ private implicit val messageIdWrites: Writes[MessageId] = id => JsString(id.serialize())
+
+ private implicit def bodyValueMapWrites(implicit bodyValueWriter: Writes[EmailBodyValue]): Writes[Map[PartId, EmailBodyValue]] =
+ mapWrites[PartId, EmailBodyValue](_.value.toString(), bodyValueWriter)
+ private def bodyPartWritesWithPropertyFilter(properties: Properties): Writes[EmailBodyPart] =
+ new Writes[EmailBodyPart] {
+ def removeJsNull(obj: JsObject): JsObject =
+ JsObject(obj.fields.filter({
+ case (_, JsNull) => false
+ case _ => true
+ }))
+ def writes(part: EmailBodyPart): JsValue = properties.filter(
+ removeJsNull(
+ Json.obj("partId" -> Json.toJson(part.partId),
+ "blobId" -> Json.toJson(part.blobId),
+ "headers" -> Json.toJson(part.headers),
+ "size" -> Json.toJson(part.size),
+ "name" -> Json.toJson(part.name),
+ "type" -> Json.toJson(part.`type`),
+ "charset" -> Json.toJson(part.charset),
+ "disposition" -> Json.toJson(part.disposition),
+ "cid" -> Json.toJson(part.cid),
+ "language" -> Json.toJson(part.language),
+ "location" -> Json.toJson(part.location),
+ "subParts" -> part.subParts.map(list => list.map(writes)))))
+ }
+
+ private def emailWritesWithPropertyFilter(properties: Properties)(implicit partsWrites: Writes[EmailBodyPart]): Writes[Email] = {
+ implicit val emailMetadataWrites: Writes[EmailMetadata] = Json.writes[EmailMetadata]
+ implicit val emailHeadersWrites: Writes[EmailHeaders] = Json.writes[EmailHeaders]
+ implicit val emailBodyWrites: Writes[EmailBody] = Json.writes[EmailBody]
+
+ val emailWrites: OWrites[Email] = (JsPath.write[EmailMetadata] and
+ JsPath.write[EmailHeaders] and
+ JsPath.write[EmailBody]) (unlift(Email.unapply))
+
+ emailWrites.transform(obj => properties.filter(obj))
+ }
+ private implicit def emailGetResponseWrites(implicit emailWrites: Writes[Email]): Writes[EmailGetResponse] = Json.writes[EmailGetResponse]
+
+ def serialize(emailGetResponse: EmailGetResponse, properties: Properties, bodyProperties: Properties): JsValue =
+ Json.toJson(emailGetResponse)(emailGetResponseWrites(emailWritesWithPropertyFilter(properties)(bodyPartWritesWithPropertyFilter(bodyProperties))))
+
+ def deserializeEmailGetRequest(input: JsValue): JsResult[EmailGetRequest] = Json.fromJson[EmailGetRequest](input)
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
new file mode 100644
index 0000000..01e8afe
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
@@ -0,0 +1,51 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.jmap.json
+
+import javax.inject.Inject
+import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState}
+import org.apache.james.jmap.model._
+import org.apache.james.mailbox.model.{MailboxId, MessageId}
+import play.api.libs.json._
+
+import scala.language.implicitConversions
+import scala.util.Try
+
+class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
+ private implicit val accountIdWrites: Format[AccountId] = Json.valueFormat[AccountId]
+
+ private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
+ private implicit val mailboxIdReads: Reads[MailboxId] = {
+ case JsString(serializedMailboxId) => Try(JsSuccess(mailboxIdFactory.fromString(serializedMailboxId))).getOrElse(JsError())
+ case _ => JsError()
+ }
+
+ private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
+ private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
+ private implicit val positionFormat: Format[Position] = Json.valueFormat[Position]
+ private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
+ private implicit val messageIdWrites: Writes[MessageId] = id => JsString(id.serialize())
+
+ private implicit def emailQueryResponseWrites: OWrites[EmailQueryResponse] = Json.writes[EmailQueryResponse]
+
+ def serialize(emailQueryResponse: EmailQueryResponse): JsObject = Json.toJsObject(emailQueryResponse)
+
+ def deserializeEmailQueryRequest(input: JsValue): JsResult[EmailQueryRequest] = Json.fromJson[EmailQueryRequest](input)
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxSerializer.scala
new file mode 100644
index 0000000..d8e4467
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxSerializer.scala
@@ -0,0 +1,185 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.jmap.json
+
+import eu.timepit.refined._
+import eu.timepit.refined.collection.NonEmpty
+import javax.inject.Inject
+import org.apache.james.core.{Domain, Username}
+import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, UnparsedMailboxId, UnparsedMailboxIdConstraint}
+import org.apache.james.jmap.mail.{DelegatedNamespace, Ids, IsSubscribed, Mailbox, MailboxCreationRequest, MailboxCreationResponse, MailboxGetRequest, MailboxGetResponse, MailboxNamespace, MailboxPatchObject, MailboxRights, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, MayAddItems, MayCreateChild, MayDelete, MayReadItems, MayRemoveItems, MayRename, MaySetKeywords, MaySetSeen, MaySubmit, NotFound, PersonalNamespace, Quota, QuotaId, QuotaRoot, Quotas, RemoveEmailsOnDestroy, [...]
+import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
+import org.apache.james.jmap.model._
+import org.apache.james.mailbox.Role
+import org.apache.james.mailbox.model.MailboxACL.{Right => JavaRight}
+import org.apache.james.mailbox.model.{MailboxACL, MailboxId}
+import play.api.libs.json._
+
+import scala.language.implicitConversions
+import scala.util.Try
+
+class MailboxSerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
+ private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
+ private implicit val mailboxIdReads: Reads[MailboxId] = {
+ case JsString(serializedMailboxId) => Try(JsSuccess(mailboxIdFactory.fromString(serializedMailboxId))).getOrElse(JsError())
+ case _ => JsError()
+ }
+
+ private implicit val roleWrites: Writes[Role] = Writes(role => JsString(role.serialize))
+ private implicit val sortOrderWrites: Writes[SortOrder] = Json.valueWrites[SortOrder]
+ private implicit val totalEmailsWrites: Writes[TotalEmails] = Json.valueWrites[TotalEmails]
+ private implicit val unreadEmailsWrites: Writes[UnreadEmails] = Json.valueWrites[UnreadEmails]
+ private implicit val totalThreadsWrites: Writes[TotalThreads] = Json.valueWrites[TotalThreads]
+ private implicit val unreadThreadsWrites: Writes[UnreadThreads] = Json.valueWrites[UnreadThreads]
+ private implicit val isSubscribedWrites: Format[IsSubscribed] = Json.valueFormat[IsSubscribed]
+
+ private implicit val mayReadItemsWrites: Writes[MayReadItems] = Json.valueWrites[MayReadItems]
+ private implicit val mayAddItemsWrites: Writes[MayAddItems] = Json.valueWrites[MayAddItems]
+ private implicit val mayRemoveItemsWrites: Writes[MayRemoveItems] = Json.valueWrites[MayRemoveItems]
+ private implicit val maySetSeenWrites: Writes[MaySetSeen] = Json.valueWrites[MaySetSeen]
+ private implicit val maySetKeywordsWrites: Writes[MaySetKeywords] = Json.valueWrites[MaySetKeywords]
+ private implicit val mayCreateChildWrites: Writes[MayCreateChild] = Json.valueWrites[MayCreateChild]
+ private implicit val mayRenameWrites: Writes[MayRename] = Json.valueWrites[MayRename]
+ private implicit val mayDeleteWrites: Writes[MayDelete] = Json.valueWrites[MayDelete]
+ private implicit val maySubmitWrites: Writes[MaySubmit] = Json.valueWrites[MaySubmit]
+ private implicit val mailboxRightsWrites: Writes[MailboxRights] = Json.writes[MailboxRights]
+
+ private implicit val mailboxNamespaceWrites: Writes[MailboxNamespace] = {
+ case _: PersonalNamespace => JsString("Personal")
+ case delegated: DelegatedNamespace => JsString(s"Delegated[${delegated.owner.asString}]")
+ }
+
+ private implicit val mailboxACLWrites: Writes[MailboxACL.Right] = right => JsString(right.asCharacter.toString)
+
+ private implicit val rightWrites: Writes[Right] = Json.valueWrites[Right]
+ private implicit val rightRead: Reads[Right] = {
+ case jsString: JsString =>
+ if (jsString.value.length != 1) {
+ JsError("Rights must have size 1")
+ } else {
+ Right.forChar(jsString.value.charAt(0))
+ .map(right => JsSuccess(right))
+ .getOrElse(JsError(s"Unknown right '${jsString.value}'"))
+ }
+ case _ => JsError("Right must be represented as a String")
+ }
+ private implicit val mailboxJavaRightReads: Reads[JavaRight] = value => rightRead.reads(value).map(right => right.toMailboxRight)
+ private implicit val mailboxRfc4314RightsReads: Reads[Rfc4314Rights] = Json.valueReads[Rfc4314Rights]
+ private implicit val rightsWrites: Writes[Rights] = Json.valueWrites[Rights]
+
+ private implicit val mapRightsReads: Reads[Map[Username, Seq[Right]]] = _.validate[Map[String, Seq[Right]]]
+ .map(rawMap =>
+ rawMap.map(entry => (Username.of(entry._1), entry._2)))
+ private implicit val rightsReads: Reads[Rights] = json => mapRightsReads.reads(json).map(rawMap => Rights(rawMap))
+
+ private implicit def rightsMapWrites(implicit rightWriter: Writes[Seq[Right]]): Writes[Map[Username, Seq[Right]]] =
+ mapWrites[Username, Seq[Right]](_.asString(), rightWriter)
+
+ private implicit val domainWrites: Writes[Domain] = domain => JsString(domain.asString)
+ private implicit val quotaRootWrites: Writes[QuotaRoot] = Json.writes[QuotaRoot]
+ private implicit val quotaIdWrites: Writes[QuotaId] = Json.valueWrites[QuotaId]
+
+ private implicit val quotaValueWrites: Writes[Value] = Json.writes[Value]
+ private implicit val quotaWrites: Writes[Quota] = Json.valueWrites[Quota]
+
+ private implicit def quotaMapWrites(implicit valueWriter: Writes[Value]): Writes[Map[Quotas.Type, Value]] =
+ mapWrites[Quotas.Type, Value](_.toString, valueWriter)
+
+ private implicit val quotasWrites: Writes[Quotas] = Json.valueWrites[Quotas]
+
+ private implicit def quotasMapWrites(implicit quotaWriter: Writes[Quota]): Writes[Map[QuotaId, Quota]] =
+ mapWrites[QuotaId, Quota](_.getName, quotaWriter)
+
+ implicit def mailboxWrites(properties: Properties): Writes[Mailbox] = Json.writes[Mailbox]
+ .transform(properties.filter(_))
+
+ implicit def mailboxCreationResponseWrites(properties: Properties): Writes[MailboxCreationResponse] =
+ Json.writes[MailboxCreationResponse]
+ .transform(properties.filter(_))
+
+ private implicit val idsRead: Reads[Ids] = Json.valueReads[Ids]
+
+ private implicit val mailboxGetRequest: Reads[MailboxGetRequest] = Json.reads[MailboxGetRequest]
+
+ private implicit val mailboxRemoveEmailsOnDestroy: Reads[RemoveEmailsOnDestroy] = Json.valueFormat[RemoveEmailsOnDestroy]
+ implicit val mailboxCreationRequest: Reads[MailboxCreationRequest] = Json.reads[MailboxCreationRequest]
+ private implicit val mailboxPatchObject: Reads[MailboxPatchObject] = Json.valueReads[MailboxPatchObject]
+
+ private implicit val mapPatchObjectByMailboxIdReads: Reads[Map[UnparsedMailboxId, MailboxPatchObject]] =
+ readMapEntry[UnparsedMailboxId, MailboxPatchObject](s => refineV[UnparsedMailboxIdConstraint](s),
+ o => MailboxPatchObject(o.value.toMap))
+
+ private implicit val mapCreationRequestByMailBoxCreationId: Reads[Map[MailboxCreationId, JsObject]] =
+ readMapEntry[MailboxCreationId, JsObject](s => refineV[NonEmpty](s), o => o)
+
+ private implicit val mailboxSetRequestReads: Reads[MailboxSetRequest] = Json.reads[MailboxSetRequest]
+
+ private implicit def notFoundWrites(implicit mailboxIdWrites: Writes[UnparsedMailboxId]): Writes[NotFound] =
+ notFound => JsArray(notFound.value.toList.map(mailboxIdWrites.writes))
+
+ private implicit def mailboxGetResponseWrites(implicit mailboxWrites: Writes[Mailbox]): Writes[MailboxGetResponse] = Json.writes[MailboxGetResponse]
+
+ private implicit def mailboxSetResponseWrites(implicit mailboxCreationResponseWrites: Writes[MailboxCreationResponse]): Writes[MailboxSetResponse] = Json.writes[MailboxSetResponse]
+
+ private implicit val mailboxSetUpdateResponseWrites: Writes[MailboxUpdateResponse] = Json.valueWrites[MailboxUpdateResponse]
+
+ private implicit def mailboxMapSetErrorForCreationWrites: Writes[Map[MailboxCreationId, SetError]] =
+ mapWrites[MailboxCreationId, SetError](_.value, setErrorWrites)
+ private implicit def mailboxMapSetErrorWrites: Writes[Map[MailboxId, SetError]] =
+ mapWrites[MailboxId, SetError](_.serialize(), setErrorWrites)
+ private implicit def mailboxMapSetErrorWritesByClientId: Writes[Map[ClientId, SetError]] =
+ mapWrites[ClientId, SetError](_.value.value, setErrorWrites)
+ private implicit def mailboxMapCreationResponseWrites(implicit mailboxSetCreationResponseWrites: Writes[MailboxCreationResponse]): Writes[Map[MailboxCreationId, MailboxCreationResponse]] =
+ mapWrites[MailboxCreationId, MailboxCreationResponse](_.value, mailboxSetCreationResponseWrites)
+ private implicit def mailboxMapUpdateResponseWrites: Writes[Map[MailboxId, MailboxUpdateResponse]] =
+ mapWrites[MailboxId, MailboxUpdateResponse](_.serialize(), mailboxSetUpdateResponseWrites)
+
+ private def mailboxWritesWithFilteredProperties(properties: Properties, capabilities: Set[CapabilityIdentifier]): Writes[Mailbox] = {
+ mailboxWrites(Mailbox.propertiesFiltered(properties, capabilities))
+ }
+
+ private def mailboxCreationResponseWritesWithFilteredProperties(capabilities: Set[CapabilityIdentifier]): Writes[MailboxCreationResponse] = {
+ mailboxCreationResponseWrites(MailboxCreationResponse.propertiesFiltered(capabilities))
+ }
+
+ def serialize(mailbox: Mailbox)(implicit mailboxWrites: Writes[Mailbox]): JsValue = Json.toJson(mailbox)
+
+ def serialize(mailboxGetResponse: MailboxGetResponse)(implicit mailboxWrites: Writes[Mailbox]): JsValue = Json.toJson(mailboxGetResponse)
+
+ def serialize(mailboxGetResponse: MailboxGetResponse, properties: Properties, capabilities: Set[CapabilityIdentifier]): JsValue =
+ serialize(mailboxGetResponse)(mailboxWritesWithFilteredProperties(properties, capabilities))
+
+ def serialize(mailboxSetResponse: MailboxSetResponse)
+ (implicit mailboxCreationResponseWrites: Writes[MailboxCreationResponse]): JsValue =
+ Json.toJson(mailboxSetResponse)(mailboxSetResponseWrites(mailboxCreationResponseWrites))
+
+ def serialize(mailboxSetResponse: MailboxSetResponse, capabilities: Set[CapabilityIdentifier]): JsValue =
+ serialize(mailboxSetResponse)(mailboxCreationResponseWritesWithFilteredProperties(capabilities))
+
+ def deserializeMailboxGetRequest(input: String): JsResult[MailboxGetRequest] = Json.parse(input).validate[MailboxGetRequest]
+
+ def deserializeMailboxGetRequest(input: JsValue): JsResult[MailboxGetRequest] = Json.fromJson[MailboxGetRequest](input)
+
+ def deserializeMailboxSetRequest(input: JsValue): JsResult[MailboxSetRequest] = Json.fromJson[MailboxSetRequest](input)
+
+ def deserializeRights(input: JsValue): JsResult[Rights] = Json.fromJson[Rights](input)
+
+ def deserializeRfc4314Rights(input: JsValue): JsResult[Rfc4314Rights] = Json.fromJson[Rfc4314Rights](input)
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
new file mode 100644
index 0000000..9ba12a7
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
@@ -0,0 +1,185 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.jmap.json
+
+import java.io.InputStream
+import java.net.URL
+
+import org.apache.james.core.Username
+import org.apache.james.jmap.model
+import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
+import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
+import org.apache.james.jmap.model.SetError.SetErrorDescription
+import org.apache.james.jmap.model.{Account, Invocation, Session, _}
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+
+import scala.collection.{Seq => LegacySeq}
+import scala.language.implicitConversions
+
+object ResponseSerializer {
+ // CreateIds
+ private implicit val clientIdFormat: Format[ClientId] = Json.valueFormat[ClientId]
+ private implicit val serverIdFormat: Format[ServerId] = Json.valueFormat[ServerId]
+ private implicit val createdIdsFormat: Format[CreatedIds] = Json.valueFormat[CreatedIds]
+
+ private def mapWrites[K, V](keyWriter: K => String, valueWriter: Writes[V]): Writes[Map[K, V]] =
+ (ids: Map[K, V]) => {
+ ids.foldLeft(JsObject.empty)((jsObject, kv) => {
+ val (key: K, value: V) = kv
+ jsObject.+(keyWriter.apply(key), valueWriter.writes(value))
+ })
+ }
+
+ private implicit def createdIdsIdWrites(implicit serverIdWriter: Writes[ServerId]): Writes[Map[ClientId, ServerId]] =
+ mapWrites[ClientId, ServerId](_.value.value, serverIdWriter)
+
+ private implicit def createdIdsIdRead(implicit serverIdReader: Reads[ServerId]): Reads[Map[ClientId, ServerId]] =
+ Reads.mapReads[ClientId, ServerId] {
+ clientIdString => Json.fromJson[ClientId](JsString(clientIdString))
+ }
+
+ // Invocation
+ private implicit val methodNameFormat: Format[MethodName] = Json.valueFormat[MethodName]
+ private implicit val argumentFormat: Format[Arguments] = Json.valueFormat[Arguments]
+ private implicit val methodCallIdFormat: Format[MethodCallId] = Json.valueFormat[MethodCallId]
+ private implicit val invocationRead: Reads[Invocation] = (
+ (JsPath \ model.Invocation.METHOD_NAME).read[MethodName] and
+ (JsPath \ model.Invocation.ARGUMENTS).read[Arguments] and
+ (JsPath \ model.Invocation.METHOD_CALL).read[MethodCallId]
+ ) (model.Invocation.apply _)
+
+ private implicit val invocationWrite: Writes[Invocation] = (invocation: Invocation) =>
+ Json.arr(invocation.methodName, invocation.arguments, invocation.methodCallId)
+
+ // RequestObject
+ private implicit val requestObjectRead: Format[RequestObject] = Json.format[RequestObject]
+
+ // ResponseObject
+ private implicit val responseObjectFormat: Format[ResponseObject] = Json.format[ResponseObject]
+
+ private implicit val maxSizeUploadWrites: Writes[MaxSizeUpload] = Json.valueWrites[MaxSizeUpload]
+ private implicit val maxConcurrentUploadWrites: Writes[MaxConcurrentUpload] = Json.valueWrites[MaxConcurrentUpload]
+ private implicit val maxSizeRequestWrites: Writes[MaxSizeRequest] = Json.valueWrites[MaxSizeRequest]
+ private implicit val maxConcurrentRequestsWrites: Writes[MaxConcurrentRequests] = Json.valueWrites[MaxConcurrentRequests]
+ private implicit val maxCallsInRequestWrites: Writes[MaxCallsInRequest] = Json.valueWrites[MaxCallsInRequest]
+ private implicit val maxObjectsInGetWrites: Writes[MaxObjectsInGet] = Json.valueWrites[MaxObjectsInGet]
+ private implicit val maxObjectsInSetWrites: Writes[MaxObjectsInSet] = Json.valueWrites[MaxObjectsInSet]
+
+ private implicit val maxMailboxesPerEmailWrites: Writes[MaxMailboxesPerEmail] = Json.valueWrites[MaxMailboxesPerEmail]
+ private implicit val maxMailboxDepthWrites: Writes[MaxMailboxDepth] = Json.valueWrites[MaxMailboxDepth]
+ private implicit val maxSizeMailboxNameWrites: Writes[MaxSizeMailboxName] = Json.valueWrites[MaxSizeMailboxName]
+ private implicit val maxSizeAttachmentsPerEmailWrites: Writes[MaxSizeAttachmentsPerEmail] = Json.valueWrites[MaxSizeAttachmentsPerEmail]
+ private implicit val mayCreateTopLevelMailboxWrites: Writes[MayCreateTopLevelMailbox] = Json.valueWrites[MayCreateTopLevelMailbox]
+
+ private implicit val usernameWrites: Writes[Username] = username => JsString(username.asString)
+ private implicit val urlWrites: Writes[URL] = url => JsString(url.toString)
+ private implicit val coreCapabilityWrites: Writes[CoreCapabilityProperties] = Json.writes[CoreCapabilityProperties]
+ private implicit val mailCapabilityWrites: Writes[MailCapabilityProperties] = Json.writes[MailCapabilityProperties]
+ private implicit val quotaCapabilityWrites: Writes[QuotaCapabilityProperties] = OWrites[QuotaCapabilityProperties](_ => Json.obj())
+ private implicit val sharesCapabilityWrites: Writes[SharesCapabilityProperties] = OWrites[SharesCapabilityProperties](_ => Json.obj())
+ private implicit val vacationResponseCapabilityWrites: Writes[VacationResponseCapabilityProperties] = OWrites[VacationResponseCapabilityProperties](_ => Json.obj())
+
+ private implicit def setCapabilityWrites(implicit corePropertiesWriter: Writes[CoreCapabilityProperties],
+ mailCapabilityWrites: Writes[MailCapabilityProperties],
+ quotaCapabilityWrites: Writes[QuotaCapabilityProperties],
+ sharesCapabilityWrites: Writes[SharesCapabilityProperties],
+ vacationResponseCapabilityWrites: Writes[VacationResponseCapabilityProperties]): Writes[Set[_ <: Capability]] =
+ (set: Set[_ <: Capability]) => {
+ set.foldLeft(JsObject.empty)((jsObject, capability) => {
+ capability match {
+ case capability: CoreCapability =>
+ jsObject.+(capability.identifier.value, corePropertiesWriter.writes(capability.properties))
+ case capability: MailCapability =>
+ jsObject.+(capability.identifier.value, mailCapabilityWrites.writes(capability.properties))
+ case capability: QuotaCapability =>
+ jsObject.+(capability.identifier.value, quotaCapabilityWrites.writes(capability.properties))
+ case capability: SharesCapability =>
+ jsObject.+(capability.identifier.value, sharesCapabilityWrites.writes(capability.properties))
+ case capability: VacationResponseCapability =>
+ jsObject.+(capability.identifier.value, vacationResponseCapabilityWrites.writes(capability.properties))
+ case _ => jsObject
+ }
+ })
+ }
+
+ private implicit val capabilitiesWrites: Writes[Capabilities] = capabilities => setCapabilityWrites.writes(capabilities.toSet)
+
+ private implicit def identifierMapWrite[Any](implicit idWriter: Writes[AccountId]): Writes[Map[CapabilityIdentifier, AccountId]] =
+ mapWrites[CapabilityIdentifier, AccountId](_.value, idWriter)
+
+ private implicit val isPersonalFormat: Format[IsPersonal] = Json.valueFormat[IsPersonal]
+ private implicit val isReadOnlyFormat: Format[IsReadOnly] = Json.valueFormat[IsReadOnly]
+ private implicit val accountWrites: Writes[Account] = (
+ (JsPath \ Account.NAME).write[Username] and
+ (JsPath \ Account.IS_PERSONAL).write[IsPersonal] and
+ (JsPath \ Account.IS_READ_ONLY).write[IsReadOnly] and
+ (JsPath \ Account.ACCOUNT_CAPABILITIES).write[Set[_ <: Capability]]
+ ) (unlift(Account.unapplyIgnoreAccountId))
+
+ private implicit def accountListWrites(implicit accountWrites: Writes[Account]): Writes[List[Account]] =
+ (list: List[Account]) => JsObject(list.map(account => (account.accountId.id.value, accountWrites.writes(account))))
+
+ private implicit val sessionWrites: Writes[Session] = Json.writes[Session]
+
+
+ private implicit val propertiesWrites: Writes[Properties] = Json.valueWrites[Properties]
+
+ private implicit val setErrorDescriptionWrites: Writes[SetErrorDescription] = Json.valueWrites[SetErrorDescription]
+
+ private implicit val mailboxSetErrorWrites: Writes[SetError] = Json.writes[SetError]
+
+ private implicit val jsonValidationErrorWrites: Writes[JsonValidationError] = error => JsString(error.message)
+
+ private implicit def jsonValidationErrorsWrites(implicit jsonValidationErrorWrites: Writes[JsonValidationError]): Writes[LegacySeq[JsonValidationError]] =
+ (errors: LegacySeq[JsonValidationError]) => {
+ JsArray(errors.map(error => jsonValidationErrorWrites.writes(error)).toArray[JsValue])
+ }
+
+ private implicit def errorsWrites(implicit jsonValidationErrorsWrites: Writes[LegacySeq[JsonValidationError]]): Writes[LegacySeq[(JsPath, LegacySeq[JsonValidationError])]] =
+ (errors: LegacySeq[(JsPath, LegacySeq[JsonValidationError])]) => {
+ errors.foldLeft(JsArray.empty)((jsArray, jsError) => {
+ val (path: JsPath, list: LegacySeq[JsonValidationError]) = jsError
+ jsArray:+ JsObject(Seq(
+ "path" -> JsString(path.toJsonString),
+ "messages" -> jsonValidationErrorsWrites.writes(list)))
+ })
+ }
+
+ private implicit def jsErrorWrites: Writes[JsError] = Json.writes[JsError]
+
+ private implicit val problemDetailsWrites: Writes[ProblemDetails] = Json.writes[ProblemDetails]
+
+ def serialize(session: Session): JsValue = Json.toJson(session)
+
+ def serialize(requestObject: RequestObject): JsValue = Json.toJson(requestObject)
+
+ def serialize(responseObject: ResponseObject): JsValue = Json.toJson(responseObject)
+
+ def serialize(problemDetails: ProblemDetails): JsValue = Json.toJson(problemDetails)
+
+ def serialize(errors: JsError): JsValue = Json.toJson(errors)
+
+ def deserializeRequestObject(input: String): JsResult[RequestObject] = Json.parse(input).validate[RequestObject]
+
+ def deserializeRequestObject(input: InputStream): JsResult[RequestObject] = Json.parse(input).validate[RequestObject]
+
+ def deserializeResponseObject(input: String): JsResult[ResponseObject] = Json.parse(input).validate[ResponseObject]
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
deleted file mode 100644
index 91530ba..0000000
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
+++ /dev/null
@@ -1,520 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one *
- * or more contributor license agreements. See the NOTICE file *
- * distributed with this work for additional information *
- * regarding copyright ownership. The ASF licenses this file *
- * to you under the Apache License, Version 2.0 (the *
- * "License"); you may not use this file except in compliance *
- * with the License. You may obtain a copy of the License at *
- * *
- * http://www.apache.org/licenses/LICENSE-2.0 *
- * *
- * Unless required by applicable law or agreed to in writing, *
- * software distributed under the License is distributed on an *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
- * KIND, either express or implied. See the License for the *
- * specific language governing permissions and limitations *
- * under the License. *
- ****************************************************************/
-
-package org.apache.james.jmap.json
-
-import java.io.InputStream
-import java.net.URL
-import java.time.ZonedDateTime
-import java.time.format.DateTimeFormatter
-
-import eu.timepit.refined._
-import eu.timepit.refined.collection.NonEmpty
-import javax.inject.Inject
-import org.apache.james.core.{Domain, Username}
-import org.apache.james.jmap.api.model.Preview
-import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, UnparsedMailboxId, UnparsedMailboxIdConstraint}
-import org.apache.james.jmap.mail.VacationResponse.{UnparsedVacationResponseId, VACATION_RESPONSE_ID}
-import org.apache.james.jmap.mail.{Address, BlobId, Charset, DelegatedNamespace, Disposition, Email, EmailAddress, EmailBody, EmailBodyPart, EmailBodyValue, EmailGetRequest, EmailGetResponse, EmailHeader, EmailHeaderName, EmailHeaderValue, EmailHeaders, EmailIds, EmailMetadata, EmailNotFound, EmailQueryRequest, EmailQueryResponse, EmailerName, FetchAllBodyValues, FetchHTMLBodyValues, FetchTextBodyValues, FromDate, HasAttachment, HeaderMessageId, HtmlBody, Ids, IsEnabled, IsEncodingProble [...]
-import org.apache.james.jmap.model
-import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
-import org.apache.james.jmap.model.SetError.SetErrorDescription
-import org.apache.james.jmap.model.{Account, Invocation, Session, _}
-import org.apache.james.jmap.routes.{BackReference, JsonPath}
-import org.apache.james.jmap.vacation.{VacationResponsePatchObject, VacationResponseSetError, VacationResponseSetRequest, VacationResponseSetResponse, VacationResponseUpdateResponse}
-import org.apache.james.mailbox.Role
-import org.apache.james.mailbox.model.MailboxACL.{Right => JavaRight}
-import org.apache.james.mailbox.model.{Cid, MailboxACL, MailboxId, MessageId}
-import play.api.libs.functional.syntax._
-import play.api.libs.json._
-
-import scala.collection.{Seq => LegacySeq}
-import scala.language.implicitConversions
-import scala.util.{Failure, Success, Try}
-
-class Serializer @Inject() (mailboxIdFactory: MailboxId.Factory) {
- // CreateIds
- private implicit val clientIdFormat: Format[ClientId] = Json.valueFormat[ClientId]
- private implicit val serverIdFormat: Format[ServerId] = Json.valueFormat[ServerId]
- private implicit val createdIdsFormat: Format[CreatedIds] = Json.valueFormat[CreatedIds]
-
- private def mapWrites[K, V](keyWriter: K => String, valueWriter: Writes[V]): Writes[Map[K, V]] =
- (ids: Map[K, V]) => {
- ids.foldLeft(JsObject.empty)((jsObject, kv) => {
- val (key: K, value: V) = kv
- jsObject.+(keyWriter.apply(key), valueWriter.writes(value))
- })
- }
-
- private implicit def createdIdsIdWrites(implicit serverIdWriter: Writes[ServerId]): Writes[Map[ClientId, ServerId]] =
- mapWrites[ClientId, ServerId](_.value.value, serverIdWriter)
-
- private implicit def createdIdsIdRead(implicit serverIdReader: Reads[ServerId]): Reads[Map[ClientId, ServerId]] =
- Reads.mapReads[ClientId, ServerId] {
- clientIdString => Json.fromJson[ClientId](JsString(clientIdString))
- }
-
- // Invocation
- private implicit val methodNameFormat: Format[MethodName] = Json.valueFormat[MethodName]
- private implicit val argumentFormat: Format[Arguments] = Json.valueFormat[Arguments]
- private implicit val methodCallIdFormat: Format[MethodCallId] = Json.valueFormat[MethodCallId]
- private implicit val invocationRead: Reads[Invocation] = (
- (JsPath \ model.Invocation.METHOD_NAME).read[MethodName] and
- (JsPath \ model.Invocation.ARGUMENTS).read[Arguments] and
- (JsPath \ model.Invocation.METHOD_CALL).read[MethodCallId]
- ) (model.Invocation.apply _)
-
- private implicit val invocationWrite: Writes[Invocation] = (invocation: Invocation) =>
- Json.arr(invocation.methodName, invocation.arguments, invocation.methodCallId)
-
- // RequestObject
- private implicit val requestObjectRead: Format[RequestObject] = Json.format[RequestObject]
-
- // ResponseObject
- private implicit val responseObjectFormat: Format[ResponseObject] = Json.format[ResponseObject]
-
- private implicit val maxSizeUploadWrites: Writes[MaxSizeUpload] = Json.valueWrites[MaxSizeUpload]
- private implicit val maxConcurrentUploadWrites: Writes[MaxConcurrentUpload] = Json.valueWrites[MaxConcurrentUpload]
- private implicit val maxSizeRequestWrites: Writes[MaxSizeRequest] = Json.valueWrites[MaxSizeRequest]
- private implicit val maxConcurrentRequestsWrites: Writes[MaxConcurrentRequests] = Json.valueWrites[MaxConcurrentRequests]
- private implicit val maxCallsInRequestWrites: Writes[MaxCallsInRequest] = Json.valueWrites[MaxCallsInRequest]
- private implicit val maxObjectsInGetWrites: Writes[MaxObjectsInGet] = Json.valueWrites[MaxObjectsInGet]
- private implicit val maxObjectsInSetWrites: Writes[MaxObjectsInSet] = Json.valueWrites[MaxObjectsInSet]
-
- private implicit val maxMailboxesPerEmailWrites: Writes[MaxMailboxesPerEmail] = Json.valueWrites[MaxMailboxesPerEmail]
- private implicit val maxMailboxDepthWrites: Writes[MaxMailboxDepth] = Json.valueWrites[MaxMailboxDepth]
- private implicit val maxSizeMailboxNameWrites: Writes[MaxSizeMailboxName] = Json.valueWrites[MaxSizeMailboxName]
- private implicit val maxSizeAttachmentsPerEmailWrites: Writes[MaxSizeAttachmentsPerEmail] = Json.valueWrites[MaxSizeAttachmentsPerEmail]
- private implicit val mayCreateTopLevelMailboxWrites: Writes[MayCreateTopLevelMailbox] = Json.valueWrites[MayCreateTopLevelMailbox]
-
- private implicit val usernameWrites: Writes[Username] = username => JsString(username.asString)
- private implicit val urlWrites: Writes[URL] = url => JsString(url.toString)
- private implicit val coreCapabilityWrites: Writes[CoreCapabilityProperties] = Json.writes[CoreCapabilityProperties]
- private implicit val mailCapabilityWrites: Writes[MailCapabilityProperties] = Json.writes[MailCapabilityProperties]
- private implicit val quotaCapabilityWrites: Writes[QuotaCapabilityProperties] = OWrites[QuotaCapabilityProperties](_ => Json.obj())
- private implicit val sharesCapabilityWrites: Writes[SharesCapabilityProperties] = OWrites[SharesCapabilityProperties](_ => Json.obj())
- private implicit val vacationResponseCapabilityWrites: Writes[VacationResponseCapabilityProperties] = OWrites[VacationResponseCapabilityProperties](_ => Json.obj())
-
- private implicit def setCapabilityWrites(implicit corePropertiesWriter: Writes[CoreCapabilityProperties],
- mailCapabilityWrites: Writes[MailCapabilityProperties],
- quotaCapabilityWrites: Writes[QuotaCapabilityProperties],
- sharesCapabilityWrites: Writes[SharesCapabilityProperties],
- vacationResponseCapabilityWrites: Writes[VacationResponseCapabilityProperties]): Writes[Set[_ <: Capability]] =
- (set: Set[_ <: Capability]) => {
- set.foldLeft(JsObject.empty)((jsObject, capability) => {
- capability match {
- case capability: CoreCapability =>
- jsObject.+(capability.identifier.value, corePropertiesWriter.writes(capability.properties))
- case capability: MailCapability =>
- jsObject.+(capability.identifier.value, mailCapabilityWrites.writes(capability.properties))
- case capability: QuotaCapability =>
- jsObject.+(capability.identifier.value, quotaCapabilityWrites.writes(capability.properties))
- case capability: SharesCapability =>
- jsObject.+(capability.identifier.value, sharesCapabilityWrites.writes(capability.properties))
- case capability: VacationResponseCapability =>
- jsObject.+(capability.identifier.value, vacationResponseCapabilityWrites.writes(capability.properties))
- case _ => jsObject
- }
- })
- }
-
- private implicit val capabilitiesWrites: Writes[Capabilities] = capabilities => setCapabilityWrites.writes(capabilities.toSet)
-
- private implicit val accountIdWrites: Format[AccountId] = Json.valueFormat[AccountId]
- private implicit def identifierMapWrite[Any](implicit idWriter: Writes[AccountId]): Writes[Map[CapabilityIdentifier, AccountId]] =
- mapWrites[CapabilityIdentifier, AccountId](_.value, idWriter)
-
- private implicit val isPersonalFormat: Format[IsPersonal] = Json.valueFormat[IsPersonal]
- private implicit val isReadOnlyFormat: Format[IsReadOnly] = Json.valueFormat[IsReadOnly]
- private implicit val accountWrites: Writes[Account] = (
- (JsPath \ Account.NAME).write[Username] and
- (JsPath \ Account.IS_PERSONAL).write[IsPersonal] and
- (JsPath \ Account.IS_READ_ONLY).write[IsReadOnly] and
- (JsPath \ Account.ACCOUNT_CAPABILITIES).write[Set[_ <: Capability]]
- ) (unlift(Account.unapplyIgnoreAccountId))
-
- private implicit def accountListWrites(implicit accountWrites: Writes[Account]): Writes[List[Account]] =
- (list: List[Account]) => JsObject(list.map(account => (account.accountId.id.value, accountWrites.writes(account))))
-
- private implicit val sessionWrites: Writes[Session] = Json.writes[Session]
-
- private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
- private implicit val mailboxIdReads: Reads[MailboxId] = {
- case JsString(serializedMailboxId) => Try(JsSuccess(mailboxIdFactory.fromString(serializedMailboxId))).getOrElse(JsError())
- case _ => JsError()
- }
-
- private implicit val roleWrites: Writes[Role] = Writes(role => JsString(role.serialize))
- private implicit val sortOrderWrites: Writes[SortOrder] = Json.valueWrites[SortOrder]
- private implicit val totalEmailsWrites: Writes[TotalEmails] = Json.valueWrites[TotalEmails]
- private implicit val unreadEmailsWrites: Writes[UnreadEmails] = Json.valueWrites[UnreadEmails]
- private implicit val totalThreadsWrites: Writes[TotalThreads] = Json.valueWrites[TotalThreads]
- private implicit val unreadThreadsWrites: Writes[UnreadThreads] = Json.valueWrites[UnreadThreads]
- private implicit val isSubscribedWrites: Format[IsSubscribed] = Json.valueFormat[IsSubscribed]
-
- private implicit val mayReadItemsWrites: Writes[MayReadItems] = Json.valueWrites[MayReadItems]
- private implicit val mayAddItemsWrites: Writes[MayAddItems] = Json.valueWrites[MayAddItems]
- private implicit val mayRemoveItemsWrites: Writes[MayRemoveItems] = Json.valueWrites[MayRemoveItems]
- private implicit val maySetSeenWrites: Writes[MaySetSeen] = Json.valueWrites[MaySetSeen]
- private implicit val maySetKeywordsWrites: Writes[MaySetKeywords] = Json.valueWrites[MaySetKeywords]
- private implicit val mayCreateChildWrites: Writes[MayCreateChild] = Json.valueWrites[MayCreateChild]
- private implicit val mayRenameWrites: Writes[MayRename] = Json.valueWrites[MayRename]
- private implicit val mayDeleteWrites: Writes[MayDelete] = Json.valueWrites[MayDelete]
- private implicit val maySubmitWrites: Writes[MaySubmit] = Json.valueWrites[MaySubmit]
- private implicit val mailboxRightsWrites: Writes[MailboxRights] = Json.writes[MailboxRights]
-
- private implicit val mailboxNamespaceWrites: Writes[MailboxNamespace] = {
- case _: PersonalNamespace => JsString("Personal")
- case delegated: DelegatedNamespace => JsString(s"Delegated[${delegated.owner.asString}]")
- }
-
- private implicit val mailboxACLWrites: Writes[MailboxACL.Right] = right => JsString(right.asCharacter.toString)
-
- private implicit val rightWrites: Writes[Right] = Json.valueWrites[Right]
- private implicit val rightRead: Reads[Right] = {
- case jsString: JsString =>
- if (jsString.value.length != 1) {
- JsError("Rights must have size 1")
- } else {
- Right.forChar(jsString.value.charAt(0))
- .map(right => JsSuccess(right))
- .getOrElse(JsError(s"Unknown right '${jsString.value}'"))
- }
- case _ => JsError("Right must be represented as a String")
- }
- private implicit val mailboxJavaRightReads: Reads[JavaRight] = value => rightRead.reads(value).map(right => right.toMailboxRight)
- private implicit val mailboxRfc4314RightsReads: Reads[Rfc4314Rights] = Json.valueReads[Rfc4314Rights]
- private implicit val rightsWrites: Writes[Rights] = Json.valueWrites[Rights]
-
- private implicit val mapRightsReads: Reads[Map[Username, Seq[Right]]] = _.validate[Map[String, Seq[Right]]]
- .map(rawMap =>
- rawMap.map(entry => (Username.of(entry._1), entry._2)))
- private implicit val rightsReads: Reads[Rights] = json => mapRightsReads.reads(json).map(rawMap => Rights(rawMap))
-
- private implicit def rightsMapWrites(implicit rightWriter: Writes[Seq[Right]]): Writes[Map[Username, Seq[Right]]] =
- mapWrites[Username, Seq[Right]](_.asString(), rightWriter)
-
- private implicit val domainWrites: Writes[Domain] = domain => JsString(domain.asString)
- private implicit val quotaRootWrites: Writes[QuotaRoot] = Json.writes[QuotaRoot]
- private implicit val quotaIdWrites: Writes[QuotaId] = Json.valueWrites[QuotaId]
-
- private implicit val quotaValueWrites: Writes[Value] = Json.writes[Value]
- private implicit val quotaWrites: Writes[Quota] = Json.valueWrites[Quota]
-
- private implicit def quotaMapWrites(implicit valueWriter: Writes[Value]): Writes[Map[Quotas.Type, Value]] =
- mapWrites[Quotas.Type, Value](_.toString, valueWriter)
-
- private implicit val quotasWrites: Writes[Quotas] = Json.valueWrites[Quotas]
-
- private implicit def quotasMapWrites(implicit quotaWriter: Writes[Quota]): Writes[Map[QuotaId, Quota]] =
- mapWrites[QuotaId, Quota](_.getName, quotaWriter)
-
- implicit def mailboxWrites(properties: Properties): Writes[Mailbox] = Json.writes[Mailbox]
- .transform(properties.filter(_))
-
- implicit def mailboxCreationResponseWrites(properties: Properties): Writes[MailboxCreationResponse] =
- Json.writes[MailboxCreationResponse]
- .transform(properties.filter(_))
-
- private implicit val idsRead: Reads[Ids] = Json.valueReads[Ids]
- private implicit val propertiesRead: Reads[Properties] = Json.valueReads[Properties]
- private implicit val jsonPathReadRead: Reads[JsonPath] = {
- case JsString(path) => JsSuccess(JsonPath.parse(path))
- case _ => JsError("JsonPath objects are represented by JsonString")
- }
- private implicit val mailboxGetRequest: Reads[MailboxGetRequest] = Json.reads[MailboxGetRequest]
- private implicit val backReferenceReads: Reads[BackReference] = Json.reads[BackReference]
-
- private implicit val mailboxRemoveEmailsOnDestroy: Reads[RemoveEmailsOnDestroy] = Json.valueFormat[RemoveEmailsOnDestroy]
- implicit val mailboxCreationRequest: Reads[MailboxCreationRequest] = Json.reads[MailboxCreationRequest]
- private implicit val mailboxPatchObject: Reads[MailboxPatchObject] = Json.valueReads[MailboxPatchObject]
-
- private implicit val fetchTextBodyValuesReads: Reads[FetchTextBodyValues] = Json.valueReads[FetchTextBodyValues]
- private implicit val fetchHTMLBodyValuesReads: Reads[FetchHTMLBodyValues] = Json.valueReads[FetchHTMLBodyValues]
- private implicit val fetchAllBodyValuesReads: Reads[FetchAllBodyValues] = Json.valueReads[FetchAllBodyValues]
- private implicit val blobIdWrites: Writes[BlobId] = Json.valueWrites[BlobId]
- private implicit val cidWrites: Writes[Cid] = cid => JsString(cid.getValue)
- private implicit val nameWrites: Writes[Name] = Json.valueWrites[Name]
- private implicit val threadIdWrites: Writes[ThreadId] = Json.valueWrites[ThreadId]
- private implicit val mailboxIdsWrites: Writes[MailboxIds] = ids => JsObject(ids.value.map(id => (id.serialize(), JsBoolean(true))))
- private implicit val typeWrites: Writes[Type] = Json.valueWrites[Type]
- private implicit val charsetWrites: Writes[Charset] = Json.valueWrites[Charset]
- private implicit val dispositionWrites: Writes[Disposition] = Json.valueWrites[Disposition]
- private implicit val languageWrites: Writes[Language] = Json.valueWrites[Language]
- private implicit val locationWrites: Writes[Location] = Json.valueWrites[Location]
- private implicit val emailerNameWrites: Writes[EmailerName] = Json.valueWrites[EmailerName]
- private implicit val addressWrites: Writes[Address] = Json.valueWrites[Address]
- private implicit val emailAddressWrites: Writes[EmailAddress] = Json.writes[EmailAddress]
- private implicit val headerMessageIdWrites: Writes[HeaderMessageId] = Json.valueWrites[HeaderMessageId]
- private implicit val isEncodingProblemWrites: Writes[IsEncodingProblem] = Json.valueWrites[IsEncodingProblem]
- private implicit val isTruncatedWrites: Writes[IsTruncated] = Json.valueWrites[IsTruncated]
- private implicit val partIdWrites: Writes[PartId] = partId => JsString(partId.serialize)
- private implicit val previewWrites: Writes[Preview] = preview => JsString(preview.getValue)
- private implicit val hasAttachmentWrites: Writes[HasAttachment] = Json.valueWrites[HasAttachment]
- private implicit val headerNameWrites: Writes[EmailHeaderName] = Json.valueWrites[EmailHeaderName]
- private implicit val headerValueWrites: Writes[EmailHeaderValue] = Json.valueWrites[EmailHeaderValue]
- private implicit val headersWrites: Writes[EmailHeader] = Json.writes[EmailHeader]
- private implicit val bodyValueWrites: Writes[EmailBodyValue] = Json.writes[EmailBodyValue]
- private implicit val emailIdsReads: Reads[EmailIds] = Json.valueReads[EmailIds]
- private implicit val emailGetRequestReads: Reads[EmailGetRequest] = Json.reads[EmailGetRequest]
- private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
- private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
- private implicit val positionFormat: Format[Position] = Json.valueFormat[Position]
- private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
- private implicit val emailNotFoundWrites: Writes[EmailNotFound] = Json.valueWrites[EmailNotFound]
- private implicit val messageIdWrites: Writes[MessageId] = id => JsString(id.serialize())
-
- private implicit def bodyValueMapWrites(implicit bodyValueWriter: Writes[EmailBodyValue]): Writes[Map[PartId, EmailBodyValue]] =
- mapWrites[PartId, EmailBodyValue](_.value.toString(), bodyValueWriter)
- private def bodyPartWritesWithPropertyFilter(properties: Properties): Writes[EmailBodyPart] =
- new Writes[EmailBodyPart] {
- def removeJsNull(obj: JsObject): JsObject =
- JsObject(obj.fields.filter({
- case (_, JsNull) => false
- case _ => true
- }))
- def writes(part: EmailBodyPart): JsValue = properties.filter(
- removeJsNull(
- Json.obj("partId" -> Json.toJson(part.partId),
- "blobId" -> Json.toJson(part.blobId),
- "headers" -> Json.toJson(part.headers),
- "size" -> Json.toJson(part.size),
- "name" -> Json.toJson(part.name),
- "type" -> Json.toJson(part.`type`),
- "charset" -> Json.toJson(part.charset),
- "disposition" -> Json.toJson(part.disposition),
- "cid" -> Json.toJson(part.cid),
- "language" -> Json.toJson(part.language),
- "location" -> Json.toJson(part.location),
- "subParts" -> part.subParts.map(list => list.map(writes)))))
- }
-
- private def emailWritesWithPropertyFilter(properties: Properties)(implicit partsWrites: Writes[EmailBodyPart]): Writes[Email] = {
- implicit val emailMetadataWrites: Writes[EmailMetadata] = Json.writes[EmailMetadata]
- implicit val emailHeadersWrites: Writes[EmailHeaders] = Json.writes[EmailHeaders]
- implicit val emailBodyWrites: Writes[EmailBody] = Json.writes[EmailBody]
-
- val emailWrites: OWrites[Email] = (JsPath.write[EmailMetadata] and
- JsPath.write[EmailHeaders] and
- JsPath.write[EmailBody]) (unlift(Email.unapply))
-
- emailWrites.transform(obj => properties.filter(obj))
- }
- private implicit def emailGetResponseWrites(implicit emailWrites: Writes[Email]): Writes[EmailGetResponse] = Json.writes[EmailGetResponse]
- private implicit def emailQueryResponseWrites: OWrites[EmailQueryResponse] = Json.writes[EmailQueryResponse]
-
- private def readMapEntry[K, V](keyValidator: String => Either[String, K], valueTransformer: JsObject => V): Reads[Map[K, V]] = _.validate[Map[String, JsObject]]
- .flatMap(mapWithStringKey =>{
- mapWithStringKey
- .foldLeft[Either[JsError, Map[K, V]]](scala.util.Right[JsError, Map[K, V]](Map.empty))((acc: Either[JsError, Map[K, V]], keyValue) => {
- acc match {
- case error@Left(_) => error
- case scala.util.Right(validatedAcc) =>
- val refinedKey: Either[String, K] = keyValidator.apply(keyValue._1)
- refinedKey match {
- case Left(error) => Left(JsError(error))
- case scala.util.Right(unparsedK) => scala.util.Right(validatedAcc + (unparsedK -> valueTransformer.apply(keyValue._2)))
- }
- }
- }) match {
- case Left(jsError) => jsError
- case scala.util.Right(value) => JsSuccess(value)
- }
- })
-
- private implicit val mapPatchObjectByMailboxIdReads: Reads[Map[UnparsedMailboxId, MailboxPatchObject]] =
- readMapEntry[UnparsedMailboxId, MailboxPatchObject](s => refineV[UnparsedMailboxIdConstraint](s),
- o => MailboxPatchObject(o.value.toMap))
-
- private implicit val mapCreationRequestByMailBoxCreationId: Reads[Map[MailboxCreationId, JsObject]] =
- readMapEntry[MailboxCreationId, JsObject](s => refineV[NonEmpty](s), o => o)
-
- private implicit val mailboxSetRequestReads: Reads[MailboxSetRequest] = Json.reads[MailboxSetRequest]
-
- private implicit val UTCDateReads: Reads[UTCDate] = {
- case JsString(value) =>
- Try(UTCDate(ZonedDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME))) match {
- case Success(value) => JsSuccess(value)
- case Failure(e) => JsError(e.getMessage)
- }
- case _ => JsError("Expecting js string to represent UTC Date")
- }
-
- private implicit val isEnabledReads: Reads[IsEnabled] = Json.valueReads[IsEnabled]
- private implicit val vacationResponsePatchObjectReads: Reads[VacationResponsePatchObject] = {
- case jsObject: JsObject => JsSuccess(VacationResponsePatchObject(jsObject))
- case _ => JsError("VacationResponsePatchObject needs to be represented by a JsObject")
- }
- private implicit val vacationResponseSetRequestReads: Reads[VacationResponseSetRequest] = Json.reads[VacationResponseSetRequest]
-
- private implicit def notFoundWrites(implicit mailboxIdWrites: Writes[UnparsedMailboxId]): Writes[NotFound] =
- notFound => JsArray(notFound.value.toList.map(mailboxIdWrites.writes))
-
- private implicit def mailboxGetResponseWrites(implicit mailboxWrites: Writes[Mailbox]): Writes[MailboxGetResponse] = Json.writes[MailboxGetResponse]
-
- private implicit def mailboxSetResponseWrites(implicit mailboxCreationResponseWrites: Writes[MailboxCreationResponse]): Writes[MailboxSetResponse] = Json.writes[MailboxSetResponse]
-
- private implicit val mailboxSetUpdateResponseWrites: Writes[MailboxUpdateResponse] = Json.valueWrites[MailboxUpdateResponse]
-
- private implicit val propertiesWrites: Writes[Properties] = Json.valueWrites[Properties]
-
- private implicit val setErrorDescriptionWrites: Writes[SetErrorDescription] = Json.valueWrites[SetErrorDescription]
-
- private implicit val mailboxSetErrorWrites: Writes[SetError] = Json.writes[SetError]
-
- private implicit val vacationResponseSetUpdateResponseWrites: Writes[VacationResponseUpdateResponse] = Json.valueWrites[VacationResponseUpdateResponse]
-
- private implicit val vacationResponseSetErrorWrites: Writes[VacationResponseSetError] = Json.writes[VacationResponseSetError]
-
- private implicit val vacationResponseSetResponseWrites: Writes[VacationResponseSetResponse] = Json.writes[VacationResponseSetResponse]
-
- private implicit def mailboxMapSetErrorForCreationWrites: Writes[Map[MailboxCreationId, SetError]] =
- mapWrites[MailboxCreationId, SetError](_.value, mailboxSetErrorWrites)
- private implicit def mailboxMapSetErrorWrites: Writes[Map[MailboxId, SetError]] =
- mapWrites[MailboxId, SetError](_.serialize(), mailboxSetErrorWrites)
- private implicit def mailboxMapSetErrorWritesByClientId: Writes[Map[ClientId, SetError]] =
- mapWrites[ClientId, SetError](_.value.value, mailboxSetErrorWrites)
- private implicit def mailboxMapCreationResponseWrites(implicit mailboxSetCreationResponseWrites: Writes[MailboxCreationResponse]): Writes[Map[MailboxCreationId, MailboxCreationResponse]] =
- mapWrites[MailboxCreationId, MailboxCreationResponse](_.value, mailboxSetCreationResponseWrites)
- private implicit def mailboxMapUpdateResponseWrites: Writes[Map[MailboxId, MailboxUpdateResponse]] =
- mapWrites[MailboxId, MailboxUpdateResponse](_.serialize(), mailboxSetUpdateResponseWrites)
-
- private implicit val jsonValidationErrorWrites: Writes[JsonValidationError] = error => JsString(error.message)
-
- private implicit def jsonValidationErrorsWrites(implicit jsonValidationErrorWrites: Writes[JsonValidationError]): Writes[LegacySeq[JsonValidationError]] =
- (errors: LegacySeq[JsonValidationError]) => {
- JsArray(errors.map(error => jsonValidationErrorWrites.writes(error)).toArray[JsValue])
- }
-
- private implicit def errorsWrites(implicit jsonValidationErrorsWrites: Writes[LegacySeq[JsonValidationError]]): Writes[LegacySeq[(JsPath, LegacySeq[JsonValidationError])]] =
- (errors: LegacySeq[(JsPath, LegacySeq[JsonValidationError])]) => {
- errors.foldLeft(JsArray.empty)((jsArray, jsError) => {
- val (path: JsPath, list: LegacySeq[JsonValidationError]) = jsError
- jsArray:+ JsObject(Seq(
- "path" -> JsString(path.toJsonString),
- "messages" -> jsonValidationErrorsWrites.writes(list)))
- })
- }
-
- private def mailboxWritesWithFilteredProperties(properties: Properties, capabilities: Set[CapabilityIdentifier]): Writes[Mailbox] = {
- mailboxWrites(Mailbox.propertiesFiltered(properties, capabilities))
- }
-
- private def mailboxCreationResponseWritesWithFilteredProperties(capabilities: Set[CapabilityIdentifier]): Writes[MailboxCreationResponse] = {
- mailboxCreationResponseWrites(MailboxCreationResponse.propertiesFiltered(capabilities))
- }
-
- private implicit val utcDateWrites: Writes[UTCDate] =
- utcDate => JsString(utcDate.asUTC.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")))
-
- private implicit val vacationResponseIdWrites: Writes[VacationResponseId] = _ => JsString(VACATION_RESPONSE_ID.value)
- private implicit val isEnabledWrites: Writes[IsEnabled] = Json.valueWrites[IsEnabled]
- private implicit val fromDateWrites: Writes[FromDate] = Json.valueWrites[FromDate]
- private implicit val toDateWrites: Writes[ToDate] = Json.valueWrites[ToDate]
- private implicit val subjectWrites: Writes[Subject] = Json.valueWrites[Subject]
- private implicit val textBodyWrites: Writes[TextBody] = Json.valueWrites[TextBody]
- private implicit val htmlBodyWrites: Writes[HtmlBody] = Json.valueWrites[HtmlBody]
-
- implicit def vacationResponseWrites(properties: Properties): Writes[VacationResponse] = Json.writes[VacationResponse]
- .transform(properties.filter(_))
-
- private implicit val vacationResponseIdReads: Reads[VacationResponseIds] = Json.valueReads[VacationResponseIds]
- private implicit val vacationResponseGetRequest: Reads[VacationResponseGetRequest] = Json.reads[VacationResponseGetRequest]
-
- private implicit def vacationResponseNotFoundWrites(implicit idWrites: Writes[UnparsedVacationResponseId]): Writes[VacationResponseNotFound] =
- notFound => JsArray(notFound.value.toList.map(idWrites.writes))
-
- private implicit def vacationResponseGetResponseWrites(implicit vacationResponseWrites: Writes[VacationResponse]): Writes[VacationResponseGetResponse] =
- Json.writes[VacationResponseGetResponse]
-
- private def vacationResponseWritesWithFilteredProperties(properties: Properties): Writes[VacationResponse] =
- vacationResponseWrites(VacationResponse.propertiesFiltered(properties))
-
- private implicit def jsErrorWrites: Writes[JsError] = Json.writes[JsError]
-
- private implicit val problemDetailsWrites: Writes[ProblemDetails] = Json.writes[ProblemDetails]
-
- def serialize(session: Session): JsValue = Json.toJson(session)
-
- def serialize(requestObject: RequestObject): JsValue = Json.toJson(requestObject)
-
- def serialize(responseObject: ResponseObject): JsValue = Json.toJson(responseObject)
-
- def serialize(problemDetails: ProblemDetails): JsValue = Json.toJson(problemDetails)
-
- def serialize(mailbox: Mailbox)(implicit mailboxWrites: Writes[Mailbox]): JsValue = Json.toJson(mailbox)
-
- def serialize(mailboxGetResponse: MailboxGetResponse)(implicit mailboxWrites: Writes[Mailbox]): JsValue = Json.toJson(mailboxGetResponse)
-
- def serialize(mailboxGetResponse: MailboxGetResponse, properties: Properties, capabilities: Set[CapabilityIdentifier]): JsValue =
- serialize(mailboxGetResponse)(mailboxWritesWithFilteredProperties(properties, capabilities))
-
- def serialize(emailQueryResponse: EmailQueryResponse): JsObject = Json.toJsObject(emailQueryResponse)
-
- def serialize(mailboxSetResponse: MailboxSetResponse)
- (implicit mailboxCreationResponseWrites: Writes[MailboxCreationResponse]): JsValue =
- Json.toJson(mailboxSetResponse)(mailboxSetResponseWrites(mailboxCreationResponseWrites))
-
- def serialize(mailboxSetResponse: MailboxSetResponse, capabilities: Set[CapabilityIdentifier]): JsValue =
- serialize(mailboxSetResponse)(mailboxCreationResponseWritesWithFilteredProperties(capabilities))
-
- def serialize(errors: JsError): JsValue = Json.toJson(errors)
-
- def serialize(vacationResponse: VacationResponse)(implicit vacationResponseWrites: Writes[VacationResponse]): JsValue = Json.toJson(vacationResponse)
-
- def serialize(vacationResponseGetResponse: VacationResponseGetResponse)(implicit vacationResponseWrites: Writes[VacationResponse]): JsValue =
- Json.toJson(vacationResponseGetResponse)
-
- def serialize(vacationResponseGetResponse: VacationResponseGetResponse, properties: Properties): JsValue =
- serialize(vacationResponseGetResponse)(vacationResponseWritesWithFilteredProperties(properties))
-
- def serialize(vacationResponseSetResponse: VacationResponseSetResponse): JsValue = Json.toJson(vacationResponseSetResponse)
-
- def serialize(emailGetResponse: EmailGetResponse, properties: Properties, bodyProperties: Properties): JsValue =
- Json.toJson(emailGetResponse)(emailGetResponseWrites(emailWritesWithPropertyFilter(properties)(bodyPartWritesWithPropertyFilter(bodyProperties))))
-
- def deserializeRequestObject(input: String): JsResult[RequestObject] = Json.parse(input).validate[RequestObject]
-
- def deserializeRequestObject(input: InputStream): JsResult[RequestObject] = Json.parse(input).validate[RequestObject]
-
- def deserializeResponseObject(input: String): JsResult[ResponseObject] = Json.parse(input).validate[ResponseObject]
-
- def deserializeMailboxGetRequest(input: String): JsResult[MailboxGetRequest] = Json.parse(input).validate[MailboxGetRequest]
-
- def deserializeMailboxGetRequest(input: JsValue): JsResult[MailboxGetRequest] = Json.fromJson[MailboxGetRequest](input)
-
- def deserializeEmailGetRequest(input: JsValue): JsResult[EmailGetRequest] = Json.fromJson[EmailGetRequest](input)
-
- def deserializeEmailQueryRequest(input: JsValue): JsResult[EmailQueryRequest] = Json.fromJson[EmailQueryRequest](input)
-
- def deserializeMailboxSetRequest(input: JsValue): JsResult[MailboxSetRequest] = Json.fromJson[MailboxSetRequest](input)
-
- def deserializeRights(input: JsValue): JsResult[Rights] = Json.fromJson[Rights](input)
-
- def deserializeRfc4314Rights(input: JsValue): JsResult[Rfc4314Rights] = Json.fromJson[Rfc4314Rights](input)
-
- def deserializeVacationResponseGetRequest(input: String): JsResult[VacationResponseGetRequest] = Json.parse(input).validate[VacationResponseGetRequest]
-
- def deserializeVacationResponseGetRequest(input: JsValue): JsResult[VacationResponseGetRequest] = Json.fromJson[VacationResponseGetRequest](input)
-
- def deserializeBackReference(input: JsValue): JsResult[BackReference] = Json.fromJson[BackReference](input)
-
- def deserializeVacationResponseSetRequest(input: JsValue): JsResult[VacationResponseSetRequest] = Json.fromJson[VacationResponseSetRequest](input)
-}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/VacationSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/VacationSerializer.scala
new file mode 100644
index 0000000..70f61b6
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/VacationSerializer.scala
@@ -0,0 +1,91 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.jmap.json
+
+import java.time.format.DateTimeFormatter
+
+import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
+import org.apache.james.jmap.mail.VacationResponse.{UnparsedVacationResponseId, VACATION_RESPONSE_ID}
+import org.apache.james.jmap.mail.{FromDate, HtmlBody, IsEnabled, NotFound, Subject, TextBody, ToDate, VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseId, VacationResponseIds, VacationResponseNotFound}
+import org.apache.james.jmap.model._
+import org.apache.james.jmap.vacation.{VacationResponsePatchObject, VacationResponseSetError, VacationResponseSetRequest, VacationResponseSetResponse, VacationResponseUpdateResponse}
+import play.api.libs.json._
+
+import scala.language.implicitConversions
+
+object VacationSerializer {
+ private implicit val isEnabledReads: Reads[IsEnabled] = Json.valueReads[IsEnabled]
+ private implicit val vacationResponsePatchObjectReads: Reads[VacationResponsePatchObject] = {
+ case jsObject: JsObject => JsSuccess(VacationResponsePatchObject(jsObject))
+ case _ => JsError("VacationResponsePatchObject needs to be represented by a JsObject")
+ }
+ private implicit val vacationResponseSetRequestReads: Reads[VacationResponseSetRequest] = Json.reads[VacationResponseSetRequest]
+
+ private implicit def notFoundWrites(implicit mailboxIdWrites: Writes[UnparsedMailboxId]): Writes[NotFound] =
+ notFound => JsArray(notFound.value.toList.map(mailboxIdWrites.writes))
+
+ private implicit val vacationResponseSetUpdateResponseWrites: Writes[VacationResponseUpdateResponse] = Json.valueWrites[VacationResponseUpdateResponse]
+
+ private implicit val vacationResponseSetErrorWrites: Writes[VacationResponseSetError] = Json.writes[VacationResponseSetError]
+
+ private implicit val vacationResponseSetResponseWrites: Writes[VacationResponseSetResponse] = Json.writes[VacationResponseSetResponse]
+
+ private implicit val utcDateWrites: Writes[UTCDate] =
+ utcDate => JsString(utcDate.asUTC.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")))
+
+ private implicit val vacationResponseIdWrites: Writes[VacationResponseId] = _ => JsString(VACATION_RESPONSE_ID.value)
+ private implicit val isEnabledWrites: Writes[IsEnabled] = Json.valueWrites[IsEnabled]
+ private implicit val fromDateWrites: Writes[FromDate] = Json.valueWrites[FromDate]
+ private implicit val toDateWrites: Writes[ToDate] = Json.valueWrites[ToDate]
+ private implicit val subjectWrites: Writes[Subject] = Json.valueWrites[Subject]
+ private implicit val textBodyWrites: Writes[TextBody] = Json.valueWrites[TextBody]
+ private implicit val htmlBodyWrites: Writes[HtmlBody] = Json.valueWrites[HtmlBody]
+
+ implicit def vacationResponseWrites(properties: Properties): Writes[VacationResponse] = Json.writes[VacationResponse]
+ .transform(properties.filter(_))
+
+ private implicit val vacationResponseIdReads: Reads[VacationResponseIds] = Json.valueReads[VacationResponseIds]
+ private implicit val vacationResponseGetRequest: Reads[VacationResponseGetRequest] = Json.reads[VacationResponseGetRequest]
+
+ private implicit def vacationResponseNotFoundWrites(implicit idWrites: Writes[UnparsedVacationResponseId]): Writes[VacationResponseNotFound] =
+ notFound => JsArray(notFound.value.toList.map(idWrites.writes))
+
+ private implicit def vacationResponseGetResponseWrites(implicit vacationResponseWrites: Writes[VacationResponse]): Writes[VacationResponseGetResponse] =
+ Json.writes[VacationResponseGetResponse]
+
+ private def vacationResponseWritesWithFilteredProperties(properties: Properties): Writes[VacationResponse] =
+ vacationResponseWrites(VacationResponse.propertiesFiltered(properties))
+
+ def serialize(vacationResponse: VacationResponse)(implicit vacationResponseWrites: Writes[VacationResponse]): JsValue = Json.toJson(vacationResponse)
+
+ def serialize(vacationResponseGetResponse: VacationResponseGetResponse)(implicit vacationResponseWrites: Writes[VacationResponse]): JsValue =
+ Json.toJson(vacationResponseGetResponse)
+
+ def serialize(vacationResponseGetResponse: VacationResponseGetResponse, properties: Properties): JsValue =
+ serialize(vacationResponseGetResponse)(vacationResponseWritesWithFilteredProperties(properties))
+
+ def serialize(vacationResponseSetResponse: VacationResponseSetResponse): JsValue = Json.toJson(vacationResponseSetResponse)
+
+ def deserializeVacationResponseGetRequest(input: String): JsResult[VacationResponseGetRequest] = Json.parse(input).validate[VacationResponseGetRequest]
+
+ def deserializeVacationResponseGetRequest(input: JsValue): JsResult[VacationResponseGetRequest] = Json.fromJson[VacationResponseGetRequest](input)
+
+ def deserializeVacationResponseSetRequest(input: JsValue): JsResult[VacationResponseSetRequest] = Json.fromJson[VacationResponseSetRequest](input)
+}
\ No newline at end of file
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 c34a31d..c79e88b 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
@@ -17,20 +17,54 @@
* under the License. *
****************************************************************/
-// code copied from https://github.com/avdv/play-json-refined/blob/master/src/main/scala/de.cbley.refined.play.json/package.scala
-
package org.apache.james.jmap
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+
import eu.timepit.refined.api.{RefType, Validate}
import org.apache.james.jmap.model.Id.Id
+import org.apache.james.jmap.model.SetError.SetErrorDescription
+import org.apache.james.jmap.model.{AccountId, Properties, SetError, UTCDate}
import play.api.libs.json._
+import scala.util.{Failure, Success, Try}
+
package object json {
+ def mapWrites[K, V](keyWriter: K => String, valueWriter: Writes[V]): Writes[Map[K, V]] =
+ (ids: Map[K, V]) => {
+ ids.foldLeft(JsObject.empty)((jsObject, kv) => {
+ val (key: K, value: V) = kv
+ jsObject.+(keyWriter.apply(key), valueWriter.writes(value))
+ })
+ }
+
+ def readMapEntry[K, V](keyValidator: String => Either[String, K], valueTransformer: JsObject => V): Reads[Map[K, V]] = _.validate[Map[String, JsObject]]
+ .flatMap(mapWithStringKey =>{
+ mapWithStringKey
+ .foldLeft[Either[JsError, Map[K, V]]](scala.util.Right[JsError, Map[K, V]](Map.empty))((acc: Either[JsError, Map[K, V]], keyValue) => {
+ acc match {
+ case error@Left(_) => error
+ case scala.util.Right(validatedAcc) =>
+ val refinedKey: Either[String, K] = keyValidator.apply(keyValue._1)
+ refinedKey match {
+ case Left(error) => Left(JsError(error))
+ case scala.util.Right(unparsedK) => scala.util.Right(validatedAcc + (unparsedK -> valueTransformer.apply(keyValue._2)))
+ }
+ }
+ }) match {
+ case Left(jsError) => jsError
+ case scala.util.Right(value) => JsSuccess(value)
+ }
+ })
+
+ // code copied from https://github.com/avdv/play-json-refined/blob/master/src/main/scala/de.cbley.refined.play.json/package.scala
implicit def writeRefined[T, P, F[_, _]](
implicit writesT: Writes[T],
reftype: RefType[F]
): Writes[F[T, P]] = Writes(value => writesT.writes(reftype.unwrap(value)))
+ // code copied from https://github.com/avdv/play-json-refined/blob/master/src/main/scala/de.cbley.refined.play.json/package.scala
implicit def readRefined[T, P, F[_, _]](
implicit readsT: Reads[T],
reftype: RefType[F],
@@ -48,4 +82,19 @@ package object json {
(m: Map[Id, Any]) => {
JsObject(m.map { case (k, v) => (k.value, vr.writes(v)) }.toSeq)
}
+
+ private[json] implicit val UTCDateReads: Reads[UTCDate] = {
+ case JsString(value) =>
+ Try(UTCDate(ZonedDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME))) match {
+ case Success(value) => JsSuccess(value)
+ case Failure(e) => JsError(e.getMessage)
+ }
+ case _ => JsError("Expecting js string to represent UTC Date")
+ }
+ private[json] implicit val accountIdWrites: Format[AccountId] = Json.valueFormat[AccountId]
+ private[json] implicit val propertiesFormat: Format[Properties] = Json.valueFormat[Properties]
+ private[json] implicit val setErrorDescriptionWrites: Writes[SetErrorDescription] = Json.valueWrites[SetErrorDescription]
+ private[json] implicit val setErrorWrites: Writes[SetError] = Json.writes[SetError]
+ private[json] implicit val utcDateWrites: Writes[UTCDate] =
+ utcDate => JsString(utcDate.asUTC.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")))
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala
index b7b11e5..7cf5355 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala
@@ -26,7 +26,7 @@ import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.refineV
import eu.timepit.refined.types.string.NonEmptyString
import org.apache.james.core.Username
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.MailboxSerializer
import org.apache.james.jmap.mail.MailboxName.MailboxName
import org.apache.james.jmap.mail.MailboxPatchObject.MailboxPatchObjectKey
import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, UnparsedMailboxId, UnparsedMailboxIdConstraint}
@@ -116,7 +116,7 @@ object MailboxPatchObject {
case class MailboxPatchObject(value: Map[String, JsValue]) {
def validate(processingContext: ProcessingContext,
mailboxIdFactory: MailboxId.Factory,
- serializer: Serializer,
+ serializer: MailboxSerializer,
capabilities: Set[CapabilityIdentifier],
mailboxSession: MailboxSession): Either[PatchUpdateValidationException, ValidatedMailboxPatchObject] = {
val asUpdatedIterable = updates(serializer, capabilities, processingContext, mailboxIdFactory, mailboxSession)
@@ -172,7 +172,7 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
rightsPartialUpdates = partialRightsUpdates)))
}
- def updates(serializer: Serializer,
+ def updates(serializer: MailboxSerializer,
capabilities: Set[CapabilityIdentifier],
processingContext: ProcessingContext,
mailboxIdFactory: MailboxId.Factory,
@@ -283,7 +283,7 @@ object NameUpdate {
}
object SharedWithResetUpdate {
- def parse(serializer: Serializer, capabilities: Set[CapabilityIdentifier])
+ def parse(serializer: MailboxSerializer, capabilities: Set[CapabilityIdentifier])
(newValue: JsValue): Either[PatchUpdateValidationException, Update] =
if (capabilities.contains(CapabilityIdentifier.JAMES_SHARES)) {
serializer.deserializeRights(input = newValue) match {
@@ -304,7 +304,7 @@ object IsSubscribedUpdate {
}
object SharedWithPartialUpdate {
- def parse(serializer: Serializer, capabilities: Set[CapabilityIdentifier])
+ def parse(serializer: MailboxSerializer, capabilities: Set[CapabilityIdentifier])
( property: String, newValue: JsValue): Either[PatchUpdateValidationException, Update] =
if (capabilities.contains(CapabilityIdentifier.JAMES_SHARES)) {
parseUsername(property)
@@ -320,7 +320,7 @@ object SharedWithPartialUpdate {
case e: Exception => Left(InvalidPropertyException(property, e.getMessage))
}
- def parseRights(newValue: JsValue, property: String, serializer: Serializer): Either[PatchUpdateValidationException, Rfc4314Rights] = serializer.deserializeRfc4314Rights(newValue) match {
+ def parseRights(newValue: JsValue, property: String, serializer: MailboxSerializer): Either[PatchUpdateValidationException, Rfc4314Rights] = serializer.deserializeRfc4314Rights(newValue) match {
case JsSuccess(rights, _) => scala.Right(rights)
case JsError(errors) =>
val refinedKey: Either[String, MailboxPatchObjectKey] = refineV(property)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
index 5290da5..7b4368a 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
@@ -23,7 +23,7 @@ import java.time.ZoneId
import eu.timepit.refined.auto._
import javax.inject.Inject
import org.apache.james.jmap.api.model.Preview
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.{EmailGetSerializer, ResponseSerializer}
import org.apache.james.jmap.mail.Email.UnparsedEmailId
import org.apache.james.jmap.mail.{Email, EmailBodyPart, EmailGetRequest, EmailGetResponse, EmailIds, EmailNotFound}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
@@ -79,12 +79,11 @@ class SystemZoneIdProvider extends ZoneIdProvider {
override def get(): ZoneId = ZoneId.systemDefault()
}
-class EmailGetMethod @Inject() (serializer: Serializer,
- messageIdManager: MessageIdManager,
- messageIdFactory: MessageId.Factory,
- zoneIdProvider: ZoneIdProvider,
- previewFactory: Preview.Factory,
- metricFactory: MetricFactory) extends Method {
+class EmailGetMethod @Inject() (messageIdManager: MessageIdManager,
+ messageIdFactory: MessageId.Factory,
+ zoneIdProvider: ZoneIdProvider,
+ previewFactory: Preview.Factory,
+ metricFactory: MetricFactory) extends Method {
override val methodName = MethodName("Email/get")
override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
@@ -106,7 +105,7 @@ class EmailGetMethod @Inject() (serializer: Serializer,
case (properties, bodyProperties) => getEmails(request, mailboxSession)
.map(response => Invocation(
methodName = methodName,
- arguments = Arguments(serializer.serialize(response, properties, bodyProperties).as[JsObject]),
+ arguments = Arguments(EmailGetSerializer.serialize(response, properties, bodyProperties).as[JsObject]),
methodCallId = invocation.methodCallId))
})
@@ -135,9 +134,9 @@ class EmailGetMethod @Inject() (serializer: Serializer,
}
private def asEmailGetRequest(arguments: Arguments): SMono[EmailGetRequest] =
- serializer.deserializeEmailGetRequest(arguments.value) match {
+ EmailGetSerializer.deserializeEmailGetRequest(arguments.value) match {
case JsSuccess(emailGetRequest, _) => SMono.just(emailGetRequest)
- case errors: JsError => SMono.raiseError(new IllegalArgumentException(serializer.serialize(errors).toString))
+ case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
private def getEmails(request: EmailGetRequest, mailboxSession: MailboxSession): SMono[EmailGetResponse] =
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
index e08446d..5ba6543 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
@@ -20,7 +20,7 @@ package org.apache.james.jmap.method
import eu.timepit.refined.auto._
import javax.inject.Inject
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer}
import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
@@ -35,7 +35,7 @@ import org.reactivestreams.Publisher
import play.api.libs.json.{JsError, JsSuccess}
import reactor.core.scala.publisher.{SFlux, SMono}
-class EmailQueryMethod @Inject() (serializer: Serializer,
+class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer,
mailboxManager: MailboxManager,
metricFactory: MetricFactory) extends Method {
override val methodName = MethodName("Email/query")
@@ -67,7 +67,7 @@ class EmailQueryMethod @Inject() (serializer: Serializer,
private def asEmailQueryRequest(arguments: Arguments): SMono[EmailQueryRequest] =
serializer.deserializeEmailQueryRequest(arguments.value) match {
case JsSuccess(emailQueryRequest, _) => SMono.just(emailQueryRequest)
- case errors: JsError => SMono.raiseError(new IllegalArgumentException(serializer.serialize(errors).toString))
+ case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
index 194dd89..e1a6a57 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
@@ -21,7 +21,7 @@ package org.apache.james.jmap.method
import eu.timepit.refined.auto._
import javax.inject.Inject
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.{MailboxSerializer, ResponseSerializer}
import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
import org.apache.james.jmap.mail._
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
@@ -43,7 +43,7 @@ import reactor.core.scheduler.Schedulers
import scala.jdk.CollectionConverters._
-class MailboxGetMethod @Inject() (serializer: Serializer,
+class MailboxGetMethod @Inject() (serializer: MailboxSerializer,
mailboxManager: MailboxManager,
subscriptionManager: SubscriptionManager,
quotaFactory : QuotaLoaderWithPreloadedDefaultFactory,
@@ -99,7 +99,7 @@ class MailboxGetMethod @Inject() (serializer: Serializer,
private def asMailboxGetRequest(arguments: Arguments): SMono[MailboxGetRequest] = {
serializer.deserializeMailboxGetRequest(arguments.value) match {
case JsSuccess(mailboxGetRequest, _) => SMono.just(mailboxGetRequest)
- case errors: JsError => SMono.raiseError(new IllegalArgumentException(serializer.serialize(errors).toString))
+ case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala
index aa472e8..514d8d2 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala
@@ -21,7 +21,7 @@ package org.apache.james.jmap.method
import eu.timepit.refined.auto._
import javax.inject.Inject
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.{MailboxSerializer, ResponseSerializer}
import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, UnparsedMailboxId}
import org.apache.james.jmap.mail.{InvalidPatchException, InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, ParentIdUpdate, RemoveEmailsOnDestroy, ServerSetPropertyException, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedException, ValidatedMailboxPatchObject}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
@@ -140,7 +140,7 @@ case class UpdateResults(results: Seq[UpdateResult]) {
}).toMap
}
-class MailboxSetMethod @Inject()(serializer: Serializer,
+class MailboxSetMethod @Inject()(serializer: MailboxSerializer,
mailboxManager: MailboxManager,
subscriptionManager: SubscriptionManager,
mailboxIdFactory: MailboxId.Factory,
@@ -484,7 +484,7 @@ class MailboxSetMethod @Inject()(serializer: Serializer,
private def asMailboxSetRequest(arguments: Arguments): SMono[MailboxSetRequest] = {
serializer.deserializeMailboxSetRequest(arguments.value) match {
case JsSuccess(mailboxSetRequest, _) => SMono.just(mailboxSetRequest)
- case errors: JsError => SMono.raiseError(new IllegalArgumentException(serializer.serialize(errors).toString))
+ case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
index 815a482..4652add 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
@@ -22,7 +22,7 @@ package org.apache.james.jmap.method
import eu.timepit.refined.auto._
import javax.inject.Inject
import org.apache.james.jmap.api.vacation.{VacationRepository, AccountId => JavaAccountId}
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.{ResponseSerializer, VacationSerializer}
import org.apache.james.jmap.mail.VacationResponse.UnparsedVacationResponseId
import org.apache.james.jmap.mail.{VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseNotFound}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
@@ -57,8 +57,7 @@ case class VacationResponseGetResult(vacationResponses: Set[VacationResponse], n
notFound = notFound)
}
-class VacationResponseGetMethod @Inject() (serializer: Serializer,
- vacationRepository: VacationRepository,
+class VacationResponseGetMethod @Inject() (vacationRepository: VacationRepository,
metricFactory: MetricFactory) extends Method {
override val methodName: MethodName = MethodName("VacationResponse/get")
override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY, VACATION_RESPONSE_CAPABILITY)
@@ -78,7 +77,7 @@ class VacationResponseGetMethod @Inject() (serializer: Serializer,
.map(vacationResult => vacationResult.asResponse(vacationResponseGetRequest.accountId))
.map(vacationResponseGetResponse => Invocation(
methodName = methodName,
- arguments = Arguments(serializer.serialize(vacationResponseGetResponse, requestedProperties).as[JsObject]),
+ arguments = Arguments(VacationSerializer.serialize(vacationResponseGetResponse, requestedProperties).as[JsObject]),
methodCallId = invocation.methodCallId))
case invalidProperties: Properties =>
SMono.just(Invocation.error(errorCode = ErrorCode.InvalidArguments,
@@ -90,9 +89,9 @@ class VacationResponseGetMethod @Inject() (serializer: Serializer,
}
private def asVacationResponseGetRequest(arguments: Arguments): Either[IllegalArgumentException, VacationResponseGetRequest] =
- serializer.deserializeVacationResponseGetRequest(arguments.value) match {
+ VacationSerializer.deserializeVacationResponseGetRequest(arguments.value) match {
case JsSuccess(vacationResponseGetRequest, _) => Right(vacationResponseGetRequest)
- case errors: JsError => Left(new IllegalArgumentException(serializer.serialize(errors).toString))
+ case errors: JsError => Left(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
private def handleRequestValidationErrors(exception: Exception, methodCallId: MethodCallId): SMono[Invocation] = exception match {
@@ -109,8 +108,8 @@ class VacationResponseGetMethod @Inject() (serializer: Serializer,
.flux()
case Some(ids) => SFlux.fromIterable(ids.value)
.flatMap(id => processingContext.resolveVacationResponseId(id)
- .fold(e => SMono.just(VacationResponseGetResult.notFound(id)),
- vacationResponseId => getVacationSingleton(mailboxSession).map(VacationResponseGetResult.found)))
+ .fold(_ => SMono.just(VacationResponseGetResult.notFound(id)),
+ _ => getVacationSingleton(mailboxSession).map(VacationResponseGetResult.found)))
}
private def getVacationSingleton(mailboxSession: MailboxSession): SMono[VacationResponse] = {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
index 0fbbea5..bd34d88 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
@@ -22,7 +22,7 @@ package org.apache.james.jmap.method
import eu.timepit.refined.auto._
import javax.inject.Inject
import org.apache.james.jmap.api.vacation.{AccountId, VacationPatch, VacationRepository}
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.{ResponseSerializer, VacationSerializer}
import org.apache.james.jmap.method.VacationResponseSetMethod.VACATION_RESPONSE_PATCH_OBJECT_KEY
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY, VACATION_RESPONSE_CAPABILITY}
@@ -72,8 +72,7 @@ object VacationResponseSetMethod {
val VACATION_RESPONSE_PATCH_OBJECT_KEY = "singleton"
}
-class VacationResponseSetMethod @Inject() (serializer: Serializer,
- vacationRepository: VacationRepository,
+class VacationResponseSetMethod @Inject() (vacationRepository: VacationRepository,
zonedDateTimeProvider: ZonedDateTimeProvider,
metricFactory: MetricFactory) extends Method {
override val methodName: MethodName = MethodName("VacationResponse/set")
@@ -115,9 +114,9 @@ class VacationResponseSetMethod @Inject() (serializer: Serializer,
}
private def asVacationResponseSetRequest(arguments: Arguments): SMono[VacationResponseSetRequest] = {
- serializer.deserializeVacationResponseSetRequest(arguments.value) match {
+ VacationSerializer.deserializeVacationResponseSetRequest(arguments.value) match {
case JsSuccess(vacationResponseSetRequest, _) => SMono.just(vacationResponseSetRequest)
- case errors: JsError => SMono.raiseError(new IllegalArgumentException(serializer.serialize(errors).toString))
+ case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
}
@@ -133,7 +132,7 @@ class VacationResponseSetMethod @Inject() (serializer: Serializer,
notDestroyed = validateNoDestroy(vacationResponseSetRequest))
Invocation(methodName,
- Arguments(serializer.serialize(response).as[JsObject]),
+ Arguments(VacationSerializer.serialize(response).as[JsObject]),
invocation.methodCallId)
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
index ebdca76..6833db8 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -34,7 +34,7 @@ import org.apache.james.jmap.JMAPUrls.JMAP
import org.apache.james.jmap.exceptions.UnauthorizedException
import org.apache.james.jmap.http.rfc8621.InjectionKeys
import org.apache.james.jmap.http.{Authenticator, MailboxesProvisioner, UserProvisioning}
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.ResponseSerializer
import org.apache.james.jmap.method.Method
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.Invocation.MethodName
@@ -56,7 +56,6 @@ object JMAPApiRoutes {
}
class JMAPApiRoutes (val authenticator: Authenticator,
- serializer: Serializer,
userProvisioner: UserProvisioning,
mailboxesProvisioner: MailboxesProvisioner,
methods: Set[Method]) extends JMAPRoutes {
@@ -65,11 +64,10 @@ class JMAPApiRoutes (val authenticator: Authenticator,
@Inject
def this(@Named(InjectionKeys.RFC_8621) authenticator: Authenticator,
- serializer: Serializer,
userProvisioner: UserProvisioning,
mailboxesProvisioner: MailboxesProvisioner,
javaMethods: java.util.Set[Method]) {
- this(authenticator, serializer, userProvisioner, mailboxesProvisioner, javaMethods.asScala.toSet)
+ this(authenticator, userProvisioner, mailboxesProvisioner, javaMethods.asScala.toSet)
}
override def routes(): stream.Stream[JMAPRoute] = Stream.of(
@@ -104,9 +102,9 @@ class JMAPApiRoutes (val authenticator: Authenticator,
}
private def parseRequestObject(inputStream: InputStream): SMono[RequestObject] =
- serializer.deserializeRequestObject(inputStream) match {
+ ResponseSerializer.deserializeRequestObject(inputStream) match {
case JsSuccess(requestObject, _) => SMono.just(requestObject)
- case errors: JsError => SMono.raiseError(new IllegalArgumentException(serializer.serialize(errors).toString()))
+ case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString()))
}
private def process(requestObject: RequestObject,
@@ -125,7 +123,7 @@ class JMAPApiRoutes (val authenticator: Authenticator,
.header(CONTENT_TYPE, JSON_CONTENT_TYPE)
.sendString(
SMono.fromCallable(() =>
- serializer.serialize(ResponseObject(ResponseObject.SESSION_STATE, invocations.map(_._1))).toString),
+ ResponseSerializer.serialize(ResponseObject(ResponseObject.SESSION_STATE, invocations.map(_._1))).toString),
StandardCharsets.UTF_8)
.`then`())
)
@@ -149,7 +147,7 @@ class JMAPApiRoutes (val authenticator: Authenticator,
private def process(capabilities: Set[CapabilityIdentifier], mailboxSession: MailboxSession, processingContext: ProcessingContext, invocation: Invocation) : SMono[(Invocation, ProcessingContext)] = {
SMono.defer(() => {
- processingContext.resolveBackReferences(serializer, invocation) match {
+ processingContext.resolveBackReferences(invocation) match {
case Left(e) => SMono.just((Invocation.error(
errorCode = ErrorCode.InvalidResultReference,
description = s"Failed resolving back-reference: ${e.message}",
@@ -196,7 +194,7 @@ class JMAPApiRoutes (val authenticator: Authenticator,
private def respondDetails(httpServerResponse: HttpServerResponse, details: ProblemDetails): SMono[Void] =
SMono.fromPublisher(httpServerResponse.status(SC_BAD_REQUEST)
.header(CONTENT_TYPE, JSON_CONTENT_TYPE)
- .sendString(SMono.fromCallable(() => serializer.serialize(details).toString),
+ .sendString(SMono.fromCallable(() => ResponseSerializer.serialize(details).toString),
StandardCharsets.UTF_8)
.`then`)
}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
index 3505032..8402d48 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
@@ -19,7 +19,7 @@
package org.apache.james.jmap.routes
-import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.json.BackReferenceDeserializer
import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
import org.apache.james.jmap.mail.VacationResponse.{UnparsedVacationResponseId, VACATION_RESPONSE_ID}
import org.apache.james.jmap.model.Id.Id
@@ -28,8 +28,6 @@ import org.apache.james.jmap.model.{ClientId, Id, Invocation, ServerId}
import org.apache.james.mailbox.model.MailboxId
import play.api.libs.json.{JsArray, JsError, JsObject, JsResult, JsSuccess, JsValue, Reads}
-import scala.collection.mutable
-
sealed trait JsonPathPart
case class WildcardPart() extends JsonPathPart
@@ -97,8 +95,8 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
def recordInvocation(invocation: Invocation): ProcessingContext = ProcessingContext(creationIds, invocations + (invocation.methodCallId -> invocation))
- def resolveBackReferences(serializer: Serializer, invocation: Invocation): Either[InvalidResultReferenceException, Invocation] =
- backReferenceResolver(serializer).reads(invocation.arguments.value) match {
+ def resolveBackReferences(invocation: Invocation): Either[InvalidResultReferenceException, Invocation] =
+ backReferenceResolver().reads(invocation.arguments.value) match {
case JsError(e) => Left(InvalidResultReferenceException(e.toString()))
case JsSuccess(JsObject(underlying), _) => Right(Invocation(methodName = invocation.methodName,
methodCallId = invocation.methodCallId,
@@ -106,21 +104,21 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
case others: JsSuccess[JsValue] => Left(InvalidResultReferenceException(s"Unexpected value $others"))
}
- private def backReferenceResolver(serializer: Serializer): Reads[JsValue] = {
- case JsArray(value) => resolveBackReferences(serializer, value)
- case JsObject(underlying) => resolveBackReference(serializer, underlying)
+ private def backReferenceResolver(): Reads[JsValue] = {
+ case JsArray(value) => resolveBackReferences(value)
+ case JsObject(underlying) => resolveBackReference(underlying)
case others: JsValue => JsSuccess(others)
}
- private def resolveBackReferences(serializer: Serializer, array: collection.IndexedSeq[JsValue]): JsResult[JsValue] = {
- val resolver: Reads[JsValue] = backReferenceResolver(serializer)
+ private def resolveBackReferences(array: collection.IndexedSeq[JsValue]): JsResult[JsValue] = {
+ val resolver: Reads[JsValue] = backReferenceResolver()
val results: Seq[JsResult[JsValue]] = array.map(resolver.reads).toSeq
results.find(_.isError)
.getOrElse(JsSuccess(JsArray(results.map(_.get))))
}
- private def resolveBackReference(serializer: Serializer, underlying: collection.Map[String, JsValue]): JsResult[JsObject] = {
- val resolutions = underlying.map(resolveBackReference(serializer))
+ private def resolveBackReference(underlying: collection.Map[String, JsValue]): JsResult[JsObject] = {
+ val resolutions = underlying.map(resolveBackReference)
val firstError = resolutions.flatMap({
case Left(jsError) => Some(jsError)
@@ -135,17 +133,17 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
firstError.getOrElse(JsSuccess(JsObject(transformedMap)))
}
- private def resolveBackReference(serializer: Serializer)(entry: (String, JsValue)): Either[JsError, (String, JsValue)] = {
+ private def resolveBackReference(entry: (String, JsValue)): Either[JsError, (String, JsValue)] = {
if (entry._1.startsWith("#")) {
val newEntry: String = entry._1.substring(1)
- serializer.deserializeBackReference(entry._2) match {
+ BackReferenceDeserializer.deserializeBackReference(entry._2) match {
case JsSuccess(backReference, _) => resolveBackReference(newEntry, backReference)
// If the JSON object is not a back-reference continue parsing (it could be a creationId)
- case JsError(_) => propagateBackReferenceResolution(serializer, entry)
+ case JsError(_) => propagateBackReferenceResolution(entry)
}
} else {
- propagateBackReferenceResolution(serializer, entry)
+ propagateBackReferenceResolution(entry)
}
}
@@ -156,8 +154,8 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
}
}
- private def propagateBackReferenceResolution(serializer: Serializer, entry: (String, JsValue)): Either[JsError, (String, JsValue)] = {
- val entryPayload: JsResult[JsValue] = backReferenceResolver(serializer).reads(entry._2)
+ private def propagateBackReferenceResolution(entry: (String, JsValue)): Either[JsError, (String, JsValue)] = {
+ val entryPayload: JsResult[JsValue] = backReferenceResolver().reads(entry._2)
entryPayload match {
case JsError(e) => Left(JsError(e))
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
index e2d41ca..e908b22 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
@@ -32,10 +32,8 @@ import org.apache.http.HttpStatus
import org.apache.james.core.Username
import org.apache.james.jmap._
import org.apache.james.jmap.http.SessionRoutesTest.{BOB, TEST_CONFIGURATION}
-import org.apache.james.jmap.json.Serializer
import org.apache.james.jmap.model.JmapRfc8621Configuration
import org.apache.james.mailbox.MailboxSession
-import org.apache.james.mailbox.model.TestId
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito._
import org.scalatest.BeforeAndAfter
@@ -65,7 +63,6 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
.thenReturn(Mono.just(mockedSession))
val sessionRoutes = new SessionRoutes(
- serializer = new Serializer(new TestId.Factory),
sessionSupplier = new SessionSupplier(JmapRfc8621Configuration.LOCALHOST_CONFIGURATION),
authenticator = mockedAuthFilter)
jmapServer = new JMAPServer(
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/BackReferenceTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/BackReferenceTest.scala
index f51c5fc..7906f7e 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/BackReferenceTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/BackReferenceTest.scala
@@ -22,16 +22,11 @@ package org.apache.james.jmap.json
import eu.timepit.refined.auto._
import org.apache.james.jmap.model.Invocation.{MethodCallId, MethodName}
import org.apache.james.jmap.routes.{BackReference, JsonPath}
-import org.apache.james.mailbox.model.TestId
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json.{JsError, JsSuccess, Json}
-object BackReferenceTest {
- val SERIALIZER: Serializer = new Serializer(new TestId.Factory)
-}
-
class BackReferenceTest extends AnyWordSpec with Matchers {
"Deserialize backReference" should {
"succeed" in {
@@ -41,7 +36,7 @@ class BackReferenceTest extends AnyWordSpec with Matchers {
resultOf = MethodCallId("c1"),
path = jsonPath)
- BackReferenceTest.SERIALIZER.deserializeBackReference(
+ BackReferenceDeserializer.deserializeBackReference(
Json.parse("""{
| "resultOf":"c1",
| "name":"Mailbox/get",
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/JsErrorSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/JsErrorSerializationTest.scala
index cd980aa..b0fd28f 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/JsErrorSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/JsErrorSerializationTest.scala
@@ -20,18 +20,11 @@
package org.apache.james.jmap.json
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
-import org.apache.james.mailbox.model.TestId
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json.{JsError, JsPath, Json, JsonValidationError}
-object JsErrorSerializationTest {
- private val SERIALIZER: Serializer = new Serializer(new TestId.Factory)
-}
-
class JsErrorSerializationTest extends AnyWordSpec with Matchers {
- import JsErrorSerializationTest.SERIALIZER
-
"Serialize JsError" should {
"succeed " in {
val errors: JsError = JsError(Seq(
@@ -60,7 +53,7 @@ class JsErrorSerializationTest extends AnyWordSpec with Matchers {
|}
|""".stripMargin
- assertThatJson(Json.stringify(SERIALIZER.serialize(errors))).isEqualTo(expectedJson)
+ assertThatJson(Json.stringify(ResponseSerializer.serialize(errors))).isEqualTo(expectedJson)
}
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
index ae971e9..de24595 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
@@ -35,7 +35,7 @@ import play.api.libs.json.{JsSuccess, Json}
object MailboxGetSerializationTest {
private val FACTORY: MailboxId.Factory = new TestId.Factory
- private val SERIALIZER: Serializer = new Serializer(FACTORY)
+ private val SERIALIZER: MailboxSerializer = new MailboxSerializer(FACTORY)
private val ACCOUNT_ID: AccountId = AccountId(id)
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala
index c3a6a1a..0f825df 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxSerializationTest.scala
@@ -129,7 +129,7 @@ class MailboxSerializationTest extends AnyWordSpec with Matchers {
| }
|}""".stripMargin
- val serializer = new Serializer(new TestId.Factory)
+ val serializer = new MailboxSerializer(new TestId.Factory)
assertThatJson(Json.stringify(serializer.serialize(MAILBOX)(serializer.mailboxWrites(Mailbox.allProperties)))).isEqualTo(expectedJson)
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
index 47ce6f9..c614cc9 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
@@ -20,16 +20,11 @@
package org.apache.james.jmap.json
import org.apache.james.jmap.json.Fixture._
-import org.apache.james.jmap.json.RequestObjectSerializationTest.SERIALIZER
import org.apache.james.jmap.model.RequestObject
-import org.apache.james.mailbox.model.TestId
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json._
-object RequestObjectSerializationTest {
- private val SERIALIZER: Serializer = new Serializer(new TestId.Factory)
-}
class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
"Deserialize RequestObject" should {
@@ -39,7 +34,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
methodCalls = Seq(invocation1),
createdIds = Option.empty)
- SERIALIZER
+ ResponseSerializer
.deserializeRequestObject(
"""
|{
@@ -60,7 +55,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
methodCalls = Seq(invocation1),
createdIds = Option(createdIds))
- SERIALIZER
+ ResponseSerializer
.deserializeRequestObject(
"""
|{
@@ -82,7 +77,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
methodCalls = Seq(invocation1, invocation2),
createdIds = Option.empty)
- SERIALIZER
+ ResponseSerializer
.deserializeRequestObject(
"""
|{
@@ -104,7 +99,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
"Serialize RequestObject" should {
"succeed when write to string without CreatedIds" in {
- val actualValue = SERIALIZER.serialize(
+ val actualValue = ResponseSerializer.serialize(
RequestObject(
using = Seq(coreIdentifier, mailIdentifier),
methodCalls = Seq(invocation1),
@@ -127,7 +122,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
}
"succeed when write to string with CreatedIds" in {
- val actualValue = SERIALIZER.serialize(
+ val actualValue = ResponseSerializer.serialize(
RequestObject(
using = Seq(coreIdentifier, mailIdentifier),
methodCalls = Seq(invocation1),
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
index 100e4d4..dd14faa 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
@@ -21,17 +21,11 @@ package org.apache.james.jmap.json
import eu.timepit.refined.auto._
import org.apache.james.jmap.json.Fixture._
-import org.apache.james.jmap.json.ResponseObjectSerializationTest.SERIALIZER
import org.apache.james.jmap.model.ResponseObject
-import org.apache.james.mailbox.model.TestId
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json._
-object ResponseObjectSerializationTest {
- private val SERIALIZER: Serializer = new Serializer(new TestId.Factory)
-}
-
class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
"Deserialize ResponseObject" should {
"succeed " in {
@@ -39,7 +33,7 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
sessionState = "75128aab4b1b",
methodResponses = Seq(invocation1))
- SERIALIZER.deserializeResponseObject(
+ ResponseSerializer.deserializeResponseObject(
"""
|{
| "methodResponses": [
@@ -58,7 +52,7 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
sessionState = "75128aab4b1b",
methodResponses = Seq(invocation1, invocation2))
- SERIALIZER.deserializeResponseObject(
+ ResponseSerializer.deserializeResponseObject(
"""
|{
| "sessionState": "75128aab4b1b",
@@ -96,7 +90,7 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
|}
|""".stripMargin))
- SERIALIZER.serialize(responseObject) should be(Json.parse(expectedJson))
+ ResponseSerializer.serialize(responseObject) should be(Json.parse(expectedJson))
}
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
index f0d5730..1983b95 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
@@ -29,7 +29,6 @@ import org.apache.james.jmap.model.CoreCapabilityProperties.CollationAlgorithm
import org.apache.james.jmap.model.MailCapability.EmailQuerySortOption
import org.apache.james.jmap.model.State.State
import org.apache.james.jmap.model._
-import org.apache.james.mailbox.model.TestId
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json.Json
@@ -208,7 +207,7 @@ class SessionSerializationTest extends AnyWordSpec with Matchers {
| "eventSourceUrl": "http://james.org",
| "state": "fda9342jcm"
|}""".stripMargin)
- new Serializer(new TestId.Factory).serialize(SESSION) should equal(json)
+ ResponseSerializer.serialize(SESSION) should equal(json)
}
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseGetSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseGetSerializationTest.scala
index fc53679..e3028aa 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseGetSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseGetSerializationTest.scala
@@ -22,7 +22,7 @@ package org.apache.james.jmap.json
import eu.timepit.refined.auto._
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.apache.james.jmap.json.Fixture.id
-import org.apache.james.jmap.json.VacationResponseGetSerializationTest.{ACCOUNT_ID, PROPERTIES, SERIALIZER, SINGLETON_ID}
+import org.apache.james.jmap.json.VacationResponseGetSerializationTest.{ACCOUNT_ID, PROPERTIES, SINGLETON_ID}
import org.apache.james.jmap.json.VacationResponseSerializationTest.VACATION_RESPONSE
import org.apache.james.jmap.mail.VacationResponse.UnparsedVacationResponseId
import org.apache.james.jmap.mail.{VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseIds, VacationResponseNotFound}
@@ -34,7 +34,6 @@ import play.api.libs.json.{JsSuccess, Json}
object VacationResponseGetSerializationTest {
private val FACTORY: MailboxId.Factory = new TestId.Factory
- private val SERIALIZER: Serializer = new Serializer(FACTORY)
private val ACCOUNT_ID: AccountId = AccountId(id)
@@ -50,7 +49,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
ids = Some(VacationResponseIds(List("invalid"))),
properties = None)
- SERIALIZER.deserializeVacationResponseGetRequest(
+ VacationSerializer.deserializeVacationResponseGetRequest(
"""
|{
| "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
@@ -65,7 +64,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
ids = Some(VacationResponseIds(List(SINGLETON_ID))),
properties = None)
- SERIALIZER.deserializeVacationResponseGetRequest(
+ VacationSerializer.deserializeVacationResponseGetRequest(
"""
|{
| "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
@@ -80,7 +79,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
ids = Some(VacationResponseIds(List(SINGLETON_ID))),
properties = None)
- SERIALIZER.deserializeVacationResponseGetRequest(
+ VacationSerializer.deserializeVacationResponseGetRequest(
"""
|{
| "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
@@ -96,7 +95,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
ids = Some(VacationResponseIds(List(SINGLETON_ID))),
properties = Some(Properties.empty()))
- SERIALIZER.deserializeVacationResponseGetRequest(
+ VacationSerializer.deserializeVacationResponseGetRequest(
"""
|{
| "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
@@ -112,7 +111,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
ids = Some(VacationResponseIds(Nil)),
properties = None)
- SERIALIZER.deserializeVacationResponseGetRequest(
+ VacationSerializer.deserializeVacationResponseGetRequest(
"""
|{
| "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
@@ -127,7 +126,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
ids = None,
properties = None)
- SERIALIZER.deserializeVacationResponseGetRequest(
+ VacationSerializer.deserializeVacationResponseGetRequest(
"""
|{
| "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
@@ -142,7 +141,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
ids = Some(VacationResponseIds(List(SINGLETON_ID, "randomId"))),
properties = Some(PROPERTIES))
- SERIALIZER.deserializeVacationResponseGetRequest(
+ VacationSerializer.deserializeVacationResponseGetRequest(
"""
|{
| "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
@@ -179,7 +178,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
|}
|""".stripMargin
- assertThatJson(Json.stringify(SERIALIZER.serialize(actualValue)(SERIALIZER.vacationResponseWrites(VacationResponse.allProperties)))).isEqualTo(expectedJson)
+ assertThatJson(Json.stringify(VacationSerializer.serialize(actualValue)(VacationSerializer.vacationResponseWrites(VacationResponse.allProperties)))).isEqualTo(expectedJson)
}
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseSerializationTest.scala
index 49f2841..dba41c3 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseSerializationTest.scala
@@ -25,7 +25,6 @@ import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.apache.james.jmap.json.VacationResponseSerializationTest.VACATION_RESPONSE
import org.apache.james.jmap.mail.{FromDate, HtmlBody, IsEnabled, Subject, TextBody, ToDate, VacationResponse, VacationResponseId}
import org.apache.james.jmap.model.UTCDate
-import org.apache.james.mailbox.model.TestId
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json.Json
@@ -67,8 +66,7 @@ class VacationResponseSerializationTest extends AnyWordSpec with Matchers {
| "htmlBody":"<b>HTML body</b>"
|}""".stripMargin
- val serializer = new Serializer(new TestId.Factory)
- assertThatJson(Json.stringify(serializer.serialize(VACATION_RESPONSE)(serializer.vacationResponseWrites(VacationResponse.allProperties)))).isEqualTo(expectedJson)
+ assertThatJson(Json.stringify(VacationSerializer.serialize(VACATION_RESPONSE)(VacationSerializer.vacationResponseWrites(VacationResponse.allProperties)))).isEqualTo(expectedJson)
}
}
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
index cbeed33..069c3d6 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -37,7 +37,6 @@ import org.apache.james.domainlist.memory.MemoryDomainList
import org.apache.james.jmap.JMAPUrls.JMAP
import org.apache.james.jmap._
import org.apache.james.jmap.http.{Authenticator, BasicAuthenticationStrategy, MailboxesProvisioner, UserProvisioning}
-import org.apache.james.jmap.json.Serializer
import org.apache.james.jmap.method.{CoreEchoMethod, Method}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.DefaultCapabilities.CORE_CAPABILITY
@@ -46,7 +45,6 @@ import org.apache.james.jmap.model.{Capabilities, RequestLevelErrorType}
import org.apache.james.jmap.routes.JMAPApiRoutesTest._
import org.apache.james.mailbox.extension.PreDeletionHook
import org.apache.james.mailbox.inmemory.{InMemoryMailboxManager, MemoryMailboxManagerProvider}
-import org.apache.james.mailbox.model.TestId
import org.apache.james.mailbox.store.StoreSubscriptionManager
import org.apache.james.metrics.tests.RecordingMetricFactory
import org.apache.james.user.memory.MemoryUsersRepository
@@ -58,7 +56,6 @@ import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
object JMAPApiRoutesTest {
- private val SERIALIZER: Serializer = new Serializer(new TestId.Factory)
private val TEST_CONFIGURATION: JMAPConfiguration = JMAPConfiguration.builder().enable().randomPort().build()
private val ACCEPT_JMAP_VERSION_HEADER = "application/json; jmapVersion="
private val ACCEPT_DRAFT_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.DRAFT.asString()
@@ -82,7 +79,7 @@ object JMAPApiRoutesTest {
private val JMAP_METHODS: Set[Method] = Set(new CoreEchoMethod)
- private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, SERIALIZER, userProvisionner, mailboxesProvisioner, JMAP_METHODS)
+ private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, mailboxesProvisioner, JMAP_METHODS)
private val ROUTES_HANDLER: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, JMAP_API_ROUTE))
private val userBase64String: String = Base64.getEncoder.encodeToString("user1:password".getBytes(StandardCharsets.UTF_8))
@@ -446,7 +443,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
when(mockCoreEchoMethod.requiredCapabilities).thenReturn(Capabilities(CORE_CAPABILITY))
val methods: Set[Method] = Set(mockCoreEchoMethod)
- val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, SERIALIZER, userProvisionner, mailboxesProvisioner, methods)
+ val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, mailboxesProvisioner, methods)
val routesHandler: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, apiRoute))
val versionParser: VersionParser = new VersionParser(SUPPORTED_VERSIONS)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 02/06: JAMES-3372 Email/get specific unparsed
headers
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit e669096e4844cc6d7b69021a326fb55bcb3b0434
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Sep 11 09:05:30 2020 +0700
JAMES-3372 Email/get specific unparsed headers
---
.../rfc8621/contract/EmailGetMethodContract.scala | 269 ++++++++++++++++++++-
.../james/jmap/json/EmailGetSerializer.scala | 5 +-
.../scala/org/apache/james/jmap/mail/Email.scala | 7 +-
.../org/apache/james/jmap/mail/EmailGet.scala | 28 ++-
.../org/apache/james/jmap/mail/EmailHeader.scala | 6 +
.../apache/james/jmap/method/EmailGetMethod.scala | 21 +-
.../org/apache/james/jmap/model/Properties.scala | 3 +-
7 files changed, 326 insertions(+), 13 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/EmailGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
index e82f685..d123fc7 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
@@ -5103,4 +5103,271 @@ trait EmailGetMethodContract {
| ]]
|}""".stripMargin)
}
-}
+
+ @Test
+ def emailGetShouldReturnSpecificUnparsedHeaders(server: GuiceJamesServer): Unit = {
+ val bobPath = MailboxPath.inbox(BOB);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val alicePath = MailboxPath.inbox(ALICE);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
+ val message: Message = Message.Builder
+ .of
+ .setSender(ANDRE.asString())
+ .setFrom(ANDRE.asString())
+ .setSubject("World domination \r\n" +
+ " and this is also part of the header")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize()}"],
+ | "properties": ["header:Subject", "header:From", "header:Sender"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].list[0]")
+ .isEqualTo(
+ s"""{
+ | "id": "${messageId.serialize()}",
+ | "header:Subject":" =?US-ASCII?Q?World_domination_=0D=0A_and_thi?=\\r\\n =?US-ASCII?Q?s_is_also_part_of_the_header?=",
+ | "header:From":" andre@domain.tld",
+ | "header:Sender":" andre@domain.tld"
+ |}""".stripMargin)
+ }
+
+ @Test
+ def emailGetShouldReturnSpecificUnparsedHeadersWithInsensitiveCaseMatching(server: GuiceJamesServer): Unit = {
+ val bobPath = MailboxPath.inbox(BOB);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val alicePath = MailboxPath.inbox(ALICE);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
+ val message: Message = Message.Builder
+ .of
+ .setSender(ANDRE.asString())
+ .setFrom(ANDRE.asString())
+ .setSubject("World domination \r\n" +
+ " and this is also part of the header")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize()}"],
+ | "properties": ["header:subJeCt"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].list[0]")
+ .isEqualTo(
+ s"""{
+ | "id": "${messageId.serialize()}",
+ | "header:subJeCt":" =?US-ASCII?Q?World_domination_=0D=0A_and_thi?=\\r\\n =?US-ASCII?Q?s_is_also_part_of_the_header?="
+ |}""".stripMargin)
+ }
+
+ @Test
+ def emailGetShouldRejectSpecificUnparsedHeadersWithColon(server: GuiceJamesServer): Unit = {
+ val bobPath = MailboxPath.inbox(BOB);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val alicePath = MailboxPath.inbox(ALICE);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
+ val message: Message = Message.Builder
+ .of
+ .setSender(ANDRE.asString())
+ .setFrom(ANDRE.asString())
+ .setSubject("World domination \r\n" +
+ " and this is also part of the header")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize()}"],
+ | "properties": ["header:From:Subject"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [[
+ | "error",
+ | {
+ | "type": "invalidArguments",
+ | "description": "The following properties [header:From:Subject] do not exist."
+ | },
+ | "c1"
+ | ]]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def emailGetShouldReturnNullWhenUnknownSpecificUnparsedHeaders(server: GuiceJamesServer): Unit = {
+ val bobPath = MailboxPath.inbox(BOB);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val alicePath = MailboxPath.inbox(ALICE);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
+ val message: Message = Message.Builder
+ .of
+ .setSender(ANDRE.asString())
+ .setFrom(ANDRE.asString())
+ .setSubject("World domination \r\n" +
+ " and this is also part of the header")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize()}"],
+ | "properties": ["header:blahblah"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].list[0]")
+ .isEqualTo(
+ s"""{
+ | "id": "${messageId.serialize()}",
+ | "header:blahblah": null
+ |}""".stripMargin)
+ }
+
+ @Test
+ def emailGetShouldRejectEmptySpecificUnparsedHeaders(server: GuiceJamesServer): Unit = {
+ val bobPath = MailboxPath.inbox(BOB);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val alicePath = MailboxPath.inbox(ALICE);
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(alicePath)
+ val message: Message = Message.Builder
+ .of
+ .setSender(ANDRE.asString())
+ .setFrom(ANDRE.asString())
+ .setSubject("World domination \r\n" +
+ " and this is also part of the header")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+ .getMessageId
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/get",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId.serialize()}"],
+ | "properties": ["header:"]
+ | },
+ | "c1"]]
+ |}""".stripMargin)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [[
+ | "error",
+ | {
+ | "type": "invalidArguments",
+ | "description": "The following properties [header:] do not exist."
+ | },
+ | "c1"
+ | ]]
+ |}""".stripMargin)
+ }
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
index 797e8f2..a26909b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
@@ -95,9 +95,10 @@ object EmailGetSerializer {
val emailWrites: OWrites[Email] = (JsPath.write[EmailMetadata] and
JsPath.write[EmailHeaders] and
- JsPath.write[EmailBody]) (unlift(Email.unapply))
+ JsPath.write[EmailBody] and
+ JsPath.write[Map[String, Option[EmailHeaderValue]]]) (unlift(Email.unapply))
- emailWrites.transform(obj => properties.filter(obj))
+ emailWrites.transform(properties.filter(_))
}
private implicit def emailGetResponseWrites(implicit emailWrites: Writes[Email]): Writes[EmailGetResponse] = Json.writes[EmailGetResponse]
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
index dfde3fc..f8cdfb7 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
@@ -75,6 +75,10 @@ object HeaderMessageId {
}
}
+object EmailHeaders {
+ val SPECIFIC_HEADER_PREFIX = "header:"
+}
+
case class HeaderMessageId(value: String) extends AnyVal
case class Subject(value: String) extends AnyVal
case class MailboxIds(value: List[MailboxId])
@@ -111,4 +115,5 @@ case class EmailBody(bodyStructure: EmailBodyPart,
case class Email(metadata: EmailMetadata,
header: EmailHeaders,
- body: EmailBody)
+ body: EmailBody,
+ specificHeaders: Map[String, Option[EmailHeaderValue]])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
index c624ffc..f75eea0 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
@@ -27,9 +27,11 @@ import cats.implicits._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric.NonNegative
+import eu.timepit.refined.types.string.NonEmptyString
import org.apache.james.jmap.api.model.Preview
import org.apache.james.jmap.mail.Email.{UnparsedEmailId, sanitizeSize}
import org.apache.james.jmap.mail.EmailGetRequest.MaxBodyValueBytes
+import org.apache.james.jmap.mail.EmailHeaders.SPECIFIC_HEADER_PREFIX
import org.apache.james.jmap.model.State.State
import org.apache.james.jmap.model.{AccountId, Properties, UTCDate}
import org.apache.james.mailbox.model.{MessageId, MessageResult}
@@ -56,6 +58,20 @@ object EmailGetRequest {
val ZERO: MaxBodyValueBytes = 0
}
+object SpecificHeaderRequest {
+ def from(property: NonEmptyString): Either[NonEmptyString, SpecificHeaderRequest] = property match {
+ case property if property.value.equals(SPECIFIC_HEADER_PREFIX) => Left(property)
+ case property if property.startsWith(SPECIFIC_HEADER_PREFIX) =>
+ val headerName = property.substring(SPECIFIC_HEADER_PREFIX.length)
+ if (headerName.contains(":")) {
+ Left(property)
+ } else {
+ scala.Right(SpecificHeaderRequest(property, headerName))
+ }
+ case _ => Left(property)
+ }
+}
+
case class EmailGetRequest(accountId: AccountId,
ids: Option[EmailIds],
fetchAllBodyValues: Option[FetchAllBodyValues],
@@ -111,7 +127,11 @@ case class EmailGetRequest(accountId: AccountId,
attachments = bodyStructure.attachments,
bodyValues = bodyValues,
hasAttachment = HasAttachment(!firstMessage.getLoadedAttachments.isEmpty),
- preview = preview))
+ preview = preview),
+ specificHeaders = properties.getOrElse(Properties.empty()).value
+ .flatMap(property => SpecificHeaderRequest.from(property).toOption)
+ .map(_.retrieveHeader(mime4JMessage))
+ .toMap)
}
}
@@ -189,3 +209,9 @@ case class EmailGetResponse(accountId: AccountId,
state: State,
list: List[Email],
notFound: EmailNotFound)
+
+case class SpecificHeaderRequest(headerName: NonEmptyString, property: String) {
+ def retrieveHeader(message: Message): (String, Option[EmailHeaderValue]) = (headerName,
+ Option(message.getHeader.getField(property))
+ .map(field => EmailHeaderValue.from(field)))
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
index b3061ae..dbb4623 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
@@ -19,12 +19,18 @@
package org.apache.james.jmap.mail
+import java.nio.charset.StandardCharsets.US_ASCII
+
import org.apache.james.mime4j.stream.Field
object EmailHeader {
def apply(field: Field): EmailHeader = EmailHeader(EmailHeaderName(field.getName), EmailHeaderValue(field.getBody))
}
+object EmailHeaderValue {
+ def from(field: Field): EmailHeaderValue = EmailHeaderValue(new String(field.getRaw.toByteArray, US_ASCII).substring(field.getName.length + 1))
+}
+
case class EmailHeaderName(value: String) extends AnyVal
case class EmailHeaderValue(value: String) extends AnyVal
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
index 7b4368a..b5bc70e 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
@@ -21,11 +21,12 @@ package org.apache.james.jmap.method
import java.time.ZoneId
import eu.timepit.refined.auto._
+import eu.timepit.refined.types.string.NonEmptyString
import javax.inject.Inject
import org.apache.james.jmap.api.model.Preview
import org.apache.james.jmap.json.{EmailGetSerializer, ResponseSerializer}
import org.apache.james.jmap.mail.Email.UnparsedEmailId
-import org.apache.james.jmap.mail.{Email, EmailBodyPart, EmailGetRequest, EmailGetResponse, EmailIds, EmailNotFound}
+import org.apache.james.jmap.mail.{Email, EmailBodyPart, EmailGetRequest, EmailGetResponse, EmailIds, EmailNotFound, SpecificHeaderRequest}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
@@ -102,7 +103,7 @@ class EmailGetMethod @Inject() (messageIdManager: MessageIdManager,
.flatMap(properties => validateBodyProperties(request).map((properties, _)))
.fold(
e => SMono.raiseError(e), {
- case (properties, bodyProperties) => getEmails(request, mailboxSession)
+ case (properties, bodyProperties) => getEmails(request, properties, mailboxSession)
.map(response => Invocation(
methodName = methodName,
arguments = Arguments(EmailGetSerializer.serialize(response, properties, bodyProperties).as[JsObject]),
@@ -113,11 +114,17 @@ class EmailGetMethod @Inject() (messageIdManager: MessageIdManager,
request.properties match {
case None => Right(Email.defaultProperties)
case Some(properties) =>
- val invalidProperties = properties -- Email.allowedProperties
- if (invalidProperties.isEmpty()) {
- Right(properties ++ Email.idProperty)
+ val invalidProperties: Set[NonEmptyString] = properties.value
+ .flatMap(property => SpecificHeaderRequest.from(property)
+ .fold(
+ invalidProperty => Some(invalidProperty),
+ _ => None
+ )) -- Email.allowedProperties.value
+
+ if (invalidProperties.nonEmpty) {
+ Left(new IllegalArgumentException(s"The following properties [${invalidProperties.map(p => p.value).mkString(", ")}] do not exist."))
} else {
- Left(new IllegalArgumentException(s"The following properties [${invalidProperties.format()}] do not exist."))
+ Right(properties ++ Email.idProperty)
}
}
@@ -139,7 +146,7 @@ class EmailGetMethod @Inject() (messageIdManager: MessageIdManager,
case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
- private def getEmails(request: EmailGetRequest, mailboxSession: MailboxSession): SMono[EmailGetResponse] =
+ private def getEmails(request: EmailGetRequest, properties: Properties, mailboxSession: MailboxSession): SMono[EmailGetResponse] =
request.ids match {
case None => SMono.raiseError(new IllegalArgumentException("ids can not be ommited for email/get"))
case Some(ids) => getEmails(ids, mailboxSession, request)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Properties.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Properties.scala
index 1827630..12d749b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Properties.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Properties.scala
@@ -50,6 +50,7 @@ case class Properties(value: Set[NonEmptyString]) {
def filter(o: JsObject): JsObject =
JsObject(o.fields.filter(entry => {
val refined: Either[String, NonEmptyString] = refineV[NonEmpty](entry._1)
- refined.fold(e => throw new RuntimeException(e), property => contains(property))
+ refined.fold(e => throw new RuntimeException(e),
+ property => value.contains(property))
}))
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 05/06: JAMES-3368 port KeywordsCombiner and
KeywordsCombinerTest
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit c93af4fc309ec7258ed068f89b24412c401570cc
Author: duc91 <vd...@linagora.com>
AuthorDate: Wed Sep 9 18:42:24 2020 +0700
JAMES-3368 port KeywordsCombiner and KeywordsCombinerTest
---
server/protocols/jmap-rfc-8621/pom.xml | 6 ++
.../apache/james/jmap/model/KeywordsCombiner.scala | 43 +++++++++++
.../james/jmap/model/KeywordsCombinerTest.scala | 90 ++++++++++++++++++++++
3 files changed, 139 insertions(+)
diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index 617bee6..dbcc7bb 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -81,6 +81,12 @@
</dependency>
<dependency>
<groupId>${james.groupId}</groupId>
+ <artifactId>james-server-util</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${james.groupId}</groupId>
<artifactId>metrics-tests</artifactId>
<scope>test</scope>
</dependency>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/KeywordsCombiner.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/KeywordsCombiner.scala
new file mode 100644
index 0000000..95262cd
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/KeywordsCombiner.scala
@@ -0,0 +1,43 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+package org.apache.james.jmap.model
+
+import java.util.function.BinaryOperator
+
+object KeywordsCombiner {
+ private val KEYWORD_TO_INTERSECT = Set(Keyword.DRAFT)
+ private val KEYWORD_NOT_TO_UNION = KEYWORD_TO_INTERSECT
+}
+
+final case class KeywordsCombiner() extends BinaryOperator[Keywords] {
+ val keywordsFactory: KeywordsFactory = KeywordsFactory.LENIENT_KEYWORDS_FACTORY
+
+ override def apply(keywords: Keywords, keywords2: Keywords): Keywords =
+ keywordsFactory.fromSet(
+ keywords.getKeywords.union(keywords2.getKeywords).union(KeywordsCombiner.KEYWORD_NOT_TO_UNION)
+ .union(keywords.getKeywords.intersect(keywords2.getKeywords).intersect(KeywordsCombiner.KEYWORD_TO_INTERSECT)))
+
+ def union(set1: Set[Keyword], set2: Set[Keyword], exceptKeywords: List[Keyword]): Set[Keyword] =
+ set1.union(set2)
+ .filter((keyword: Keyword) => !exceptKeywords.contains(keyword))
+
+ def intersect(set1: Set[Keyword], set2: Set[Keyword], forKeywords: List[Keyword]): Set[Keyword] =
+ set1.intersect(set2)
+ .filter(forKeywords.contains)
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsCombinerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsCombinerTest.scala
new file mode 100644
index 0000000..0fec305
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/model/KeywordsCombinerTest.scala
@@ -0,0 +1,90 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ * **************************************************************/
+
+package org.apache.james.jmap.model
+
+import org.apache.james.jmap.model.KeywordsFactory.LENIENT_KEYWORDS_FACTORY
+import org.apache.james.util.CommutativityChecker
+import org.assertj.core.api.Assertions.assertThat
+import org.scalatest.matchers.must.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+import scala.jdk.CollectionConverters._
+
+class KeywordsCombinerTest extends AnyWordSpec with Matchers {
+ "apply should union seen keyword" in {
+ val keywordsCombiner = KeywordsCombiner()
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN)))
+ .isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN))
+ }
+
+ "apply should union answered keyword" in {
+ val keywordsCombiner = KeywordsCombiner()
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED))).isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED))
+ }
+
+ "apply should union flagged keyword" in {
+ val keywordsCombiner = KeywordsCombiner()
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED))).isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED))
+ }
+
+ "apply should intersect draft keyword" in {
+ val keywordsCombiner = KeywordsCombiner()
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(Keyword.DRAFT))).isEqualTo(Keywords.DEFAULT_VALUE)
+ }
+
+ "apply should union custom keyword" in {
+ val keywordsCombiner = KeywordsCombiner()
+ val customKeyword = Keyword.of("$Any")
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, LENIENT_KEYWORDS_FACTORY.from(customKeyword.get))).isEqualTo(LENIENT_KEYWORDS_FACTORY.from(customKeyword.get))
+ }
+
+ "apply should accept empty as a zeroValue" in {
+ val keywordsCombiner = KeywordsCombiner()
+ assertThat(keywordsCombiner.apply(Keywords.DEFAULT_VALUE, Keywords.DEFAULT_VALUE)).isEqualTo(Keywords.DEFAULT_VALUE)
+ }
+
+ "apply should union different flags" in {
+ val keywordsCombiner = KeywordsCombiner()
+ assertThat(keywordsCombiner.apply(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED), LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED))).isEqualTo(LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED, Keyword.ANSWERED))
+ }
+
+ "keywords combiner should be commutative" in {
+ val allKeyword = LENIENT_KEYWORDS_FACTORY.from(
+ Keyword.ANSWERED,
+ Keyword.DELETED,
+ Keyword.DRAFT,
+ Keyword.FLAGGED,
+ Keyword.SEEN,
+ Keyword.of("$Forwarded").get,
+ Keyword.of("$Any").get)
+
+ val values:Set[Keywords] = Set(
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.ANSWERED),
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.DELETED),
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.DRAFT),
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.FLAGGED),
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.SEEN),
+ LENIENT_KEYWORDS_FACTORY.from(),
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.of("$Forwarded").get),
+ LENIENT_KEYWORDS_FACTORY.from(Keyword.of("$Any").get),
+ allKeyword)
+
+ assertThat(new CommutativityChecker[Keywords](values.asJava, KeywordsCombiner()).findNonCommutativeInput).isEmpty()
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org