From cc0b3c17e2cd1c17b9286af75c11b878fe73ac4e Mon Sep 17 00:00:00 2001
From: ccornu <ccornu@takima.fr>
Date: Mon, 10 Feb 2025 19:06:12 +0100
Subject: [PATCH] feat: better exception handling

---
 build.gradle.kts                              | 13 +++++---
 src/main/kotlin/Application.kt                |  2 ++
 .../configuration/ExceptionConfiguration.kt   | 31 +++++++++++++++++++
 src/main/kotlin/player/PlayerRoute.kt         | 29 +++--------------
 .../exceptions/AlreadyExistingException.kt    |  2 +-
 5 files changed, 46 insertions(+), 31 deletions(-)
 create mode 100644 src/main/kotlin/configuration/ExceptionConfiguration.kt

diff --git a/build.gradle.kts b/build.gradle.kts
index f445208..281e989 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,6 @@
 val koin_version: String by project
 val kotlin_version: String by project
+val ktor_version: String by project
 val logback_version: String by project
 val dynamo_version: String by project
 val dynamo_kt_version: String by project
@@ -32,11 +33,12 @@ repositories {
 
 dependencies {
     // Ktor
-    implementation("io.ktor:ktor-server-core")
-    implementation("io.ktor:ktor-server-auth")
-    implementation("io.ktor:ktor-server-openapi")
-    implementation("io.ktor:ktor-server-config-yaml")
-    implementation("io.ktor:ktor-server-netty")
+    implementation("io.ktor:ktor-server-core:$ktor_version")
+    implementation("io.ktor:ktor-server-auth:$ktor_version")
+    implementation("io.ktor:ktor-server-openapi:$ktor_version")
+    implementation("io.ktor:ktor-server-config-yaml:$ktor_version")
+    implementation("io.ktor:ktor-server-netty:$ktor_version")
+    implementation("io.ktor:ktor-server-status-pages:$ktor_version")
 
     // Dependency injection
     implementation("io.insert-koin:koin-ktor:$koin_version")
@@ -52,6 +54,7 @@ dependencies {
     implementation("software.amazon.awssdk:dynamodb:$dynamo_version")
     implementation("dev.andrewohara:dynamokt:$dynamo_kt_version")
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$kotlin_reactive_version")
+    implementation("io.ktor:ktor-server-host-common:$ktor_version")
 
     // Tests
     testImplementation("io.ktor:ktor-server-test-host")
diff --git a/src/main/kotlin/Application.kt b/src/main/kotlin/Application.kt
index 024521d..8814813 100644
--- a/src/main/kotlin/Application.kt
+++ b/src/main/kotlin/Application.kt
@@ -1,5 +1,6 @@
 package betclic.test
 
+import betclic.test.configuration.configureExceptionHandling
 import betclic.test.configuration.configureKoin
 import betclic.test.configuration.configureRouting
 import betclic.test.configuration.configureSerialization
@@ -19,6 +20,7 @@ fun Application.module() {
 fun Application.configuration() {
     configureKoin()
     configureSerialization()
+    configureExceptionHandling()
     configureRouting()
 }
 
diff --git a/src/main/kotlin/configuration/ExceptionConfiguration.kt b/src/main/kotlin/configuration/ExceptionConfiguration.kt
new file mode 100644
index 0000000..c2a6274
--- /dev/null
+++ b/src/main/kotlin/configuration/ExceptionConfiguration.kt
@@ -0,0 +1,31 @@
+package betclic.test.configuration
+
+import betclic.test.player.exceptions.AlreadyExistingPlayerException
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.plugins.*
+import io.ktor.server.plugins.statuspages.*
+import io.ktor.server.response.*
+import software.amazon.awssdk.services.dynamodb.model.DynamoDbException
+
+private const val SOMETHING_WENT_WRONG = "Something went wrong"
+
+fun Application.configureExceptionHandling() {
+    install(StatusPages) {
+        exception<Throwable> { call, cause ->
+            when (cause) {
+                is NotFoundException -> call.respond(HttpStatusCode.NotFound, cause.message ?: SOMETHING_WENT_WRONG)
+                is DynamoDbException -> call.respond(
+                    HttpStatusCode.InternalServerError,
+                    cause.message ?: "$SOMETHING_WENT_WRONG with DynamoDb"
+                )
+
+                is AlreadyExistingPlayerException -> call.respond(
+                    HttpStatusCode.BadRequest,
+                    cause.message ?: SOMETHING_WENT_WRONG
+                )
+            }
+            call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/player/PlayerRoute.kt b/src/main/kotlin/player/PlayerRoute.kt
index b52c4c3..439e37b 100644
--- a/src/main/kotlin/player/PlayerRoute.kt
+++ b/src/main/kotlin/player/PlayerRoute.kt
@@ -3,56 +3,35 @@ package betclic.test.player
 import betclic.test.player.dtos.PlayerCreationDTO
 import betclic.test.player.dtos.PlayerInfoDTO
 import betclic.test.player.dtos.PlayerUpdateDTO
-import betclic.test.player.exceptions.AlreadyExistingPlayerException
 import io.ktor.http.*
 import io.ktor.server.application.*
-import io.ktor.server.plugins.*
 import io.ktor.server.request.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
 import org.koin.ktor.ext.inject
 
-private const val SOMETHING_WENT_WRONG = "Something went wrong."
-
 fun Routing.playerRoutes() {
     val playerService by inject<PlayerService>()
     // const
     route("/players") {
         post {
             val request = call.receive<PlayerCreationDTO>()
-            try {
-                call.respond(HttpStatusCode.Created, playerService.createNewPlayer(request))
-            } catch (e: AlreadyExistingPlayerException) {
-                call.respond(status = HttpStatusCode.BadRequest, message = e.message ?: SOMETHING_WENT_WRONG)
-            }
+            call.respond(HttpStatusCode.Created, playerService.createNewPlayer(request))
         }
 
         put {
             val request = call.receive<PlayerUpdateDTO>()
-            try {
-                call.respond(HttpStatusCode.OK, playerService.updatePlayer(request))
-            } catch (e: NotFoundException) {
-                call.respond(status = HttpStatusCode.NotFound, message = e.message ?: SOMETHING_WENT_WRONG)
-            }
-
+            call.respond(HttpStatusCode.OK, playerService.updatePlayer(request))
         }
 
         get("/{pseudo}") {
             val pseudo = call.parameters["pseudo"] ?: return@get call.respond(HttpStatusCode.BadRequest)
-            try {
-                call.respond<Player>(playerService.findPlayerByPseudo(pseudo))
-            } catch (e: NotFoundException) {
-                call.respond(status = HttpStatusCode.NotFound, message = e.message ?: SOMETHING_WENT_WRONG)
-            }
+            call.respond<Player>(playerService.findPlayerByPseudo(pseudo))
         }
 
         get("/{pseudo}/info") {
             val pseudo = call.parameters["pseudo"] ?: return@get call.respond(HttpStatusCode.BadRequest)
-            try {
-                call.respond<PlayerInfoDTO>(playerService.getPlayerInfoByPseudo(pseudo))
-            } catch (e: NotFoundException) {
-                call.respond(status = HttpStatusCode.NotFound, message = e.message ?: SOMETHING_WENT_WRONG)
-            }
+            call.respond<PlayerInfoDTO>(playerService.getPlayerInfoByPseudo(pseudo))
         }
 
         get("/ranking") {
diff --git a/src/main/kotlin/player/exceptions/AlreadyExistingException.kt b/src/main/kotlin/player/exceptions/AlreadyExistingException.kt
index 4deded5..f08bc6f 100644
--- a/src/main/kotlin/player/exceptions/AlreadyExistingException.kt
+++ b/src/main/kotlin/player/exceptions/AlreadyExistingException.kt
@@ -1,4 +1,4 @@
 package betclic.test.player.exceptions
 
 class AlreadyExistingPlayerException(val pseudo: String) :
-    RuntimeException("$pseudo already exists. You still can update this player points")
\ No newline at end of file
+    Exception("$pseudo already exists. You still can update this player points")
\ No newline at end of file
-- 
GitLab