Skip to content

Commit

Permalink
#60 Added InvalidArgument and NotFound exceptions (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
vityaman authored Apr 19, 2024
1 parent 301a9a3 commit 2c61e4c
Show file tree
Hide file tree
Showing 23 changed files with 197 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ru.vityaman.lms.botalka.api.http.message.toModel
import ru.vityaman.lms.botalka.api.http.server.UserDraftMessage
import ru.vityaman.lms.botalka.api.http.server.UserMessage
import ru.vityaman.lms.botalka.api.http.server.apis.UserApi
import ru.vityaman.lms.botalka.domain.exception.orNotFound
import ru.vityaman.lms.botalka.domain.model.User
import ru.vityaman.lms.botalka.logic.UserService

Expand All @@ -18,6 +19,7 @@ class UserHttpApi(
override suspend fun userIdGet(id: Int): ResponseEntity<UserMessage> {
val userId = User.Id(id)
val user = userService.getById(userId)
.orNotFound("User with id ${userId.number} not found")
return ResponseEntity.ok(user.toMessage())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ru.vityaman.lms.botalka.api.http.error

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import ru.vityaman.lms.botalka.api.http.server.GeneralErrorMessage
import ru.vityaman.lms.botalka.domain.exception.DomainException
import ru.vityaman.lms.botalka.domain.exception.InvalidValueException
import ru.vityaman.lms.botalka.domain.exception.NotFoundException

val DomainException.httpCode: HttpStatus
get() = when (this) {
is InvalidValueException -> HttpStatus.NOT_FOUND
is NotFoundException -> HttpStatus.NOT_FOUND
else -> TODO()
}

val HttpStatus.reason: String
get() = this.reasonPhrase

fun DomainException.toResponseEntity() =
ResponseEntity
.status(this.httpCode)
.body(
GeneralErrorMessage(
code = this.httpCode.value(),
status = this.httpCode.reasonPhrase,
message = this.message!!,
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ru.vityaman.lms.botalka.api.http.error

import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import ru.vityaman.lms.botalka.domain.exception.DomainException

@RestControllerAdvice
class DomainExceptionMapping {
@ExceptionHandler(DomainException::class)
fun handle(exception: DomainException) =
exception.toResponseEntity()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ru.vityaman.lms.botalka.api.http.message

import ru.vityaman.lms.botalka.api.http.server.WorkspaceCommentMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventKindMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceFeedbackMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceSubmissionMessage
import ru.vityaman.lms.botalka.domain.model.Workspace

private val Workspace.Event.kind
get() =
when (this) {
is Workspace.Comment -> WorkspaceEventKindMessage.COMMENT
is Workspace.Feedback -> WorkspaceEventKindMessage.FEEDBACK
is Workspace.Submission -> WorkspaceEventKindMessage.SUBMISSION
}

fun Workspace.Comment.toMessage() =
WorkspaceCommentMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
text = this.text,
creationMoment = this.creationMoment,
)

fun Workspace.Feedback.toMessage() =
WorkspaceFeedbackMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
comment = this.comment,
score = this.score?.value?.toInt(),
creationMoment = this.creationMoment,
)

fun Workspace.Submission.toMessage() =
WorkspaceSubmissionMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
note = this.note,
creationMoment = this.creationMoment,
)

fun Workspace.Event.toMessage(): WorkspaceEventMessage =
when (this) {
is Workspace.Comment -> this.toMessage()
is Workspace.Feedback -> this.toMessage()
is Workspace.Submission -> this.toMessage()
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
package ru.vityaman.lms.botalka.api.http.message

import ru.vityaman.lms.botalka.api.http.server.WorkspaceCommentDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceCommentMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventKindMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceFeedbackDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceFeedbackMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceSubmissionDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceSubmissionMessage
import ru.vityaman.lms.botalka.domain.model.Homework
import ru.vityaman.lms.botalka.domain.model.Teacher
import ru.vityaman.lms.botalka.domain.model.User
Expand Down Expand Up @@ -56,45 +51,3 @@ fun WorkspaceFeedbackDraftMessage.toModel(producer: User.Id) =
comment = this.comment,
score = this.score?.let { Homework.Score(it.toShort()) },
)

private val Workspace.Event.kind
get() =
when (this) {
is Workspace.Comment -> WorkspaceEventKindMessage.COMMENT
is Workspace.Feedback -> WorkspaceEventKindMessage.FEEDBACK
is Workspace.Submission -> WorkspaceEventKindMessage.SUBMISSION
}

fun Workspace.Event.toMessage(): WorkspaceEventMessage =
when (this) {
is Workspace.Comment -> {
WorkspaceCommentMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
text = this.text,
creationMoment = this.creationMoment,
)
}

is Workspace.Feedback -> {
WorkspaceFeedbackMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
comment = this.comment,
score = this.score?.value?.toInt(),
creationMoment = this.creationMoment,
)
}

is Workspace.Submission -> {
WorkspaceSubmissionMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
note = this.note,
creationMoment = this.creationMoment,
)
}
}
12 changes: 0 additions & 12 deletions botalka/src/main/kotlin/ru/vityaman/lms/botalka/commons/Require.kt

This file was deleted.

15 changes: 15 additions & 0 deletions botalka/src/main/kotlin/ru/vityaman/lms/botalka/domain/Require.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.vityaman.lms.botalka.domain

import ru.vityaman.lms.botalka.domain.exception.InvalidValueException

fun expect(isValid: Boolean, message: StringBuilder.() -> Unit) {
if (!isValid) {
throw InvalidValueException(buildString(message))
}
}

