All Downloads are FREE. Search and download functionalities are using the official Maven repository.

play.api.libs.json.JsResult.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. 
 */

package play.api.libs.json

import scala.collection.Seq

/**
 * The result for a successful parsing.
 */
case class JsSuccess[T](value: T, path: JsPath = JsPath()) extends JsResult[T] {
  def get: T = value

  def isSuccess = true
  def isError   = false

  def fold[U](invalid: Seq[(JsPath, Seq[JsonValidationError])] => U, valid: T => U): U = valid(value)

  def map[U](f: T => U): JsResult[U] = copy(value = f(value))

  def flatMap[U](f: T => JsResult[U]): JsResult[U] = f(value).repath(path)

  def foreach(f: T => Unit): Unit = f(value)

  def contains[AA >: T](elem: AA): Boolean = elem == value

  def exists(p: T => Boolean): Boolean = p(value)

  def forall(p: T => Boolean): Boolean = p(value)

  def repath(path: JsPath): JsResult[T] = JsSuccess(value, path ++ this.path)

  def getOrElse[U >: T](t: => U): U = value

  def orElse[U >: T](t: => JsResult[U]): JsResult[U] = this

  def asOpt: Option[T] = Some(value)

  def asEither: Either[Seq[(JsPath, Seq[JsonValidationError])], T] = Right(value)

  def recover[U >: T](errManager: PartialFunction[JsError, U]): JsResult[U] = this

  def recoverTotal[U >: T](errManager: JsError => U): U = value

  def recoverWith[U >: T](errManager: JsError => JsResult[U]): JsResult[U] = this
}

/**
 * The result in case of parsing `errors`.
 */
case class JsError(errors: Seq[(JsPath, Seq[JsonValidationError])]) extends JsResult[Nothing] {
  def get: Nothing = throw new NoSuchElementException("JsError.get")

  def ++(error: JsError): JsError = JsError.merge(this, error)

  def :+(error: (JsPath, JsonValidationError)): JsError     = JsError.merge(this, JsError(error))
  def append(error: (JsPath, JsonValidationError)): JsError = this.:+(error)

  def +:(error: (JsPath, JsonValidationError)): JsError      = JsError.merge(JsError(error), this)
  def prepend(error: (JsPath, JsonValidationError)): JsError = this.+:(error)

  def isSuccess = false
  def isError   = true

  def fold[U](invalid: Seq[(JsPath, Seq[JsonValidationError])] => U, valid: Nothing => U): U = invalid(errors)

  def map[U](f: Nothing => U): JsResult[U] = this

  def flatMap[U](f: Nothing => JsResult[U]): JsResult[U] = this

  def foreach(f: Nothing => Unit): Unit = ()

  def contains[AA >: Nothing](elem: AA): Boolean = false

  def exists(p: Nothing => Boolean): Boolean = false

  def forall(p: Nothing => Boolean): Boolean = true

  def repath(path: JsPath): JsResult[Nothing] =
    JsError(JsResult.repath(errors, path))

  def getOrElse[U >: Nothing](t: => U): U = t

  def orElse[U >: Nothing](t: => JsResult[U]): JsResult[U] = t

  def asOpt = None

  def asEither: Either[Seq[(JsPath, Seq[JsonValidationError])], Nothing] = Left(errors)

  def recover[U >: Nothing](errManager: PartialFunction[JsError, U]): JsResult[U] = JsSuccess(errManager(this))

  def recoverTotal[U >: Nothing](errManager: JsError => U): U = errManager(this)

  def recoverWith[U >: Nothing](errManager: JsError => JsResult[U]): JsResult[U] = errManager(this)
}

object JsError {
  def apply(): JsError = JsError(Seq(JsPath -> Seq()))

  def apply(error: JsonValidationError): JsError = JsError(Seq(JsPath -> Seq(error)))

  def apply(error: String): JsError = JsError(JsonValidationError(error))

  def apply(error: (JsPath, JsonValidationError)): JsError = JsError(Seq(error._1 -> Seq(error._2)))

  def apply(path: JsPath, error: JsonValidationError): JsError = JsError(path -> error)

  def apply(path: JsPath, error: String): JsError = JsError(path -> JsonValidationError(error))

  def merge(
      e1: Seq[(JsPath, Seq[JsonValidationError])],
      e2: Seq[(JsPath, Seq[JsonValidationError])]
  ): Seq[(JsPath, Seq[JsonValidationError])] = {
    (e1 ++ e2).groupBy(_._1).iterator.map { case (k, v) => k -> v.flatMap(_._2) }.toList
  }

  def merge(e1: JsError, e2: JsError): JsError = {
    JsError(merge(e1.errors, e2.errors))
  }

  def toJson(e: JsError): JsObject = toJson(e.errors, false)

  def toJson(errors: Seq[(JsPath, Seq[JsonValidationError])]): JsObject = toJson(errors, false)

