JAMES-3374: Add Sort by ReceivedAt + integration test
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 a262e44..7792c9a 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
@@ -20,8 +20,8 @@
package org.apache.james.jmap.rfc8621.contract
import java.nio.charset.StandardCharsets
-import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
+import java.time.{Instant, ZonedDateTime}
import java.util.Date
import java.util.concurrent.TimeUnit
import java.util.stream.Stream
@@ -47,6 +47,7 @@
import org.junit.jupiter.api.{BeforeEach, Test}
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.{Arguments, MethodSource}
+import org.threeten.extra.Seconds
object EmailQueryMethodContract {
def jmapSystemKeywords : Stream[Arguments] = {
@@ -59,6 +60,7 @@
}
}
+
trait EmailQueryMethodContract {
private lazy val slowPacedPollInterval = ONE_HUNDRED_MILLISECONDS
@@ -328,6 +330,230 @@
}
@Test
+ def listMailsShouldBeSortedInAscendingOrderByDefault(server: GuiceJamesServer): Unit = {
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+ val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+ val now: Instant = Instant.now()
+ val requestDateMessage1: Date = Date.from(now)
+ val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
+ .getMessageId
+ val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
+ val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
+ .getMessageId
+ val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
+ val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
+ .getMessageId
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/query",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "comparator": [{
+ | "property":"receivedAt"
+ | }]
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ awaitAtMostTenSeconds.untilAsserted { () =>
+ 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].ids")
+ .isEqualTo(s"""["${messageId1.serialize()}", "${messageId2.serialize()}", "${messageId3.serialize()}"]""")
+ }
+ }
+
+ @Test
+ def listMailsShouldBeSortedInAscendingOrder(server: GuiceJamesServer): Unit = {
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val now: Instant = Instant.now()
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+ val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+ val requestDateMessage1: Date = Date.from(now)
+ val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
+ .getMessageId
+ val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
+ val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
+ .getMessageId
+ val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
+ val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
+ .getMessageId
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/query",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "comparator": [{
+ | "property":"receivedAt",
+ | "isAscending": true
+ | }]
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ awaitAtMostTenSeconds.untilAsserted { () =>
+ 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].ids")
+ .isEqualTo(s"""["${messageId1.serialize()}", "${messageId2.serialize()}", "${messageId3.serialize()}"]""")
+ }
+ }
+
+ @Test
+ def listMailsShouldBeSortedInDescendingOrder(server: GuiceJamesServer): Unit = {
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val now: Instant = Instant.now()
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+ val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+ val requestDateMessage1: Date = Date.from(now)
+ val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
+ .getMessageId
+ val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
+ val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
+ .getMessageId
+ val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
+ val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
+ .getMessageId
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/query",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "comparator": [{
+ | "property":"receivedAt",
+ | "isAscending": false
+ | }]
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ awaitAtMostTenSeconds.untilAsserted { () =>
+ 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].ids")
+ .isEqualTo(s"""["${messageId3.serialize()}", "${messageId2.serialize()}", "${messageId1.serialize()}"]""")
+ }
+ }
+
+ @Test
+ def listMailsShouldReturnErrorWhenPropertyFieldInComparatorIsOmitted(server: GuiceJamesServer): Unit = {
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+ val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+ val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/query",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "filter": {
+ | "inMailbox": "${otherMailboxId.serialize()}"
+ | },
+ | "comparator": [{
+ | "isAscending":true
+ | }]
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ awaitAtMostTenSeconds.untilAsserted { () =>
+ 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("""
+ {
+ "type": "invalidArguments",
+ "description": "{\"errors\":[{\"path\":\"obj.comparator[0].property\",\"messages\":[\"error.path.missing\"]}]}"
+ }
+ """)
+ }
+ }
+
+ @Test
def shouldListMailsInASpecificUserMailboxes(server: GuiceJamesServer): Unit = {
val message: Message = Message.Builder
.of
@@ -380,6 +606,55 @@
}
@Test
+ def listMailsShouldReturnErrorWhenPropertyFieldInComparatorIsInvalid(server: GuiceJamesServer): Unit = {
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+ val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+ val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [[
+ | "Email/query",
+ | {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "filter": {
+ | "inMailbox": "${otherMailboxId.serialize()}"
+ | },
+ | "comparator": [{
+ | "property":"unsupported",
+ | "isAscending":true
+ | }]
+ | },
+ | "c1"]]
+ |}""".stripMargin
+
+ awaitAtMostTenSeconds.untilAsserted { () =>
+ 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("""
+ {
+ "type": "invalidArguments",
+ "description": "{\"errors\":[{\"path\":\"obj.comparator[0].property\",\"messages\":[\"'unsupported' is not a supported sort property\"]}]}"
+ }
+ """)
+ }
+ }
+
+ @Test
def shouldReturnIllegalArgumentErrorForAnUnknownSpecificUserMailboxes(server: GuiceJamesServer): Unit = {
val message: Message = Message.Builder
.of
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 83c8b0f..8251f96 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,8 +20,8 @@
package org.apache.james.jmap.json
import javax.inject.Inject
-import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, FilterCondition}
-import org.apache.james.jmap.model._
+import org.apache.james.jmap.mail.{CanCalculateChanges, Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, IsAscending, Limit, Position, QueryState, ReceivedAtSortProperty, SortProperty}
+import org.apache.james.jmap.model.{AccountId, Keyword}
import org.apache.james.mailbox.model.{MailboxId, MessageId}
import play.api.libs.json._
@@ -49,13 +49,28 @@
case _ => JsError("Expecting keywords to be represented by a JsString")
}
private implicit val filterConditionReads: Reads[FilterCondition] = Json.reads[FilterCondition]
- private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
- private implicit val canCalculateChangeWrites: Writes[CanCalculateChange] = Json.valueWrites[CanCalculateChange]
+ private implicit val CanCalculateChangesFormat: Format[CanCalculateChanges] = Json.valueFormat[CanCalculateChanges]
+
private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
private implicit val positionFormat: Format[Position] = Json.valueFormat[Position]
private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
private implicit val messageIdWrites: Writes[MessageId] = id => JsString(id.serialize())
+ private implicit val sortPropertyReads: Reads[SortProperty] = {
+ case JsString("receivedAt") => JsSuccess(ReceivedAtSortProperty)
+ case JsString(others) => JsError(s"'$others' is not a supported sort property")
+ case _ => JsError(s"Expecting a JsString to represent a sort property")
+ }
+ private implicit val sortPropertyWrites: Writes[SortProperty] = {
+ case ReceivedAtSortProperty => JsString("receivedAt")
+ }
+
+ private implicit val isAscendingFormat: Format[IsAscending] = Json.valueFormat[IsAscending]
+ private implicit val collationFormat: Format[Collation] = Json.valueFormat[Collation]
+ private implicit val comparatorFormat: Format[Comparator] = Json.format[Comparator]
+
+ private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
+
private implicit def 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/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
index 7e9a012..f5b282e 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
@@ -19,8 +19,13 @@
package org.apache.james.jmap.mail
-import org.apache.james.jmap.model.{AccountId, CanCalculateChange, Keyword, Limit, Position, QueryState, UTCDate}
-import org.apache.james.mailbox.model.{MailboxId, MessageId}
+import org.apache.james.jmap.model.{Keyword, UTCDate}
+import com.google.common.hash.Hashing
+import org.apache.james.jmap.mail.IsAscending.{ASCENDING, DESCENDING}
+import org.apache.james.jmap.model.AccountId
+import org.apache.james.mailbox.model.SearchQuery.Sort.Order.{NATURAL, REVERSE}
+import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause
+import org.apache.james.mailbox.model.{MailboxId, MessageId, SearchQuery}
case class FilterCondition(inMailbox: Option[MailboxId],
inMailboxOtherThan: Option[Seq[MailboxId]],
@@ -29,11 +34,65 @@
hasKeyword: Option[Keyword],
notKeyword: Option[Keyword])
-case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition])
+case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition], comparator: Option[Set[Comparator]])
+
+case class Position(value: Int) extends AnyVal
+object Position{
+ val zero: Position = Position(0)
+}
+case class Limit(value: Long) extends AnyVal
+object Limit {
+ val default: Limit = Limit(256L)
+}
+case class QueryState(value: String) extends AnyVal
+
+sealed trait SortProperty {
+ def toSortClause: SortClause
+}
+case object ReceivedAtSortProperty extends SortProperty {
+ override def toSortClause: SortClause = SortClause.Arrival
+}
+
+object IsAscending {
+ val DESCENDING: IsAscending = IsAscending(false)
+ val ASCENDING: IsAscending = IsAscending(true)
+}
+case class IsAscending(sortByASC: Boolean) extends AnyVal {
+ def toSortOrder: SearchQuery.Sort.Order = if (sortByASC) {
+ NATURAL
+ } else {
+ REVERSE
+ }
+}
+
+object Comparator {
+ val default: Comparator = Comparator(ReceivedAtSortProperty, Some(DESCENDING), None)
+}
+
+case class Collation(value: String) extends AnyVal
+
+case class Comparator(property: SortProperty,
+ isAscending: Option[IsAscending],
+ collation: Option[Collation]) {
+ def toSort: SearchQuery.Sort = new SearchQuery.Sort(property.toSortClause, isAscending.getOrElse(ASCENDING).toSortOrder)
+}
+
+object QueryState {
+ def forIds(ids: Seq[MessageId]): QueryState = QueryState(
+ Hashing.murmur3_32()
+ .hashUnencodedChars(ids.map(_.serialize()).mkString(" "))
+ .toString)
+}
+
+object CanCalculateChanges {
+ val CANT: CanCalculateChanges = CanCalculateChanges(false)
+}
+
+case class CanCalculateChanges(value: Boolean) extends AnyVal
case class EmailQueryResponse(accountId: AccountId,
queryState: QueryState,
- canCalculateChanges: CanCalculateChange,
+ canCalculateChanges: CanCalculateChanges,
ids: Seq[MessageId],
position: Position,
limit: Option[Limit])
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 32cbe3f..ab5ddb8 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
@@ -18,22 +18,18 @@
****************************************************************/
package org.apache.james.jmap.method
-import java.util.Date
-
import eu.timepit.refined.auto._
import javax.inject.Inject
import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer}
-import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse}
+import org.apache.james.jmap.mail.{CanCalculateChanges, Comparator, EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState}
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}
-import org.apache.james.jmap.model._
+import org.apache.james.jmap.model.{Capabilities, ErrorCode, Invocation}
import org.apache.james.jmap.routes.ProcessingContext
-import org.apache.james.mailbox.exception.MailboxNotFoundException
-import org.apache.james.jmap.utils.search.MailboxFilter
import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
-import org.apache.james.mailbox.model.SearchQuery.{Conjunction, ConjunctionCriterion, Criterion, DateComparator, DateOperator, DateResolution, InternalDateCriterion}
-import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause
+import org.apache.james.jmap.utils.search.MailboxFilter
+import org.apache.james.mailbox.exception.MailboxNotFoundException
import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
import org.apache.james.mailbox.{MailboxManager, MailboxSession}
import org.apache.james.metrics.api.MetricFactory
@@ -41,7 +37,7 @@
import play.api.libs.json.{JsError, JsSuccess}
import reactor.core.scala.publisher.{SFlux, SMono}
-import scala.collection.JavaConverters.seqAsJavaListConverter
+import scala.jdk.CollectionConverters._
class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer,
mailboxManager: MailboxManager,
@@ -61,12 +57,13 @@
.map(invocationResult => (invocationResult, processingContext)))
private def processRequest(mailboxSession: MailboxSession, invocation: Invocation, request: EmailQueryRequest): SMono[Invocation] = {
- val searchQuery = searchQueryFromRequest(request)
+ val searchQuery: MultimailboxesSearchQuery = searchQueryFromRequest(request)
+
SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, Limit.default.value))
.collectSeq()
.map(ids => EmailQueryResponse(accountId = request.accountId,
queryState = QueryState.forIds(ids),
- canCalculateChanges = CanCalculateChange(false),
+ canCalculateChanges = CanCalculateChanges.CANT,
ids = ids,
position = Position.zero,
limit = Some(Limit.default)))
@@ -74,11 +71,12 @@
}
private def searchQueryFromRequest(request: EmailQueryRequest): MultimailboxesSearchQuery = {
- val query = QueryFilter.buildQuery(request)
- val defaultSort = new SearchQuery.Sort(SortClause.Arrival, SearchQuery.Sort.Order.REVERSE)
- val querySorted = query.sorts(defaultSort)
+ val comparators: List[Comparator] = request.comparator.getOrElse(Set(Comparator.default)).toList
+ val sortedSearchQuery: SearchQuery = QueryFilter.buildQuery(request)
+ .sorts(comparators.map(_.toSort).asJava)
+ .build()
- MailboxFilter.buildQuery(request, querySorted.build())
+ MailboxFilter.buildQuery(request, sortedSearchQuery)
}
private def asEmailQueryRequest(arguments: Arguments): SMono[EmailQueryRequest] =