fun expectId(number: Int) {
expect(0 < number) {
append("Unique id must be a positive, got $number")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.vityaman.lms.botalka.domain.exception

class InvalidValueException(string: String) :
DomainException(string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.vityaman.lms.botalka.domain.exception

class NotFoundException(message: String) :
DomainException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.vityaman.lms.botalka.domain.exception

fun <T> T?.orNotFound(message: String): T =
this ?: throw NotFoundException(message)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package ru.vityaman.lms.botalka.domain.model

import ru.vityaman.lms.botalka.commons.abbreviated
import ru.vityaman.lms.botalka.commons.expect
import ru.vityaman.lms.botalka.commons.expectId
import ru.vityaman.lms.botalka.domain.expect
import ru.vityaman.lms.botalka.domain.expectId
import java.time.OffsetDateTime

data class Homework(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ru.vityaman.lms.botalka.domain.model

import ru.vityaman.lms.botalka.commons.expectId
import ru.vityaman.lms.botalka.domain.expectId

data class PromotionRequest(
val id: Id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package ru.vityaman.lms.botalka.domain.model

import ru.vityaman.lms.botalka.commons.abbreviated
import ru.vityaman.lms.botalka.commons.expect
import ru.vityaman.lms.botalka.commons.expectId
import ru.vityaman.lms.botalka.domain.expect
import ru.vityaman.lms.botalka.domain.expectId

data class User(
val id: Id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ru.vityaman.lms.botalka.domain.model

import ru.vityaman.lms.botalka.commons.expectId
import ru.vityaman.lms.botalka.domain.expectId
import java.time.OffsetDateTime

data class Workspace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package ru.vityaman.lms.botalka.logic
import ru.vityaman.lms.botalka.domain.model.User

interface UserService {
suspend fun getById(id: User.Id): User
suspend fun getById(id: User.Id): User?
suspend fun create(user: User.Draft): User
suspend fun promote(id: User.Id, role: User.Role)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ru.vityaman.lms.botalka.logic.basic
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import ru.vityaman.lms.botalka.domain.exception.PromotionRequestResolvedException
import ru.vityaman.lms.botalka.domain.exception.orNotFound
import ru.vityaman.lms.botalka.domain.model.PromotionRequest
import ru.vityaman.lms.botalka.logic.PromotionService
import ru.vityaman.lms.botalka.logic.UserService
Expand All @@ -19,18 +20,22 @@ class BasicPromotionService(
storage.create(promotion)

override suspend fun approve(id: PromotionRequest.Id) {
val request = storage.getById(id)
if (request.isResolved) {
throw PromotionRequestResolvedException(request.id)
}
val request = getUnresolvedRequestById(id)
storage.updateStatus(request.id, PromotionRequest.Status.APPROVED)
userService.promote(request.user, request.role)
}

override suspend fun reject(id: PromotionRequest.Id) {
if (storage.getById(id).isResolved) {
throw PromotionRequestResolvedException(id)
}
getUnresolvedRequestById(id)
storage.updateStatus(id, PromotionRequest.Status.REJECTED)
}

private suspend fun getUnresolvedRequestById(id: PromotionRequest.Id) =
storage.getById(id)
.orNotFound("Promotion request with id ${id.number} not found")
.also {
if (it.isResolved) {
throw PromotionRequestResolvedException(it.id)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ru.vityaman.lms.botalka.storage.UserStorage
class BasicUserService(
@Autowired private val storage: UserStorage,
) : UserService {
override suspend fun getById(id: User.Id): User =
override suspend fun getById(id: User.Id): User? =
storage.getById(id)

override suspend fun create(user: User.Draft): User =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ interface PromotionStorage {
status: PromotionRequest.Status,
)

suspend fun getById(id: PromotionRequest.Id): PromotionRequest
suspend fun getById(id: PromotionRequest.Id): PromotionRequest?
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ru.vityaman.lms.botalka.domain.model.Teacher
import ru.vityaman.lms.botalka.domain.model.User

interface UserStorage {
suspend fun getById(id: User.Id): User
suspend fun getById(id: User.Id): User?
suspend fun create(user: User.Draft): User
suspend fun create(teacher: Teacher)
suspend fun create(student: Student)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ru.vityaman.lms.botalka.storage.jooq

import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository
import reactor.kotlin.core.publisher.toMono
Expand Down Expand Up @@ -42,11 +43,11 @@ class JooqPromotionStorage(
.map { }
.awaitSingle()

override suspend fun getById(id: PromotionRequest.Id): PromotionRequest =
override suspend fun getById(id: PromotionRequest.Id): PromotionRequest? =
database.execute
.selectFrom(PROMOTION_REQUEST)
.where(PROMOTION_REQUEST.ID.equal(id.number))
.toMono()
.map { it.toModel() }
.awaitSingle()
.awaitSingleOrNull()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import ru.vityaman.lms.botalka.storage.jooq.tables.references.USER
class JooqUserStorage(
@Autowired private val database: JooqDatabase,
) : UserStorage {
override suspend fun getById(id: User.Id): User =
override suspend fun getById(id: User.Id): User? =
database.execute
.selectFrom(USER)
.where(USER.ID.equal(id.number))
.toMono()
.map { it.toModel() }
.awaitSingle()
.copy(
.awaitSingleOrNull()
?.copy(
roles = setOfNotNull(
teacher(id)?.let { User.Role.TEACHER },
student(id)?.let { User.Role.STUDENT },
Expand Down
Loading

0 comments on commit 2c61e4c

Please sign in to comment.