  // def toJsonErrorsOnly: JsValue = original // TODO
  def toFlatForm(e: JsError): Seq[(String, Seq[JsonValidationError])] = e.errors.map { case (path, seq) =>
    path.toJsonString -> seq
  }

  private def toJson(errors: Seq[(JsPath, Seq[JsonValidationError])], flat: Boolean): JsObject = {
    errors.foldLeft(JsObject.empty) { (obj, error) =>
      obj ++ JsObject(Seq(error._1.toJsonString -> error._2.foldLeft(JsArray.empty) { (arr, err) =>
        val msg = JsArray(Predef.wrapRefArray[JsValue] {
          if (flat) Array(JsString(err.message))
          else err.messages.map(JsString(_)).toArray[JsValue]
        })
        arr :+ JsObject(
          Seq(
            "msg"  -> msg,
            "args" -> JsArray(err.args.map(toJson).toArray[JsValue])
          )
        )
      }))
    }
  }

  /**
   * Serializer for Any, used for the args of errors.
   */
  private def toJson(a: Any): JsValue = a match {
    case s: String   => JsString(s)
    case nb: Int     => JsNumber(nb)
    case nb: Short   => JsNumber(BigDecimal(nb))
    case nb: Long    => JsNumber(BigDecimal(nb))
    case nb: Double  => JsNumber(BigDecimal(nb))
    case nb: Float   => JsNumber(BigDecimal.decimal(nb))
    case b: Boolean  => JsBoolean(b)
    case js: JsValue => js
    case x           => JsString(x.toString)
  }

  /**
   * Extracts the first error message.
   *
   * {{{
   * import play.api.libs.json.JsError
   *
   * def msg(err: JsError): Option[String] = err match {
   *   case JsError.Message(msg) => Some(msg)
   *   case _ => None
   * }
   * }}}
   */
  object Message {
    def unapply(error: JsError): Option[String] =
      error.errors.headOption.collect { case (_, JsonValidationError.Message(msg) +: _) =>
        msg
      }
  }

  /**
   * Extracts the first error details (message and its first argument).
   *
   * {{{
   * import play.api.libs.json.JsError
   *
   * def cause(err: JsError): Option[(String, Exception)] = err match {
   *   case JsError.Detailed(msg, ex: Exception) => Some(msg -> ex)
   *   case _ => None
   * }
   * }}}
   */
  object Detailed {
    def unapply(error: JsError): Option[(String, Any)] =
      error.errors.headOption.collect { case (_, JsonValidationError.Detailed(msg, arg) +: _) =>
        msg -> arg
      }
  }
}

sealed trait JsResult[+A] { self =>
  def isSuccess: Boolean
  def isError: Boolean

  /**
   * Either applies the `invalid` function if this result is an error,
   * or applies the `valid` function on the successful value.
   */
  def fold[B](invalid: Seq[(JsPath, Seq[JsonValidationError])] => B, valid: A => B): B

  /**
   * If this result is successful, applies the function `f` on the parsed value.
   */
  def map[B](f: A => B): JsResult[B]

  /**
   * If this result is successful, applies the function `f` on the parsed value.
   */
  def flatMap[B](f: A => JsResult[B]): JsResult[B]

  /**
   * If this result is successful, applies the function `f` on the parsed value.
   */
  def foreach(f: A => Unit): Unit

  def filterNot(error: JsError)(p: A => Boolean): JsResult[A] =
    flatMap { a =>
      if (p(a)) error else JsSuccess(a)
    }

  def filterNot(p: A => Boolean): JsResult[A] =
    flatMap { a =>
      if (p(a)) JsError() else JsSuccess(a)
    }

  def filter(p: A => Boolean): JsResult[A] =
    flatMap { a =>
      if (p(a)) JsSuccess(a) else JsError()
    }

  def filter(otherwise: JsError)(p: A => Boolean): JsResult[A] =
    flatMap { a =>
      if (p(a)) JsSuccess(a) else otherwise
    }

  def collect[B](otherwise: JsonValidationError)(p: PartialFunction[A, B]): JsResult[B] = flatMap {
    case t if p.isDefinedAt(t) => JsSuccess(p(t))
    case _                     => JsError(otherwise)
  }

  def withFilter(p: A => Boolean) = new WithFilter(p)

  final class WithFilter(p: A => Boolean) {
    def map[B](f: A => B): JsResult[B] = self match {
      case JsSuccess(a, path) =>
        if (p(a)) JsSuccess(f(a), path)
        else JsError()
      case e @ JsError(_) => e
    }

    def flatMap[B](f: A => JsResult[B]): JsResult[B] = self match {
      case JsSuccess(a, path) =>
        if (p(a)) f(a).repath(path)
        else JsError()
      case e @ JsError(_) => e
    }

    def foreach(f: A => Unit): Unit = self match {
      case JsSuccess(a, _) if p(a) => f(a)
      case _                       => ()
    }

    def withFilter(q: A => Boolean) = new WithFilter(a => p(a) && q(a))
  }

