From a84b947862099f8c30570ade89a54f22b2ee64b2 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Tue, 29 Oct 2019 01:59:35 +0300 Subject: [PATCH] improve api --- .../me/arcanis/ffxivbis/http/BiSHelper.scala | 8 +++ .../me/arcanis/ffxivbis/http/LootHelper.scala | 8 +++ .../arcanis/ffxivbis/http/PlayerHelper.scala | 9 ++- .../ffxivbis/http/api/v1/BiSEndpoint.scala | 58 ++++++++++------- .../http/api/v1/HttpExceptionsHandler.scala | 16 +++++ .../ffxivbis/http/api/v1/LootEndpoint.scala | 55 +++++++++------- .../ffxivbis/http/api/v1/PlayerEndpoint.scala | 38 +++++++----- .../ffxivbis/http/api/v1/UserEndpoint.scala | 62 ++++++++++++------- .../http/api/v1/json/ErrorResponse.scala | 6 ++ .../http/api/v1/json/JsonSupport.scala | 1 + 10 files changed, 173 insertions(+), 88 deletions(-) create mode 100644 src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpExceptionsHandler.scala create mode 100644 src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorResponse.scala diff --git a/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala index 1d2f679..0bc9d96 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/BiSHelper.scala @@ -11,6 +11,7 @@ package me.arcanis.ffxivbis.http import akka.actor.ActorRef import akka.pattern.ask import akka.util.Timeout +import me.arcanis.ffxivbis.http.api.v1.json.ApiAction import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId} import me.arcanis.ffxivbis.service.impl.DatabaseBiSHandler @@ -26,6 +27,13 @@ class BiSHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariy (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] = (storage ? DatabaseBiSHandler.GetBiS(partyId, playerId)).mapTo[Seq[Player]] + def doModifyBiS(action: ApiAction.Value, playerId: PlayerId, piece: Piece) + (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = + action match { + case ApiAction.add => addPieceBiS(playerId, piece) + case ApiAction.remove => removePieceBiS(playerId, piece) + } + def putBiS(playerId: PlayerId, link: String) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = downloadBiS(link, playerId.job).flatMap { bis => diff --git a/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala index 6534a78..5a1a9c0 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/LootHelper.scala @@ -11,6 +11,7 @@ package me.arcanis.ffxivbis.http import akka.actor.ActorRef import akka.pattern.ask import akka.util.Timeout +import me.arcanis.ffxivbis.http.api.v1.json.ApiAction import me.arcanis.ffxivbis.models.{Piece, Player, PlayerId, PlayerIdWithCounters} import me.arcanis.ffxivbis.service.LootSelector.LootSelectorResult import me.arcanis.ffxivbis.service.impl.DatabaseLootHandler @@ -23,6 +24,13 @@ class LootHelper(storage: ActorRef) { (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = (storage ? DatabaseLootHandler.AddPieceTo(playerId, piece)).mapTo[Int] + def doModifyLoot(action: ApiAction.Value, playerId: PlayerId, piece: Piece) + (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = + action match { + case ApiAction.add => addPieceLoot(playerId, piece) + case ApiAction.remove => removePieceLoot(playerId, piece) + } + def loot(partyId: String, playerId: Option[PlayerId]) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] = (storage ? DatabaseLootHandler.GetLoot(partyId, playerId)).mapTo[Seq[Player]] diff --git a/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala b/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala index 85e46cf..813ba7a 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/PlayerHelper.scala @@ -11,11 +11,11 @@ package me.arcanis.ffxivbis.http import akka.actor.ActorRef import akka.pattern.ask import akka.util.Timeout +import me.arcanis.ffxivbis.http.api.v1.json.ApiAction import me.arcanis.ffxivbis.models.{Party, Player, PlayerId} import me.arcanis.ffxivbis.service.impl.{DatabaseBiSHandler, DatabasePartyHandler} import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Success} class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(ariyala) { @@ -31,6 +31,13 @@ class PlayerHelper(storage: ActorRef, ariyala: ActorRef) extends AriyalaHelper(a } }.flatten + def doModifyPlayer(action: ApiAction.Value, player: Player) + (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] = + action match { + case ApiAction.add => addPlayer(player) + case ApiAction.remove => removePlayer(player.playerId) + } + def getPlayers(partyId: String, maybePlayerId: Option[PlayerId]) (implicit executionContext: ExecutionContext, timeout: Timeout): Future[Seq[Player]] = maybePlayerId match { diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala index 060732f..5565b23 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/BiSEndpoint.scala @@ -13,6 +13,7 @@ import akka.http.scaladsl.model.{HttpEntity, StatusCodes} import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server._ import akka.util.Timeout +import com.typesafe.scalalogging.StrictLogging import io.swagger.v3.oas.annotations.enums.ParameterIn import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema} import io.swagger.v3.oas.annotations.responses.ApiResponse @@ -24,9 +25,11 @@ import me.arcanis.ffxivbis.http.{Authorization, BiSHelper} import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.models.PlayerId +import scala.util.{Failure, Success} + @Path("api/v1") class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) - extends BiSHelper(storage, ariyala) with Authorization with JsonSupport { + extends BiSHelper(storage, ariyala) with Authorization with JsonSupport with HttpExceptionsHandler { def route: Route = createBiS ~ getBiS ~ modifyBiS @@ -51,12 +54,17 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti ) def createBiS: Route = path("party" / Segment / "bis") { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => - put { - entity(as[PlayerBiSLinkResponse]) { bisLink => - val playerId = bisLink.playerId.withPartyId(partyId) - complete(putBiS(playerId, bisLink.link).map(_ => (StatusCodes.Created, HttpEntity.Empty))) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + put { + entity(as[PlayerBiSLinkResponse]) { bisLink => + val playerId = bisLink.playerId.withPartyId(partyId) + onComplete(putBiS(playerId, bisLink.link)) { + case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty) + case Failure(exception) => throw exception + } + } } } } @@ -86,12 +94,17 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti ) def getBiS: Route = path("party" / Segment / "bis") { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => - get { - parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => - val playerId = PlayerId(partyId, maybeNick, maybeJob) - complete(bis(partyId, playerId).map(_.map(PlayerResponse.fromPlayer))) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + get { + parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => + val playerId = PlayerId(partyId, maybeNick, maybeJob) + onComplete(bis(partyId, playerId)) { + case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) + case Failure(exception) => throw exception + } + } } } } @@ -119,17 +132,16 @@ class BiSEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ti ) def modifyBiS: Route = path("party" / Segment / "bis") { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => - post { - entity(as[PieceActionResponse]) { action => - val playerId = action.playerIdResponse.withPartyId(partyId) - complete { - val result = action.action match { - case ApiAction.add => addPieceBiS(playerId, action.piece.toPiece) - case ApiAction.remove => removePieceBiS(playerId, action.piece.toPiece) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + post { + entity(as[PieceActionResponse]) { action => + val playerId = action.playerIdResponse.withPartyId(partyId) + onComplete(doModifyBiS(action.action, playerId, action.piece.toPiece)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception } - result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty)) } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpExceptionsHandler.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpExceptionsHandler.scala new file mode 100644 index 0000000..75fdd56 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpExceptionsHandler.scala @@ -0,0 +1,16 @@ +package me.arcanis.ffxivbis.http.api.v1 + +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server._ +import com.typesafe.scalalogging.StrictLogging +import me.arcanis.ffxivbis.http.api.v1.json._ + +trait HttpExceptionsHandler extends StrictLogging { this: JsonSupport => + + def exceptionHandler: ExceptionHandler = ExceptionHandler { + case other: Exception => + logger.error("exception during request completion", other) + complete(StatusCodes.InternalServerError, ErrorResponse("unknown server error")) + } +} diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala index 7777ec8..02651d8 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/LootEndpoint.scala @@ -24,9 +24,11 @@ import me.arcanis.ffxivbis.http.{Authorization, LootHelper} import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.models.PlayerId +import scala.util.{Failure, Success} + @Path("api/v1") class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) - extends LootHelper(storage) with Authorization with JsonSupport { + extends LootHelper(storage) with Authorization with JsonSupport with HttpExceptionsHandler { def route: Route = getLoot ~ modifyLoot @@ -53,12 +55,17 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) ) def getLoot: Route = path("party" / Segment / "loot") { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => - get { - parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => - val playerId = PlayerId(partyId, maybeNick, maybeJob) - complete(loot(partyId, playerId).map(_.map(PlayerResponse.fromPlayer))) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + get { + parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => + val playerId = PlayerId(partyId, maybeNick, maybeJob) + onComplete(loot(partyId, playerId)) { + case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) + case Failure(exception) => throw exception + } + } } } } @@ -86,17 +93,16 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) ) def modifyLoot: Route = path("party" / Segment / "loot") { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => - post { - entity(as[PieceActionResponse]) { action => - val playerId = action.playerIdResponse.withPartyId(partyId) - complete { - val result = action.action match { - case ApiAction.add => addPieceLoot(playerId, action.piece.toPiece) - case ApiAction.remove => removePieceLoot(playerId, action.piece.toPiece) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + post { + entity(as[PieceActionResponse]) { action => + val playerId = action.playerIdResponse.withPartyId(partyId) + onComplete(doModifyLoot(action.action, playerId, action.piece.toPiece)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception } - result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty)) } } } @@ -129,13 +135,14 @@ class LootEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) ) def suggestLoot: Route = path("party" / Segment / "loot") { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => - put { - entity(as[PieceResponse]) { piece => - complete { - suggestPiece(partyId, piece.toPiece).map { players => - players.map(PlayerIdWithCountersResponse.fromPlayerId) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + put { + entity(as[PieceResponse]) { piece => + onComplete(suggestPiece(partyId, piece.toPiece)) { + case Success(response) => complete(response.map(PlayerIdWithCountersResponse.fromPlayerId)) + case Failure(exception) => throw exception } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala index 1676dd6..8506027 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/PlayerEndpoint.scala @@ -24,9 +24,11 @@ import me.arcanis.ffxivbis.http.{Authorization, PlayerHelper} import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.models.PlayerId +import scala.util.{Failure, Success} + @Path("api/v1") class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit timeout: Timeout) - extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport { + extends PlayerHelper(storage, ariyala) with Authorization with JsonSupport with HttpExceptionsHandler { def route: Route = getParty ~ modifyParty @@ -53,12 +55,17 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ) def getParty: Route = path("party" / Segment) { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => - get { - parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => - val playerId = PlayerId(partyId, maybeNick, maybeJob) - complete(getPlayers(partyId, playerId).map(_.map(PlayerResponse.fromPlayer))) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authGet(partyId)) { _ => + get { + parameters("nick".as[String].?, "job".as[String].?) { (maybeNick, maybeJob) => + val playerId = PlayerId(partyId, maybeNick, maybeJob) + onComplete(getPlayers(partyId, playerId)) { + case Success(response) => complete(response.map(PlayerResponse.fromPlayer)) + case Failure(exception) => throw exception + } + } } } } @@ -86,16 +93,15 @@ class PlayerEndpoint(override val storage: ActorRef, ariyala: ActorRef)(implicit ) def modifyParty: Route = path("party" / Segment) { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => - entity(as[PlayerActionResponse]) { action => - val player = action.playerIdResponse.toPlayer.copy(partyId = partyId) - complete { - val result = action.action match { - case ApiAction.add => addPlayer(player) - case ApiAction.remove => removePlayer(player.playerId) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authPost(partyId)) { _ => + entity(as[PlayerActionResponse]) { action => + val player = action.playerIdResponse.toPlayer.copy(partyId = partyId) + onComplete(doModifyPlayer(action.action, player)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception } - result.map(_ => (StatusCodes.Accepted, HttpEntity.Empty)) } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala index 1d1993a..b9e33fc 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/UserEndpoint.scala @@ -24,9 +24,11 @@ import me.arcanis.ffxivbis.http.{Authorization, UserHelper} import me.arcanis.ffxivbis.http.api.v1.json._ import me.arcanis.ffxivbis.models.Permission +import scala.util.{Failure, Success} + @Path("api/v1") class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) - extends UserHelper(storage) with Authorization with JsonSupport { + extends UserHelper(storage) with Authorization with JsonSupport with HttpExceptionsHandler { def route: Route = createParty ~ createUser ~ deleteUser ~ getUsers @@ -49,12 +51,15 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) ) def createParty: Route = path("party" / Segment / "create") { partyId => - extractExecutionContext { implicit executionContext => - put { - entity(as[UserResponse]) { user => - val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin) - complete { - addUser(admin, isHashedPassword = false).map(_ => (StatusCodes.Created, HttpEntity.Empty)) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + put { + entity(as[UserResponse]) { user => + val admin = user.toUser.copy(partyId = partyId, permission = Permission.admin) + onComplete(addUser(admin, isHashedPassword = false)) { + case Success(_) => complete(StatusCodes.Created, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } @@ -82,13 +87,16 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) ) def createUser: Route = path("party" / Segment / "users") { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => - post { - entity(as[UserResponse]) { user => - val withPartyId = user.toUser.copy(partyId = partyId) - complete { - addUser(withPartyId, isHashedPassword = false).map(_ => (StatusCodes.Accepted, HttpEntity.Empty)) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => + post { + entity(as[UserResponse]) { user => + val withPartyId = user.toUser.copy(partyId = partyId) + onComplete(addUser(withPartyId, isHashedPassword = false)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } @@ -114,11 +122,14 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) ) def deleteUser: Route = path("party" / Segment / "users" / Segment) { (partyId, username) => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => - delete { - complete { - removeUser(partyId, username).map(_ => (StatusCodes.Accepted, HttpEntity.Empty)) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => + delete { + onComplete(removeUser(partyId, username)) { + case Success(_) => complete(StatusCodes.Accepted, HttpEntity.Empty) + case Failure(exception) => throw exception + } } } } @@ -146,11 +157,14 @@ class UserEndpoint(override val storage: ActorRef)(implicit timeout: Timeout) ) def getUsers: Route = path("party" / Segment / "users") { partyId => - extractExecutionContext { implicit executionContext => - authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => - get { - complete { - users(partyId).map(_.map(UserResponse.fromUser)) + handleExceptions(exceptionHandler) { + extractExecutionContext { implicit executionContext => + authenticateBasicBCrypt(s"party $partyId", authAdmin(partyId)) { _ => + get { + onComplete(users(partyId)) { + case Success(response) => complete(response.map(UserResponse.fromUser)) + case Failure(exception) => throw exception + } } } } diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorResponse.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorResponse.scala new file mode 100644 index 0000000..8f8e2f8 --- /dev/null +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/ErrorResponse.scala @@ -0,0 +1,6 @@ +package me.arcanis.ffxivbis.http.api.v1.json + +import io.swagger.v3.oas.annotations.media.Schema + +case class ErrorResponse( + @Schema(description = "error message", required = true) message: String) diff --git a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala index 4b05a5a..a1b4b09 100644 --- a/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala +++ b/src/main/scala/me/arcanis/ffxivbis/http/api/v1/json/JsonSupport.scala @@ -27,6 +27,7 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol { implicit val actionFormat: RootJsonFormat[ApiAction.Value] = enumFormat(ApiAction) implicit val permissionFormat: RootJsonFormat[Permission.Value] = enumFormat(Permission) + implicit val errorFormat: RootJsonFormat[ErrorResponse] = jsonFormat1(ErrorResponse.apply) implicit val pieceFormat: RootJsonFormat[PieceResponse] = jsonFormat3(PieceResponse.apply) implicit val playerFormat: RootJsonFormat[PlayerResponse] = jsonFormat7(PlayerResponse.apply) implicit val playerActionFormat: RootJsonFormat[PlayerActionResponse] = jsonFormat2(PlayerActionResponse.apply)