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

scala.coursier.parse.JsonRuleParser.scala Maven / Gradle / Ivy

There is a newer version: 2.1.24
Show newest version
package coursier.parse

import com.github.plokhotnyuk.jsoniter_scala.core._
import com.github.plokhotnyuk.jsoniter_scala.macros._
import coursier.core.Module
import coursier.params.rule._
import coursier.util.{ModuleMatcher, ModuleMatchers}

import java.nio.charset.StandardCharsets

import scala.collection.mutable

class JsonRuleParser(
  defaultScalaVersion: String,
  defaultRuleResolution: RuleResolution
) {

  private implicit val moduleCodec: JsonValueCodec[Module] = {
    val strCodec = JsonCodecMaker.make[String]
    strCodec.xmapE[Module](
      (str, in) =>
        ModuleParser.module(str, defaultScalaVersion) match {
          case Left(err)  => in.decodeError(s"Cannot decode module '$str': $err")
          case Right(mod) => mod
        },
      _.repr,
      _ => null
    )
  }

  private val moduleMatchersCodec: JsonValueCodec[ModuleMatchers] = {

    final case class Helper(
      exclude: List[Module] = Nil,
      include: List[Module] = Nil
    )

    val codec = JsonCodecMaker.make[Helper]

    codec.xmap[ModuleMatchers](
      helper =>
        ModuleMatchers(
          helper.exclude.map(ModuleMatcher(_)).toSet,
          helper.include.map(ModuleMatcher(_)).toSet
        ),
      // This should be unused… We should sort things right after the toList calls if this ends up being used.
      matchers =>
        Helper(matchers.exclude.toList.map(_.matcher), matchers.include.toList.map(_.matcher)),
      _ => ModuleMatchers(Set.empty)
    )
  }

  private implicit class JsonValueCodecOps[T](private val codec: JsonValueCodec[T]) {
    def xmap[U](from: T => U, to: U => T, nullValue: T => U): JsonValueCodec[U] = {
      val nullValue0 = nullValue
      new JsonValueCodec[U] {
        def decodeValue(in: JsonReader, default: U) =
          from(codec.decodeValue(in, codec.nullValue))
        def encodeValue(x: U, out: JsonWriter) =
          codec.encodeValue(to(x), out)
        def nullValue =
          nullValue0(codec.nullValue)
      }
    }
    def xmapE[U](from: (T, JsonReader) => U, to: U => T, nullValue: T => U): JsonValueCodec[U] = {
      val nullValue0 = nullValue
      new JsonValueCodec[U] {
        def decodeValue(in: JsonReader, default: U) =
          from(codec.decodeValue(in, codec.nullValue), in)
        def encodeValue(x: U, out: JsonWriter) =
          codec.encodeValue(to(x), out)
        def nullValue =
          nullValue0(codec.nullValue)
      }
    }
  }

  private val alwaysFailCodec: JsonValueCodec[AlwaysFail] = {
    final case class Helper()
    val codec = JsonCodecMaker.make[Helper]
    codec.xmap(
      _ => AlwaysFail(),
      _ => Helper(),
      _ => AlwaysFail()
    )
  }

  private val sameVersionCodec: JsonValueCodec[SameVersion] = {
    final case class Repr(modules: List[Module])
    val codec = JsonCodecMaker.make[Repr]
    codec.xmap(
      repr => SameVersion(repr.modules.map(ModuleMatcher(_)).toSet),
      // should be unused… we should probably sort modules if this gets used:
      sv => Repr(sv.matchers.toList.map(_.matcher)),
      _ => SameVersion(Set.empty[ModuleMatcher])
    )
  }

  private val dontBumpRootDependenciesCodec: JsonValueCodec[DontBumpRootDependencies] =
    moduleMatchersCodec.xmap[DontBumpRootDependencies](
      DontBumpRootDependencies(_),
      _.matchers,
      _ => DontBumpRootDependencies()
    )

  private val strictCodec: JsonValueCodec[Strict] = {

    final case class Repr(include: List[Module] = Nil, exclude: List[Module] = Nil)
    val codec = JsonCodecMaker.make[Repr]

    codec.xmap[Strict](
      repr => {
        val include =
          if (repr.include.isEmpty) Set(ModuleMatcher.all)
          else repr.include.map(ModuleMatcher(_)).toSet
        Strict(
          include,
          repr.exclude.map(ModuleMatcher(_)).toSet
        )
      },
      strict => {
        val include =
          if (strict.include == Set(ModuleMatcher.all)) Nil
          else strict.include.toList.map(_.matcher)
        Repr(
          include,
          strict.exclude.toList.map(_.matcher)
        )
      },
      _ => Strict()
    )
  }

  private val ruleCodecs = Map[String, JsonValueCodec[_ <: Rule]](
    "always-fail"                 -> alwaysFailCodec,
    "same-version"                -> sameVersionCodec,
    "dont-bump-root-dependencies" -> dontBumpRootDependenciesCodec,
    "strict"                      -> strictCodec
  )

  private def decodeRuleResolution(ruleBytes: Array[Byte]): Option[RuleResolution] = {

    // FIXME We're ignoring malformed "action" fields here

    final case class Helper(action: Option[String] = None)
    val codec = JsonCodecMaker.make[Helper]

    val helper = readFromArray(ruleBytes)(codec)
    helper.action.collect {
      case "fail"        => RuleResolution.Fail
      case "warn"        => RuleResolution.Warn
      case "try-resolve" => RuleResolution.TryResolve
    }
  }

  private def decodeRuleName(ruleBytes: Array[Byte]): Option[String] = {

    // FIXME We're ignoring malformed "action" fields here

    final case class Helper(rule: Option[String] = None)
    val codec = JsonCodecMaker.make[Helper]

    val helper = readFromArray(ruleBytes)(codec)
    helper.rule
  }

  def parseRule(s: String): Either[String, (Rule, RuleResolution)] = {
    val b = s.getBytes(StandardCharsets.UTF_8)
    parseRule(b)
  }

  def parseRule(b: Array[Byte]): Either[String, (Rule, RuleResolution)] = {
    val ruleResOpt  = decodeRuleResolution(b)
    val ruleNameOpt = decodeRuleName(b)

    ruleNameOpt match {
      case None => Left("No rule name found")
      case Some(name) =>
        ruleCodecs.get(name) match {
          case None => Left(
              s"Rule '$name' not found (available rules: ${ruleCodecs.keys.toVector.sorted.mkString(", ")})"
            )
          case Some(ruleCodec) =>
            val rule    = readFromArray(b)(ruleCodec)
            val ruleRes = ruleResOpt.getOrElse(defaultRuleResolution)
            Right((rule, ruleRes))
        }
    }
  }

  def parseRules(s: String): Either[String, Seq[(Rule, RuleResolution)]] = {
    val b = s.getBytes(StandardCharsets.UTF_8)
    parseRules(b)
  }

  def parseRules(b: Array[Byte]): Either[String, Seq[(Rule, RuleResolution)]] = {

    val codec = JsonCodecMaker.make[List[RawJson]]

    val elems = readFromArray(b)(codec)

    val b0     = new mutable.ListBuffer[(Rule, RuleResolution)]
    var errOpt = Option.empty[String]
    val it     = elems.iterator
    while (it.hasNext && errOpt.isEmpty) {
      val rawJson = it.next()
      parseRule(rawJson.value) match {
        case Left(err) =>
          errOpt = Some(err)
        case Right(value) =>
          b0 += value
      }
    }

    errOpt.toLeft(b0.result())
  }

}

