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]

Reply via email to