Skip to content

Commit 7754219

Browse files
authored
chore: rename detekt ruleset to NullabilityRuleSet and fix nullability violations (#62)
* chore: adopt detekt-rules 1.1.0 NullabilityRuleSet and fix nullability violations * refactor: use Application.monitor instead of deprecated environment.monitor * refactor: drop redundant Unit expression in MetaHealthService.checkPostgresHealth * fix: annotate Application with @ConsistentCopyVisibility to silence copy() warning * refactor: migrate audit timestamps to kotlin.time and Exposed timestamp column * refactor: replace deprecated Kotest register(extension) with extension(...) * fix: capture version at configuration time in generateReleaseProperties * build: document why detekt-plugin stays pinned at 1.23.8 * refactor: read config defaults from HOCON only, drop getXxxOrDefault helpers * docs: trim verbose comments from detekt pin and config test
1 parent 755384f commit 7754219

31 files changed

Lines changed: 91 additions & 107 deletions

build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,14 @@ tasks.register("generateReleaseProperties") {
2929
description = "Generates release.properties file with the current project version"
3030
group = "build"
3131

32-
inputs.property("version", version)
32+
val releaseVersion = version
33+
inputs.property("version", releaseVersion)
3334
val resourcesDir = tasks.processResources.get().destinationDir
3435
outputs.file(File(resourcesDir, "release.properties"))
3536

3637
doLast {
3738
resourcesDir.mkdirs()
38-
File(resourcesDir, "release.properties").writeText("release=${project.version}")
39+
File(resourcesDir, "release.properties").writeText("release=$releaseVersion")
3940
}
4041
}
4142

detekt.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ style:
2323
UseCheckOrError:
2424
active: false
2525

26-
SdkmanRuleSet:
26+
NullabilityRuleSet:
2727
NoNullableTypes:
2828
active: true
29+
NoElvisOperator:
30+
active: true
31+
NoNotNullAssertion:
32+
active: true
33+
NoSafeCall:
34+
active: true
35+
NoPlatformTypes:
36+
active: true

gradle/libs.versions.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ kotlin = "2.4.0"
44
axion-release-plugin = "1.21.1"
55
jib-plugin = "3.5.3"
66
ktlint-plugin = "14.2.0"
7+
# Pinned to detekt 1.x; 2.x is alpha-only and breaks the custom detekt-rules NullabilityRuleSet.
78
detekt-plugin = "1.23.8"
89

910
# Libraries
@@ -23,7 +24,7 @@ mockk = "1.14.11"
2324
flyway = "12.6.2"
2425

2526
# Detekt rules
26-
detekt-rules = "1.0.1"
27+
detekt-rules = "1.1.0"
2728

2829
[libraries]
2930
arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }

specs/postgres_connection_pool.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,12 @@ connection pool is configured and wired.
6464
overridable per environment.
6565

6666
2. **`AppConfig` exposes the pool tunables.** The `AppConfig` interface gains
67-
five members and `DefaultAppConfig` reads them with the existing
68-
`getIntOrDefault` / `getLongOrDefault` helpers so absent keys fall back to
69-
the defaults in Rule 1. Timeouts are `Long` (milliseconds); sizes are `Int`.
70-
`PostgresConnectivity` consumes these instead of literals.
67+
five members and `DefaultAppConfig` reads them directly as required values
68+
(`config.getInt(...)` / `config.getLong(...)`). Per `.claude/rules/hocon.md`
69+
every default lives exactly once — in `application.conf` (Rule 1) — never as
70+
a duplicated Kotlin literal; a missing key is a deploy misconfiguration that
71+
must fail fast at startup. Timeouts are `Long` (milliseconds); sizes are
72+
`Int`. `PostgresConnectivity` consumes these instead of literals.
7173

7274
3. **The pool is named.** `HikariConfig.poolName = "sdkman-broker-pool"`,
7375
mirroring state's `sdkman-state-pool`. This makes the two pools
@@ -185,15 +187,15 @@ interface AppConfig {
185187
}
186188

