add zio-json support#1197
Conversation
| ) | ||
| .dependsOn(core) | ||
|
|
||
| lazy val zioJson: ProjectMatrix = (projectMatrix in file("json/zio")) |
There was a problem hiding this comment.
should be added to the root project aggregates as well
There was a problem hiding this comment.
Ah that's good catch. Done!
There was a problem hiding this comment.
I realized that docs were not compiling due to the missing dependsOn to zioJson. I fixed that one as well so CI should pass now.
|
Thanks! :) looks good |
1968ba5 to
5b8d411
Compare
| implicit def zioCodec[T: JsonEncoder: JsonDecoder: Schema]: JsonCodec[T] = | ||
| sttp.tapir.Codec.json[T] { s => | ||
| zio.json.JsonDecoder.apply[T].decodeJson(s) match { | ||
| case Left(error) => Error(s, new IllegalArgumentException(error)) | ||
| case Right(value) => Value(value) | ||
| } | ||
| } { t => t.toJson } |
There was a problem hiding this comment.
Here's my two cents in parsing zio-json's error message into Tapir's json path:
| implicit def zioCodec[T: JsonEncoder: JsonDecoder: Schema]: JsonCodec[T] = | |
| sttp.tapir.Codec.json[T] { s => | |
| zio.json.JsonDecoder.apply[T].decodeJson(s) match { | |
| case Left(error) => Error(s, new IllegalArgumentException(error)) | |
| case Right(value) => Value(value) | |
| } | |
| } { t => t.toJson } | |
| implicit def zioJsonCodec[T: JsonEncoder: JsonDecoder: Schema]: JsonCodec[T] = | |
| sttp.tapir.Codec.json[T] { s => | |
| s.fromJson[T] match { | |
| case Left(msg) => | |
| val (message, path) = parseErrorMessage(msg) | |
| Error(s, JsonDecodeException(List(JsonError(message, path)), new Exception(msg))) | |
| case Right(v) => Value(v) | |
| } | |
| } { t => t.toJson } | |
| private def parseErrorMessage(errorMessage: String): (String, List[FieldName]) = { | |
| val leftParenIndex = errorMessage.indexOf('(') | |
| if (leftParenIndex >= 0) { | |
| val path = errorMessage.substring(0, leftParenIndex) | |
| val message = errorMessage.substring(leftParenIndex + 1, errorMessage.length - 1) | |
| return message -> path.split("\\.").toList.filter(_.nonEmpty).map(FieldName.apply) | |
| } else { | |
| return errorMessage -> List.empty | |
| } | |
| } |
There was a problem hiding this comment.
Thanks @alphaho. I will do the changes tonight ;)
There was a problem hiding this comment.
@alphaho let me know if you have any feedback ;)
| it should "return a JSON specific error on object decode failure" in { | ||
| val input = """{"items":[]}""" | ||
|
|
||
| val codec = zioCodec[Customer] | ||
| val actual = codec.decode(input) | ||
| actual shouldBe a[DecodeResult.Error] | ||
|
|
||
| val failure = actual.asInstanceOf[DecodeResult.Error] | ||
| failure.original shouldEqual input | ||
| failure.error shouldBe a[IllegalArgumentException] | ||
| failure.error.getMessage shouldBe ".name(missing)" | ||
| } |
There was a problem hiding this comment.
If we are turning zio-json's error message into Tapir's the test will be:
| it should "return a JSON specific error on object decode failure" in { | |
| val input = """{"items":[]}""" | |
| val codec = zioCodec[Customer] | |
| val actual = codec.decode(input) | |
| actual shouldBe a[DecodeResult.Error] | |
| val failure = actual.asInstanceOf[DecodeResult.Error] | |
| failure.original shouldEqual input | |
| failure.error shouldBe a[IllegalArgumentException] | |
| failure.error.getMessage shouldBe ".name(missing)" | |
| } | |
| it should "return a JSON specific error on object decode failure" in { | |
| val input = """{"items":[]}""" | |
| val actual = customerCodec.decode(input) | |
| actual shouldBe a[DecodeResult.Error] | |
| val failure = actual.asInstanceOf[DecodeResult.Error] | |
| failure.original shouldEqual input | |
| failure.error shouldBe a[JsonDecodeException] | |
| val error = failure.error.asInstanceOf[JsonDecodeException] | |
| error.errors shouldEqual | |
| List(JsonError("missing", List(FieldName("name")))) | |
| error.underlying shouldBe a[Exception] | |
| } | |
| it should "return a JSON specific error on array decode failure" in { | |
| val input = """[{}]""" | |
| val actual = zioJsonCodec[Seq[Item]].decode(input) | |
| actual shouldBe a[DecodeResult.Error] | |
| val failure = actual.asInstanceOf[DecodeResult.Error] | |
| failure.original shouldEqual input | |
| failure.error shouldBe a[JsonDecodeException] | |
| val error = failure.error.asInstanceOf[JsonDecodeException] | |
| error.errors shouldEqual | |
| List(JsonError("missing", List(FieldName("[0]"), FieldName("serialNumber")))) | |
| error.underlying shouldBe a[Exception] | |
| } |
|
Thank you @endertunc @alphaho ! |
Resolves #1196