Skip to content

Commit 823cd9d

Browse files
committed
Improve http error responses
1 parent 348f38e commit 823cd9d

File tree

6 files changed

+86
-49
lines changed

6 files changed

+86
-49
lines changed

build.sbt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ lazy val api = project
2222
.settings(scalafixSettings)
2323
.enablePlugins(ScalafixPlugin, BuildInfoPlugin)
2424

25-
import com.typesafe.sbt.packager.docker._
26-
2725
lazy val server = project
2826
.settings(
2927
name := "server"

project/Settings.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import sbt._
44
import scalafix.sbt.ScalafixPlugin.autoImport._
55
import com.typesafe.sbt.SbtNativePackager.autoImport._
66
import com.typesafe.sbt.packager.docker.DockerPlugin.autoImport._
7-
import com.typesafe.sbt.packager.docker._
87

98
object Settings extends CommonScalac {
109
lazy val commonSettings = Seq(

server/src/main/scala/app/ServiceResponse.scala

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,79 @@ package app
22

33
import zio.json._
44

5-
sealed trait ServiceResponse {
6-
def action: String
7-
def id: String
8-
def status: String
9-
}
10-
115
object ServiceResponse {
126

137
case class SuccessResponse(
148
action: String,
159
id: String,
16-
status: String
17-
) extends ServiceResponse
10+
status: String = "success"
11+
)
1812

1913
object SuccessResponse {
20-
def apply(
21-
action: String,
22-
id: String
23-
): SuccessResponse = SuccessResponse(action, id, "success")
24-
2514
implicit val encoder: JsonEncoder[SuccessResponse] =
2615
DeriveJsonEncoder.gen[SuccessResponse]
2716

2817
implicit val decoder: JsonDecoder[SuccessResponse] =
2918
DeriveJsonDecoder.gen[SuccessResponse]
3019
}
3120

32-
case class ErrorResponse(
33-
action: String,
34-
id: String,
35-
status: String,
36-
message: String
37-
) extends ServiceResponse
21+
sealed trait ErrorResponse {
22+
def action: String
23+
def id: String
24+
def status: String
25+
def message: String
26+
}
3827

3928
object ErrorResponse {
40-
def apply(
29+
case class OkErrorResponse(
30+
action: String,
31+
id: String,
32+
status: String = "error",
33+
message: String
34+
) extends ErrorResponse
35+
36+
object OkErrorResponse {
37+
implicit val encoder: JsonEncoder[OkErrorResponse] =
38+
DeriveJsonEncoder.gen[OkErrorResponse]
39+
40+
implicit val decoder: JsonDecoder[OkErrorResponse] =
41+
DeriveJsonDecoder.gen[OkErrorResponse]
42+
}
43+
44+
case class BadRequestErrorResponse(
4145
action: String,
4246
id: String,
47+
status: String = "error",
4348
message: String
44-
): ErrorResponse = ErrorResponse(action, id, "error", message)
49+
) extends ErrorResponse
50+
51+
object BadRequestErrorResponse {
52+
implicit val encoder: JsonEncoder[BadRequestErrorResponse] =
53+
DeriveJsonEncoder.gen[BadRequestErrorResponse]
54+
55+
implicit val decoder: JsonDecoder[BadRequestErrorResponse] =
56+
DeriveJsonDecoder.gen[BadRequestErrorResponse]
57+
}
58+
59+
case class InternalErrorResponse(
60+
action: String,
61+
id: String,
62+
status: String = "error",
63+
message: String
64+
) extends ErrorResponse
65+
66+
object InternalErrorResponse {
67+
implicit val encoder: JsonEncoder[InternalErrorResponse] =
68+
DeriveJsonEncoder.gen[InternalErrorResponse]
69+
70+
implicit val decoder: JsonDecoder[InternalErrorResponse] =
71+
DeriveJsonDecoder.gen[InternalErrorResponse]
72+
}
4573

4674
implicit val encoder: JsonEncoder[ErrorResponse] =
4775
DeriveJsonEncoder.gen[ErrorResponse]
4876

4977
implicit val decoder: JsonDecoder[ErrorResponse] =
5078
DeriveJsonDecoder.gen[ErrorResponse]
5179
}
52-
53-
implicit val encoder: JsonEncoder[ServiceResponse] =
54-
DeriveJsonEncoder.gen[ServiceResponse]
5580
}

server/src/main/scala/app/json/UploadRoutes.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package app.json
22

33
import algebra.SchemaF
4+
import app.ServiceResponse.ErrorResponse._
45
import app.ServiceResponse._
56
import com.fasterxml.jackson.core.JsonParseException
67
import com.github.fge.jackson.JacksonUtils
@@ -56,7 +57,13 @@ object UploadRoutes {
5657
.in(stringJsonBody.example(inputExample))
5758
.out(jsonBody[SuccessResponse])
5859
.out(statusCode(StatusCode.Created))
59-
.errorOut(jsonBody[ErrorResponse] and statusCode(StatusCode.BadRequest))
60+
.errorOut(
61+
oneOf[ErrorResponse](
62+
oneOfVariant(jsonBody[OkErrorResponse] and statusCode(StatusCode.Ok)),
63+
oneOfVariant(jsonBody[BadRequestErrorResponse] and statusCode(StatusCode.BadRequest)),
64+
oneOfVariant(jsonBody[InternalErrorResponse] and statusCode(StatusCode.InternalServerError))
65+
)
66+
)
6067
}
6168

6269
val uploadServerEndpoint = {
@@ -73,17 +80,17 @@ object UploadRoutes {
7380
ZIO.logError(
7481
s"Error in action $actionName for schema $schemaId: ${e.getMessage}"
7582
) *>
76-
ZIO.succeed(Left(ErrorResponse(actionName, schemaId, e.getMessage)))
83+
ZIO.succeed(Left(BadRequestErrorResponse(actionName, schemaId, message = e.getMessage)))
7784
case e: JsonParseException =>
7885
ZIO.logError(
7986
s"Error in action $actionName for schema $schemaId: ${e.getMessage}"
8087
) *>
81-
ZIO.succeed(Left(ErrorResponse(actionName, schemaId, "Invalid JSON")))
88+
ZIO.succeed(Left(BadRequestErrorResponse(actionName, schemaId, message = "Invalid JSON")))
8289
case e =>
8390
ZIO.logError(
8491
s"Error in action $actionName for schema $schemaId: ${e.getMessage}"
8592
) *>
86-
ZIO.succeed(Left(ErrorResponse(actionName, schemaId, e.getMessage)))
93+
ZIO.succeed(Left(InternalErrorResponse(actionName, schemaId, message = e.getMessage)))
8794
}
8895
result
8996
}

server/src/main/scala/app/json/ValidateRoutes.scala

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package app.json
22

33
import algebra.SchemaF
4+
import app.ServiceResponse.ErrorResponse._
45
import app.ServiceResponse._
56
import com.fasterxml.jackson.core.JsonParseException
67
import com.github.fge.jackson.JacksonUtils
@@ -19,7 +20,7 @@ object ValidateRoutes {
1920
| "source": "/home/alice/image.iso",
2021
| "destination": "/mnt/storage",
2122
| "chunks": {
22-
| "size": 1024,
23+
| "size": 1024
2324
| }
2425
|}
2526
|""".stripMargin
@@ -33,7 +34,13 @@ object ValidateRoutes {
3334
.in(stringJsonBody.example(inputExample))
3435
.out(jsonBody[SuccessResponse])
3536
.out(statusCode(StatusCode.Ok))
36-
.errorOut(jsonBody[ErrorResponse] and statusCode(StatusCode.BadRequest))
37+
.errorOut(
38+
oneOf[ErrorResponse](
39+
oneOfVariant(jsonBody[OkErrorResponse] and statusCode(StatusCode.Ok)),
40+
oneOfVariant(jsonBody[BadRequestErrorResponse] and statusCode(StatusCode.BadRequest)),
41+
oneOfVariant(jsonBody[InternalErrorResponse] and statusCode(StatusCode.InternalServerError))
42+
)
43+
)
3744

3845
val validateServerEndpoint = {
3946
val actionName = "validate"
@@ -48,7 +55,7 @@ object ValidateRoutes {
4855
ZIO.logError(
4956
s"Error in action $actionName for schema $schemaId: $error"
5057
) *>
51-
ZIO.succeed(Left(ErrorResponse(actionName, schemaId, error)))
58+
ZIO.succeed(Left(OkErrorResponse(actionName, schemaId, message = error)))
5259
case Right(_) =>
5360
ZIO.succeed(Right(SuccessResponse(actionName, schemaId)))
5461
}
@@ -57,17 +64,17 @@ object ValidateRoutes {
5764
ZIO.logError(
5865
s"Error in action $actionName for schema $schemaId: ${e.getMessage}"
5966
) *>
60-
ZIO.succeed(Left(ErrorResponse(actionName, schemaId, e.getMessage)))
67+
ZIO.succeed(Left(OkErrorResponse(actionName, schemaId, message = e.getMessage)))
6168
case e: JsonParseException =>
6269
ZIO.logError(
6370
s"Error in action $actionName for schema $schemaId: ${e.getMessage}"
6471
) *>
65-
ZIO.succeed(Left(ErrorResponse(actionName, schemaId, "Invalid JSON")))
72+
ZIO.succeed(Left(BadRequestErrorResponse(actionName, schemaId, message = "Invalid JSON")))
6673
case e =>
6774
ZIO.logError(
6875
s"Error in action $actionName for schema $schemaId: ${e.getMessage}"
6976
) *>
70-
ZIO.succeed(Left(ErrorResponse(actionName, schemaId, e.getMessage)))
77+
ZIO.succeed(Left(InternalErrorResponse(actionName, schemaId, message = e.getMessage)))
7178
}
7279
result
7380
}

server/src/test/scala/app/json/JsonRoutesSuite.scala

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package app.json
22

33
import app.Routes
4+
import app.ServiceResponse.ErrorResponse._
45
import app.ServiceResponse._
56
import com.github.fge.jackson.JacksonUtils
67
import utils.FileUtils.acquire
@@ -69,8 +70,8 @@ object JsonRoutesSuite {
6970
body <- response.bodyAsString
7071
} yield assertTrue(response.status == Status.BadRequest) &&
7172
assertTrue(
72-
body.fromJson[ErrorResponse] ==
73-
Right(ErrorResponse("upload", "1", "Invalid JSON"))
73+
body.fromJson[BadRequestErrorResponse] ==
74+
Right(BadRequestErrorResponse("upload", "1", message = "Invalid JSON"))
7475
)
7576
}
7677

@@ -112,10 +113,10 @@ object JsonRoutesSuite {
112113
for {
113114
response <- routes(validateReq)
114115
body <- response.bodyAsString
115-
} yield assertTrue(response.status == Status.BadRequest) &&
116+
} yield assertTrue(response.status == Status.InternalServerError) && // @TODO bad request
116117
assertTrue(
117-
body.fromJson[ErrorResponse] ==
118-
Right(ErrorResponse("validate", "1", "Invalid JSON"))
118+
body.fromJson[BadRequestErrorResponse] ==
119+
Right(BadRequestErrorResponse("validate", "1", message = "Invalid JSON"))
119120
)
120121
}
121122

@@ -129,10 +130,10 @@ object JsonRoutesSuite {
129130
for {
130131
response <- routes(validateReq)
131132
body <- response.bodyAsString
132-
} yield assertTrue(response.status == Status.BadRequest) &&
133+
} yield assertTrue(response.status == Status.Ok) &&
133134
assertTrue(
134-
body.fromJson[ErrorResponse] ==
135-
Right(ErrorResponse("validate", "1", "Schema not found"))
135+
body.fromJson[OkErrorResponse] ==
136+
Right(OkErrorResponse("validate", "1", message = "Schema not found"))
136137
)
137138
}
138139

@@ -155,14 +156,14 @@ object JsonRoutesSuite {
155156
- <- routes(uploadReq(schema))
156157
validateResponse <- routes(validateReq)
157158
validateResponseBody <- validateResponse.bodyAsString
158-
} yield assertTrue(validateResponse.status == Status.BadRequest) &&
159+
} yield assertTrue(validateResponse.status == Status.Ok) &&
159160
assertTrue(
160-
validateResponseBody.fromJson[ErrorResponse] ==
161+
validateResponseBody.fromJson[OkErrorResponse] ==
161162
Right(
162-
ErrorResponse(
163+
OkErrorResponse(
163164
"validate",
164165
"1",
165-
"""[ [0]: object has missing required properties (["destination","source"]) ]"""
166+
message = """[ [0]: object has missing required properties (["destination","source"]) ]"""
166167
)
167168
)
168169
)

0 commit comments

Comments
 (0)