Is there a common approach to handle PATCH requests in REST API using circe library? By default, circe does not allow decoding partial JSON with only a part of the fields specified, i.e. it requires all fields to be set. You could use a withDefaults
config, but it will be impossible to know if the field you received is null
or just not specified. Here is a simplified sample of the possible solution. It uses Left[Unit]
as a value to handle cases when the field is not specified at all:
# possible payloads
{
"firstName": "Foo",
"lastName": "Bar"
}
{
"firstName": "Foo"
}
{
"firstName": null
}
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
import io.circe.generic.auto._
import io.circe.{Decoder, HCursor}
case class User(firstName: Option[String], lastName: String)
// In PATCH request only 1 field can be specified. The rest could be omitted. Left represents `not specified`
case class PatchUserRequest(firstName: Either[Unit, Option[String]], lastName: Either[Unit, String])
object PatchUserRequest {
implicit val decode: Decoder[PatchUserRequest] = new Decoder[PatchUserRequest] {
final def apply(c: HCursor): Decoder.Result[PatchUserRequest] =
for {
// Here we handle `no field specified` error cases as Left[Unit]
foo <- c.downField("firstName").as[Option[String]] match {
case Left(noFieldSpecified) => Right(Left(()))
case Right(result) => Right(Right(result))
}
bar <- c.downField("lastName").as[String] match {
case Left(noFieldSpecified) => Right(Left(()))
case Right(result) => Right(Right(result))
}
} yield PatchUserRequest(foo, bar)
}
}
object Apis extends Directives {
var user = User("Foo", "Bar")
val create = path("user")(post(entity(as[User])(newUser => user = newUser)))
val patch = path("user")(patch(entity(as[PatchUserRequest])(patchRequest => patch(patchRequest))))
// If field is specified - update the record, ignore otherwise
def patch(request: PatchUserRequest) {
request.firstName.foreach(newFirstName => user = user.copy(firstName = newFirstName)
request.lastName.foreach(newlastName => user = user.copy(lastName = newlastName)
}
Is there a better way to handle PATCH requests (with nullable fields) instead of writing custom codec that falls back to no value
if field is not specified in the JSON payload? Thanks