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

org.specs2.matcher.JsonMatchers.scala Maven / Gradle / Ivy

package org.specs2
package matcher

import scala.util.parsing.json._
import text.Quote._
import text.NotNullStrings._
import text.Regexes._
import text.Trim._
import json.Json._
import util.matching.Regex

/**
 * Matchers for Json expressions (entered as strings)
 */
trait JsonMatchers extends JsonBaseMatchers with JsonBaseBeHaveMatchers

private[specs2]
trait JsonBaseMatchers extends Expectations {
  /** match if the document contains the value at the top-level */
  def /(value: Any): JsonValueMatcher = new JsonValueMatcher(value)
  /** match if the document contains the value at any level */
  def */(value: Any): JsonDeepValueMatcher = new JsonDeepValueMatcher(value)

  /** match if the document contains the pair at the top level */
  def /(pair: (Any, Any)): JsonPairMatcher = new JsonPairMatcher(pair._1, pair._2)
  /** match if the document contains the pair at any level */
  def */(pair: (Any, Any)): JsonDeepPairMatcher = new JsonDeepPairMatcher(pair._1, pair._2)

  class JsonPairMatcher(key: Any, value: Any) extends Matcher[String] {
    def navigate(json: JSONType): Option[JSONType] = Some(json)

    def apply[S <: String](s: Expectable[S]) = {
      parse(s.value).map(navigate) match {
        case Some(Some(JSONObject(obj))) => result(havePair(obj, obj.toSeq, key, value), s)
        case Some(Some(JSONArray(list))) => result(false, "ok", list.mkString("[", ", ", "]")+" doesn't contain "+stringOrRegex(key, value), s)
        case Some(None)                  => result(false, "ok", s.value+" is empty", s)
        case None                        => result(false, "ok", "Could not parse:\n"+s.value, s)
      }
    }
    override def not = new JsonPairMatcher(key, value) {
      override def apply[S <: String](s: Expectable[S]) = super.apply(s).negate
    }
  }
  class JsonValueMatcher(value: Any) extends Matcher[Any] { outer =>
    def navigate(json: JSONType): Option[JSONType] = Some(json)

    def apply[S <: Any](s: Expectable[S]) = {
      parse(s.value.notNull).map(navigate) match {
        case Some(Some(JSONObject(obj))) => result(false, "ok", obj.map(p => p._1+" : "+p._2).mkString("{", ", ", "}")+" doesn't contain "+stringOrRegex(value), s)
        case Some(Some(JSONArray(list))) => result(containValue(list, value), s)
        case Some(None)                  => result(false, "ok", s.value+" is empty", s)
        case None                        => result(false, "ok", "Could not parse:\n"+s.value, s)
      }
    }
    override def not = new JsonValueMatcher(value) {
      override def apply[S <: Any](s: Expectable[S]) = super.apply(s).negate
    }
    /** in this case, interpret 'value' as the key and value1 as the expected value in the Array */
    def /(value1: Any) = new JsonValueMatcher(value1) {
      override def navigate(json: JSONType): Option[JSONType] = outer.navigate(json).flatMap(find(value, _))
    }
    /** in this case, interpret 'value' as the key and key1/value1 as the expected pair in the Map */
    def /(pair1: (Any, Any)) = new JsonPairMatcher(pair1._1, pair1._2) {
      override def navigate(json: JSONType): Option[JSONType] = outer.navigate(json).flatMap(find(value, _))
    }
    /** in this case, interpret 'value' as the key and value1 as the expected value in the Array */
    def */(value1: Any) = new JsonDeepValueMatcher(value1) {
      override def navigate(json: JSONType): Option[JSONType] = outer.navigate(json).flatMap(find(value, _))
    }
    /** in this case, interpret 'value' as the key and value1 as the expected pair in the map */
    def */(pair1: (Any, Any)) = new JsonDeepPairMatcher(pair1._1, pair1._2) {
      override def navigate(json: JSONType): Option[JSONType] = outer.navigate(json).flatMap(find(value, _))
    }

  }
  class JsonDeepPairMatcher(key: Any, value: Any) extends Matcher[String] {
    def navigate(json: JSONType): Option[JSONType] = Some(json)

    def apply[S <: String](s: Expectable[S]) = {
      parse(s.value).map(navigate) match {
        case Some(Some(JSONObject(o: Map[_,_]))) if havePair(o.toSeq, key, value) => result(true,
                                                                                            havePairOkMessage(o, o.toSeq, key, value), havePairKoMessage(o, o.toSeq, key, value), s)
        case Some(Some(o))                                                        => result(havePair(o, terminalPairs(o), key, value), s)
        case Some(None)                                                           => result(false, "ok", s.value+" is empty", s)
        case None                                                                 => result(false, "ok", "Could not parse:\n"+s.value, s)
      }
    }

    override def not = new JsonDeepPairMatcher(key, value) {
      override def apply[S <: String](s: Expectable[S]) = super.apply(s).negate
    }
  }
  class JsonDeepValueMatcher(value: Any) extends Matcher[Any] { outer =>
    def navigate(json: JSONType): Option[JSONType] = Some(json)