  /** If this result is successful than checks for presence for '''elem''', otherwise return '''false''' */
  def contains[AA >: A](elem: AA): Boolean

  /** If this result is successful than check value with predicate '''p''', otherwise return '''false''' */
  def exists(p: A => Boolean): Boolean

  /**
   * If this result is successful than check value with predicate '''p''', otherwise return '''true'''.
   * Follows [[scala.collection.Traversable.forall]] semantics
   */
  def forall(p: A => Boolean): Boolean

  /** Updates the JSON path */
  def repath(path: JsPath): JsResult[A]

  /** Not recommended */
  def get: A

  /** Either returns the successful value, or the value from `t`. */
  def getOrElse[AA >: A](t: => AA): AA

  /**
   * Either returns this result if successful, or the result from `t`.
   */
  def orElse[AA >: A](t: => JsResult[AA]): JsResult[AA]

  /**
   * Transforms this result either `Some` option with the successful value,
   * or as `None` in case of JSON error.
   */
  def asOpt: Option[A]

  /**
   * Returns either the result errors (at `Left`),
   * or the successful value (at `Right`).
   */
  def asEither: Either[Seq[(JsPath, Seq[JsonValidationError])], A]

  /**
   * If this result is not successful,
   * recovers the errors with the given function.
   */
  def recover[AA >: A](errManager: PartialFunction[JsError, AA]): JsResult[AA]

  /**
   * If this result is not successful,
   * recovers the errors with the given function.
   */
  def recoverTotal[AA >: A](errManager: JsError => AA): AA

  /**
   * If this result is not successful,
   * recovers the errors with the given function.
   */
  def recoverWith[AA >: A](errManager: JsError => JsResult[AA]): JsResult[AA]
}

object JsResult {
  import scala.util.Failure
  import scala.util.Try
  import scala.util.Success
  import play.api.libs.functional._

  case class Exception(cause: JsError)
      extends java.lang.Exception(Json.stringify(JsError.toJson(cause)))
      with scala.util.control.NoStackTrace

  /**
   * Returns a JSON validation as a [[scala.util.Try]].
   *
   * @tparam T the type for the parsing
   * @param result the JSON validation result
   * @param err the function to be applied if the results is an error
   *
   * {{{
   * import scala.concurrent.Future
   * import play.api.libs.json.JsResult
   *
   * def toFuture[T](res: JsResult[T]): Future[T] =
   *   Future.fromTry(JsResult.toTry(res))
   * }}}
   */
  def toTry[T](result: JsResult[T], err: JsError => Throwable = Exception(_)): Try[T] = result match {
    case e @ JsError(_)      => Failure(err(e))
    case s @ JsSuccess(v, _) => Success(v)
  }

  /**
   * Returns a [[scala.util.Try]] as JSON validation.
   *
   * @tparam T the type for the parsing
   * @param result the result
   * @param err the function to be applied for [[scala.util.Failure]]
   */
  def fromTry[T](
      result: Try[T],
      err: Throwable => JsError = { e =>
        JsError(e.getMessage)
      }
  ): JsResult[T] = result match {
    case Success(v) => JsSuccess(v)
    case Failure(e) => err(e)
  }

  implicit def alternativeJsResult(implicit a: Applicative[JsResult]): Alternative[JsResult] =
    new Alternative[JsResult] {
      val app = a
      def |[A, B >: A](alt1: JsResult[A], alt2: JsResult[B]): JsResult[B] = (alt1, alt2) match {
        case (JsError(e), JsSuccess(t, p)) => JsSuccess(t, p)
        case (JsSuccess(t, p), _)          => JsSuccess(t, p)
        case (JsError(e1), JsError(e2))    => JsError(JsError.merge(e1, e2))
      }
      def empty: JsResult[Nothing] = JsError(Seq.empty)
    }

  implicit val applicativeJsResult: Applicative[JsResult] = new Applicative[JsResult] {
    def pure[A](f: => A): JsResult[A] = JsSuccess(f)

    def map[A, B](m: JsResult[A], f: A => B): JsResult[B] = m.map(f)

    def apply[A, B](mf: JsResult[A => B], ma: JsResult[A]): JsResult[B] = (mf, ma) match {
      case (JsSuccess(f, _), JsSuccess(a, _)) => JsSuccess(f(a))
      case (JsError(e1), JsError(e2))         => JsError(JsError.merge(e1, e2))
      case (JsError(e), _)                    => JsError(e)
      case (_, JsError(e))                    => JsError(e)
    }
  }

  implicit val functorJsResult: Functor[JsResult] = new Functor[JsResult] {
    override def fmap[A, B](m: JsResult[A], f: A => B) = m.map(f)
  }

  private[JsResult] type Errors = Seq[(JsPath, Seq[JsonValidationError])]

  private[json] def repath(errors: Errors, path: JsPath): Errors =
    errors.map { case (p, s) => (path ++ p) -> s }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy