I want an Action.async that (1) try to get values from the DB. If the DB is not available, it will try to connect to another resource and (2) get the values from there. Because the two resources that I am using return Future, I am separating them with "recover" keyword. I am not sure if it is the best way..... But the statement inside the recovery{} has a type mismatch error:
def show(url: String) = Action.async { implicit request: Request[AnyContent] =>
println("url: " + url)
val repositoryUrl = RepositoryUrl(url)
val repositoryId = RepositoryId.createFromUrl(url)
// Listing commits from the DB
val f: Future[Seq[Commit]] = commit.listByRepository(repositoryId.toString())
f.map { f: Seq[Commit] =>
val json = JsObject(Seq(
"project URL" -> JsString(url),
"list of commits" -> Json.toJson(f)))
Ok(json)
}.recover {
case e: scala.concurrent.TimeoutException =>
// InternalServerError("timeout")
// Listing commits from the Git CLI
val github = rules.GitHub(repositoryUrl)
val seq: Future[Seq[Commit]] = github.listCommits
seq.map { seq: Seq[Commit] =>
val json = JsObject(Seq(
"project URL" -> JsString(url),
"list of commits" -> Json.toJson(seq)))
Ok(json)
}
}
}
I am getting the error type mismatch; found : scala.concurrent.Future[play.api.mvc.Result] required: play.api.mvc.Result on the line seq.map { seq: Seq[Commit] =>. How can I return another result if I have a failure from my future?
Thanks!
recover wraps plain result in Future for you (analogue of map), while recoverWith expects Future as the result (analogue of flatMap). (https://stackoverflow.com/a/36585703/5794617). So, you should use recoverWith:
def show(url: String): EssentialAction = Action.async { implicit request: Request[AnyContent] =>
// This future will throw ArithmeticException because of division to zero
val f: Future[Seq[Int]] = Future.successful(Seq[Int](1, 2, 3, 4 / 0))
val fResult: Future[JsObject] = f.map { r =>
JsObject(Seq(
"project URL" -> JsString(url),
"list of commits" -> Json.toJson(r)))
}.recoverWith {
case e: ArithmeticException =>
val seq: Future[Seq[Int]] = Future.successful(Seq(1, 2, 3, 4))
seq.map { seq: Seq[Int] =>
JsObject(Seq(
"project URL" -> JsString(url),
"list of commits" -> Json.toJson(seq)))
}
}
fResult.map { r =>
Ok(r)
}
}
Scala Future.recover has a signature of
def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U]
Try to use recoverWith instead
def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U]
Related
I created a generic function to get from Bitbucket-API (It gives you the data in pages if there is to much data and the next page URL is under "next" in the response).
def getAggregatedDataWithNext[T](url: String)(implicit reader: Reads[T]): Future[Seq[T]]= {
val featureResponse = ws.url(url).withAuth(AuthUserName, AuthPassword, WSAuthScheme.BASIC).addQueryStringParameters("pagelen" -> "100").get()
featureResponse.map(response => {
val resJson = response.json
val tSequence = (resJson \ "values").as[Seq[T]]
(resJson \ "next").asOpt[String] match {
case None => tSequence
case Some(nextUrl) => getAggregatedDataWithNext(nextUrl).flatMap(tSequence ++ _)
}
})
}
I get an error:
type mismatch;
found : Seq[T]
required: scala.concurrent.Future[?]
And IntelliJ gives me this:
Have a slick table with columns:
def name: Rep[Option[String]] = ???
def email: Rep[String] = ???
def fraudScores: Rep[Int] = ???
also there is typeclass to calcualte rate for different fields:
trait Rater[T] {
def apply(rep: T): Result
}
object Rater {
def getRater[T](t: T)(implicit rater: Rater[T]): Result = rater(t)
implicit val int: Rater[Rep[Int]] = v => calculateRate(v, _)
implicit val str: Rater[Rep[String]] = v => calculateRate(v, _)
implicit val strOpt: Rater[Rep[Option[String]]] = v => calculateRate(v, _)
}
and map:
val map: Map[String, Rep[_ >: Option[String] with String with Int]] = Map(
"name" -> name,
"email" -> email,
"scores" -> fraudScores
)
what I'd like to do is getting correct instance based on dynamic input, like:
val fname = "scores" // value getting from http request
map.get(fname).fold(default)(f => {
val rater = getRater(f)
rater(someVal)
})
but getting error that there is no implicit for Rep[_ >: Option[String] with String with Int], is there some workaround for this?
I think, your problem is that Map is a wrong way to represent this.
Use a case class:
case class Foo(
name: Rep[Option[String]],
email: Rep[String],
score: Rep[Int]
)
def applyRater[T : Rater](t: T) = implicitly[Rater[T]](t)
def rate(foo: Foo, what: String) = what match {
case "name" => applyRater(foo.name)
case "email" => applyRater(foo.email)
case "score" => applyRater(foo.score)
case _ => default
}
I'm using spray-json and I need to parse the given request body (PATCH, POST), request body attributes can have following possibilities represented by Either[Unit.type, Option[A]]
value Not given Left[Unit.type]
value=null Null Right[None]
value=XXX Some value is provided Right[Some(value)]
Using the above possibilities I need to create a entity from the request body. While parsing I need to validate each field with some business logic (String length, integer range ...).
I have a following function for the business logic validation.
def validateValue[T](fieldName: String,
maybeValue: Try[T],
businessValidation: T => Boolean): Option[T] = {
maybeValue match {
case Success(value) if businessValidation(value) => Some(value)
case _ => None
}
}
Similarly another function readFieldWithValidation, here I will be parsing each attribute based on the input type and apply the business validation.
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)(
parse: S => T
): Option[T] = {
fields.get(fieldName) match {
case None => None
case Some(jsValue) =>
jsValue match {
case jsString: JsString =>
validateValue(fieldName, Try(parse(jsString.value)), businessValidation)
case JsNumber(jsNumber) =>
validateValue(fieldName, Try(parse(jsNumber.intValue)), businessValidation)
case _ => None
}
}
}
I have S ( Source ) and T ( Target ) which is used for given a JsValue returns T type. Here I only care about JsString and JsNumber.
The above lines of code is giving type mismatch error,
<console>:112: error: type mismatch;
found : jsString.value.type (with underlying type String)
required: S
validateValue(fieldName, Try(parse(jsString.value)), businessValidation)
^
<console>:114: error: type mismatch;
found : Int
required: S
validateValue(fieldName, Try(parse(jsNumber.intValue)), businessValidation)
Can someone help me how to overcome this error?
This is how I can use above function
val attributes = Map("String" -> JsString("ThisIsString"), "Int" -> JsNumber(23))
def stringLengthConstraint(min: Int, max: Int)(value: String) = value.length > min && value.length < max
readFieldWithValidation[JsString, String](attributes, "String", stringLengthConstraint(1, 10))(_.toString)
Your example is still not quite clear because it does not show the role of parse and actually looks contradictory to the other code: particularly you specify the generic parameter S as JsString in readFieldWithValidation[JsString, String] but given current (borken) readFieldWithValidation implementation your parse argument is probably expected to be of type String => String because jsString.value is String.
Anyway here is a piece of code that seem to implement something that is hopefully sufficiently close to what you want:
trait JsValueExtractor[T] {
def getValue(jsValue: JsValue): Option[T]
}
object JsValueExtractor {
implicit val decimalExtractor = new JsValueExtractor[BigDecimal] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber)
case _ => None
}
}
implicit val intExtractor = new JsValueExtractor[Int] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber.intValue)
case _ => None
}
}
implicit val doubleExtractor = new JsValueExtractor[Double] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber.doubleValue)
case _ => None
}
}
implicit val stringExtractor = new JsValueExtractor[String] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsString(string) => Some(string)
case _ => None
}
}
}
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)(parse: S => T)(implicit valueExtractor: JsValueExtractor[S]) = {
fields.get(fieldName)
.flatMap(jsValue => valueExtractor.getValue(jsValue))
.flatMap(rawValue => Try(parse(rawValue)).toOption)
.filter(businessValidation)
}
and usage example:
def test(): Unit = {
val attributes = Map("String" -> JsString("ThisIsString"), "Int" -> JsNumber(23))
def stringLengthConstraint(min: Int, max: Int)(value: String) = value.length > min && value.length < max
val value = readFieldWithValidation[String, String](attributes, "String", stringLengthConstraint(1, 10))(identity)
println(value)
}
Your current code uses Option[T] as your return type. If I were using a code like this I'd probably added some error logging and/or handling for a case where the code contains a bug and attributes do contain a value for key fieldName but of some different, unexpected type (like JsNumber instead of JsString).
Update
It is not clear from your comment whether you are satisfied with my original answer or want to add some error handling. If you want to report the type mismatch errors, and since you are using cats, something like ValidatedNel is an obvious choice:
type ValidationResult[A] = ValidatedNel[String, A]
trait JsValueExtractor[T] {
def getValue(jsValue: JsValue, fieldName: String): ValidationResult[T]
}
object JsValueExtractor {
implicit val decimalExtractor = new JsValueExtractor[BigDecimal] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[BigDecimal] = jsValue match {
case JsNumber(jsNumber) => jsNumber.validNel
case _ => s"Field '$fieldName' is expected to be decimal".invalidNel
}
}
implicit val intExtractor = new JsValueExtractor[Int] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[Int] = jsValue match {
case JsNumber(jsNumber) => Try(jsNumber.toIntExact) match {
case scala.util.Success(intValue) => intValue.validNel
case scala.util.Failure(e) => s"Field $fieldName is expected to be int".invalidNel
}
case _ => s"Field '$fieldName' is expected to be int".invalidNel
}
}
implicit val doubleExtractor = new JsValueExtractor[Double] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[Double] = jsValue match {
case JsNumber(jsNumber) => jsNumber.doubleValue.validNel
case _ => s"Field '$fieldName' is expected to be double".invalidNel
}
}
implicit val stringExtractor = new JsValueExtractor[String] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[String] = jsValue match {
case JsString(string) => string.validNel
case _ => s"Field '$fieldName' is expected to be string".invalidNel
}
}
}
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)
(parse: S => T)(implicit valueExtractor: JsValueExtractor[S]): ValidationResult[T] = {
fields.get(fieldName) match {
case None => s"Field '$fieldName' is required".invalidNel
case Some(jsValue) => valueExtractor.getValue(jsValue, fieldName)
.andThen(rawValue => Try(parse(rawValue).validNel).getOrElse("".invalidNel))
.andThen(parsedValue => if (businessValidation(parsedValue)) parsedValue.validNel else s"Business validation for field '$fieldName' has failed".invalidNel)
}
}
And the test example remains the same. Probably in your real code you want to use something more specific than just String for errors but that's up to you.
Imagine I have OptionT[IO, Value] like this
case class FailureMsg(code: String, ex: Option[Throwable])
val fff: IO[Either[FailureMsg, Int]] = OptionT.some[IO](12345)
.map { value ⇒
println("Mapping over")
value
}
.flatMapF[Int](_ ⇒ IO.raiseError(new RuntimeException("err1")))
.toRight(FailureMsg("Code0", None))
.recoverWith {
case ex ⇒ // Not Throwable!
EitherT.leftT[IO, Int](FailureMsg("Code1", Some(ex)))
}
.value
How can I catch err1 and wrap it into Left[FailureMsg]. I expected recoverWith help me but surprisingly it's alias of mapLeft. What should I do ?
I wrote helper class to do this.
implicit class EitherTExt[F[_], A, B](val obj: EitherT[F, A, B]) {
def recoverThrowable(pf: PartialFunction[Throwable, Either[A, B]])(implicit A: ApplicativeError[F, Throwable]): EitherT[F, A, B] =
EitherT(obj.value.recover(pf))
}
Let me know if there is more elegant shorter way.
I would follow the types.
val start: OptionT[IO, Int] = OptionT.some[IO](12345)
val thenMap: OptionT[IO, Int] = start.map { value ⇒
println("Mapping over")
value
}
// here it will get off the rails
val thenFlatMapF: OptionT[IO, Int] =
thenMap.flatMapF[Int](_ ⇒ IO.raiseError(new RuntimeException("err1")))
val thenToRight: EitherT[IO, FailureMsg, Int] =
thenFlatMapF.toRight(FailureMsg("Code0", None))
val result: IO[Either[FailureMsg, Int]] = thenToRight.value
thenFlatMapF won't produce OptionT[IO, Int] if IO.raiseError is the case, because no default mapping of Throwable to what? And you will get exception in folding result of IO.raiseError.
First attempt to fix it, will illustrate it:
val thenFlatMapF: OptionT[IO, Int] = thenMap.flatMapF[Int](_ ⇒ {
IO.raiseError[Option[Int]](new RuntimeException("err1")).recoverWith {
case err =>
val result: Option[Int] = ???
IO.pure(result)
}
})
How to handle error in place without breaking IO and return Option so that OptionT[IO, Int] is produced?
So basically, in this case, if you expect flatMapF to fail and need information on error, then it is better to have EitherT as its container, rather than OptionT.
Once done, possible solution shows that at some point leftMap or variance should be done to map the Throwable to FailureMsg. One of reasons is because IO has default error expressed as Throwable. One can't just mix FailureMsg and Throwable. Either inheritance is required so that FailureMsg is of type Throwable/Exception, or mapping should be done of errors in suitable places.
My rough solution would be:
val fff: IO[Either[FailureMsg, Int]] = OptionT.some[IO](12345)
// ok, let's do some input interpretation
.map { value ⇒
println("Mapping over")
value
}
// if no input, means error
.toRight(FailureMsg("Code0", None))
// if there is interpreted input to process, do it and map the errors
.flatMapF(_ ⇒ IO.raiseError[Int](new RuntimeException("err1")).attempt.map(_.leftMap {
case err: RuntimeException if err.getMessage == "err1" => FailureMsg("err1", Some(err))
// just for illustration, that if you have temptation to map on error,
// most likely there won't be only exception
case t: Throwable => FailureMsg("unexpected", Some(t))
}))
.value
However, normally contents of flatMapF would be a separate function or effect that would include error handling. Something like this:
val doJob: Int => IO[Either[FailureMsg, Int]] = arg =>
IO.raiseError[Int](new RuntimeException("err1")).attempt.map(_.leftMap {
case err: RuntimeException if err.getMessage == "err1" => FailureMsg("err1", Some(err))
case t: Throwable => FailureMsg("unexpected", Some(t))
})
val fff: IO[Either[FailureMsg, Int]] = OptionT.some[IO](12345)
.map { value ⇒
println("Mapping over")
value
}
.toRight(FailureMsg("Code0", None))
.flatMapF(arg ⇒ doJob(arg))
.value
I have this code where I'm trying to call a partial function. When I build my project I get an error stating missing parameter type ++ headerExtractor.applyOrElse(event, _ => Map.empty).
I've looked at other posts, but I feel like this should be working. What am I doing wrong?
I'm calling headerExtractor here
private def publishToProfileExchange(customer: Schema.Customer, event: Schema.CustomerEvent): Future[Done] = {
val messageType: String = "ProfileEvent"
val headers: Map[String, AnyRef] = Map(
"profileId" -> customer.id.toString,
"eventType" -> event.eventType,
"type" -> messageType
) ++ headerExtractor.applyOrElse(event, _ => Map.empty)
...
//valid return values would be here
}
private val headerExtractor: PartialFunction[Schema.CustomerEvent, Map[String, AnyRef]] = {
case x if x.eventType.equalsIgnoreCase("message") && (x.eventData \ "incoming").asOpt[Boolean].getOrElse(false) =>
Map("incomingMessage" -> "true")
}
you have to provide the type of paramater on your .applyOrElse(). See example below
case class CustomerEvent(eventType: String, eventData: String)
val headerExtractor = new PartialFunction[CustomerEvent, Map[String, AnyRef]] {
def apply(x: CustomerEvent): Map[String, AnyRef] = Map("incomingMessage" -> "true")
def isDefinedAt(x: CustomerEvent): Boolean = x.eventType.equalsIgnoreCase("message")
}
assert(headerExtractor.applyOrElse(CustomerEvent("message", "{}"), (customerEvent: CustomerEvent) => Map.empty)
== Map("incomingMessage" -> "true"))
// or simply do (_: CustomerEvent)
assert(headerExtractor.applyOrElse(CustomerEvent("message", "{}"), (_: CustomerEvent) => Map.empty)
== Map("incomingMessage" -> "true"))
negative case
assert(headerExtractor.applyOrElse(CustomerEvent("something-else", "{}"), (_: CustomerEvent) => Map.empty)
== Map())