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/11/16 01:50:41 UTC
[james-project] 01/05: JAMES-3369 Enhance EmailGetSerializer
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 334bde6b365afa76dc807710fbf085a96dacd2a8
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Nov 6 13:08:42 2020 +0700
JAMES-3369 Enhance EmailGetSerializer
---
.../rfc8621/contract/EmailSetMethodContract.scala | 14 +-
.../org/apache/james/jmap/core/Properties.scala | 1 +
.../james/jmap/json/EmailGetSerializer.scala | 174 ++++++++++++++-------
.../james/jmap/json/EmailQuerySerializer.scala | 14 +-
.../james/jmap/json/EmailSetSerializer.scala | 40 ++---
.../jmap/json/EmailSubmissionSetSerializer.scala | 12 +-
.../james/jmap/json/MailboxQuerySerializer.scala | 5 +-
.../apache/james/jmap/json/MailboxSerializer.scala | 93 ++++++-----
.../james/jmap/json/ResponseSerializer.scala | 42 ++---
.../james/jmap/json/VacationSerializer.scala | 37 +++--
.../scala/org/apache/james/jmap/json/package.scala | 42 ++---
.../apache/james/jmap/routes/JMAPApiRoutes.scala | 8 +-
.../jmap/json/MailboxGetSerializationTest.scala | 4 +-
.../james/jmap/json/MailboxSerializationTest.scala | 2 +-
.../VacationResponseGetSerializationTest.scala | 2 +-
.../json/VacationResponseSerializationTest.scala | 2 +-
.../james/jmap/routes/JMAPApiRoutesTest.scala | 4 +-
17 files changed, 250 insertions(+), 246 deletions(-)
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index 8bd5a9e..38bfb79 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -430,7 +430,7 @@ trait EmailSetMethodContract {
.isEqualTo(
s"""|{
| "type":"invalidPatch",
- | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with keywords is invalid: List((,List(JsonValidationError(List(keyword value can only be true),ArraySeq()))))),ArraySeq()))))"
+ | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with keywords is invalid: List((/movie,List(JsonValidationError(List(map marker value can only be true),ArraySeq()))))),ArraySeq()))))"
|}""".stripMargin)
}
@@ -964,7 +964,7 @@ trait EmailSetMethodContract {
.inPath("methodResponses[0][1].notCreated.aaaaaa")
.isEqualTo(
s"""{
- | "description": "List((/mailboxIds,List(JsonValidationError(List(${invalidMessageIdMessage("invalid")}),ArraySeq()))))",
+ | "description": "List((/mailboxIds/invalid,List(JsonValidationError(List(${invalidMessageIdMessage("invalid")}),ArraySeq()))))",
| "type": "invalidArguments"
|}""".stripMargin)
}
@@ -3312,7 +3312,7 @@ trait EmailSetMethodContract {
.isEqualTo(
"""|{
| "type":"invalidPatch",
- | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with keywords is invalid: List((,List(JsonValidationError(List(FlagName must not be null or empty, must have length form 1-255,must not contain characters with hex from '\\u0000' to '\\u00019' or {'(' ')' '{' ']' '%' '*' '\"' '\\'} ),ArraySeq()))))),ArraySeq()))))"
+ | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with keywords is invalid: List((/mus*c,List(JsonValidationError(List(FlagName must not be null or empty, must have length form 1-255,must not contain characters with hex from '\\u0000' to '\\u00019' or {'(' ')' '{' ']' '%' '*' '\"' '\\'} ),ArraySeq()))))),ArraySeq()))))"
|}""".stripMargin)
}
@@ -5694,7 +5694,7 @@ trait EmailSetMethodContract {
| "notUpdated": {
| "${messageId.serialize}": {
| "type": "invalidPatch",
- | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((,List(JsonValidationError(List(${invalidMessageIdMessage("invalid")}),ArraySeq()))))),ArraySeq()))))"
+ | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((/invalid,List(JsonValidationError(List(${invalidMessageIdMessage("invalid")}),ArraySeq()))))),ArraySeq()))))"
| }
| }
| }, "c1"]
@@ -5755,7 +5755,7 @@ trait EmailSetMethodContract {
| "notUpdated": {
| "${messageId.serialize}": {
| "type": "invalidPatch",
- | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((,List(JsonValidationError(List(Expecting mailboxId value to be a boolean),ArraySeq()))))),ArraySeq()))))"
+ | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((/${messageId.serialize},List(JsonValidationError(List(Expecting mailboxId value to be a boolean),ArraySeq()))))),ArraySeq()))))"
| }
| }
| }, "c1"]
@@ -5816,7 +5816,7 @@ trait EmailSetMethodContract {
| "notUpdated": {
| "${messageId.serialize}": {
| "type": "invalidPatch",
- | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((,List(JsonValidationError(List(Expecting mailboxId value to be a boolean),ArraySeq()))))),ArraySeq()))))"
+ | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((/${messageId.serialize},List(JsonValidationError(List(Expecting mailboxId value to be a boolean),ArraySeq()))))),ArraySeq()))))"
| }
| }
| }, "c1"]
@@ -5897,7 +5897,7 @@ trait EmailSetMethodContract {
| "notUpdated": {
| "${messageId2.serialize}": {
| "type": "invalidPatch",
- | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((,List(JsonValidationError(List(${invalidMessageIdMessage("invalid")}),ArraySeq()))))),ArraySeq()))))"
+ | "description": "Message update is invalid: List((,List(JsonValidationError(List(Value associated with mailboxIds is invalid: List((/invalid,List(JsonValidationError(List(${invalidMessageIdMessage("invalid")}),ArraySeq()))))),ArraySeq()))))"
| }
| }
| }, "c1"]
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Properties.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Properties.scala
index bfbe18d..90f4627 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Properties.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Properties.scala
@@ -44,6 +44,7 @@ case class Properties(value: Set[NonEmptyString]) {
def isEmpty(): Boolean = value.isEmpty
def contains(property: NonEmptyString): Boolean = value.contains(property)
+ def containsString(property: String): Boolean = refineV[NonEmpty](property).fold(e => false, refined => contains(refined))
def format(): String = value.mkString(", ")
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 a53b734..0eafd02 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
@@ -21,6 +21,7 @@ package org.apache.james.jmap.json
import org.apache.james.jmap.api.model.Preview
import org.apache.james.jmap.core.Properties
+import org.apache.james.jmap.mail.Email.Size
import org.apache.james.jmap.mail.{AddressesHeaderValue, BlobId, Charset, DateHeaderValue, Disposition, EmailAddress, EmailAddressGroup, EmailBody, EmailBodyMetadata, EmailBodyPart, EmailBodyValue, EmailFastView, EmailFullView, EmailGetRequest, EmailGetResponse, EmailHeader, EmailHeaderName, EmailHeaderValue, EmailHeaderView, EmailHeaders, EmailIds, EmailMetadata, EmailMetadataView, EmailNotFound, EmailView, EmailerName, FetchAllBodyValues, FetchHTMLBodyValues, FetchTextBodyValues, Group [...]
import org.apache.james.mailbox.model.{Cid, MailboxId, MessageId}
import play.api.libs.functional.syntax._
@@ -28,6 +29,35 @@ import play.api.libs.json._
import scala.language.implicitConversions
+object EmailBodyPartToSerialize {
+ def from(part: EmailBodyPart): EmailBodyPartToSerialize = EmailBodyPartToSerialize(
+ partId = part.partId,
+ blobId = part.blobId,
+ headers = part.headers,
+ size = part.size,
+ `type` = part.`type`,
+ charset = part.charset,
+ disposition = part.disposition,
+ cid = part.cid,
+ language = part.language,
+ location = part.location,
+ name = part.name,
+ subParts = part.subParts.map(list => list.map(EmailBodyPartToSerialize.from)))
+}
+
+case class EmailBodyPartToSerialize(partId: PartId,
+ blobId: Option[BlobId],
+ headers: List[EmailHeader],
+ size: Size,
+ name: Option[Name],
+ `type`: Type,
+ charset: Option[Charset],
+ disposition: Option[Disposition],
+ cid: Option[Cid],
+ language: Option[Languages],
+ location: Option[Location],
+ subParts: Option[List[EmailBodyPartToSerialize]])
+
object EmailGetSerializer {
private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
@@ -88,65 +118,97 @@ object EmailGetSerializer {
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)
- 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[EmailView] = {
- implicit val emailMetadataWrites: OWrites[EmailMetadata] = Json.writes[EmailMetadata]
- implicit val emailHeadersWrites: Writes[EmailHeaders] = Json.writes[EmailHeaders]
- implicit val emailBodyWrites: Writes[EmailBody] = Json.writes[EmailBody]
- implicit val emailBodyMetadataWrites: Writes[EmailBodyMetadata] = Json.writes[EmailBodyMetadata]
-
- val emailFullViewWrites: OWrites[EmailFullView] = (JsPath.write[EmailMetadata] and
- JsPath.write[EmailHeaders] and
- JsPath.write[EmailBody] and
- JsPath.write[EmailBodyMetadata] and
- JsPath.write[Map[String, Option[EmailHeaderValue]]]) (unlift(EmailFullView.unapply))
-
- val emailFastViewWrites: OWrites[EmailFastView] = (JsPath.write[EmailMetadata] and
- JsPath.write[EmailHeaders] and
- JsPath.write[EmailBodyMetadata] and
- JsPath.write[Map[String, Option[EmailHeaderValue]]]) (unlift(EmailFastView.unapply))
- val emailHeaderViewWrites: OWrites[EmailHeaderView] = (JsPath.write[EmailMetadata] and
- JsPath.write[EmailHeaders] and
- JsPath.write[Map[String, Option[EmailHeaderValue]]]) (unlift(EmailHeaderView.unapply))
- val emailMetadataViewWrites: OWrites[EmailMetadataView] = view => Json.toJsObject(view.metadata)
-
- val emailWrites: OWrites[EmailView] = {
- case view: EmailMetadataView => emailMetadataViewWrites.writes(view)
- case view: EmailHeaderView => emailHeaderViewWrites.writes(view)
- case view: EmailFastView => emailFastViewWrites.writes(view)
- case view: EmailFullView => emailFullViewWrites.writes(view)
- }
-
- emailWrites.transform(properties.filter(_))
+ private implicit val bodyValueMapWrites: Writes[Map[PartId, EmailBodyValue]] =
+ mapWrites[PartId, EmailBodyValue](_.value.toString(), bodyValueWrites)
+
+ private implicit val bodyPartWritesToSerializeWrites: Writes[EmailBodyPartToSerialize] = (
+ (__ \ "partId").write[PartId] and
+ (__ \ "blobId").writeNullable[BlobId] and
+ (__ \ "headers").write[List[EmailHeader]] and
+ (__ \ "size").write[Size] and
+ (__ \ "name").writeNullable[Name] and
+ (__ \ "type").write[Type] and
+ (__ \ "charset").writeNullable[Charset] and
+ (__ \ "disposition").writeNullable[Disposition] and
+ (__ \ "cid").writeNullable[Cid] and
+ (__ \ "language").writeNullable[Languages] and
+ (__ \ "location").writeNullable[Location] and
+ (__ \ "subParts").lazyWriteNullable(implicitly[Writes[List[EmailBodyPartToSerialize]]])
+ )(unlift(EmailBodyPartToSerialize.unapply))
+
+ private implicit val bodyPartWrites: Writes[EmailBodyPart] = part => bodyPartWritesToSerializeWrites.writes(EmailBodyPartToSerialize.from(part))
+
+ private implicit val emailMetadataWrites: OWrites[EmailMetadata] = Json.writes[EmailMetadata]
+ private implicit val emailHeadersWrites: Writes[EmailHeaders] = Json.writes[EmailHeaders]
+ private implicit val emailBodyMetadataWrites: Writes[EmailBodyMetadata] = Json.writes[EmailBodyMetadata]
+
+ private val emailFastViewWrites: OWrites[EmailFastView] = (JsPath.write[EmailMetadata] and
+ JsPath.write[EmailHeaders] and
+ JsPath.write[EmailBodyMetadata] and
+ JsPath.write[Map[String, Option[EmailHeaderValue]]]) (unlift(EmailFastView.unapply))
+ private val emailHeaderViewWrites: OWrites[EmailHeaderView] = (JsPath.write[EmailMetadata] and
+ JsPath.write[EmailHeaders] and
+ JsPath.write[Map[String, Option[EmailHeaderValue]]]) (unlift(EmailHeaderView.unapply))
+ private val emailMetadataViewWrites: OWrites[EmailMetadataView] = view => Json.toJsObject(view.metadata)
+ private implicit val emailBodyWrites: Writes[EmailBody] = Json.writes[EmailBody]
+ private implicit val emailFullViewWrites: OWrites[EmailFullView] = (JsPath.write[EmailMetadata] and
+ JsPath.write[EmailHeaders] and
+ JsPath.write[EmailBody] and
+ JsPath.write[EmailBodyMetadata] and
+ JsPath.write[Map[String, Option[EmailHeaderValue]]]) (unlift(EmailFullView.unapply))
+ private implicit val emailWrites: OWrites[EmailView] = {
+ case view: EmailMetadataView => emailMetadataViewWrites.writes(view)
+ case view: EmailHeaderView => emailHeaderViewWrites.writes(view)
+ case view: EmailFastView => emailFastViewWrites.writes(view)
+ case view: EmailFullView => emailFullViewWrites.writes(view)
}
- private implicit def emailGetResponseWrites(implicit emailWrites: Writes[EmailView]): Writes[EmailGetResponse] = Json.writes[EmailGetResponse]
+ private implicit val emailGetResponseWrites: Writes[EmailGetResponse] = Json.writes[EmailGetResponse]
def serialize(emailGetResponse: EmailGetResponse, properties: Properties, bodyProperties: Properties): JsValue =
- Json.toJson(emailGetResponse)(emailGetResponseWrites(emailWritesWithPropertyFilter(properties)(bodyPartWritesWithPropertyFilter(bodyProperties))))
+ Json.toJson(emailGetResponse)
+ .transform((__ \ "list").json.update {
+ case JsArray(underlying) => JsSuccess(JsArray(underlying.map(js => js.transform {
+ case jsonObject: JsObject =>
+ bodyPropertiesFilteringTransformation(bodyProperties)
+ .reads(properties.filter(jsonObject))
+ case js => JsSuccess(js)
+ }.fold(_ => JsArray(underlying), o => o))))
+ case jsValue => JsSuccess(jsValue)
+ }).get
+
+ private def bodyPropertiesFilteringTransformation(bodyProperties: Properties): Reads[JsValue] = {
+ case serializedMailbox: JsObject =>
+ val bodyPropertiesToRemove = EmailBodyPart.allowedProperties -- bodyProperties
+ val noop: JsValue => JsValue = o => o
+
+ JsSuccess(Seq(
+ bodyPropertiesFilteringTransformation(bodyPropertiesToRemove, "attachments"),
+ bodyPropertiesFilteringTransformation(bodyPropertiesToRemove, "bodyStructure"),
+ bodyPropertiesFilteringTransformation(bodyPropertiesToRemove, "textBody"),
+ bodyPropertiesFilteringTransformation(bodyPropertiesToRemove, "htmlBody"))
+ .reduceLeftOption(_ compose _)
+ .getOrElse(noop)
+ .apply(serializedMailbox))
+ case js => JsSuccess(js)
+ }
+
+ private def bodyPropertiesFilteringTransformation(properties: Properties, field: String): JsValue => JsValue =
+ {
+ case JsObject(underlying) =>JsObject(underlying.map {
+ case (key, jsValue) if key.equals(field) => (field, removeFieldsRecursively(properties).apply(jsValue))
+ case (key, jsValue) => (key, jsValue)
+ })
+ case jsValue => jsValue
+ }
+
+ private def removeFieldsRecursively(properties: Properties): JsValue => JsValue = {
+ case JsObject(underlying) => JsObject(underlying.flatMap {
+ case (key, _) if properties.containsString(key) => None
+ case (key, value) => Some((key, removeFieldsRecursively(properties).apply(value)))
+ })
+ case JsArray(others) => JsArray(others.map(removeFieldsRecursively(properties)))
+ case o: JsValue => o
+ }
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
index ab020df..7a12c2b 100644
--- 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
@@ -20,7 +20,7 @@
package org.apache.james.jmap.json
import javax.inject.Inject
-import org.apache.james.jmap.core.{AccountId, CanCalculateChanges, LimitUnparsed, PositionUnparsed, QueryState}
+import org.apache.james.jmap.core.{CanCalculateChanges, LimitUnparsed, PositionUnparsed, QueryState}
import org.apache.james.jmap.mail.{AllInThreadHaveKeywordSortProperty, Anchor, AnchorOffset, And, Bcc, Body, Cc, CollapseThreads, Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, FilterOperator, FilterQuery, From, FromSortProperty, HasAttachment, HasKeywordSortProperty, Header, HeaderContains, HeaderExist, IsAscending, Keyword, Not, Operator, Or, ReceivedAtSortProperty, SentAtSortProperty, SizeSortProperty, SomeInThreadHaveKeywordSortProperty, SortProperty, [...]
import org.apache.james.mailbox.model.{MailboxId, MessageId}
import play.api.libs.json._
@@ -29,8 +29,6 @@ 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())
@@ -81,16 +79,16 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
case _ => JsError(s"Expecting a JsString to represent a known operator")
}
+ private val filterConditionRawRead: Reads[FilterCondition] = Json.reads[FilterCondition]
private implicit val filterConditionReads: Reads[FilterCondition] = {
- case JsObject(underlying) => {
+ case JsObject(underlying) =>
val unsupported: collection.Set[String] = underlying.keySet.diff(FilterCondition.SUPPORTED)
if (unsupported.nonEmpty) {
JsError(s"These '${unsupported.mkString("[", ", ", "]")}' was unsupported filter options")
} else {
- Json.reads[FilterCondition].reads(JsObject(underlying))
+ filterConditionRawRead.reads(JsObject(underlying))
}
- }
- case jsValue => Json.reads[FilterCondition].reads(jsValue)
+ case jsValue => filterConditionRawRead.reads(jsValue)
}
private implicit val limitUnparsedReads: Reads[LimitUnparsed] = Json.valueReads[LimitUnparsed]
@@ -133,7 +131,7 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
- private implicit def emailQueryResponseWrites: OWrites[EmailQueryResponse] = Json.writes[EmailQueryResponse]
+ private implicit val emailQueryResponseWrites: OWrites[EmailQueryResponse] = Json.writes[EmailQueryResponse]
def serialize(emailQueryResponse: EmailQueryResponse): JsObject = Json.toJsObject(emailQueryResponse)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
index fa97676..6ebc2c6 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
@@ -173,12 +173,7 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
}
private implicit val mailboxIdsMapReads: Reads[Map[MailboxId, Boolean]] =
- readMapEntry[MailboxId, Boolean](s => Try(mailboxIdFactory.fromString(s)).toEither.left.map(error => error.getMessage),
- {
- case JsBoolean(true) => JsSuccess(true)
- case JsBoolean(false) => JsError("mailboxId value can only be true")
- case _ => JsError("Expecting mailboxId value to be a boolean")
- })
+ Reads.mapReads[MailboxId, Boolean] {s => Try(mailboxIdFactory.fromString(s)).fold(e => JsError(e.getMessage), JsSuccess(_)) } (mapMarkerReads)
private implicit val mailboxIdsReads: Reads[MailboxIds] = jsValue => mailboxIdsMapReads.reads(jsValue).map(
mailboxIdsMap => MailboxIds(mailboxIdsMap.keys.toList))
@@ -189,18 +184,10 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
}
private implicit val updatesMapReads: Reads[Map[UnparsedMessageId, JsObject]] =
- readMapEntry[UnparsedMessageId, JsObject](s => refineV[UnparsedMessageIdConstraint](s),
- {
- case o: JsObject => JsSuccess(o)
- case _ => JsError("Expecting a JsObject as an update entry")
- })
+ Reads.mapReads[UnparsedMessageId, JsObject] {string => refineV[UnparsedMessageIdConstraint](string).fold(JsError(_), id => JsSuccess(id)) }
private implicit val createsMapReads: Reads[Map[EmailCreationId, JsObject]] =
- readMapEntry[EmailCreationId, JsObject](s => refineV[IdConstraint](s),
- {
- case o: JsObject => JsSuccess(o)
- case _ => JsError("Expecting a JsObject as an update entry")
- })
+ Reads.mapReads[EmailCreationId, JsObject] {s => refineV[IdConstraint](s).fold(JsError(_), JsSuccess(_)) }
private implicit val keywordReads: Reads[Keyword] = {
case jsString: JsString => Keyword.parse(jsString.value)
@@ -210,12 +197,7 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
}
private implicit val keywordsMapReads: Reads[Map[Keyword, Boolean]] =
- readMapEntry[Keyword, Boolean](s => Keyword.parse(s),
- {
- case JsBoolean(true) => JsSuccess(true)
- case JsBoolean(false) => JsError("keyword value can only be true")
- case _ => JsError("Expecting keyword value to be a boolean")
- })
+ Reads.mapReads[Keyword, Boolean] {string => Keyword.parse(string).fold(JsError(_), JsSuccess(_)) } (mapMarkerReads)
private implicit val keywordsReads: Reads[Keywords] = jsValue => keywordsMapReads.reads(jsValue).flatMap(
keywordsMap => STRICT_KEYWORDS_FACTORY.fromSet(keywordsMap.keys.toSet)
.fold(e => JsError(e.getMessage), keywords => JsSuccess(keywords)))
@@ -227,6 +209,10 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
private implicit val destroyIdsWrites: Writes[DestroyIds] = Json.valueWrites[DestroyIds]
private implicit val emailRequestSetReads: Reads[EmailSetRequest] = Json.reads[EmailSetRequest]
private implicit val emailCreationResponseWrites: Writes[EmailCreationResponse] = Json.writes[EmailCreationResponse]
+ private implicit val createsMapWrites: Writes[Map[EmailCreationId, EmailCreationResponse]] =
+ mapWrites[EmailCreationId, EmailCreationResponse](_.value, emailCreationResponseWrites)
+ private implicit val notCreatedMapWrites: Writes[Map[EmailCreationId, SetError]] =
+ mapWrites[EmailCreationId, SetError](_.value, setErrorWrites)
private implicit val emailResponseSetWrites: OWrites[EmailSetResponse] = Json.writes[EmailSetResponse]
private implicit val subjectReads: Reads[Subject] = Json.valueReads[Subject]
@@ -248,17 +234,21 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
private implicit val clientEmailBodyValueReads: Reads[ClientEmailBodyValue] = Json.reads[ClientEmailBodyValue]
private implicit val typeReads: Reads[Type] = Json.valueReads[Type]
private implicit val clientPartIdReads: Reads[ClientPartId] = Json.valueReads[ClientPartId]
+ private val rawHTMLReads: Reads[ClientHtmlBody] = Json.reads[ClientHtmlBody]
private implicit val clientHtmlBodyReads: Reads[ClientHtmlBody] = {
case JsObject(underlying) if underlying.contains("charset") => JsError("charset must not be specified in htmlBody")
case JsObject(underlying) if underlying.contains("size") => JsError("size must not be specified in htmlBody")
case JsObject(underlying) if underlying.contains("header:Content-Transfer-Encoding:asText") => JsError("Content-Transfer-Encoding must not be specified in htmlBody")
- case o: JsObject => Json.reads[ClientHtmlBody].reads(o)
+ case o: JsObject => rawHTMLReads.reads(o)
case _ => JsError("Expecting a JsObject to represent an ClientHtmlBody")
}
private implicit val bodyValuesReads: Reads[Map[ClientPartId, ClientEmailBodyValue]] =
- readMapEntry[ClientPartId, ClientEmailBodyValue](s => Id.validate(s).fold(e => Left(e.getMessage), partId => Right(ClientPartId(partId))),
- clientEmailBodyValueReads)
+ Reads.mapReads[ClientPartId, ClientEmailBodyValue] {
+ s => Id.validate(s).fold(
+ e => JsError(e.getMessage),
+ partId => JsSuccess(ClientPartId(partId)))
+ }
case class EmailCreationRequestWithoutHeaders(mailboxIds: MailboxIds,
messageId: Option[MessageIdsHeaderValue],
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSubmissionSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSubmissionSetSerializer.scala
index 645e90e..2d5460a 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSubmissionSetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSubmissionSetSerializer.scala
@@ -34,11 +34,7 @@ import scala.util.Try
class EmailSubmissionSetSerializer @Inject()(messageIdFactory: MessageId.Factory) {
private implicit val mapCreationRequestByEmailSubmissionCreationId: Reads[Map[EmailSubmissionCreationId, JsObject]] =
- readMapEntry[EmailSubmissionCreationId, JsObject](s => refineV[IdConstraint](s),
- {
- case o: JsObject => JsSuccess(o)
- case _ => JsError("Expecting a JsObject as a creation entry")
- })
+ Reads.mapReads[EmailSubmissionCreationId, JsObject] {string => refineV[IdConstraint](string).fold(JsError(_), id => JsSuccess(id)) }
private implicit val messageIdReads: Reads[MessageId] = {
case JsString(serializedMessageId) => Try(JsSuccess(messageIdFactory.fromString(serializedMessageId)))
@@ -54,11 +50,7 @@ class EmailSubmissionSetSerializer @Inject()(messageIdFactory: MessageId.Factory
}
private implicit val emailUpdatesMapReads: Reads[Map[UnparsedMessageId, JsObject]] =
- readMapEntry[UnparsedMessageId, JsObject](s => refineV[UnparsedMessageIdConstraint](s),
- {
- case o: JsObject => JsSuccess(o)
- case _ => JsError("Expecting a JsObject as an update entry")
- })
+ Reads.mapReads[UnparsedMessageId, JsObject] {string => refineV[UnparsedMessageIdConstraint](string).fold(JsError(_), id => JsSuccess(id)) }
private implicit val destroyIdsReads: Reads[DestroyIds] = Json.valueFormat[DestroyIds]
private implicit val emailSubmissionSetRequestReads: Reads[EmailSubmissionSetRequest] = Json.reads[EmailSubmissionSetRequest]
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
index a10a316..8110aaf 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
@@ -19,7 +19,7 @@
package org.apache.james.jmap.json
-import org.apache.james.jmap.core.{AccountId, CanCalculateChanges, QueryState}
+import org.apache.james.jmap.core.{CanCalculateChanges, QueryState}
import org.apache.james.jmap.mail.{MailboxFilter, MailboxQueryRequest, MailboxQueryResponse}
import org.apache.james.mailbox.Role
import org.apache.james.mailbox.model.MailboxId
@@ -29,7 +29,6 @@ import scala.jdk.OptionConverters._
import scala.language.implicitConversions
object MailboxQuerySerializer {
- private implicit val accountIdWrites: Format[AccountId] = Json.valueFormat[AccountId]
private implicit val canCalculateChangeWrites: Writes[CanCalculateChanges] = Json.valueWrites[CanCalculateChanges]
private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
@@ -56,7 +55,7 @@ object MailboxQuerySerializer {
private implicit val emailQueryRequestReads: Reads[MailboxQueryRequest] = Json.reads[MailboxQueryRequest]
private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
- private implicit def mailboxQueryResponseWrites: OWrites[MailboxQueryResponse] = Json.writes[MailboxQueryResponse]
+ private implicit val mailboxQueryResponseWrites: OWrites[MailboxQueryResponse] = Json.writes[MailboxQueryResponse]
def serialize(mailboxQueryResponse: MailboxQueryResponse): JsObject = Json.toJsObject(mailboxQueryResponse)
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
index 8a0970e..20697ef 100644
--- 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
@@ -83,6 +83,9 @@ class MailboxSerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
}
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 rightSeqWrites: Writes[Seq[Right]] = seq => JsArray(seq.map(rightWrites.writes))
+ private implicit val rightsMapWrites: Writes[Map[Username, Seq[Right]]] =
+ mapWrites[Username, Seq[Right]](_.asString(), rightSeqWrites)
private implicit val rightsWrites: Writes[Rights] = Json.valueWrites[Rights]
private implicit val mapRightsReads: Reads[Map[Username, Seq[Right]]] = _.validate[Map[String, Seq[Right]]]
@@ -90,30 +93,21 @@ class MailboxSerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
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 quotaMapWrites: Writes[Map[Quotas.Type, Value]] =
+ mapWrites[Quotas.Type, Value](_.toString, quotaValueWrites)
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 quotasMapWrites: Writes[Map[QuotaId, Quota]] =
+ mapWrites[QuotaId, Quota](_.getName, quotaWrites)
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 val mailboxWrites: Writes[Mailbox] = Json.writes[Mailbox]
- 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(_))
+ implicit val mailboxCreationResponseWrites: Writes[MailboxCreationResponse] = Json.writes[MailboxCreationResponse]
private implicit val idsRead: Reads[Ids] = Json.valueReads[Ids]
@@ -124,59 +118,62 @@ class MailboxSerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
private implicit val mailboxPatchObject: Reads[MailboxPatchObject] = Json.valueReads[MailboxPatchObject]
private implicit val mapPatchObjectByMailboxIdReads: Reads[Map[UnparsedMailboxId, MailboxPatchObject]] =
- readMapEntry[UnparsedMailboxId, MailboxPatchObject](s => refineV[UnparsedMailboxIdConstraint](s),
- mailboxPatchObject)
+ Reads.mapReads[UnparsedMailboxId, MailboxPatchObject] {string => refineV[UnparsedMailboxIdConstraint](string).fold(JsError(_), id => JsSuccess(id)) }
private implicit val mapCreationRequestByMailBoxCreationId: Reads[Map[MailboxCreationId, JsObject]] =
- readMapEntry[MailboxCreationId, JsObject](s => refineV[NonEmpty](s),
- {
- case o: JsObject => JsSuccess(o)
- case _ => JsError("Expecting a JsObject as a creation entry")
- })
+ Reads.mapReads[MailboxCreationId, JsObject] {string => refineV[NonEmpty](string).fold(JsError(_), id => JsSuccess(id)) }
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 val notFoundWrites: Writes[NotFound] = Json.valueWrites[NotFound]
- private implicit def mailboxGetResponseWrites(implicit mailboxWrites: Writes[Mailbox]): Writes[MailboxGetResponse] = Json.writes[MailboxGetResponse]
+ private implicit val mailboxGetResponseWrites: 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]] =
+ private implicit val mailboxMapSetErrorForCreationWrites: Writes[Map[MailboxCreationId, SetError]] =
mapWrites[MailboxCreationId, SetError](_.value, setErrorWrites)
- private implicit def mailboxMapSetErrorWrites: Writes[Map[MailboxId, SetError]] =
+ private implicit val mailboxMapSetErrorWrites: Writes[Map[MailboxId, SetError]] =
mapWrites[MailboxId, SetError](_.serialize(), setErrorWrites)
- private implicit def mailboxMapSetErrorWritesByClientId: Writes[Map[ClientId, SetError]] =
+ private implicit val 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]] =
+ private implicit val mailboxMapCreationResponseWrites: Writes[Map[MailboxCreationId, MailboxCreationResponse]] =
+ mapWrites[MailboxCreationId, MailboxCreationResponse](_.value, mailboxCreationResponseWrites)
+ private implicit val 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 implicit val mailboxSetResponseWrites: Writes[MailboxSetResponse] = Json.writes[MailboxSetResponse]
- 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(mailbox: Mailbox): JsValue = Json.toJson(mailbox)
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))
+ Json.toJson(mailboxGetResponse)
+ .transform((__ \ "list").json.update {
+ case JsArray(underlying) => JsSuccess(JsArray(underlying.map {
+ case jsonObject: JsObject =>
+ Mailbox.propertiesFiltered(properties, capabilities)
+ .filter(jsonObject)
+ case jsValue => jsValue
+ }))
+ }).get
def serialize(mailboxSetResponse: MailboxSetResponse, capabilities: Set[CapabilityIdentifier]): JsValue =
- serialize(mailboxSetResponse)(mailboxCreationResponseWritesWithFilteredProperties(capabilities))
+ Json.toJson(mailboxSetResponse)
+ .transform[JsValue] {
+ case JsObject(underlying) => JsSuccess[JsValue](JsObject(underlying.map {
+ case ("created", createdEntry: JsObject) =>
+ ("created", createdEntry match {
+ case JsObject(createdEntries) => JsObject(createdEntries.map {
+ case (key, serializedMailbox: JsObject) => (key, MailboxCreationResponse.propertiesFiltered(capabilities).filter(serializedMailbox))
+ case (key, value) => (key, value)
+ })
+ case jsValue: JsValue => jsValue
+ })
+ case (key, value) => (key, value)
+ }))
+ case jsValue => JsSuccess[JsValue](jsValue)
+ }.get
def deserializeMailboxGetRequest(input: String): JsResult[MailboxGetRequest] = Json.parse(input).validate[MailboxGetRequest]
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
index b6038e0..6d69b4e 100644
--- 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
@@ -22,9 +22,11 @@ package org.apache.james.jmap.json
import java.io.InputStream
import java.net.URL
+import eu.timepit.refined.refineV
import org.apache.james.core.Username
import org.apache.james.jmap.core
import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
+import org.apache.james.jmap.core.Id.IdConstraint
import org.apache.james.jmap.core.Invocation.{Arguments, MethodCallId, MethodName}
import org.apache.james.jmap.core.SetError.SetErrorDescription
import org.apache.james.jmap.core.{Account, Invocation, Session, _}
@@ -38,23 +40,13 @@ 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 val createdIdsIdWrites: Writes[Map[ClientId, ServerId]] =
+ mapWrites[ClientId, ServerId](_.value.value, serverIdFormat)
- private implicit def createdIdsIdRead(implicit serverIdReader: Reads[ServerId]): Reads[Map[ClientId, ServerId]] =
- Reads.mapReads[ClientId, ServerId] {
- clientIdString => Json.fromJson[ClientId](JsString(clientIdString))
- }
+ private implicit val createdIdsIdRead: Reads[Map[ClientId, ServerId]] =
+ Reads.mapReads[ClientId, ServerId] { clientIdString => refineV[IdConstraint](clientIdString).fold(JsError(_), id => JsSuccess(ClientId(id)))}
+ private implicit val createdIdsFormat: Format[CreatedIds] = Json.valueFormat[CreatedIds]
// Invocation
private implicit val methodNameFormat: Format[MethodName] = Json.valueFormat[MethodName]
@@ -97,16 +89,12 @@ object ResponseSerializer {
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]] =
+ private implicit val setCapabilityWrites: 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))
+ jsObject.+(capability.identifier.value, coreCapabilityWrites.writes(capability.properties))
case capability: MailCapability =>
jsObject.+(capability.identifier.value, mailCapabilityWrites.writes(capability.properties))
case capability: QuotaCapability =>
@@ -122,8 +110,8 @@ object ResponseSerializer {
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 identifierMapWrite: Writes[Map[CapabilityIdentifier, AccountId]] =
+ mapWrites[CapabilityIdentifier, AccountId](_.value, accountIdWrites)
private implicit val isPersonalFormat: Format[IsPersonal] = Json.valueFormat[IsPersonal]
private implicit val isReadOnlyFormat: Format[IsReadOnly] = Json.valueFormat[IsReadOnly]
@@ -134,7 +122,7 @@ object ResponseSerializer {
(JsPath \ Account.ACCOUNT_CAPABILITIES).write[Set[_ <: Capability]]
) (unlift(Account.unapplyIgnoreAccountId))
- private implicit def accountListWrites(implicit accountWrites: Writes[Account]): Writes[List[Account]] =
+ private implicit val accountListWrites: 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]
@@ -148,12 +136,12 @@ object ResponseSerializer {
private implicit val jsonValidationErrorWrites: Writes[JsonValidationError] = error => JsString(error.message)
- private implicit def jsonValidationErrorsWrites(implicit jsonValidationErrorWrites: Writes[JsonValidationError]): Writes[LegacySeq[JsonValidationError]] =
+ private implicit val jsonValidationErrorsWrites: 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])]] =
+ private implicit val errorsWrites: Writes[LegacySeq[(JsPath, LegacySeq[JsonValidationError])]] =
(errors: LegacySeq[(JsPath, LegacySeq[JsonValidationError])]) => {
errors.foldLeft(JsArray.empty)((jsArray, jsError) => {
val (path: JsPath, list: LegacySeq[JsonValidationError]) = jsError
@@ -163,7 +151,7 @@ object ResponseSerializer {
})
}
- private implicit def jsErrorWrites: Writes[JsError] = Json.writes[JsError]
+ private implicit val jsErrorWrites: Writes[JsError] = Json.writes[JsError]
private implicit val problemDetailsWrites: Writes[ProblemDetails] = Json.writes[ProblemDetails]
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
index 3a3ecbd..102e16a 100644
--- 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
@@ -19,11 +19,9 @@
package org.apache.james.jmap.json
-import java.time.format.DateTimeFormatter
-
-import org.apache.james.jmap.core.{Properties, UTCDate}
+import org.apache.james.jmap.core.Properties
import org.apache.james.jmap.mail.Subject
-import org.apache.james.jmap.vacation.VacationResponse.{UnparsedVacationResponseId, VACATION_RESPONSE_ID}
+import org.apache.james.jmap.vacation.VacationResponse.VACATION_RESPONSE_ID
import org.apache.james.jmap.vacation.{FromDate, HtmlBody, IsEnabled, TextBody, ToDate, VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseId, VacationResponseIds, VacationResponseNotFound, VacationResponsePatchObject, VacationResponseSetError, VacationResponseSetRequest, VacationResponseSetResponse, VacationResponseUpdateResponse}
import play.api.libs.json._
@@ -43,9 +41,6 @@ object VacationSerializer {
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 vacationResponseIdReads: Reads[VacationResponseId] = {
case JsString("singleton") => JsSuccess(VacationResponseId())
@@ -59,29 +54,33 @@ object VacationSerializer {
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 vacationResponseWrites: Writes[VacationResponse] = Json.writes[VacationResponse]
private implicit val vacationResponseIdsReads: 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 implicit val vacationResponseNotFoundWrites: Writes[VacationResponseNotFound] =
+ notFound => JsArray(notFound.value.toList.map(id => JsString(id.value)))
- private def vacationResponseWritesWithFilteredProperties(properties: Properties): Writes[VacationResponse] =
- vacationResponseWrites(VacationResponse.propertiesFiltered(properties))
+ private implicit val vacationResponseGetResponseWrites: Writes[VacationResponseGetResponse] = Json.writes[VacationResponseGetResponse]
- def serialize(vacationResponse: VacationResponse)(implicit vacationResponseWrites: Writes[VacationResponse]): JsValue = Json.toJson(vacationResponse)
+ def serialize(vacationResponse: VacationResponse): JsValue = Json.toJson(vacationResponse)
def serialize(vacationResponseGetResponse: VacationResponseGetResponse)(implicit vacationResponseWrites: Writes[VacationResponse]): JsValue =
- Json.toJson(vacationResponseGetResponse)
+ serialize(vacationResponseGetResponse, VacationResponse.allProperties)
def serialize(vacationResponseGetResponse: VacationResponseGetResponse, properties: Properties): JsValue =
- serialize(vacationResponseGetResponse)(vacationResponseWritesWithFilteredProperties(properties))
+ Json.toJson(vacationResponseGetResponse)
+ .transform((__ \ "list").json.update {
+ case JsArray(underlying) => JsSuccess(JsArray(underlying.map {
+ case jsonObject: JsObject =>
+ VacationResponse.propertiesFiltered(properties)
+ .filter(jsonObject)
+ case jsValue => jsValue
+ }))
+ }).get
+
def serialize(vacationResponseSetResponse: VacationResponseSetResponse): JsValue = Json.toJson(vacationResponseSetResponse)
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 8b05ba9..536a620 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
@@ -24,7 +24,6 @@ import java.time.format.DateTimeFormatter
import eu.timepit.refined.api.{RefType, Validate}
import org.apache.james.core.MailAddress
-import org.apache.james.jmap.core.Id.Id
import org.apache.james.jmap.core.SetError.SetErrorDescription
import org.apache.james.jmap.core.{AccountId, Properties, SetError, UTCDate}
import play.api.libs.json._
@@ -32,6 +31,17 @@ import play.api.libs.json._
import scala.util.{Failure, Success, Try}
package object json {
+ implicit val jsObjectReads: Reads[JsObject] = {
+ case o: JsObject => JsSuccess(o)
+ case _ => JsError("Expecting a JsObject as a creation entry")
+ }
+
+ val mapMarkerReads: Reads[Boolean] = {
+ case JsBoolean(true) => JsSuccess(true)
+ case JsBoolean(false) => JsError("map marker value can only be true")
+ case _ => JsError("Expecting mailboxId value to be a boolean")
+ }
+
def mapWrites[K, V](keyWriter: K => String, valueWriter: Writes[V]): Writes[Map[K, V]] =
(ids: Map[K, V]) => {
ids.foldLeft(JsObject.empty)((jsObject, kv) => {
@@ -40,31 +50,6 @@ package object json {
})
}
- def readMapEntry[K, V](keyValidator: String => Either[String, K], valueReads: Reads[V]): Reads[Map[K, V]] =
- _.validate[Map[String, JsValue]]
- .flatMap(mapWithStringKey =>{
- val firstAcc = scala.util.Right[JsError, Map[K, V]](Map.empty)
- mapWithStringKey
- .foldLeft[Either[JsError, Map[K, V]]](firstAcc)((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) =>
- val transformedValue: JsResult[V] = valueReads.reads(keyValue._2)
- transformedValue.fold(
- error => Left(JsError(error)),
- v => scala.util.Right(validatedAcc + (unparsedK -> v)))
- }
- }
- }) 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],
@@ -85,11 +70,6 @@ package object json {
}
})
- implicit def idMapWrite[Any](implicit vr: Writes[Any]): Writes[Map[Id, Any]] =
- (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 {
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 e7d2c8f..1e34ed7 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
@@ -58,8 +58,7 @@ object JMAPApiRoutes {
class JMAPApiRoutes (val authenticator: Authenticator,
userProvisioner: UserProvisioning,
mailboxesProvisioner: MailboxesProvisioner,
- methods: Set[Method],
- sessionSupplier: SessionSupplier) extends JMAPRoutes {
+ methods: Set[Method]) extends JMAPRoutes {
private val methodsByName: Map[MethodName, Method] = methods.map(method => method.methodName -> method).toMap
@@ -67,9 +66,8 @@ class JMAPApiRoutes (val authenticator: Authenticator,
def this(@Named(InjectionKeys.RFC_8621) authenticator: Authenticator,
userProvisioner: UserProvisioning,
mailboxesProvisioner: MailboxesProvisioner,
- javaMethods: java.util.Set[Method],
- sessionSupplier: SessionSupplier) {
- this(authenticator, userProvisioner, mailboxesProvisioner, javaMethods.asScala.toSet, sessionSupplier)
+ javaMethods: java.util.Set[Method]) {
+ this(authenticator, userProvisioner, mailboxesProvisioner, javaMethods.asScala.toSet)
}
override def routes(): stream.Stream[JMAPRoute] = Stream.of(
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 f39412c..55a4a7b 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
@@ -21,7 +21,7 @@ package org.apache.james.jmap.json
import eu.timepit.refined.auto._
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
-import org.apache.james.jmap.core.{AccountId, Properties}
+import org.apache.james.jmap.core.{AccountId, DefaultCapabilities, Properties}
import org.apache.james.jmap.json.Fixture._
import org.apache.james.jmap.json.MailboxGetSerializationTest._
import org.apache.james.jmap.json.MailboxSerializationTest.MAILBOX
@@ -196,7 +196,7 @@ class MailboxGetSerializationTest extends AnyWordSpec with Matchers {
|}
|""".stripMargin
- assertThatJson(Json.stringify(SERIALIZER.serialize(actualValue)(SERIALIZER.mailboxWrites(Mailbox.allProperties)))).isEqualTo(expectedJson)
+ assertThatJson(Json.stringify(SERIALIZER.serialize(actualValue, Mailbox.allProperties, DefaultCapabilities.SUPPORTED_CAPABILITY_IDENTIFIERS))).isEqualTo(expectedJson)
}
}
}
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 0f825df..c9f5891 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
@@ -130,7 +130,7 @@ class MailboxSerializationTest extends AnyWordSpec with Matchers {
|}""".stripMargin
val serializer = new MailboxSerializer(new TestId.Factory)
- assertThatJson(Json.stringify(serializer.serialize(MAILBOX)(serializer.mailboxWrites(Mailbox.allProperties)))).isEqualTo(expectedJson)
+ assertThatJson(Json.stringify(serializer.serialize(MAILBOX))).isEqualTo(expectedJson)
}
}
}
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 b37910f..dda1b9a 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
@@ -175,7 +175,7 @@ class VacationResponseGetSerializationTest extends AnyWordSpec with Matchers {
|}
|""".stripMargin
- assertThatJson(Json.stringify(VacationSerializer.serialize(actualValue)(VacationSerializer.vacationResponseWrites(VacationResponse.allProperties)))).isEqualTo(expectedJson)
+ assertThatJson(Json.stringify(VacationSerializer.serialize(actualValue, 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 b805538..0a6e8a1 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
@@ -68,7 +68,7 @@ class VacationResponseSerializationTest extends AnyWordSpec with Matchers {
| "htmlBody":"<b>HTML body</b>"
|}""".stripMargin
- assertThatJson(Json.stringify(VacationSerializer.serialize(VACATION_RESPONSE)(VacationSerializer.vacationResponseWrites(VacationResponse.allProperties)))).isEqualTo(expectedJson)
+ assertThatJson(Json.stringify(VacationSerializer.serialize(VACATION_RESPONSE))).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 526da86..6bbce45 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
@@ -78,7 +78,7 @@ object JMAPApiRoutesTest {
private val sessionSupplier: SessionSupplier = new SessionSupplier(JmapRfc8621Configuration(JmapRfc8621Configuration.LOCALHOST_URL_PREFIX))
private val JMAP_METHODS: Set[Method] = Set(new CoreEchoMethod)
- private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, mailboxesProvisioner, JMAP_METHODS, sessionSupplier)
+ 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))
@@ -442,7 +442,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
when(mockCoreEchoMethod.requiredCapabilities).thenReturn(Set(JMAP_CORE))
val methods: Set[Method] = Set(mockCoreEchoMethod)
- val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, mailboxesProvisioner, methods, sessionSupplier)
+ 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