Skip to content

add zio-json support#1197

Merged
adamw merged 2 commits intosoftwaremill:masterfrom
endertunc:add-zio-json
Apr 29, 2021
Merged

add zio-json support#1197
adamw merged 2 commits intosoftwaremill:masterfrom
endertunc:add-zio-json

Conversation

@endertunc
Copy link
Copy Markdown
Contributor

Resolves #1196

Comment thread build.sbt
)
.dependsOn(core)

lazy val zioJson: ProjectMatrix = (projectMatrix in file("json/zio"))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be added to the root project aggregates as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's good catch. Done!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@adamw
Copy link
Copy Markdown
Member

adamw commented Apr 24, 2021

Thanks! :) looks good

@endertunc endertunc force-pushed the add-zio-json branch 2 times, most recently from 1968ba5 to 5b8d411 Compare April 24, 2021 16:59
Comment on lines +17 to +26
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 }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's my two cents in parsing zio-json's error message into Tapir's json path:

Suggested change
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
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @alphaho. I will do the changes tonight ;)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alphaho let me know if you have any feedback ;)

Comment on lines +58 to +86
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)"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are turning zio-json's error message into Tapir's the test will be:

Suggested change
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]
}

@adamw adamw merged commit 24a4727 into softwaremill:master Apr 29, 2021
@adamw
Copy link
Copy Markdown
Member

adamw commented Apr 29, 2021

Thank you @endertunc @alphaho !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add zio-json support

3 participants