diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10be5e370..4a40aa60c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: fail-fast: false matrix: java: ['adopt@1.8', 'adopt@1.11'] - scala: ['2.12.14', '2.13.6', '3.0.2'] + scala: ['2.12.14', '2.13.6', '3.1.0'] platform: ['JVM', 'JS'] steps: - name: Checkout current branch @@ -83,10 +83,10 @@ jobs: - name: Cache scala dependencies uses: coursier/cache-action@v6 - name: Run tests - if: ${{ !startsWith(matrix.scala, '3.0.') }} + if: ${{ !startsWith(matrix.scala, '3.1.') }} run: sbt ++${{ matrix.scala }}! test${{ matrix.platform }} - name: Run Dotty tests - if: ${{ startsWith(matrix.scala, '3.0.') && matrix.platform == 'JVM' }} + if: ${{ startsWith(matrix.scala, '3.1.') && matrix.platform == 'JVM' }} run: sbt ++${{ matrix.scala }}! testJVMDotty ci: diff --git a/README.md b/README.md index 43d085b51..27200ddea 100644 --- a/README.md +++ b/README.md @@ -451,7 +451,7 @@ This attack is very effective in schemas with lots of numbers, causing ops/sec t # Even Moar Performance -If `zio-json` isn't fast enough for you, then try out [jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala); whereas `zio-json` is fully integrated into ZIO, including streams and transducer support, jsoniter is library agnostic. +If `zio-json` isn't fast enough for you, then try out [jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala); whereas `zio-json` is fully integrated into ZIO, including streams and pipeline support, jsoniter is library agnostic. JSON is an inefficient transport format and everybody would benefit from a port of this library to msgpack or protobuf. For legacy services, a port supporting XML is also be possible. diff --git a/build.sbt b/build.sbt index 6e4089980..201b4893b 100644 --- a/build.sbt +++ b/build.sbt @@ -35,7 +35,7 @@ addCommandAlias( addCommandAlias("testJS", "zioJsonJS/test") -val zioVersion = "1.0.12" +val zioVersion = "2.0.0-RC1" lazy val root = project .in(file(".")) @@ -265,12 +265,12 @@ lazy val zioJsonInteropHttp4s = project .settings( crossScalaVersions --= Vector("3.0.2"), libraryDependencies ++= Seq( - "org.http4s" %% "http4s-dsl" % "0.21.31", + "org.http4s" %% "http4s-dsl" % "0.23.7", "dev.zio" %% "zio" % zioVersion, - "org.typelevel" %% "cats-effect" % "2.5.4", - "dev.zio" %% "zio-interop-cats" % "2.5.1.0" % "test", - "dev.zio" %% "zio-test" % zioVersion % "test", - "dev.zio" %% "zio-test-sbt" % zioVersion % "test" + "org.typelevel" %% "cats-effect" % "3.3.0", + "dev.zio" %% "zio-interop-cats" % "3.3.0-RC1" % "test", + "dev.zio" %% "zio-test" % zioVersion % "test", + "dev.zio" %% "zio-test-sbt" % zioVersion % "test" ), testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") ) diff --git a/project/BuildHelper.scala b/project/BuildHelper.scala index 1135fafd3..ad03ef82e 100644 --- a/project/BuildHelper.scala +++ b/project/BuildHelper.scala @@ -20,7 +20,7 @@ object BuildHelper { } val Scala212: String = versions("2.12") val Scala213: String = versions("2.13") - val ScalaDotty: String = "3.0.2" + val ScalaDotty: String = "3.1.0" val SilencerVersion = "1.7.7" @@ -139,7 +139,7 @@ object BuildHelper { def extraOptions(scalaVersion: String, optimize: Boolean) = CrossVersion.partialVersion(scalaVersion) match { - case Some((3, 0)) => + case Some((3, 1)) => Seq( "-language:implicitConversions", "-Xignore-scala2-macros" diff --git a/zio-json-interop-http4s/src/test/scala/zio/json/interop/http4s/ZIOJsonInstancesSpec.scala b/zio-json-interop-http4s/src/test/scala/zio/json/interop/http4s/ZIOJsonInstancesSpec.scala index aa10d4aa3..dc3e98e60 100644 --- a/zio-json-interop-http4s/src/test/scala/zio/json/interop/http4s/ZIOJsonInstancesSpec.scala +++ b/zio-json-interop-http4s/src/test/scala/zio/json/interop/http4s/ZIOJsonInstancesSpec.scala @@ -1,6 +1,7 @@ package zio.json.interop.http4s import org.http4s._ +import org.typelevel.ci._ import zio.Task import zio.interop.catz._ import zio.json._ @@ -15,8 +16,8 @@ object ZIOJsonInstancesSpec extends DefaultRunnableSpec { def spec: ZSpec[Environment, Failure] = suite("json instances")( suite("jsonEncoderOf") { - testM("returns an EntityEncoder that can encode for the given effect and type") { - checkM(Gen.anyString, Gen.anyInt) { (s, i) => + test("returns an EntityEncoder that can encode for the given effect and type") { + check(Gen.string, Gen.int) { (s, i) => val result = jsonEncoderOf[Task, Test] .toEntity(Test(s, i)) .body @@ -29,37 +30,37 @@ object ZIOJsonInstancesSpec extends DefaultRunnableSpec { } }, suite("jsonOf")( - testM("returns an EntityDecoder that can decode for the given effect and type")( - checkM(Gen.anyString, Gen.anyInt) { (s, i) => + test("returns an EntityDecoder that can decode for the given effect and type")( + check(Gen.string, Gen.int) { (s, i) => val media = Request[Task]() .withEntity(s"""{"string":"$s","int":$i}""") - .withHeaders(Header("Content-Type", "application/json")) + .withHeaders(Header.Raw(ci"Content-Type", "application/json")) assertM(jsonOf[Task, Test].decode(media, true).value)(isRight(equalTo(Test(s, i)))) } ), - testM("returns MalformedMessageBodyFailure when json is empty") { + test("returns MalformedMessageBodyFailure when json is empty") { val media = Request[Task]() .withEntity("") - .withHeaders(Header("Content-Type", "application/json")) + .withHeaders(Header.Raw(ci"Content-Type", "application/json")) assertM(jsonOf[Task, Test].decode(media, true).value)( isLeft(equalTo(MalformedMessageBodyFailure("Invalid JSON: empty body"))) ) }, - testM("returns MalformedMessageBodyFailure when json is invalid") { + test("returns MalformedMessageBodyFailure when json is invalid") { val media = Request[Task]() .withEntity("""{"bad" "json"}""") - .withHeaders(Header("Content-Type", "application/json")) + .withHeaders(Header.Raw(ci"Content-Type", "application/json")) assertM(jsonOf[Task, Test].decode(media, true).value)( isLeft(equalTo(MalformedMessageBodyFailure("(expected ':' got '\"')"))) ) }, - testM("returns MalformedMessageBodyFailure when message body is not a json") { + test("returns MalformedMessageBodyFailure when message body is not a json") { val media = Request[Task]() .withEntity("not a json") - .withHeaders(Header("Content-Type", "text/plain")) + .withHeaders(Header.Raw(ci"Content-Type", "text/plain")) assertM(jsonOf[Task, Test].decode(media, true).value)(isLeft(isSubtype[MediaTypeMismatch](anything))) } diff --git a/zio-json-macros/shared/src/test/scala/zio/json/DeriveSpec.scala b/zio-json-macros/shared/src/test/scala/zio/json/DeriveSpec.scala index 9a252afdc..d9f0488e7 100644 --- a/zio-json-macros/shared/src/test/scala/zio/json/DeriveSpec.scala +++ b/zio-json-macros/shared/src/test/scala/zio/json/DeriveSpec.scala @@ -3,7 +3,6 @@ package testzio.json import zio.json._ import zio.test.Assertion._ import zio.test._ -import zio.test.environment.TestEnvironment object DeriveSpec extends DefaultRunnableSpec { diff --git a/zio-json-yaml/src/test/scala/zio/json/yaml/YamlDecoderSpec.scala b/zio-json-yaml/src/test/scala/zio/json/yaml/YamlDecoderSpec.scala index f22df3149..f55726391 100644 --- a/zio-json-yaml/src/test/scala/zio/json/yaml/YamlDecoderSpec.scala +++ b/zio-json-yaml/src/test/scala/zio/json/yaml/YamlDecoderSpec.scala @@ -3,7 +3,6 @@ package zio.json.yaml import zio.json.yaml.YamlEncoderSpec.{ Example, ex1, ex1Yaml, ex1Yaml2 } import zio.test.Assertion.{ equalTo, isRight } import zio.test._ -import zio.test.environment.TestEnvironment object YamlDecoderSpec extends DefaultRunnableSpec { override def spec: ZSpec[TestEnvironment, Any] = diff --git a/zio-json-yaml/src/test/scala/zio/json/yaml/YamlEncoderSpec.scala b/zio-json-yaml/src/test/scala/zio/json/yaml/YamlEncoderSpec.scala index 188038b17..9c3a42740 100644 --- a/zio-json-yaml/src/test/scala/zio/json/yaml/YamlEncoderSpec.scala +++ b/zio-json-yaml/src/test/scala/zio/json/yaml/YamlEncoderSpec.scala @@ -5,7 +5,6 @@ import zio.json._ import zio.json.ast.Json import zio.test.Assertion._ import zio.test._ -import zio.test.environment.TestEnvironment object YamlEncoderSpec extends DefaultRunnableSpec { override def spec: ZSpec[TestEnvironment, Any] = diff --git a/zio-json/jvm/src/jmh/scala/zio/json/UUIDBenchmarks.scala b/zio-json/jvm/src/jmh/scala/zio/json/UUIDBenchmarks.scala index d2d1ad74b..235c9b474 100644 --- a/zio-json/jvm/src/jmh/scala/zio/json/UUIDBenchmarks.scala +++ b/zio-json/jvm/src/jmh/scala/zio/json/UUIDBenchmarks.scala @@ -3,7 +3,7 @@ package zio.json import org.openjdk.jmh.annotations._ import zio.Chunk import zio.json.uuid.UUIDParser -import zio.random.Random +import zio.Random import zio.test.Gen import java.util.UUID diff --git a/zio-json/jvm/src/main/scala/zio/JsonPackagePlatformSpecific.scala b/zio-json/jvm/src/main/scala/zio/JsonPackagePlatformSpecific.scala index 6b5742de5..b9e7e53ff 100644 --- a/zio-json/jvm/src/main/scala/zio/JsonPackagePlatformSpecific.scala +++ b/zio-json/jvm/src/main/scala/zio/JsonPackagePlatformSpecific.scala @@ -1,6 +1,5 @@ package zio -import zio.blocking.Blocking import zio.json.{ JsonDecoder, JsonEncoder, JsonStreamDelimiter, ast } import zio.stream._ @@ -10,84 +9,104 @@ import java.nio.charset.StandardCharsets import java.nio.file.{ Path, Paths } trait JsonPackagePlatformSpecific { - def readJsonAs(file: File): ZStream[Blocking, Throwable, ast.Json] = + def readJsonAs(file: File): ZStream[Any, Throwable, ast.Json] = readJsonLinesAs[ast.Json](file) - def readJsonAs(path: Path): ZStream[Blocking, Throwable, ast.Json] = + def readJsonAs(path: Path): ZStream[Any, Throwable, ast.Json] = readJsonLinesAs[ast.Json](path) - def readJsonAs(path: String): ZStream[Blocking, Throwable, ast.Json] = + def readJsonAs(path: String): ZStream[Any, Throwable, ast.Json] = readJsonLinesAs[ast.Json](path) - def readJsonAs(url: URL): ZStream[Blocking, Throwable, ast.Json] = + def readJsonAs(url: URL): ZStream[Any, Throwable, ast.Json] = readJsonLinesAs[ast.Json](url) - def readJsonLinesAs[A: JsonDecoder](file: File): ZStream[Blocking, Throwable, A] = + def readJsonLinesAs[A: JsonDecoder](file: File): ZStream[Any, Throwable, A] = readJsonLinesAs(file.toPath) - def readJsonLinesAs[A: JsonDecoder](path: Path): ZStream[Blocking, Throwable, A] = + def readJsonLinesAs[A: JsonDecoder](path: Path): ZStream[Any, Throwable, A] = ZStream - .fromFile(path) - .transduce( - ZTransducer.utf8Decode >>> + .fromPath(path) + .via( + ZPipeline.utf8Decode >>> stringToChars >>> - JsonDecoder[A].decodeJsonTransducer(JsonStreamDelimiter.Newline) + JsonDecoder[A].decodeJsonPipeline(JsonStreamDelimiter.Newline) ) - def readJsonLinesAs[A: JsonDecoder](path: String): ZStream[Blocking, Throwable, A] = + def readJsonLinesAs[A: JsonDecoder](path: String): ZStream[Any, Throwable, A] = readJsonLinesAs(Paths.get(path)) - def readJsonLinesAs[A: JsonDecoder](url: URL): ZStream[Blocking, Throwable, A] = { + def readJsonLinesAs[A: JsonDecoder](url: URL): ZStream[Any, Throwable, A] = { val managed = ZManaged - .fromAutoCloseable(ZIO.effect(url.openStream())) + .fromAutoCloseable(ZIO.attempt(url.openStream())) .refineToOrDie[IOException] ZStream .fromInputStreamManaged(managed) - .transduce( - ZTransducer.utf8Decode >>> + .via( + ZPipeline.utf8Decode >>> stringToChars >>> - JsonDecoder[A].decodeJsonTransducer(JsonStreamDelimiter.Newline) + JsonDecoder[A].decodeJsonPipeline(JsonStreamDelimiter.Newline) ) } - def writeJsonLines[R <: Blocking](file: File, stream: ZStream[R, Throwable, ast.Json]): RIO[R, Unit] = + def writeJsonLines[R](file: File, stream: ZStream[R, Throwable, ast.Json]): RIO[R, Unit] = writeJsonLinesAs(file, stream) - def writeJsonLines[R <: Blocking](path: Path, stream: ZStream[R, Throwable, ast.Json]): RIO[R, Unit] = + def writeJsonLines[R](path: Path, stream: ZStream[R, Throwable, ast.Json]): RIO[R, Unit] = writeJsonLinesAs(path, stream) - def writeJsonLines[R <: Blocking](path: String, stream: ZStream[R, Throwable, ast.Json]): RIO[R, Unit] = + def writeJsonLines[R](path: String, stream: ZStream[R, Throwable, ast.Json]): RIO[R, Unit] = writeJsonLinesAs(path, stream) - def writeJsonLinesAs[R <: Blocking, A: JsonEncoder](file: File, stream: ZStream[R, Throwable, A]): RIO[R, Unit] = + def writeJsonLinesAs[R, A: JsonEncoder](file: File, stream: ZStream[R, Throwable, A]): RIO[R, Unit] = writeJsonLinesAs(file.toPath, stream) - def writeJsonLinesAs[R <: Blocking, A: JsonEncoder](path: Path, stream: ZStream[R, Throwable, A]): RIO[R, Unit] = + def writeJsonLinesAs[R, A: JsonEncoder](path: Path, stream: ZStream[R, Throwable, A]): RIO[R, Unit] = stream - .transduce( - JsonEncoder[A].encodeJsonLinesTransducer >>> + .via( + JsonEncoder[A].encodeJsonLinesPipeline >>> charsToUtf8 ) - .run(ZSink.fromFile(path)) + .run(ZSink.fromPath(path)) .unit - def writeJsonLinesAs[R <: Blocking, A: JsonEncoder](path: String, stream: ZStream[R, Throwable, A]): RIO[R, Unit] = + def writeJsonLinesAs[R, A: JsonEncoder](path: String, stream: ZStream[R, Throwable, A]): RIO[R, Unit] = writeJsonLinesAs(Paths.get(path), stream) - private def stringToChars: ZTransducer[Any, Nothing, String, Char] = - ZTransducer - .fromFunction[String, Chunk[Char]](s => Chunk.fromArray(s.toCharArray)) - .mapChunks(_.flatten) + private def stringToChars: ZPipeline[Any, Nothing, String, Char] = + ZPipeline.mapChunks[String, Char](_.flatMap(_.toCharArray)) - private def charsToUtf8: ZTransducer[Any, Nothing, Char, Byte] = - ZTransducer.fromPush { - case None => - ZIO.succeed(Chunk.empty) - - case Some(xs) => - ZIO.effectTotal { - Chunk.fromArray((new String(xs.toArray)).getBytes(StandardCharsets.UTF_8)) + private def charsToUtf8: ZPipeline[Any, Nothing, Char, Byte] = + ZPipeline.mapChunksZIO[Any, Nothing, Char, Byte] { chunk => + ZIO.succeed { + Chunk.fromArray { + new String(chunk.toArray).getBytes(StandardCharsets.UTF_8) } + } + } + + private[zio] def fromManagedPush[Env, Err, In, Out]( + push: ZManaged[Env, Nothing, Option[Chunk[In]] => ZIO[Env, Err, Chunk[Out]]] + ): ZPipeline[Env, Err, In, Out] = { + + def pull( + push: Option[Chunk[In]] => ZIO[Env, Err, Chunk[Out]] + ): ZChannel[Env, Nothing, Chunk[In], Any, Err, Chunk[Out], Any] = + ZChannel.readWith[Env, Nothing, Chunk[In], Any, Err, Chunk[Out], Any]( + in => + ZChannel + .fromZIO(push(Some(in))) + .flatMap(out => ZChannel.write(out)) + .zipRight[Env, Nothing, Chunk[In], Any, Err, Chunk[Out], Any](pull(push)), + err => ZChannel.fail(err), + _ => ZChannel.fromZIO(push(None)).flatMap(out => ZChannel.write(out)) + ) + + ZPipeline.fromChannel { + ZChannel.unwrapManaged[Env, Nothing, Chunk[In], Any, Err, Chunk[Out], Any] { + push.map(pull) + } } + } } diff --git a/zio-json/jvm/src/main/scala/zio/json/JsonDecoderPlatformSpecific.scala b/zio-json/jvm/src/main/scala/zio/json/JsonDecoderPlatformSpecific.scala index 591ef0d42..704fb41b5 100644 --- a/zio-json/jvm/src/main/scala/zio/json/JsonDecoderPlatformSpecific.scala +++ b/zio-json/jvm/src/main/scala/zio/json/JsonDecoderPlatformSpecific.scala @@ -1,18 +1,17 @@ package zio.json import zio._ -import zio.blocking._ import zio.json.JsonDecoder.JsonError import zio.json.internal._ -import zio.stream.{ Take, ZStream, ZTransducer } +import zio.stream.{ Take, ZPipeline, ZStream } import java.nio.charset.{ Charset, StandardCharsets } import scala.annotation.tailrec trait JsonDecoderPlatformSpecific[A] { self: JsonDecoder[A] => - private def readAll(reader: java.io.Reader): ZIO[Blocking, Throwable, A] = - effectBlocking { + private def readAll(reader: java.io.Reader): ZIO[Any, Throwable, A] = + ZIO.attemptBlocking { try unsafeDecode(Nil, new zio.json.internal.WithRetractReader(reader)) catch { case JsonDecoder.UnsafeJson(trace) => throw new Exception(JsonError.render(trace)) @@ -28,7 +27,7 @@ trait JsonDecoderPlatformSpecific[A] { self: JsonDecoder[A] => * * @see [[decodeJsonStream]] For a `Char` stream variant */ - final def decodeJsonStreamInput[R <: Blocking]( + final def decodeJsonStreamInput[R]( stream: ZStream[R, Throwable, Byte], charset: Charset = StandardCharsets.UTF_8 ): ZIO[R, Throwable, A] = @@ -44,123 +43,123 @@ trait JsonDecoderPlatformSpecific[A] { self: JsonDecoder[A] => * * @see also [[decodeJsonStreamInput]] */ - final def decodeJsonStream[R <: Blocking](stream: ZStream[R, Throwable, Char]): ZIO[R, Throwable, A] = + final def decodeJsonStream[R](stream: ZStream[R, Throwable, Char]): ZIO[R, Throwable, A] = stream.toReader.use(readAll) - final def decodeJsonTransducer( + final def decodeJsonPipeline( delimiter: JsonStreamDelimiter = JsonStreamDelimiter.Array - ): ZTransducer[Blocking, Throwable, Char, A] = - ZTransducer { + ): ZPipeline[Any, Throwable, Char, A] = + fromManagedPush { for { // format: off - runtime <- ZManaged.runtime[Any] - inQueue <- Queue.unbounded[Take[Nothing, Char]].toManaged_ - outQueue <- Queue.unbounded[Take[Throwable, A]].toManaged_ - ended <- Ref.makeManaged(false) - reader <- ZManaged.fromAutoCloseable { - UIO { - def readPull: Iterator[Chunk[Char]] = - runtime.unsafeRun(inQueue.take) - .fold( - end = Iterator.empty, - error = _ => Iterator.empty, // impossible - value = v => Iterator.single(v) ++ readPull - ) - - new zio.stream.internal.ZReader(Iterator.empty ++ readPull) - } + runtime <- ZManaged.runtime[Any] + inQueue <- Queue.unbounded[Take[Nothing, Char]].toManaged + outQueue <- Queue.unbounded[Take[Throwable, A]].toManaged + ended <- Ref.makeManaged(false) + reader <- ZManaged.fromAutoCloseable { + UIO { + def readPull: Iterator[Chunk[Char]] = + runtime.unsafeRun(inQueue.take) + .fold( + end = Iterator.empty, + error = _ => Iterator.empty, // impossible + value = v => Iterator.single(v) ++ readPull + ) + + new zio.stream.internal.ZReader(Iterator.empty ++ readPull) } - jsonReader <- ZManaged.fromAutoCloseable(UIO(new WithRetractReader(reader))) - process <- effectBlockingInterrupt { - // Exceptions fall through and are pushed into the queue - @tailrec def loop(atBeginning: Boolean): Unit = { - val nextElem = try { - if (atBeginning && delimiter == JsonStreamDelimiter.Array) { - Lexer.char(Nil, jsonReader, '[') - - jsonReader.nextNonWhitespace() match { - case ']' => - // returning empty here instead of falling through, which would - // attempt to decode a value that we know doesn’t exist. - return () - - case _ => - jsonReader.retract() - } - } else { - delimiter match { - case JsonStreamDelimiter.Newline => - jsonReader.readChar() match { - case '\r' => - jsonReader.readChar() match { - case '\n' => () - case _ => jsonReader.retract() - } - case '\n' => () - case _ => jsonReader.retract() - } - - case JsonStreamDelimiter.Array => - jsonReader.nextNonWhitespace() match { - case ',' | ']' => () - case _ => jsonReader.retract() - } - } + } + jsonReader <- ZManaged.fromAutoCloseable(UIO(new WithRetractReader(reader))) + process <- ZIO.attemptBlockingInterrupt { + // Exceptions fall through and are pushed into the queue + @tailrec def loop(atBeginning: Boolean): Unit = { + val nextElem = try { + if (atBeginning && delimiter == JsonStreamDelimiter.Array) { + Lexer.char(Nil, jsonReader, '[') + + jsonReader.nextNonWhitespace() match { + case ']' => + // returning empty here instead of falling through, which would + // attempt to decode a value that we know doesn’t exist. + return () + + case _ => + jsonReader.retract() + } + } else { + delimiter match { + case JsonStreamDelimiter.Newline => + jsonReader.readChar() match { + case '\r' => + jsonReader.readChar() match { + case '\n' => () + case _ => jsonReader.retract() + } + case '\n' => () + case _ => jsonReader.retract() + } + + case JsonStreamDelimiter.Array => + jsonReader.nextNonWhitespace() match { + case ',' | ']' => () + case _ => jsonReader.retract() + } } - - unsafeDecode(Nil, jsonReader) - } catch { - case t @ JsonDecoder.UnsafeJson(trace) => - throw new Exception(JsonError.render(trace)) } - runtime.unsafeRun(outQueue.offer(Take.single(nextElem))) - - loop(false) + unsafeDecode(Nil, jsonReader) + } catch { + case t @ JsonDecoder.UnsafeJson(trace) => + throw new Exception(JsonError.render(trace)) } - loop(true) + runtime.unsafeRun(outQueue.offer(Take.single(nextElem))) + + loop(false) } - .catchAll { - case t: zio.json.internal.UnexpectedEnd => - // swallow if stream ended - ZIO.unlessM(ended.get) { - outQueue.offer(Take.fail(t)) - } - case t: Throwable => + loop(true) + } + .catchAll { + case t: zio.json.internal.UnexpectedEnd => + // swallow if stream ended + ZIO.unlessZIO(ended.get) { outQueue.offer(Take.fail(t)) - } - .interruptible - .forkManaged - push = { (is: Option[Chunk[Char]]) => - val pollElements: IO[Throwable, Chunk[A]] = - outQueue - .takeUpTo(ZStream.DefaultChunkSize) - .flatMap { takes => - ZIO.foldLeft(takes)(Chunk[A]()) { case (acc, take) => - take.fold(ZIO.succeedNow(acc), e => ZIO.fail(e.squash), c => ZIO.succeedNow(acc ++ c)) - } - } + } - val pullRest = - outQueue - .takeAll - .flatMap { takes => - ZIO.foldLeft(takes)(Chunk[A]()) { case (acc, take) => - take.fold(ZIO.succeedNow(acc), e => ZIO.fail(e.squash), c => ZIO.succeedNow(acc ++ c)) - } + case t: Throwable => + outQueue.offer(Take.fail(t)) + } + .interruptible + .forkManaged + push = { (is: Option[Chunk[Char]]) => + val pollElements: IO[Throwable, Chunk[A]] = + outQueue + .takeUpTo(ZStream.DefaultChunkSize) + .flatMap { takes => + ZIO.foldLeft(takes)(Chunk[A]()) { case (acc, take) => + take.fold(ZIO.succeedNow(acc), e => ZIO.fail(e.squash), c => ZIO.succeedNow(acc ++ c)) } + } + + val pullRest = + outQueue + .takeAll + .flatMap { takes => + ZIO.foldLeft(takes)(Chunk[A]()) { case (acc, take) => + take.fold(ZIO.succeedNow(acc), e => ZIO.fail(e.squash), c => ZIO.succeedNow(acc ++ c)) + } + } - is match { - case Some(c) => - inQueue.offer(Take.chunk(c)) *> pollElements + is match { + case Some(c) => + inQueue.offer(Take.chunk(c)) *> pollElements - case None => - ended.set(true) *> inQueue.offer(Take.end) *> process.join *> pullRest - } + case None => + ended.set(true) *> inQueue.offer(Take.end) *> process.join *> pullRest } - } yield push - // format: on + } + } yield push + // format: on } } diff --git a/zio-json/jvm/src/main/scala/zio/json/JsonEncoderPlatformSpecific.scala b/zio-json/jvm/src/main/scala/zio/json/JsonEncoderPlatformSpecific.scala index 082c69b3d..62ba86ac3 100644 --- a/zio-json/jvm/src/main/scala/zio/json/JsonEncoderPlatformSpecific.scala +++ b/zio-json/jvm/src/main/scala/zio/json/JsonEncoderPlatformSpecific.scala @@ -1,7 +1,5 @@ package zio.json -import com.github.ghik.silencer.silent -import zio.blocking._ import zio.json.internal.WriteWriter import zio.stream._ import zio.{ Chunk, Ref, ZIO, ZManaged } @@ -11,20 +9,20 @@ trait JsonEncoderPlatformSpecific[A] { self: JsonEncoder[A] => /** * Encodes the specified value into a character stream. */ - final def encodeJsonStream(a: A): ZStream[Blocking, Throwable, Char] = - ZStream(a).transduce(encodeJsonDelimitedTransducer(None, None, None)) + final def encodeJsonStream(a: A): ZStream[Any, Throwable, Char] = + ZStream(a).via(encodeJsonDelimitedPipeline(None, None, None)) - final private def encodeJsonDelimitedTransducer( + final private def encodeJsonDelimitedPipeline( startWith: Option[Char], delimiter: Option[Char], endWith: Option[Char] - ): ZTransducer[Blocking, Throwable, A, Char] = - ZTransducer { + ): ZPipeline[Any, Throwable, A, Char] = + fromManagedPush { for { - runtime <- ZIO.runtime[Any].toManaged_ + runtime <- ZIO.runtime[Any].toManaged chunkBuffer <- Ref.makeManaged(Chunk.fromIterable(startWith.toList)) writer <- ZManaged.fromAutoCloseable { - ZIO.effectTotal { + ZIO.succeed { new java.io.BufferedWriter( new java.io.Writer { override def write(buffer: Array[Char], offset: Int, len: Int): Unit = { @@ -48,7 +46,7 @@ trait JsonEncoderPlatformSpecific[A] { self: JsonEncoder[A] => is match { case None => - effectBlocking(writer.close()) *> pushChars.map { terminal => + ZIO.attemptBlocking(writer.close()) *> pushChars.map { terminal => endWith.fold(terminal) { last => // Chop off terminal deliminator (if (delimiter.isDefined) terminal.dropRight(1) else terminal) :+ last @@ -56,7 +54,7 @@ trait JsonEncoderPlatformSpecific[A] { self: JsonEncoder[A] => } case Some(xs) => - effectBlocking { + ZIO.attemptBlocking { for (x <- xs) { unsafeEncode(x, indent = None, writeWriter) @@ -69,9 +67,9 @@ trait JsonEncoderPlatformSpecific[A] { self: JsonEncoder[A] => } yield push } - final val encodeJsonLinesTransducer: ZTransducer[Blocking, Throwable, A, Char] = - encodeJsonDelimitedTransducer(None, Some('\n'), None) + final val encodeJsonLinesPipeline: ZPipeline[Any, Throwable, A, Char] = + encodeJsonDelimitedPipeline(None, Some('\n'), None) - final val encodeJsonArrayTransducer: ZTransducer[Blocking, Throwable, A, Char] = - encodeJsonDelimitedTransducer(Some('['), Some(','), Some(']')) + final val encodeJsonArrayPipeline: ZPipeline[Any, Throwable, A, Char] = + encodeJsonDelimitedPipeline(Some('['), Some(','), Some(']')) } diff --git a/zio-json/jvm/src/test/scala-2/zio/json/DecoderPlatformSpecificSpec.scala b/zio-json/jvm/src/test/scala-2/zio/json/DecoderPlatformSpecificSpec.scala index 9afa13584..1527b0d0b 100644 --- a/zio-json/jvm/src/test/scala-2/zio/json/DecoderPlatformSpecificSpec.scala +++ b/zio-json/jvm/src/test/scala-2/zio/json/DecoderPlatformSpecificSpec.scala @@ -6,15 +6,12 @@ import testzio.json.TestUtils._ import testzio.json.data.googlemaps._ import testzio.json.data.twitter._ import zio._ -import zio.blocking._ -import zio.duration._ import zio.json._ import zio.json.ast._ import zio.stream.ZStream import zio.test.Assertion._ import zio.test.TestAspect._ import zio.test._ -import zio.test.environment.TestEnvironment import java.nio.charset.StandardCharsets import java.nio.file.Paths @@ -23,38 +20,38 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { def spec: Spec[TestEnvironment, TestFailure[Any], TestSuccess] = suite("Decoder")( - testM("excessively nested structures") { + test("excessively nested structures") { // JVM specific: getResourceAsString not yet supported val testFile = "json_test_suite/n_structure_open_array_object.json" for { s <- getResourceAsStringM(testFile) - r <- ZIO.fromEither(s.fromJson[Json]).run + r <- ZIO.fromEither(s.fromJson[Json]).exit } yield { assert(r)(fails(equalTo("Unexpected structure"))) } }, - testM("googleMapsNormal") { + test("googleMapsNormal") { getResourceAsStringM("google_maps_api_response.json").map { str => assert(str.fromJson[DistanceMatrix])(matchesCirceDecoded[DistanceMatrix](str)) } }, - testM("googleMapsCompact") { + test("googleMapsCompact") { getResourceAsStringM("google_maps_api_compact_response.json").map { str => assert(str.fromJson[DistanceMatrix])(matchesCirceDecoded[DistanceMatrix](str)) } }, - testM("googleMapsExtra") { + test("googleMapsExtra") { getResourceAsStringM("google_maps_api_extra.json").map { str => assert(str.fromJson[DistanceMatrix])(matchesCirceDecoded[DistanceMatrix](str)) } }, - testM("googleMapsError") { + test("googleMapsError") { getResourceAsStringM("google_maps_api_error_response.json").map { str => assert(str.fromJson[DistanceMatrix])(isLeft(equalTo(".rows[0].elements[0].distance.value(missing)"))) } }, - testM("googleMapsAst") { + test("googleMapsAst") { val response = getResourceAsStringM("google_maps_api_response.json") val compact = getResourceAsStringM("google_maps_api_compact_response.json") @@ -62,33 +59,33 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { assert(response.fromJson[Json])(equalTo(compact.fromJson[Json])) } }, - testM("twitter") { + test("twitter") { getResourceAsStringM("twitter_api_response.json").map { str => assert(str.fromJson[List[Tweet]])(matchesCirceDecoded[List[Tweet]](str)) } }, - testM("geojson1") { + test("geojson1") { import testzio.json.data.geojson.generated._ getResourceAsStringM("che.geo.json").map { str => assert(str.fromJson[GeoJSON])(matchesCirceDecoded[GeoJSON](str)) } }, - testM("geojson1 alt") { + test("geojson1 alt") { import testzio.json.data.geojson.handrolled._ getResourceAsStringM("che.geo.json").map { str => assert(str.fromJson[GeoJSON])(matchesCirceDecoded[GeoJSON](str)) } }, - testM("geojson2") { + test("geojson2") { import testzio.json.data.geojson.generated._ getResourceAsStringM("che-2.geo.json").map { str => assert(str.fromJson[GeoJSON])(matchesCirceDecoded[GeoJSON](str)) } }, - testM("geojson2 lowlevel") { + test("geojson2 lowlevel") { import testzio.json.data.geojson.generated._ // this uses a lower level Reader to ensure that the more general recorder // impl is covered by the tests @@ -97,7 +94,7 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { ZManaged.fromAutoCloseable(Task(getResourceAsReader("che-2.geo.json"))).use { reader => for { circe <- ZIO.fromEither(circe.parser.decode[GeoJSON](str)) - got <- effectBlocking(JsonDecoder[GeoJSON].unsafeDecode(Nil, reader)) + got <- ZIO.attemptBlocking(JsonDecoder[GeoJSON].unsafeDecode(Nil, reader)) } yield { assert(got)(equalTo(circe)) } @@ -116,106 +113,106 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { testAst("ugh10k") ), suite("ZIO Streams integration")( - testM("decodes a stream of chars") { + test("decodes a stream of chars") { for { int <- JsonDecoder[Int].decodeJsonStream(ZStream('1', '2', '3')) } yield { assert(int)(equalTo(123)) } }, - testM("decodes an encoded stream of bytes") { + test("decodes an encoded stream of bytes") { for { int <- JsonDecoder[Int].decodeJsonStreamInput(ZStream.fromIterable("123".getBytes(StandardCharsets.UTF_8))) } yield assert(int)(equalTo(123)) }, - suite("decodeJsonTransducer")( + suite("decodeJsonPipeline")( suite("Newline delimited")( - testM("decodes single elements") { + test("decodes single elements") { ZStream .fromIterable("1001".toSeq) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Newline)) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Newline)) .runCollect .map { xs => assert(xs)(equalTo(Chunk(1001))) } }, - testM("decodes multiple elements") { + test("decodes multiple elements") { ZStream .fromIterable("1001\n1002".toSeq) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Newline)) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Newline)) .runCollect .map { xs => assert(xs)(equalTo(Chunk(1001, 1002))) } }, - testM("decodes multiple elements when fed in smaller chunks") { + test("decodes multiple elements when fed in smaller chunks") { ZStream .fromIterable("1001\n1002".toSeq) - .chunkN(1) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Newline)) + .rechunk(1) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Newline)) .runCollect .map { xs => assert(xs)(equalTo(Chunk(1001, 1002))) } }, - testM("accepts trailing NL") { + test("accepts trailing NL") { ZStream .fromIterable("1001\n1002\n".toSeq) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Newline)) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Newline)) .runCollect .map { xs => assert(xs)(equalTo(Chunk(1001, 1002))) } }, - testM("errors") { + test("errors") { ZStream .fromIterable("1\nfalse\n3".toSeq) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Newline)) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Newline)) .runDrain - .run + .exit .map { exit => assert(exit)(fails(anything)) } }, - testM("is interruptible") { - (ZStream.fromIterable("1\n2\n3\n4") ++ ZStream.fromEffect(ZIO.interrupt)) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Newline)) + test("is interruptible") { + (ZStream.fromIterable("1\n2\n3\n4") ++ ZStream.fromZIO(ZIO.interrupt)) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Newline)) .runDrain - .run + .exit .map { exit => assert(exit)(isInterrupted) } } @@ timeout(2.seconds) ), suite("Array delimited")( - testM("decodes single elements") { + test("decodes single elements") { ZStream .fromIterable("[1001]".toSeq) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Array)) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Array)) .runCollect .map { xs => assert(xs)(equalTo(Chunk(1001))) } }, - testM("empty array") { + test("empty array") { ZStream .fromIterable("[]".toSeq) - .transduce(JsonDecoder[String].decodeJsonTransducer(JsonStreamDelimiter.Array)) + .via(JsonDecoder[String].decodeJsonPipeline(JsonStreamDelimiter.Array)) .runCollect .map { xs => assert(xs)(isEmpty) } }, - testM("decodes multiple elements") { + test("decodes multiple elements") { ZStream .fromIterable("[ 1001, 1002, 1003 ]".toSeq) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Array)) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Array)) .runCollect .map { xs => assert(xs)(equalTo(Chunk(1001, 1002, 1003))) } }, - testM("handles whitespace leniently") { + test("handles whitespace leniently") { val in = """[ 1001, 1002, @@ -224,7 +221,7 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { ZStream .fromIterable(in.toSeq) - .transduce(JsonDecoder[Int].decodeJsonTransducer(JsonStreamDelimiter.Array)) + .via(JsonDecoder[Int].decodeJsonPipeline(JsonStreamDelimiter.Array)) .runCollect .map { xs => assert(xs)(equalTo(Chunk(1001, 1002, 1003))) @@ -233,7 +230,7 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { ) ), suite("helpers in zio.json")( - testM("readJsonLines reads from files") { + test("readJsonLines reads from files") { import logEvent._ for { @@ -243,7 +240,7 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { assert(lines(1))(equalTo(Event(1603669876, "world"))) } }, - testM("readJsonLines reads from URLs") { + test("readJsonLines reads from URLs") { import logEvent._ val url = this.getClass.getClassLoader.getResource("log.jsonlines") @@ -274,8 +271,8 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { ) ) - def testAst(label: String): ZSpec[Blocking with zio.console.Console, Throwable] = - testM(label) { + def testAst(label: String): ZSpec[Console, Throwable] = + test(label) { getResourceAsStringM(s"jawn/$label.json").flatMap { input => val expected = jawn.JParser.parseFromString(input).toEither.map(fromJawn) val got = input.fromJson[Json].map(normalize) @@ -291,13 +288,13 @@ object DecoderPlatformSpecificSpec extends DefaultRunnableSpec { val expectedf = s"${label}-expected.json" for { - _ <- effectBlocking(writeFile(gotf, e2s(got))) - _ <- effectBlocking(writeFile(expectedf, e2s(expected))) - _ <- console.putStrLn(s"dumped .json files, use `cmp <(jq . ${expectedf}) <(jq . ${gotf})`") + _ <- ZIO.attemptBlocking(writeFile(gotf, e2s(got))) + _ <- ZIO.attemptBlocking(writeFile(expectedf, e2s(expected))) + _ <- Console.printLine(s"dumped .json files, use `cmp <(jq . ${expectedf}) <(jq . ${gotf})`") } yield { assert(got)(equalTo(expected.left.map(_.getMessage))) } - } else ZIO.effectTotal(assertCompletes) + } else ZIO.succeed(assertCompletes) } } diff --git a/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala b/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala index cb26c1846..3b8bf0761 100644 --- a/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala +++ b/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala @@ -6,12 +6,10 @@ import testzio.json.data.geojson.generated._ import testzio.json.data.googlemaps._ import testzio.json.data.twitter._ import zio.Chunk -import zio.blocking.Blocking import zio.json.ast.Json import zio.stream.ZStream import zio.test.Assertion._ -import zio.test.environment.TestEnvironment -import zio.test.{ DefaultRunnableSpec, assert, _ } +import zio.test.{ DefaultRunnableSpec, TestEnvironment, assert, _ } import java.io.IOException import java.nio.file.Files @@ -27,7 +25,7 @@ object EncoderPlatformSpecificSpec extends DefaultRunnableSpec { testRoundTrip[GeoJSON]("che.geo") ), suite("ZIO Streams integration")( - testM("encodes into a ZStream of Char") { + test("encodes into a ZStream of Char") { val intEncoder = JsonEncoder[Int] val value = 1234 @@ -37,7 +35,7 @@ object EncoderPlatformSpecificSpec extends DefaultRunnableSpec { assert(chars.mkString)(equalTo("1234")) } }, - testM("encodes values that yield a result of length > DefaultChunkSize") { + test("encodes values that yield a result of length > DefaultChunkSize") { val longString = List.fill(ZStream.DefaultChunkSize * 2)('x').mkString for { @@ -47,40 +45,40 @@ object EncoderPlatformSpecificSpec extends DefaultRunnableSpec { assert(chars.mkString(""))(equalTo("\"" ++ longString ++ "\"")) } }, - testM("encodeJsonLinesTransducer") { + test("encodeJsonLinesPipeline") { val ints = ZStream(1, 2, 3, 4) for { - xs <- ints.transduce(JsonEncoder[Int].encodeJsonLinesTransducer).runCollect + xs <- ints.via(JsonEncoder[Int].encodeJsonLinesPipeline).runCollect } yield { assert(xs.mkString)(equalTo("1\n2\n3\n4\n")) } }, - testM("encodeJsonLinesTransducer handles elements which take up > DefaultChunkSize to encode") { + test("encodeJsonLinesPipeline handles elements which take up > DefaultChunkSize to encode") { val longString = List.fill(5000)('x').mkString val ints = ZStream(longString, longString) val encoder = JsonEncoder[String] for { - xs <- ints.transduce(encoder.encodeJsonLinesTransducer).runCollect + xs <- ints.via(encoder.encodeJsonLinesPipeline).runCollect } yield { // leading `"`, trailing `"` and `\n` = 3 assert(xs.size)(equalTo((5000 + 3) * 2)) } }, - testM("encodeJsonArrayTransducer") { + test("encodeJsonArrayPipeline XYZ") { val ints = ZStream(1, 2, 3).map(n => Json.Obj(Chunk("id" -> Json.Num(BigDecimal(n).bigDecimal)))) for { - xs <- ints.transduce(JsonEncoder[Json].encodeJsonArrayTransducer).runCollect + xs <- ints.via(JsonEncoder[Json].encodeJsonArrayPipeline).runCollect } yield { assert(xs.mkString)(equalTo("""[{"id":1},{"id":2},{"id":3}]""")) } } ), suite("helpers in zio.json")( - testM("writeJsonLines writes JSON lines") { + test("writeJsonLines writes JSON lines") { val path = Files.createTempFile("log", "json") val events = Chunk( Event(1603669876, "hello"), @@ -97,8 +95,8 @@ object EncoderPlatformSpecificSpec extends DefaultRunnableSpec { ) ) - def testRoundTrip[A: circe.Decoder: JsonEncoder](label: String): ZSpec[Blocking, IOException] = - testM(label) { + def testRoundTrip[A: circe.Decoder: JsonEncoder](label: String): ZSpec[Any, IOException] = + test(label) { getResourceAsStringM(s"$label.json").map { input => val circeDecoded = circe.parser.decode[A](input) val circeRecoded = circeDecoded.toOption.get.toJson diff --git a/zio-json/jvm/src/test/scala/zio/json/CarterSpec.scala b/zio-json/jvm/src/test/scala/zio/json/CarterSpec.scala index 8d8839a50..ac7d92506 100644 --- a/zio-json/jvm/src/test/scala/zio/json/CarterSpec.scala +++ b/zio-json/jvm/src/test/scala/zio/json/CarterSpec.scala @@ -1,6 +1,5 @@ package testzio.json -import zio.blocking._ import zio.json._ import zio.test.Assertion._ import zio.test._ @@ -28,7 +27,7 @@ object CarterSpec extends DefaultRunnableSpec { implicit val decoder: JsonDecoder[Testing1] = DeriveJsonDecoder.gen } - def spec: ZSpec[Blocking, Any] = + def spec: ZSpec[Any, Any] = suite("Carter")( test("simple left") { type Data = Union[String, Int] diff --git a/zio-json/jvm/src/test/scala/zio/json/JsonTestSuiteSpec.scala b/zio-json/jvm/src/test/scala/zio/json/JsonTestSuiteSpec.scala index 1997dd1ab..4e1bfc92b 100644 --- a/zio-json/jvm/src/test/scala/zio/json/JsonTestSuiteSpec.scala +++ b/zio-json/jvm/src/test/scala/zio/json/JsonTestSuiteSpec.scala @@ -2,7 +2,6 @@ package testzio.json import testzio.json.TestUtils._ import zio._ -import zio.blocking.Blocking import zio.json._ import zio.json.ast.Json import zio.test.Assertion._ @@ -11,16 +10,16 @@ import zio.test._ object JsonTestSuiteSpec extends DefaultRunnableSpec { - def spec: Spec[Blocking with Annotations, TestFailure[Any], TestSuccess] = suite("JsonTestSuite")( + def spec: Spec[Annotations, TestFailure[Any], TestSuccess] = suite("JsonTestSuite")( // Uses files from JSONTestSuite by Nicolas Seriot: // https://github.com/nst/JSONTestSuite - testM("passes all tests") { + test("passes all tests") { for { f <- getResourcePaths("json_test_suite") a <- ZIO.foreach(f.sorted) { path => for { input <- getResourceAsStringM(s"json_test_suite/$path") - exit <- ZIO.effectTotal { + exit <- ZIO.succeed { // Catch Stack overflow try { JsonDecoder[Json] diff --git a/zio-json/jvm/src/test/scala/zio/json/TestUtils.scala b/zio-json/jvm/src/test/scala/zio/json/TestUtils.scala index 58ff085a7..e96cfdae7 100644 --- a/zio-json/jvm/src/test/scala/zio/json/TestUtils.scala +++ b/zio-json/jvm/src/test/scala/zio/json/TestUtils.scala @@ -1,7 +1,6 @@ package testzio.json import zio._ -import zio.blocking._ import zio.stream._ import java.io.{ File, IOException } @@ -26,14 +25,14 @@ object TestUtils { } finally is.close() } - def getResourceAsStringM(res: String): ZIO[Blocking, IOException, String] = + def getResourceAsStringM(res: String): ZIO[Any, IOException, String] = ZStream .fromResource(res) - .transduce(ZTransducer.utf8Decode) + .via(ZPipeline.utf8Decode) .run(ZSink.foldLeftChunks("")((acc, c) => acc ++ c.mkString)) - def getResourcePaths(folderPath: String): ZIO[Blocking, IOException, Vector[String]] = - effectBlockingIO { + def getResourcePaths(folderPath: String): ZIO[Any, IOException, Vector[String]] = + ZIO.attemptBlockingIO { val url = getClass.getClassLoader.getResource(folderPath) val folder = new File(url.getPath) diff --git a/zio-json/jvm/src/test/scala/zio/json/internal/SafeNumbersSpec.scala b/zio-json/jvm/src/test/scala/zio/json/internal/SafeNumbersSpec.scala index 35b78e1cb..14c87539a 100644 --- a/zio-json/jvm/src/test/scala/zio/json/internal/SafeNumbersSpec.scala +++ b/zio-json/jvm/src/test/scala/zio/json/internal/SafeNumbersSpec.scala @@ -8,7 +8,7 @@ import zio.test.{ DefaultRunnableSpec, _ } object SafeNumbersSpec extends DefaultRunnableSpec { def spec: ZSpec[Environment, Failure] = suite("SafeNumbers")( - testM("valid big decimals") { + test("valid big decimals") { check(genBigDecimal)(i => assert(SafeNumbers.bigDecimal(i.toString, 2048))(isSome(equalTo(i)))) }, test("invalid big decimals") { @@ -28,7 +28,7 @@ object SafeNumbersSpec extends DefaultRunnableSpec { assert(invalidBigDecimalEdgeCases)(forall(isNone)) }, - testM("valid big decimal edge cases") { + test("valid big decimal edge cases") { val invalidBigDecimalEdgeCases = List( ".0", "-.0", @@ -48,10 +48,10 @@ object SafeNumbersSpec extends DefaultRunnableSpec { ) } }, - testM("invalid BigDecimal text") { + test("invalid BigDecimal text") { check(genAlphaLowerString)(s => assert(SafeNumbers.bigDecimal(s))(isNone)) }, - testM("valid BigInteger edge cases") { + test("valid BigInteger edge cases") { val inputs = List( "00", "01", @@ -70,48 +70,48 @@ object SafeNumbersSpec extends DefaultRunnableSpec { ) } }, - testM("invalid BigInteger edge cases") { + test("invalid BigInteger edge cases") { val inputs = List("0foo", "01foo", "0.1", "", "1 ") check(Gen.fromIterable(inputs))(s => assert(SafeNumbers.bigInteger(s))(isNone)) }, - testM("valid big Integer") { + test("valid big Integer") { check(genBigInteger)(i => assert(SafeNumbers.bigInteger(i.toString, 2048))(isSome(equalTo(i)))) }, - testM("invalid BigInteger") { + test("invalid BigInteger") { check(genAlphaLowerString)(s => assert(SafeNumbers.bigInteger(s))(isNone)) }, - testM("valid Byte") { + test("valid Byte") { check(Gen.byte(Byte.MinValue, Byte.MaxValue)) { b => assert(SafeNumbers.byte(b.toString))(equalTo(ByteSome(b))) } }, - testM("invalid Byte (numbers)") { - check(Gen.anyLong.filter(i => i < Byte.MinValue || i > Byte.MaxValue)) { b => + test("invalid Byte (numbers)") { + check(Gen.long.filter(i => i < Byte.MinValue || i > Byte.MaxValue)) { b => assert(SafeNumbers.byte(b.toString))(equalTo(ByteNone)) } }, - testM("invalid Byte (text)") { + test("invalid Byte (text)") { check(genAlphaLowerString)(b => assert(SafeNumbers.byte(b.toString))(equalTo(ByteNone))) }, suite("Double")( - testM("valid") { - check(Gen.anyDouble.filterNot(_.isNaN)) { d => + test("valid") { + check(Gen.double.filterNot(_.isNaN)) { d => assert(SafeNumbers.double(d.toString))(equalTo(DoubleSome(d))) } }, - testM("valid (from Int)") { - check(Gen.anyInt)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.toDouble)))) + test("valid (from Int)") { + check(Gen.int)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.toDouble)))) }, - testM("valid (from Long)") { - check(Gen.anyLong)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.toDouble)))) + test("valid (from Long)") { + check(Gen.long)(i => assert(SafeNumbers.double(i.toString))(equalTo(DoubleSome(i.toDouble)))) }, - testM("invalid edge cases") { + test("invalid edge cases") { val inputs = List("N", "Inf", "-NaN", "+NaN", "e1", "1.1.1", "1 ") check(Gen.fromIterable(inputs))(i => assert(SafeNumbers.double(i))(equalTo(DoubleNone))) }, - testM("valid edge cases") { + test("valid edge cases") { val inputs = List( ".0", "-.0", @@ -142,30 +142,30 @@ object SafeNumbersSpec extends DefaultRunnableSpec { assert(SafeNumbers.double("+Infinity"))(not(equalTo(DoubleNone))) && assert(SafeNumbers.double("-Infinity"))(not(equalTo(DoubleNone))) }, - testM("invalid doubles (text)") { + test("invalid doubles (text)") { check(genAlphaLowerString)(s => assert(SafeNumbers.double(s))(equalTo(DoubleNone))) } ), suite("Float")( - testM("valid") { - check(Gen.anyFloat.filterNot(_.isNaN))(d => assert(SafeNumbers.float(d.toString))(equalTo(FloatSome(d)))) + test("valid") { + check(Gen.float.filterNot(_.isNaN))(d => assert(SafeNumbers.float(d.toString))(equalTo(FloatSome(d)))) }, test("large mantissa") { // https://github.com/zio/zio-json/issues/221 assert(SafeNumbers.float("1.199999988079071"))(equalTo(FloatSome(1.1999999f))) }, - testM("valid (from Int)") { - check(Gen.anyInt)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.toFloat)))) + test("valid (from Int)") { + check(Gen.int)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.toFloat)))) }, - testM("valid (from Long)") { - check(Gen.anyLong)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.toFloat)))) + test("valid (from Long)") { + check(Gen.long)(i => assert(SafeNumbers.float(i.toString))(equalTo(FloatSome(i.toFloat)))) }, - testM("invalid edge cases") { + test("invalid edge cases") { val inputs = List("N", "Inf", "-NaN", "+NaN", "e1", "1.1.1") check(Gen.fromIterable(inputs))(i => assert(SafeNumbers.float(i))(equalTo(FloatNone))) }, - testM("valid edge cases") { + test("valid edge cases") { val inputs = List( ".0", "-.0", @@ -189,35 +189,35 @@ object SafeNumbersSpec extends DefaultRunnableSpec { ) } }, - testM("valid (from Double)") { - check(Gen.anyDouble.filterNot(_.isNaN)) { d => + test("valid (from Double)") { + check(Gen.double.filterNot(_.isNaN)) { d => assert(SafeNumbers.float(d.toString))(equalTo(FloatSome(d.toFloat))) } }, - testM("invalid float (text)") { + test("invalid float (text)") { check(genAlphaLowerString)(s => assert(SafeNumbers.float(s))(equalTo(FloatNone))) } ), suite("Int")( - testM("valid") { - check(Gen.anyInt)(d => assert(SafeNumbers.int(d.toString))(equalTo(IntSome(d)))) + test("valid") { + check(Gen.int)(d => assert(SafeNumbers.int(d.toString))(equalTo(IntSome(d)))) }, - testM("invalid (out of range)") { - check(Gen.anyLong.filter(i => i < Int.MinValue || i > Int.MaxValue))(d => + test("invalid (out of range)") { + check(Gen.long.filter(i => i < Int.MinValue || i > Int.MaxValue))(d => assert(SafeNumbers.int(d.toString))(equalTo(IntNone)) ) }, - testM("invalid (text)") { + test("invalid (text)") { check(genAlphaLowerString)(s => assert(SafeNumbers.int(s))(equalTo(IntNone))) } ), suite("Long")( - testM("valid edge cases") { + test("valid edge cases") { val input = List("00", "01", "0000001", "-9223372036854775807", "9223372036854775806") check(Gen.fromIterable(input))(x => assert(SafeNumbers.long(x))(equalTo(LongSome(x.toLong)))) }, - testM("in valid edge cases") { + test("in valid edge cases") { val input = List( "0foo", "01foo", @@ -230,29 +230,29 @@ object SafeNumbersSpec extends DefaultRunnableSpec { check(Gen.fromIterable(input))(x => assert(SafeNumbers.long(x))(equalTo(LongNone))) }, - testM("valid") { - check(Gen.anyLong)(d => assert(SafeNumbers.long(d.toString))(equalTo(LongSome(d)))) + test("valid") { + check(Gen.long)(d => assert(SafeNumbers.long(d.toString))(equalTo(LongSome(d)))) }, - testM("invalid (out of range)") { + test("invalid (out of range)") { val outOfRange = genBigInteger .filter(_.bitLength > 63) check(outOfRange)(x => assert(SafeNumbers.long(x.toString))(equalTo(LongNone))) }, - testM("invalid (text)") { + test("invalid (text)") { check(genAlphaLowerString)(s => assert(SafeNumbers.long(s))(equalTo(LongNone))) } ), suite("Short")( - testM("valid") { - check(Gen.anyShort)(d => assert(SafeNumbers.short(d.toString))(equalTo(ShortSome(d)))) + test("valid") { + check(Gen.short)(d => assert(SafeNumbers.short(d.toString))(equalTo(ShortSome(d)))) }, - testM("invalid (out of range)") { - check(Gen.anyLong.filter(i => i < Short.MinValue || i > Short.MaxValue))(d => + test("invalid (out of range)") { + check(Gen.long.filter(i => i < Short.MinValue || i > Short.MaxValue))(d => assert(SafeNumbers.short(d.toString))(equalTo(ShortNone)) ) }, - testM("invalid (text)") { + test("invalid (text)") { check(genAlphaLowerString)(s => assert(SafeNumbers.short(s))(equalTo(ShortNone))) } ) diff --git a/zio-json/jvm/src/test/scala/zio/json/internal/StringMatrixSpec.scala b/zio-json/jvm/src/test/scala/zio/json/internal/StringMatrixSpec.scala index 7051e588f..5aeca2355 100644 --- a/zio-json/jvm/src/test/scala/zio/json/internal/StringMatrixSpec.scala +++ b/zio-json/jvm/src/test/scala/zio/json/internal/StringMatrixSpec.scala @@ -1,13 +1,13 @@ package testzio.json.internal import zio.json.internal._ -import zio.random.Random +import zio.Random import zio.test.Assertion._ import zio.test.{ DefaultRunnableSpec, _ } object StringMatrixSpec extends DefaultRunnableSpec { def spec: ZSpec[Environment, Failure] = suite("StringMatrix")( - testM("positive succeeds") { + test("positive succeeds") { // Watch out: TestStrings were passed check(genTestStrings) { xs => val asserts = xs.map(s => matcher(xs, s).contains(s)) @@ -15,13 +15,13 @@ object StringMatrixSpec extends DefaultRunnableSpec { assert(asserts)(forall(isTrue)) } }, - testM("negative fails") { + test("negative fails") { check(genTestStrings.filterNot(_.startsWith("wibble")))(xs => assert(matcher(xs, "wibble"))(isEmpty)) }, - testM("substring fails") { + test("substring fails") { check(genTestStrings.filter(_.length > 1))(xs => assert(matcher(xs, xs.mkString))(isEmpty)) }, - testM("trivial") { + test("trivial") { check(genNonEmptyString)(s => assert(matcher(List(s), s))(equalTo(List(s)))) }, test("exact match is a substring") { diff --git a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala index 3cedf3420..fbb9e15ac 100644 --- a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala @@ -4,7 +4,6 @@ import zio._ import zio.json._ import zio.test.Assertion._ import zio.test._ -import zio.test.environment.TestEnvironment import scala.collection.immutable 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 a65e34ea4..a995179a0 100644 --- a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala @@ -81,7 +81,7 @@ object DecoderSpec extends DefaultRunnableSpec { assert("""{}""".fromJson[DefaultString])(isRight(equalTo(DefaultString("")))) && assert("""{"s": null}""".fromJson[DefaultString])(isRight(equalTo(DefaultString("")))) - } @@ TestAspect.exceptDotty, + } @@ TestAspect.exceptScala3, test("sum encoding") { import examplesum._ @@ -272,7 +272,7 @@ object DecoderSpec extends DefaultRunnableSpec { assert(Json.Obj().as[DefaultString])(isRight(equalTo(DefaultString("")))) && assert(Json.Obj("s" -> Json.Null).as[DefaultString])(isRight(equalTo(DefaultString("")))) - } @@ TestAspect.exceptDotty, + } @@ TestAspect.exceptScala3, test("sum encoding") { import examplesum._ diff --git a/zio-json/shared/src/test/scala/zio/json/Gens.scala b/zio-json/shared/src/test/scala/zio/json/Gens.scala index 3c06c4f36..56e570835 100644 --- a/zio-json/shared/src/test/scala/zio/json/Gens.scala +++ b/zio-json/shared/src/test/scala/zio/json/Gens.scala @@ -1,6 +1,6 @@ package testzio.json -import zio.random.Random +import zio.Random import zio.test.{ Gen, Sized } import java.math.BigInteger @@ -99,9 +99,9 @@ object Gens { } yield OffsetTime.of(localTime, zoneOffset) val genPeriod: Gen[Random, Period] = for { - year <- Gen.anyInt - month <- Gen.anyInt - day <- Gen.anyInt + year <- Gen.int + month <- Gen.int + day <- Gen.int } yield Period.of(year, month, day) val genYearMonth: Gen[Random, YearMonth] = for { diff --git a/zio-json/shared/src/test/scala/zio/json/RoundTripSpec.scala b/zio-json/shared/src/test/scala/zio/json/RoundTripSpec.scala index 687361609..dcea44464 100644 --- a/zio-json/shared/src/test/scala/zio/json/RoundTripSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/RoundTripSpec.scala @@ -3,7 +3,7 @@ package testzio.json import testzio.json.Gens._ import zio.json._ import zio.json.ast.Json -import zio.random.Random +import zio.Random import zio.test.Assertion._ import zio.test.TestAspect._ import zio.test._ @@ -14,82 +14,82 @@ object RoundTripSpec extends DefaultRunnableSpec { def spec: ZSpec[Environment, Failure] = suite("RoundTrip")( - testM("booleans") { - check(Gen.boolean)(assertRoundtrips) + test("booleans") { + check(Gen.boolean)(assertRoundtrips[Boolean]) }, - testM("bytes") { - check(Gen.anyByte)(assertRoundtrips) + test("bytes") { + check(Gen.byte)(assertRoundtrips[Byte]) }, - testM("shorts") { - check(Gen.anyShort)(assertRoundtrips) + test("shorts") { + check(Gen.short)(assertRoundtrips[Short]) } @@ samples(10000), - testM("ints") { - check(Gen.anyInt)(assertRoundtrips) + test("ints") { + check(Gen.int)(assertRoundtrips[Int]) } @@ samples(10000), - testM("longs") { - check(Gen.anyLong)(assertRoundtrips) + test("longs") { + check(Gen.long)(assertRoundtrips[Long]) } @@ samples(10000), - testM("bigInts") { - check(genBigInteger)(assertRoundtrips) + test("bigInts") { + check(genBigInteger)(assertRoundtrips[java.math.BigInteger]) } @@ samples(10000), - testM("floats") { + test("floats") { // NaN / Infinity is tested manually, because of == semantics - check(Gen.anyFloat.filter(java.lang.Float.isFinite))(assertRoundtrips) + check(Gen.float.filter(java.lang.Float.isFinite))(assertRoundtrips[Float]) } @@ samples(10000), - testM("doubles") { + test("doubles") { // NaN / Infinity is tested manually, because of == semantics - check(Gen.anyDouble.filter(java.lang.Double.isFinite))(assertRoundtrips) + check(Gen.double.filter(java.lang.Double.isFinite))(assertRoundtrips[Double]) } @@ samples(10000), - testM("AST") { - check(genAst)(assertRoundtrips) + test("AST") { + check(genAst)(assertRoundtrips[Json]) }, suite("java.time")( - testM("DayOfWeek") { - check(genDayOfWeek)(assertRoundtrips) + test("DayOfWeek") { + check(genDayOfWeek)(assertRoundtrips[DayOfWeek]) }, - testM("Duration") { - check(genDuration)(assertRoundtrips) + test("Duration") { + check(genDuration)(assertRoundtrips[Duration]) } @@ samples(10000), - testM("Instant") { - check(genInstant)(assertRoundtrips) + test("Instant") { + check(genInstant)(assertRoundtrips[Instant]) } @@ samples(10000), - testM("LocalDate") { - check(genLocalDate)(assertRoundtrips) + test("LocalDate") { + check(genLocalDate)(assertRoundtrips[LocalDate]) } @@ samples(10000), - testM("LocalDateTime") { - check(genLocalDateTime)(assertRoundtrips) + test("LocalDateTime") { + check(genLocalDateTime)(assertRoundtrips[LocalDateTime]) } @@ samples(10000), - testM("LocalTime") { - check(genLocalTime)(assertRoundtrips) + test("LocalTime") { + check(genLocalTime)(assertRoundtrips[LocalTime]) } @@ samples(10000), - testM("Month") { - check(genMonth)(assertRoundtrips) + test("Month") { + check(genMonth)(assertRoundtrips[Month]) }, - testM("MonthDay") { - check(genMonthDay)(assertRoundtrips) + test("MonthDay") { + check(genMonthDay)(assertRoundtrips[MonthDay]) }, - testM("OffsetDateTime") { - check(genOffsetDateTime)(assertRoundtrips) + test("OffsetDateTime") { + check(genOffsetDateTime)(assertRoundtrips[OffsetDateTime]) } @@ samples(10000), - testM("OffsetTime") { - check(genOffsetTime)(assertRoundtrips) + test("OffsetTime") { + check(genOffsetTime)(assertRoundtrips[OffsetTime]) } @@ samples(10000), - testM("Period") { - check(genPeriod)(assertRoundtrips) + test("Period") { + check(genPeriod)(assertRoundtrips[Period]) } @@ samples(10000), - testM("Year") { - check(genYear)(assertRoundtrips) + test("Year") { + check(genYear)(assertRoundtrips[Year]) } @@ samples(10000), - testM("YearMonth") { - check(genYearMonth)(assertRoundtrips) + test("YearMonth") { + check(genYearMonth)(assertRoundtrips[YearMonth]) } @@ samples(10000), - testM("ZonedDateTime") { - check(genZonedDateTime)(assertRoundtrips) + test("ZonedDateTime") { + check(genZonedDateTime)(assertRoundtrips[ZonedDateTime]) } @@ samples(10000), - testM("ZoneId") { + test("ZoneId") { check(genZoneId)(assertRoundtrips[ZoneId]) }, - testM("ZoneOffset") { + test("ZoneOffset") { check(genZoneOffset)(assertRoundtrips[ZoneOffset]) } )