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 93c30a0694961027cc8276da369f4d28c9530cfe Author: Rene Cordier <[email protected]> AuthorDate: Tue Sep 22 15:55:16 2020 +0700 JAMES-3384 allInThreadHaveKeyword, someInThreadHaveKeyword and noneInThreadHaveKeyword parameters are unsupported for filtering on Email/query --- .../contract/EmailQueryMethodContract.scala | 45 +++++++ .../org/apache/james/jmap/mail/EmailQuery.scala | 6 +- .../james/jmap/method/EmailQueryMethod.scala | 12 +- .../org/apache/james/jmap/model/Invocation.scala | 4 + .../james/jmap/utils/search/MailboxFilter.scala | 137 +++++++++++++-------- 5 files changed, 146 insertions(+), 58 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/EmailQueryMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala index ebf740d..c835a00 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala @@ -913,6 +913,51 @@ trait EmailQueryMethodContract { @ParameterizedTest @ValueSource(strings = Array( + "allInThreadHaveKeyword", + "someInThreadHaveKeyword", + "noneInThreadHaveKeyword" + )) + def listMailsShouldReturnUnsupportedFilterWhenValidButUnsupported(unsupportedFilter: String): Unit = { + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "filter" : { + | "$unsupportedFilter": "abc" + | } + | }, + | "c1"]] + |}""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("$.methodResponses[0][1]") + .isEqualTo(s""" + { + "type": "unsupportedFilter", + "description": "The filter $unsupportedFilter is syntactically valid, but the server cannot process it. If the filter was the result of a user’s search input, the client SHOULD suggest that the user simplify their search." + } + """) + } + + @ParameterizedTest + @ValueSource(strings = Array( "true", "false" )) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala index 6fa3ef6..f6f3041 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala @@ -28,6 +28,7 @@ import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause import org.apache.james.mailbox.model.{MailboxId, MessageId, SearchQuery} case class UnsupportedSortException(unsupportedSort: String) extends UnsupportedOperationException +case class UnsupportedFilterException(unsupportedFilter: String) extends UnsupportedOperationException case class FilterCondition(inMailbox: Option[MailboxId], inMailboxOtherThan: Option[Seq[MailboxId]], @@ -37,7 +38,10 @@ case class FilterCondition(inMailbox: Option[MailboxId], notKeyword: Option[Keyword], minSize: Option[Size], maxSize: Option[Size], - hasAttachment: Option[HasAttachment]) + hasAttachment: Option[HasAttachment], + allInThreadHaveKeyword: Option[Keyword], + someInThreadHaveKeyword: Option[Keyword], + noneInThreadHaveKeyword: Option[Keyword]) case class EmailQueryRequest(accountId: AccountId, position: Option[PositionUnparsed], 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 196b3f8..0c4e16a 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 @@ -22,7 +22,7 @@ import cats.implicits._ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer} -import org.apache.james.jmap.mail.{Comparator, EmailQueryRequest, EmailQueryResponse, UnsupportedSortException} +import org.apache.james.jmap.mail.{Comparator, EmailQueryRequest, EmailQueryResponse, UnsupportedFilterException, UnsupportedSortException} 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} @@ -57,6 +57,10 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer, ErrorCode.UnsupportedSort, s"The sort ${e.unsupportedSort} is syntactically valid, but it includes a property the server does not support sorting on or a collation method it does not recognise.", invocation.methodCallId)) + case e: UnsupportedFilterException => SMono.just(Invocation.error( + ErrorCode.UnsupportedFilter, + s"The filter ${e.unsupportedFilter} is syntactically valid, but the server cannot process it. If the filter was the result of a user’s search input, the client SHOULD suggest that the user simplify their search.", + invocation.methodCallId)) case e: IllegalArgumentException => SMono.just(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, invocation.methodCallId)) case e: MailboxNotFoundException => SMono.just(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, invocation.methodCallId)) case e: Throwable => SMono.raiseError(e) @@ -86,12 +90,14 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer, limit = Some(limitToUse).filterNot(used => request.limit.map(_.value).contains(used.value)))) } - private def searchQueryFromRequest(request: EmailQueryRequest): Either[UnsupportedSortException, MultimailboxesSearchQuery] = { + private def searchQueryFromRequest(request: EmailQueryRequest): Either[UnsupportedOperationException, MultimailboxesSearchQuery] = { val comparators: List[Comparator] = request.comparator.getOrElse(Set(Comparator.default)).toList comparators.map(_.toSort) .sequence - .map(sorts => QueryFilter.buildQuery(request) + .flatMap(sorts => for { + queryFilter <- QueryFilter.buildQuery(request) + } yield queryFilter .sorts(sorts.asJava) .build()) .map(MailboxFilter.buildQuery(request, _)) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala index 6bfdd43..0676d8c 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala @@ -73,4 +73,8 @@ object ErrorCode { case object UnsupportedSort extends ErrorCode { override def code: String = "unsupportedSort" } + + case object UnsupportedFilter extends ErrorCode { + override def code: String = "unsupportedFilter" + } } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala index 443996b..73fe224 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala @@ -20,7 +20,8 @@ package org.apache.james.jmap.utils.search import java.util.Date -import org.apache.james.jmap.mail.EmailQueryRequest +import cats.implicits._ +import org.apache.james.jmap.mail.{EmailQueryRequest, UnsupportedFilterException} import org.apache.james.mailbox.model.SearchQuery.DateResolution.Second import org.apache.james.mailbox.model.SearchQuery.{DateComparator, DateOperator, DateResolution, InternalDateCriterion} import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery} @@ -54,84 +55,112 @@ object MailboxFilter { } sealed trait QueryFilter { - def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): SearchQuery.Builder + def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] } object QueryFilter { - def buildQuery(request: EmailQueryRequest): SearchQuery.Builder = { - List(ReceivedBefore, ReceivedAfter, HasAttachment, HasKeyWord, NotKeyWord, MinSize, MaxSize) - .foldLeft(SearchQuery.builder())((builder, filter) => filter.toQuery(builder, request)) + def buildQuery(request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = { + List(ReceivedBefore, ReceivedAfter, HasAttachment, HasKeyWord, NotKeyWord, MinSize, MaxSize, + AllInThreadHaveKeyword, NoneInThreadHaveKeyword, SomeInThreadHaveKeyword) + .foldLeftM(new SearchQuery.Builder())((builder, filter) => filter.toQuery(builder, request)) } } case object ReceivedBefore extends QueryFilter { - override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): SearchQuery.Builder = request.filter.flatMap(_.before) match { - case Some(before) => - val strictlyBefore = SearchQuery.internalDateBefore(Date.from(before.asUTC.toInstant), Second) - val sameDate = SearchQuery.internalDateOn(Date.from(before.asUTC.toInstant), Second) - builder - .andCriteria(SearchQuery.or(strictlyBefore, sameDate)) - case None => builder - } + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.before) match { + case Some(before) => + val strictlyBefore = SearchQuery.internalDateBefore(Date.from(before.asUTC.toInstant), Second) + val sameDate = SearchQuery.internalDateOn(Date.from(before.asUTC.toInstant), Second) + Right(builder + .andCriteria(SearchQuery.or(strictlyBefore, sameDate))) + case None => Right(builder) + } } case object ReceivedAfter extends QueryFilter { - override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): SearchQuery.Builder = request.filter.flatMap(_.after) match { - case Some(after) => - val strictlyAfter = new InternalDateCriterion(new DateOperator(DateComparator.AFTER, Date.from(after.asUTC.toInstant), DateResolution.Second)) - builder - .andCriteria(strictlyAfter) - case None => builder - } + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.after) match { + case Some(after) => + val strictlyAfter = new InternalDateCriterion(new DateOperator(DateComparator.AFTER, Date.from(after.asUTC.toInstant), DateResolution.Second)) + Right(builder + .andCriteria(strictlyAfter)) + case None => Right(builder) + } } case object HasAttachment extends QueryFilter { - override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): SearchQuery.Builder = + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = request.filter.flatMap(_.hasAttachment) match { - case Some(hasAttachment) => builder - .andCriteria(SearchQuery.hasAttachment(hasAttachment.value)) - case None => builder + case Some(hasAttachment) => Right(builder + .andCriteria(SearchQuery.hasAttachment(hasAttachment.value))) + case None => Right(builder) } } case object MinSize extends QueryFilter { - override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): SearchQuery.Builder = request.filter.flatMap(_.minSize) match { - case Some(minSize) => - builder - .andCriteria(SearchQuery.or( - SearchQuery.sizeGreaterThan(minSize.value), - SearchQuery.sizeEquals(minSize.value))) - case None => builder - } + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.minSize) match { + case Some(minSize) => + Right(builder + .andCriteria(SearchQuery.or( + SearchQuery.sizeGreaterThan(minSize.value), + SearchQuery.sizeEquals(minSize.value)))) + case None => Right(builder) + } } case object MaxSize extends QueryFilter { - override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): SearchQuery.Builder = request.filter.flatMap(_.maxSize) match { - case Some(maxSize) => - builder - .andCriteria(SearchQuery.sizeLessThan(maxSize.value)) - case None => builder - } + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.maxSize) match { + case Some(maxSize) => + Right(builder + .andCriteria(SearchQuery.sizeLessThan(maxSize.value))) + case None => Right(builder) + } } case object HasKeyWord extends QueryFilter { - override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): SearchQuery.Builder = request.filter.flatMap(_.hasKeyword) match { - case Some(keyword) => - keyword.asSystemFlag match { - case Some(systemFlag) => builder.andCriteria(SearchQuery.flagIsSet(systemFlag)) - case None => builder.andCriteria(SearchQuery.flagIsSet(keyword.flagName)) - } - case None => builder - } + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.hasKeyword) match { + case Some(keyword) => + keyword.asSystemFlag match { + case Some(systemFlag) => Right(builder.andCriteria(SearchQuery.flagIsSet(systemFlag))) + case None => Right(builder.andCriteria(SearchQuery.flagIsSet(keyword.flagName))) + } + case None => Right(builder) + } } case object NotKeyWord extends QueryFilter { - override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): SearchQuery.Builder = request.filter.flatMap(_.notKeyword) match { - case Some(keyword) => - keyword.asSystemFlag match { - case Some(systemFlag) => builder.andCriteria(SearchQuery.flagIsUnSet(systemFlag)) - case None => builder.andCriteria(SearchQuery.flagIsUnSet(keyword.flagName)) - } - case None => builder - } + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.notKeyword) match { + case Some(keyword) => + keyword.asSystemFlag match { + case Some(systemFlag) => Right(builder.andCriteria(SearchQuery.flagIsUnSet(systemFlag))) + case None => Right(builder.andCriteria(SearchQuery.flagIsUnSet(keyword.flagName))) + } + case None => Right(builder) + } + } + case object AllInThreadHaveKeyword extends QueryFilter { + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.allInThreadHaveKeyword) match { + case Some(_) => Left(UnsupportedFilterException("allInThreadHaveKeyword")) + case None => Right(builder) + } + } + case object NoneInThreadHaveKeyword extends QueryFilter { + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.noneInThreadHaveKeyword) match { + case Some(_) => Left(UnsupportedFilterException("noneInThreadHaveKeyword")) + case None => Right(builder) + } + } + case object SomeInThreadHaveKeyword extends QueryFilter { + override def toQuery(builder: SearchQuery.Builder, request: EmailQueryRequest): Either[UnsupportedFilterException, SearchQuery.Builder] = + request.filter.flatMap(_.someInThreadHaveKeyword) match { + case Some(_) => Left(UnsupportedFilterException("someInThreadHaveKeyword")) + case None => Right(builder) + } } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