    def apply[S <: Any](s: Expectable[S]) = {
      parse(s.value.notNull).map(navigate) match {
        case Some(Some(o)) => result(containValue(terminalValues(o), value), s)
        case Some(None)    => result(false, "ok", s.value.notNull+" is empty", s)
        case None          => result(false, "ok", "Could not parse:\n"+s.value, s)
      }
    }

    override def not = new JsonDeepValueMatcher(value) {
      override def apply[S <: Any](s: Expectable[S]) = super.apply(s).negate
    }

    /** in this case, interpret 'value' as the key and value1 as the expected value in the Array */
    def /(value1: Any) = new JsonValueMatcher(value1) {
      override def navigate(json: JSONType): Option[JSONType] = outer.navigate(json).flatMap(findDeep(value, _))
    }
    /** in this case, interpret 'value' as the key and pair1 as the expected pair in the map */
    def /(pair1: (Any, Any)) = new JsonPairMatcher(pair1._1, pair1._2) {
      override def navigate(json: JSONType): Option[JSONType] = outer.navigate(json).flatMap(findDeep(value, _))
    }
    /** in this case, interpret 'value' as the key and value1 as the expected value in the Array */
    def */(value1: Any) = new JsonDeepValueMatcher(value1) {
      override def navigate(json: JSONType): Option[JSONType] = outer.navigate(json).flatMap(findDeep(value, _))
    }
    /** in this case, interpret 'value' as the key and value1 as the expected pair in the map */
    def */(pair1: (Any, Any)) = new JsonDeepPairMatcher(pair1._1, pair1._2) {
      override def navigate(json: JSONType): Option[JSONType] = outer.navigate(json).flatMap(findDeep(value, _))
    }
  }

  /**
   * @return true if there is one pair is matching the (k, v) pair where k and/or v can be a Regex +
   * an ok message and a ko message
   */
  private def havePair(o: Any, pairs: Seq[(Any, Any)], k: Any, v: Any): (Boolean, String, String) =
    (havePair(pairs, k, v),
     havePairOkMessage(o, pairs, k, v),
     havePairKoMessage(o, pairs, k, v))

  /** @return ok message for the presence of a pair in a map */
  private def havePairOkMessage(o: Any, pairs: Seq[(Any, Any)], k: Any, v: Any) =
    mapString(o)+" contains "+stringOrRegex(k, v)
  /** @return ko message for the presence of a pair in a map */
  private def havePairKoMessage(o: Any, pairs: Seq[(Any, Any)], k: Any, v: Any) =
    mapString(o)+" doesn't contain "+stringOrRegex(k, v)

  /**
   * @return true if there is one pair is matching the (k, v) pair where k and/or v can be a Regex
   */
  private def havePair(pairs: Seq[(Any, Any)], k: Any, v: Any): Boolean =
    pairs.collect { case (key, value) if regexOrEqualMatch(key, k) && regexOrEqualMatch(value, v) => k }.nonEmpty

  /**
   * @return true if there is one pair is matching the (k, v) pair where k and/or v can be a Regex +
   * an ok message and a ko message
   */
  private def containValue(values: Seq[Any], v: Any): (Boolean, String, String) =
    (values.collect { case value if regexOrEqualMatch(value, v) => v  }.nonEmpty,
      listString(values)+" contains "+stringOrRegex(v),
      listString(values)+" doesn't contain "+stringOrRegex(v))

  /**
   * @return s represented as a Regex if it is one
   */
  private def stringOrRegex(s: Any): String = s match {
    case r: Regex => q(r)+".r"
    case other    => q(other)
  }

  /**
   * @return (k, v) represented as a pair of Regex or String depending on what there types are
   */
  private def stringOrRegex(k: Any, v: Any): String = stringOrRegex(k)+" : "+stringOrRegex(v)

  /**
   * @return true if v is a regex and it matches value or if the 2 are equal
   */
  private def regexOrEqualMatch(value: Any, v: Any) = v match {
    case r: Regex => r matches value.notNull
    case other    => other == value
  }

  private def mapString(o: Any) =
    o match {
      case m: Map[_,_] => m.iterator.map { case (k, v) => k+" : "+v}.mkString("{", ",", "}")
      case other       => other.notNull.remove("\"")
    }

  private def listString(values: Seq[Any]) =
    values.mkString("[", ", ", "]")

}

private[specs2]
trait JsonBaseBeHaveMatchers { outer: JsonBaseMatchers =>

  implicit def toNotMatcherJson(result: NotMatcher[Any]) : NotMatcherJson = new NotMatcherJson(result)
  class NotMatcherJson(result: NotMatcher[Any]) {
    def /(pair: (Any, Any)): JsonPairMatcher = outer./(pair).not
    def */(pair: (Any, Any)): JsonDeepPairMatcher = outer.*/(pair).not
    def /(value: Any): JsonValueMatcher = outer./(value).not
    def */(value: Any): JsonDeepValueMatcher = outer.*/(value).not
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy