From d0496557bea52e17dd10d7b7aa85bbe4fe40b53e Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Wed, 12 Feb 2025 23:24:12 +0100 Subject: [PATCH] Fix "diverging implicit expansion" error with Scala 2 --- project/BuildHelper.scala | 8 +- .../scala-2.x/zio/json/JsonFieldDecoder.scala | 91 +++++++++++++++++++ .../zio/json/JsonFieldDecoder.scala | 0 .../src/test/scala/zio/json/DecoderSpec.scala | 12 +++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 zio-json/shared/src/main/scala-2.x/zio/json/JsonFieldDecoder.scala rename zio-json/shared/src/main/{scala => scala-3}/zio/json/JsonFieldDecoder.scala (100%) diff --git a/project/BuildHelper.scala b/project/BuildHelper.scala index 5b3bd69ca..633c779ef 100644 --- a/project/BuildHelper.scala +++ b/project/BuildHelper.scala @@ -249,7 +249,13 @@ object BuildHelper { mimaBinaryIssueFilters ++= Seq( exclude[Problem]("zio.json.internal.*"), exclude[Problem]("zio.json.yaml.internal.*") - ), + ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, _)) => + Seq( + exclude[Problem]("zio.json.JsonFieldDecoder.stringLike") // FIXME: remove after v0.7.19 release + ) + case _ => Seq.empty + }), mimaFailOnProblem := true ) diff --git a/zio-json/shared/src/main/scala-2.x/zio/json/JsonFieldDecoder.scala b/zio-json/shared/src/main/scala-2.x/zio/json/JsonFieldDecoder.scala new file mode 100644 index 000000000..0dc35e599 --- /dev/null +++ b/zio-json/shared/src/main/scala-2.x/zio/json/JsonFieldDecoder.scala @@ -0,0 +1,91 @@ +/* + * Copyright 2019-2022 John A. De Goes and the ZIO Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package zio.json + +import zio.json.internal.Lexer +import zio.json.uuid.UUIDParser + +/** When decoding a JSON Object, we only allow the keys that implement this interface. */ +trait JsonFieldDecoder[+A] { + self => + + final def map[B](f: A => B): JsonFieldDecoder[B] = + new JsonFieldDecoder[B] { + + def unsafeDecodeField(trace: List[JsonError], in: String): B = + f(self.unsafeDecodeField(trace, in)) + } + + final def mapOrFail[B](f: A => Either[String, B]): JsonFieldDecoder[B] = + new JsonFieldDecoder[B] { + + def unsafeDecodeField(trace: List[JsonError], in: String): B = + f(self.unsafeDecodeField(trace, in)) match { + case Left(err) => Lexer.error(err, trace) + case Right(b) => b + } + } + + def unsafeDecodeField(trace: List[JsonError], in: String): A +} + +object JsonFieldDecoder extends LowPriorityJsonFieldDecoder { + def apply[A](implicit a: JsonFieldDecoder[A]): JsonFieldDecoder[A] = a + + implicit val string: JsonFieldDecoder[String] = new JsonFieldDecoder[String] { + def unsafeDecodeField(trace: List[JsonError], in: String): String = in + } + + implicit val int: JsonFieldDecoder[Int] = new JsonFieldDecoder[Int] { + def unsafeDecodeField(trace: List[JsonError], in: String): Int = + try in.toInt + catch { + case _: NumberFormatException => Lexer.error(s"Invalid Int: ${strip(in)}", trace) + } + } + + implicit val long: JsonFieldDecoder[Long] = new JsonFieldDecoder[Long] { + def unsafeDecodeField(trace: List[JsonError], in: String): Long = + try in.toLong + catch { + case _: NumberFormatException => Lexer.error(s"Invalid Long: ${strip(in)}", trace) + } + } + + implicit val uuid: JsonFieldDecoder[java.util.UUID] = new JsonFieldDecoder[java.util.UUID] { + def unsafeDecodeField(trace: List[JsonError], in: String): java.util.UUID = + try UUIDParser.unsafeParse(in) + catch { + case _: IllegalArgumentException => Lexer.error(s"Invalid UUID: ${strip(in)}", trace) + } + } + + // use this instead of `string.mapOrFail` in supertypes (to prevent class initialization error at runtime) + private[json] def mapStringOrFail[A](f: String => Either[String, A]): JsonFieldDecoder[A] = + new JsonFieldDecoder[A] { + def unsafeDecodeField(trace: List[JsonError], in: String): A = + f(string.unsafeDecodeField(trace, in)) match { + case Left(err) => Lexer.error(err, trace) + case Right(value) => value + } + } + + private[json] def strip(s: String, len: Int = 50): String = + if (s.length <= len) s + else s.substring(0, len) + "..." +} + +private[json] trait LowPriorityJsonFieldDecoder diff --git a/zio-json/shared/src/main/scala/zio/json/JsonFieldDecoder.scala b/zio-json/shared/src/main/scala-3/zio/json/JsonFieldDecoder.scala similarity index 100% rename from zio-json/shared/src/main/scala/zio/json/JsonFieldDecoder.scala rename to zio-json/shared/src/main/scala-3/zio/json/JsonFieldDecoder.scala diff --git a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala index 014568683..64d6548fc 100644 --- a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala @@ -930,4 +930,16 @@ object DecoderSpec extends ZIOSpecDefault { implicit val eventDecoder: JsonDecoder[Event] = DeriveJsonDecoder.gen[Event] implicit val eventEncoder: JsonEncoder[Event] = DeriveJsonEncoder.gen[Event] } + + object fieldDecoder { + case class PersonId(value: String) + + object PersonId { + implicit val jsonFieldEncoder: JsonFieldEncoder[PersonId] = JsonFieldEncoder.string.contramap(_.value) + implicit val jsonFieldDecoder: JsonFieldDecoder[PersonId] = JsonFieldDecoder.string.map(PersonId.apply) + } + + implicitly[JsonFieldEncoder[PersonId]] + implicitly[JsonFieldDecoder[PersonId]] + } }