187189
class DefaultAppConfig : AppConfig {
188-
//
189-
override val postgresPoolMaxSize: Int = config.getIntOrDefault("postgres.pool.maxSize", 20)
190-
override val postgresPoolMinIdle: Int = config.getIntOrDefault("postgres.pool.minIdle", 2)
190+
// defaults live in application.conf (Rule 1); reads are required and fail fast
191+
override val postgresPoolMaxSize: Int = config.getInt("postgres.pool.maxSize")
192+
override val postgresPoolMinIdle: Int = config.getInt("postgres.pool.minIdle")
191193
override val postgresPoolConnectionTimeoutMs: Long =
192-
config.getLongOrDefault("postgres.pool.connectionTimeoutMs", 5_000L)
194+
config.getLong("postgres.pool.connectionTimeoutMs")
193195
override val postgresPoolMaxLifetimeMs: Long =
194-
config.getLongOrDefault("postgres.pool.maxLifetimeMs", 1_800_000L)
196+
config.getLong("postgres.pool.maxLifetimeMs")
195197
override val postgresPoolIdleTimeoutMs: Long =
196-
config.getLongOrDefault("postgres.pool.idleTimeoutMs", 600_000L)
198+
config.getLong("postgres.pool.idleTimeoutMs")
197199
}
198200
```
199201

src/main/kotlin/io/sdkman/broker/App.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ object App {
7575
// Start Ktor server
7676
embeddedServer(Netty, port = config.serverPort, host = config.serverHost) {
7777
// Close the Hikari pool cleanly when the application stops (spec Business Rule 6).
78-
environment.monitor.subscribe(ApplicationStopped) { postgresDataSource.close() }
78+
monitor.subscribe(ApplicationStopped) { postgresDataSource.close() }
7979
configureApp(
8080
metaHealthService,
8181
metaReleaseService,

src/main/kotlin/io/sdkman/broker/adapter/primary/rest/DownloadRoutes.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.sdkman.broker.adapter.primary.rest
22

33
import arrow.core.Option
4+
import arrow.core.getOrElse
5+
import arrow.core.toOption
46
import io.ktor.http.HttpStatusCode
57
import io.ktor.server.application.Application
68
import io.ktor.server.application.ApplicationCall
@@ -22,9 +24,9 @@ fun Application.downloadRoutes(
2224
) {
2325
routing {
2426
get("/download/{candidate}/{version}/{platform}") {
25-
val candidate = call.parameters["candidate"] ?: return@get call.respondBadRequest()
26-
val version = call.parameters["version"] ?: return@get call.respondBadRequest()
27-
val platform = call.parameters["platform"] ?: return@get call.respondBadRequest()
27+
val candidate = call.parameters["candidate"].toOption().getOrElse { return@get call.respondBadRequest() }
28+
val version = call.parameters["version"].toOption().getOrElse { return@get call.respondBadRequest() }
29+
val platform = call.parameters["platform"].toOption().getOrElse { return@get call.respondBadRequest() }
2830

2931
val auditContext =
3032
AuditContext(
@@ -48,9 +50,9 @@ fun Application.downloadRoutes(
4850
}
4951

5052
get("/download/sdkman/{command}/{version}/{platform}") {
51-
val command = call.parameters["command"] ?: return@get call.respondBadRequest()
52-
val version = call.parameters["version"] ?: return@get call.respondBadRequest()
53-
val platform = call.parameters["platform"] ?: return@get call.respondBadRequest()
53+
val command = call.parameters["command"].toOption().getOrElse { return@get call.respondBadRequest() }
54+
val version = call.parameters["version"].toOption().getOrElse { return@get call.respondBadRequest() }
55+
val platform = call.parameters["platform"].toOption().getOrElse { return@get call.respondBadRequest() }
5456

5557
sdkmanCliDownloadService
5658
.downloadSdkmanCli(command, version, platform)
@@ -64,9 +66,9 @@ fun Application.downloadRoutes(
6466
}
6567

6668
get("/download/native/{command}/{version}/{platform}") {
67-
val command = call.parameters["command"] ?: return@get call.respondBadRequest()
68-
val version = call.parameters["version"] ?: return@get call.respondBadRequest()
69-
val platform = call.parameters["platform"] ?: return@get call.respondBadRequest()
69+
val command = call.parameters["command"].toOption().getOrElse { return@get call.respondBadRequest() }
70+
val version = call.parameters["version"].toOption().getOrElse { return@get call.respondBadRequest() }
71+
val platform = call.parameters["platform"].toOption().getOrElse { return@get call.respondBadRequest() }
7072

7173
sdkmanNativeDownloadService
7274
.downloadNativeCli(command, version, platform)

src/main/kotlin/io/sdkman/broker/adapter/secondary/persistence/PostgresAuditRepository.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import io.sdkman.broker.domain.repository.DatabaseFailure
77
import org.jetbrains.exposed.v1.core.Column
88
import org.jetbrains.exposed.v1.core.Table
99
import org.jetbrains.exposed.v1.core.java.javaUUID
10-
import org.jetbrains.exposed.v1.datetime.xTimestamp
10+
import org.jetbrains.exposed.v1.datetime.timestamp
1111
import org.jetbrains.exposed.v1.jdbc.Database
1212
import org.jetbrains.exposed.v1.jdbc.insert
1313
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
@@ -24,7 +24,7 @@ object AuditTable : Table("audit") {
2424
val distribution = text("distribution").nullable()
2525
val host = text("host").nullable()
2626
val agent = text("agent").nullable()
27-
val timestamp = xTimestamp("timestamp")
27+
val timestamp = timestamp("timestamp")
2828

2929
override val primaryKey = PrimaryKey(id)
3030
}

src/main/kotlin/io/sdkman/broker/adapter/secondary/persistence/PostgresVersionRepository.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,11 @@ private fun ResultRow.toVersion(): Version =
8484
)
8585

8686
private fun ResultRow.toChecksums(): Map<String, String> =
87-
listOfNotNull(
88-
this[VersionsTable.md5Sum]?.let { ALGO_MD5 to it },
89-
this[VersionsTable.sha256Sum]?.let { ALGO_SHA_256 to it },
90-
this[VersionsTable.sha512Sum]?.let { ALGO_SHA_512 to it }
91-
).toMap()
87+
listOf(
88+
this[VersionsTable.md5Sum].toOption().map { ALGO_MD5 to it },
89+
this[VersionsTable.sha256Sum].toOption().map { ALGO_SHA_256 to it },
90+
this[VersionsTable.sha512Sum].toOption().map { ALGO_SHA_512 to it }
91+
).flatMap { it.toList() }.toMap()
9292

9393
private const val ALGO_MD5 = "MD5"
9494
private const val ALGO_SHA_256 = "SHA-256"

src/main/kotlin/io/sdkman/broker/application/service/AuditCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import io.sdkman.broker.domain.model.Audit
77
import io.sdkman.broker.domain.model.JavaDistribution
88
import io.sdkman.broker.domain.model.Platform
99
import io.sdkman.broker.domain.model.Version
10-
import kotlinx.datetime.Clock
1110
import java.util.UUID
11+
import kotlin.time.Clock
1212

1313
data class AuditCommand(
1414
val candidate: String,

src/main/kotlin/io/sdkman/broker/application/service/MetaHealthService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class MetaHealthServiceImpl(
9191
is DatabaseFailure.QueryExecutionFailure ->
9292
HealthCheckError.DatabaseError("PostgreSQL", databaseFailure.exception)
9393
}
94-
}.map { Unit }
94+
}.map { }
9595
}
9696

9797
enum class HealthStatus {

0 commit comments

Comments
 (0)