Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return Exit for methods in Pagination #2298

Merged
merged 2 commits into from
Jun 23, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 43 additions & 54 deletions core/src/main/scala/caliban/relay/PaginationArgs.scala
Original file line number Diff line number Diff line change
@@ -1,89 +1,84 @@
package caliban.relay

import caliban.CalibanError
import zio._
import zio.Exit
import zio.prelude.Validation

object Pagination {
import PaginationCount._
import PaginationCursor._

def apply[C: Cursor](
args: PaginationArgs[C]
): ZIO[Any, CalibanError, Pagination[C]] =
def apply[C: Cursor](args: PaginationArgs[C]): Exit[CalibanError, Pagination[C]] =
apply(args.first, args.last, args.before, args.after)

def apply[C: Cursor](
args: ForwardPaginationArgs[C]
): ZIO[Any, CalibanError, Pagination[C]] =
def apply[C: Cursor](args: ForwardPaginationArgs[C]): Exit[CalibanError, Pagination[C]] =
(args.first match {
case None => ZIO.fail(s"first cannot be empty")
case Some(a) => validatePositive("first", a).map(First(_))
case None => Validation.fail("first cannot be empty")
case Some(a) => validatePositive("first", a).map(First.apply)
})
.validate(args.after match {
case None => ZIO.succeed(NoCursor)
case Some(x) => ZIO.fromEither(Cursor[C].decode(x)).map(After(_))
})
.map { case (count, cursor) => new Pagination[C](count, cursor) }
.parallelErrors
.mapError((errors: ::[String]) => CalibanError.ExecutionError(msg = errors.mkString(", ")))
.zipWith(args.after match {
case None => Validation.succeed(NoCursor)
case Some(x) => Validation.fromEither(Cursor[C].decode(x)).map(After.apply)
})(new Pagination[C](_, _))
.toExit

def apply[C: Cursor](
args: BackwardPaginationArgs[C]
): ZIO[Any, CalibanError, Pagination[C]] =
def apply[C: Cursor](args: BackwardPaginationArgs[C]): Exit[CalibanError, Pagination[C]] =
(args.last match {
case None => ZIO.fail(s"last cannot be empty")
case Some(a) => validatePositive("last", a).map(Last(_))
case None => Validation.fail("last cannot be empty")
case Some(a) => validatePositive("last", a).map(Last.apply)
})
.validate(args.before match {
case None => ZIO.succeed(NoCursor)
case Some(x) => ZIO.fromEither(Cursor[C].decode(x)).map(Before(_))
})
.map { case (count, cursor) => new Pagination[C](count, cursor) }
.parallelErrors
.mapError((errors: ::[String]) => CalibanError.ExecutionError(msg = errors.mkString(", ")))
.zipWith(args.before match {
case None => Validation.succeed(NoCursor)
case Some(x) => Validation.fromEither(Cursor[C].decode(x)).map(Before.apply)
})(new Pagination[C](_, _))
.toExit

def apply[C: Cursor](
first: Option[Int],
last: Option[Int],
before: Option[String],
after: Option[String]
): ZIO[Any, CalibanError, Pagination[C]] =
): Exit[CalibanError, Pagination[C]] =
validateFirstLast(first, last)
.validate(
validateCursors(before, after)
)
.map { case (count, cursor) => new Pagination[C](count, cursor) }
.parallelErrors
.mapError((errors: ::[String]) => CalibanError.ExecutionError(msg = errors.mkString(", ")))
.zipWith(validateCursors(before, after))(new Pagination[C](_, _))
.toExit

private def validateCursors[C: Cursor](
before: Option[String],
after: Option[String]
): ZIO[Any, String, PaginationCursor[C]] =
): Validation[String, PaginationCursor[C]] =
(before, after) match {
case (Some(_), Some(_)) =>
ZIO.fail("before and after cannot both be set")
Validation.fail("before and after cannot both be set")
case (Some(x), _) =>
ZIO.fromEither(Cursor[C].decode(x)).map(Before(_))
Validation.fromEither(Cursor[C].decode(x)).map(Before.apply)
case (_, Some(x)) =>
ZIO.fromEither(Cursor[C].decode(x)).map(After(_))
case (None, None) => ZIO.succeed(NoCursor)
Validation.fromEither(Cursor[C].decode(x)).map(After.apply)
case (None, None) => Validation.succeed(NoCursor)
}

private def validateFirstLast(first: Option[Int], last: Option[Int]) =
(first, last) match {
case (None, None) =>
ZIO.fail("first and last cannot both be empty")
Validation.fail("first and last cannot both be empty")
case (Some(_), Some(_)) =>
ZIO.fail("first and last cannot both be set")
Validation.fail("first and last cannot both be set")
case (Some(a), _) =>
validatePositive("first", a).map(First(_))
validatePositive("first", a).map(First.apply)
case (_, Some(b)) =>
validatePositive("last", b).map(Last(_))
validatePositive("last", b).map(Last.apply)
}

private def validatePositive(which: String, i: Int) =
ZIO.cond(i > -1, i, s"$which cannot be negative")
if (i > -1) Validation.succeed(i) else Validation.fail(s"$which cannot be negative")

private implicit class ValidationOps[E, A](private val v: Validation[String, A]) extends AnyVal {
def toExit: Exit[CalibanError, A] = {
val either = v.toEitherWith(errors => CalibanError.ExecutionError(msg = errors.mkString(", ")))
Exit.fromEither(either)
}
}

}

sealed trait PaginationCount extends Product with Serializable {
Expand Down Expand Up @@ -112,27 +107,21 @@ abstract class PaginationArgs[C: Cursor] { self =>
val before: Option[String]
val after: Option[String]

def toPagination: ZIO[Any, CalibanError, Pagination[C]] = Pagination(
self
)
def toPagination: Exit[CalibanError, Pagination[C]] = Pagination(self)
}

abstract class ForwardPaginationArgs[C: Cursor] { self =>
val first: Option[Int]
val after: Option[String]

def toPagination: ZIO[Any, CalibanError, Pagination[C]] = Pagination(
self
)
def toPagination: Exit[CalibanError, Pagination[C]] = Pagination(self)
}

abstract class BackwardPaginationArgs[C: Cursor] { self =>
val last: Option[Int]
val before: Option[String]

def toPagination: ZIO[Any, CalibanError, Pagination[C]] = Pagination(
self
)
def toPagination: Exit[CalibanError, Pagination[C]] = Pagination(self)
}

case class PaginationError(reason: String)