object JsonRuleParser {

  def parseRule(
    s: String,
    defaultScalaVersion: String,
    defaultRuleResolution: RuleResolution = RuleResolution.TryResolve
  ): Either[String, (Rule, RuleResolution)] =
    new JsonRuleParser(defaultScalaVersion, defaultRuleResolution).parseRule(s)

  def parseRule(
    content: Array[Byte],
    defaultScalaVersion: String,
    defaultRuleResolution: RuleResolution
  ): Either[String, (Rule, RuleResolution)] =
    new JsonRuleParser(defaultScalaVersion, defaultRuleResolution).parseRule(content)

  def parseRule(
    content: Array[Byte],
    defaultScalaVersion: String
  ): Either[String, (Rule, RuleResolution)] =
    new JsonRuleParser(defaultScalaVersion, RuleResolution.TryResolve).parseRule(content)

  def parseRules(
    s: String,
    defaultScalaVersion: String,
    defaultRuleResolution: RuleResolution = RuleResolution.TryResolve
  ): Either[String, Seq[(Rule, RuleResolution)]] =
    new JsonRuleParser(defaultScalaVersion, defaultRuleResolution).parseRules(s)

  def parseRules(
    content: Array[Byte],
    defaultScalaVersion: String,
    defaultRuleResolution: RuleResolution
  ): Either[String, Seq[(Rule, RuleResolution)]] =
    new JsonRuleParser(defaultScalaVersion, defaultRuleResolution).parseRules(content)

  def parseRules(
    content: Array[Byte],
    defaultScalaVersion: String
  ): Either[String, Seq[(Rule, RuleResolution)]] =
    new JsonRuleParser(defaultScalaVersion, RuleResolution.TryResolve).parseRules(content)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy