InícioProjetosArtigosSobreContato

Crud no Ktor

Rodrigo Leutz

Sealed class de resposta para o crud.

sealed class Resource<T> {

    data class Error<T>(
        val status: HttpStatusCode = HttpStatusCode.Conflict, val data: T? = null
    ) : Resource<T>()

    data class NotImplemented<T>(
        val status: HttpStatusCode = HttpStatusCode.NotImplemented, val data: T? = null
    ) : Resource<T>()

    data class Success<T>(
        val status: HttpStatusCode = HttpStatusCode.OK, val data: T? = null
    ) : Resource<T>()

    val isImplemented: Boolean
        get() = this !is NotImplemented

    val isSuccess: Boolean
        get() = this is Success

    val dataValue: T?
        get() = when (this) {
            is Success -> data as T?
            is Error -> data as T?
            is NotImplemented -> null
        }

    val statusCode: HttpStatusCode
        get() = when (this) {
            is Success -> status
            is Error -> status
            is NotImplemented -> HttpStatusCode.NotImplemented
        }
}

Crud para o ktor.

class InvalidRequest(message: InvalidRequestType) : Exception(message.name)
enum class InvalidRequestType { AUTH, DATA }

suspend fun ApplicationCall.throwException(type: InvalidRequestType): Nothing = kotlin.run {
    this.respond(if (type == InvalidRequestType.AUTH) HttpStatusCode.Unauthorized else HttpStatusCode.BadRequest)
    throw InvalidRequest(type)
}

suspend inline fun <reified R> ApplicationCall.responseRequest(resource: Resource<R>) =
    this.respond(resource.statusCode, resource.dataValue ?: "")

suspend inline fun <reified T, reified R> ApplicationCall.receiveObject(
    useCase: (T) -> Resource<R>
) = responseRequest(useCase(this.receiveNullable<T>() ?: throwException(InvalidRequestType.DATA)))

suspend inline fun <reified R> ApplicationCall.receiveParameter(
    name: String,
    useCase: (String) -> Resource<R>
) = responseRequest(useCase(this.parameters[name] ?: throwException(InvalidRequestType.DATA)))

suspend inline fun <reified R> ApplicationCall.receiveGetAll(
    useCase: () -> Resource<List<R>>
) = responseRequest(useCase())

suspend fun ApplicationCall.getProfileIdFromJwt() = this.principal<JWTPrincipal>()?.get(Constants.PROFILE_ID)
    ?: throwException(InvalidRequestType.AUTH)

suspend fun ApplicationCall.getSignIdFromJwt() = this.principal<JWTPrincipal>()?.get(Constants.SIGN_ID)
    ?: throwException(InvalidRequestType.AUTH)

inline fun <reified T, reified R> Route.crud(
    crossinline add: suspend (userId: String, data: T) -> Resource<R>,
    crossinline delete: suspend (userId: String, data: T) -> Resource<R>,
    crossinline getAll: suspend (userId: String) -> Resource<List<R>>,
    crossinline getById: suspend (userId: String, data: String) -> Resource<R>,
    crossinline update: suspend (signId: String, profileId: String, data: T) -> Resource<R>
) {
    post(Routes.Crud.Add.route) { call.receiveObject<T, R> { add(call.getProfileIdFromJwt(), it) } }
    delete(Routes.Crud.Delete.route) { call.receiveObject<T, R> { delete(call.getProfileIdFromJwt(), it) } }
    get(Routes.Crud.GetAll.route) { call.receiveGetAll { getAll(call.getProfileIdFromJwt()) } }
    get(Routes.Crud.GetById.route + "/{$\{Constants.SIGN_ID}}") {
        call.receiveParameter<R>(Constants.SIGN_ID) { getById(call.getProfileIdFromJwt(), it) }
    }
    put(Routes.Crud.Update.route) {
        call.receiveObject<T, R> {
            update(
                call.getSignIdFromJwt(),
                call.getProfileIdFromJwt(),
                it
            )
        }
    }
}