所以最后我实现了它:)
它使用'fallBackToNext',这是我们在代码库中已经拥有的一种方法,它的行为类似于fallBackTo,但带有一个异步lambda参数,因此只有在第一个future已经失败时才会执行下一个future(防止在不需要时发生大计算,但减少并行度)。
以下是大部分逻辑:
/**
* This combination of action is the implementation class of the "orElse" operator,
* allowing to have one and only one action to be executed within the given actions
*/
class EitherActions[A](actions: Seq[Action[A]]) extends Action[A] {
require(actions.nonEmpty, "The actions to combine should not be empty")
override def parser: BodyParser[A] = actions.head.parser
override def executionContext: ExecutionContext = actions.head.executionContext
/**
* @param request
* @return either the first result to be successful, or the first to be failure
*/
override def apply(
request: Request[A]
): Future[Result] = {
// as we know actions is nonEmpty, we can start with actions.head and directly fold on actions.tail
// this removes the need to manage an awkward "zero" value in the fold
val firstResult = actions.head.apply(request)
// we wrap all apply() calls into changeUnauthorizedIntoFailure to be able to use fallbackToNext on 403
val finalResult = actions.tail.foldLeft( changeUnauthorizedIntoFailure(firstResult) ) {
( previousResult, nextAction ) =>
RichFuture(previousResult).fallbackToNext{ () =>
changeUnauthorizedIntoFailure(nextAction.apply(request))
}(executionContext)
}
// restore the original message
changeUnauthorizedIntoSuccess(finalResult)
}
/**
* to use fallBackToNext, we need to have failed Future, thus we change the Success(403) into a Failure(403)
* we keep the original result to be able to restore it at the end if none of the combined actions did success
*/
private def changeUnauthorizedIntoFailure(
before: Future[Result]
): Future[Result] = {
val after = before.transform {
case Success(originalResult) if originalResult.header.status == Unauthorized =>
Failure(EitherActions.UnauthorizedWrappedException(originalResult = originalResult))
case Success(originalResult) if originalResult.header.status == Forbidden =>
Failure(EitherActions.UnauthorizedWrappedException(originalResult = originalResult))
case keepResult@_ => keepResult
}(executionContext)
after
}
/**
* after the last call, if we still have a UnauthorizedWrappedException, we change it back to a Success(403)
* to restore the original message
*/
private def changeUnauthorizedIntoSuccess(
before: Future[Result]
): Future[Result] = {
val after = before.transform {
case Failure(EitherActions.UnauthorizedWrappedException(_, _, result)) => Success(result)
case keepResult@_ => keepResult
}(executionContext)
after
}
def orElse( other: Action[A]): EitherActions[A] = {
new EitherActions[A]( actions :+ other)
}
}
object EitherActions {
private case class UnauthorizedWrappedException(
private val message: String = "",
private val cause: Throwable = None.orNull,
val originalResult: Result,
) extends Exception(message, cause)
}