[M] Move core extensions to shared

This commit is contained in:
Azalea 2026-03-28 13:50:38 -04:00
parent adaa255dd3
commit 3dbb0095bf
13 changed files with 61 additions and 41 deletions

View File

@ -0,0 +1,5 @@
---
trigger: always_on
---
Make sure `./gradlew build` compiles before you say you're done.

View File

@ -32,10 +32,7 @@ fun HttpServletResponse.details() = mapOf(
// HTTP
operator fun HttpStatus.invoke(message: String? = null): Nothing = throw ApiException(value(), message ?: this.reasonPhrase)
operator fun Int.minus(message: String): Nothing {
ApiException.log.info("> Error $this: $message")
throw ApiException(this, message)
}
fun <R> parsing(block: () -> R) = try { block() }
catch (e: ApiException) { throw e }
catch (e: Exception) { 400 - e.message.toString() }

View File

@ -1,29 +1,21 @@
package icu.samnyan.aqua.net.utils
import ext.Str
import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
val SUCCESS = ResponseEntity.ok().body(mapOf("status" to "ok"))
class ApiException(val code: Int, message: Str) : RuntimeException(message) {
companion object {
val log = LoggerFactory.getLogger(ApiException::class.java)
}
fun resp() = ResponseEntity.status(code).body(message.toString())
}
fun Exception.simpleDescribe(): String = if (this is ApiException) "E${code}" else javaClass.simpleName
@ControllerAdvice(basePackages = ["icu.samnyan"])
class GlobalExceptionHandler {
@ExceptionHandler(ApiException::class)
fun handleCustomApiException(e: ApiException): ResponseEntity<String> {
// On error, return the error code and message
return e.resp()
}
package icu.samnyan.aqua.net.utils
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
val SUCCESS: ResponseEntity<Map<String, String>> = ResponseEntity.ok().body(mapOf("status" to "ok"))
fun ApiException.resp(): ResponseEntity<String> = ResponseEntity.status(code).body(message.toString())
fun Exception.simpleDescribe(): String = if (this is ApiException) "E${code}" else javaClass.simpleName
@ControllerAdvice(basePackages = ["icu.samnyan"])
class GlobalExceptionHandler {
@ExceptionHandler(ApiException::class)
fun handleCustomApiException(e: ApiException): ResponseEntity<String> {
// On error, return the error code and message
return e.resp()
}
}

View File

@ -4,6 +4,8 @@ import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlin.random.Random
import kotlin.random.nextInt
import ext.ensureEndingSlash
import ext.jsonMap
const val BOARD_ID = "ACAE-01A99999999"
const val FULL_CLIENT_ID = "A123-45678909999"

View File

@ -9,4 +9,11 @@ dependencies {
api("jakarta.servlet:jakarta.servlet-api:6.0.0")
api("com.fasterxml.jackson.core:jackson-annotations:2.17.0")
api("com.fasterxml.jackson.core:jackson-databind:2.17.0")
api("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0")
// Core libraries
api("org.slf4j:slf4j-api:2.0.12")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
api("org.jetbrains.kotlin:kotlin-reflect:2.1.10")
}

View File

@ -2,6 +2,7 @@
package ext
import icu.samnyan.aqua.net.utils.ApiException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.slf4j.LoggerFactory
@ -37,12 +38,12 @@ val emailRegex = "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9
fun Str.isValidEmail(): Bool = emailRegex.matches(this)
// Class resource
object Ext { val log = logger() }
object Ext
fun res(name: Str) = Ext::class.java.getResourceAsStream(name)
fun resStr(name: Str) = res(name)?.reader()?.readText()
inline fun <reified T> resJson(name: Str, warn: Boolean = true) = resStr(name)?.let {
JSON.decodeFromString<T>(it)
} ?: run { if (warn) Ext.log.warn("Resource $name is not found"); null }
} ?: run { if (warn) ApiException.log.warn("Resource $name is not found"); null }
// Encodings
fun Long.toHex(len: Int = 16): Str = "0x${this.toString(len).padStart(len, '0').uppercase()}"
@ -56,6 +57,10 @@ fun Any.long() = when (this) {
is String -> toLong()
else -> 400 - "Invalid number: $this"
}
operator fun Int.minus(message: String): Nothing {
ApiException.log.info("> Error $this: $message")
throw ApiException(this, message)
}
fun Any.uint32() = long() and 0xFFFFFFFF
fun Any.int() = long().toInt()
val Any.long get() = long()
@ -120,17 +125,19 @@ val Str.some get() = ifBlank { null }
val ByteArray.hexStr get() = toHexString()
operator fun StringBuilder.plusAssign(other: String) { this.append(other) }
// Coroutine
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }
// Coroutine-lite
fun <T> thread(block: () -> T) = Thread { block() }.apply { start() }
fun <T> Lock.maybeLock(block: () -> T) = if (tryLock()) try { block() } finally { unlock() } else null
// Coroutine
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }
// Paths
fun path(part1: Str, vararg parts: Str) = Path.of(part1, *parts)
fun Str.path() = Path.of(this)
operator fun Path.div(part: Str) = resolve(part)
operator fun File.div(fileName: Str) = File(this, fileName)
fun Str.ensureEndingSlash() = if (endsWith('/')) this else "$this/"
fun String.ensureEndingSlash() = if (endsWith('/')) this else "$this/"
fun Str.ensureNoEndingSlash() = if (endsWith('/')) dropLast(1) else this
fun <T: Any> T.logger() = LoggerFactory.getLogger(this::class.java)

View File

@ -56,7 +56,7 @@ catch (e: Exception) {
throw e
}
fun String.jsonMap(): Map<String, Any?> = json() ?: emptyMap()
fun String.jsonArray(): List<Map<String, Any?>> = json() ?: emptyList()
fun String.jsonMaybeMap(): Map<String, Any?>? = json()
fun String.jsonMaybeArray(): List<Map<String, Any?>>? = json()
fun String.jsonMap(): Map<String, Any?> = json<Map<String, Any?>>() ?: emptyMap()
fun String.jsonArray(): List<Map<String, Any?>> = json<List<Map<String, Any?>>>() ?: emptyList()
fun String.jsonMaybeMap(): Map<String, Any?>? = json<Map<String, Any?>>()
fun String.jsonMaybeArray(): List<Map<String, Any?>>? = json<List<Map<String, Any?>>>()

View File

@ -0,0 +1,10 @@
package icu.samnyan.aqua.net.utils
import ext.Str
import org.slf4j.LoggerFactory
class ApiException(val code: Int, message: Str) : RuntimeException(message) {
companion object {
val log = LoggerFactory.getLogger(ApiException::class.java)
}
}