Improve error handling

This commit is contained in:
Paul-Henri Froidmont 2025-10-22 15:30:36 +02:00
parent 87bd780f9f
commit e6a8150483
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
7 changed files with 78 additions and 62 deletions

View file

@ -5,7 +5,6 @@ import zio.http.*
import zio.http.codec.*
import zio.http.endpoint.*
import zio.schema.*
import zio.schema.annotation.discriminatorName
import lu.foyer.JsonApiResponse.Many
import lu.foyer.JsonApiResponse.One
@ -36,15 +35,6 @@ object JsonApiResponse:
case class RelationshipsData(id: String, `type`: String) derives Schema
case class RelationshipsLinks(related: String) derives Schema
@discriminatorName("errorType")
enum Error derives Schema:
case NotFound(id: String)
case InternalServerError(title: String)
object Error:
given Schema[Error.NotFound] = DeriveSchema.gen
given Schema[Error.InternalServerError] = DeriveSchema.gen
end JsonApiResponse
final case class ProxyHeaders(
protocol: Option[String],
host: Option[String],
@ -80,26 +70,26 @@ trait JsonApiController:
endpoint: Endpoint[PathInput, Input, ZNothing, ZNothing, AuthType.None]
)
inline def jsonApiOne[Output: Schema]
: Endpoint[PathInput, Input, JsonApiResponse.Error, One[Output], AuthType.None.type] =
: Endpoint[PathInput, Input, JsonApiError, One[Output], AuthType.None.type] =
jsonApiOneWithStatus(Status.Ok)
def jsonApiOneWithStatus[Output: Schema](status: Status) =
endpoint
.out[JsonApiResponse.One[Output]](status)
.outErrors[JsonApiResponse.Error](
HttpCodec.error[JsonApiResponse.Error.NotFound](Status.NotFound),
HttpCodec.error[JsonApiResponse.Error.InternalServerError](Status.InternalServerError)
.outErrors[JsonApiError](
HttpCodec.error[JsonApiError.NotFound](Status.NotFound),
HttpCodec.error[JsonApiError.InternalServerError](Status.InternalServerError)
)
def jsonApiMany[Output: Schema] =
endpoint
.out[JsonApiResponse.Many[Output]]
.outError[JsonApiResponse.Error](Status.InternalServerError)
.outError[JsonApiError](Status.InternalServerError)
extension [PathInput, Input, Output, Auth <: AuthType](
endpoint: Endpoint[PathInput, Input, JsonApiResponse.Error, One[Output], AuthType.None]
endpoint: Endpoint[PathInput, Input, JsonApiError, One[Output], AuthType.None]
)
def implementJsonApiOne[Env, A](
f: Input => RIO[Env, Option[A]],
f: Input => ZIO[Env, JsonApiError | Throwable, Option[A]],
getId: A => String,
getEntity: A => Output,
onthology: String = this.onthology,
@ -113,11 +103,12 @@ trait JsonApiController:
endpoint.implement(input =>
for
item <- f(input)
.mapError(e => JsonApiResponse.Error.InternalServerError(e.getMessage))
.flatMap {
case Some(paged) => ZIO.succeed(paged)
case None => ZIO.fail(JsonApiResponse.Error.NotFound(input.toString))
.tapErrorCause(ZIO.logErrorCause(_))
.mapError {
case e: Throwable => JsonApiError.InternalServerError(e.getMessage)
case e: JsonApiError => e
}
.someOrFail(JsonApiError.NotFound(input.toString))
headers <- ZIO.service[ProxyHeaders]
yield JsonApiResponse.One(
JsonApiResponse.Entity(
@ -146,7 +137,7 @@ trait JsonApiController:
)
def implementJsonApiOneEvent[Env](
f: Input => RIO[Env, Option[Event[Output]]]
f: Input => ZIO[Env, JsonApiError | Throwable, Option[Event[Output]]]
)(implicit trace: Trace
): Route[Env & ProxyHeaders, Nothing] =
implementJsonApiOne(
@ -168,7 +159,7 @@ trait JsonApiController:
end extension
extension [PathInput, Input, Output, Auth <: AuthType](
endpoint: Endpoint[PathInput, Input, JsonApiResponse.Error, Many[Output], AuthType.None]
endpoint: Endpoint[PathInput, Input, JsonApiError, Many[Output], AuthType.None]
)
def implementJsonApiMany[Env, A](
f: Input => RIO[Env, Paged[A]],
@ -205,7 +196,8 @@ trait JsonApiController:
page = JsonApiResponse.Page(number = 0, size = 10)
)
))
.mapError(e => JsonApiResponse.Error.InternalServerError(e.getMessage))
.tapErrorCause(ZIO.logErrorCause(_))
.mapError(e => JsonApiError.InternalServerError(e.getMessage))
)
inline def implementJsonApiManyEntity[Env](