{"filename":"kotlin-ktor-development-cursorrules-prompt-file.mdc","content":"---\ndescription: \"Cursor rules for Kotlin development with Ktor integration.\"\nglobs: **/*\nalwaysApply: false\n---\n## Instruction to developer: save this file as .cursorrules and place it on the root project directory\n\n## Core Principles\n- Follow **SOLID**, **DRY**, **KISS**, and **YAGNI** principles\n- Adhere to **OWASP** security best practices\n- Break tasks into smallest units and solve problems step-by-step\n\n## Technology Stack\n- **Framework**: Kotlin Ktor with Kotlin 2.1.20+\n- **JDK**: 21 (LTS)\n- **Build**: Gradle with Kotlin DSL\n- **Dependencies**: Ktor Server Core/Netty, kotlinx.serialization, Exposed, HikariCP, kotlin-logging, Koin, Kotest\n\n## Application Structure (Feature-Based)\n- **Organize by business features, not technical layers**\n- Each feature is self-contained with all related components\n- Promotes modularity, reusability, and better team collaboration\n- Makes codebase easier to navigate and maintain\n- Enables parallel development on different features\n```\nsrc/main/kotlin/com/company/app/\n├── common/              # Shared utilities, extensions\n├── config/              # Application configuration, DI\n└── features/\n    ├── auth/            # Feature directory\n    │   ├── models/\n    │   ├── repositories/\n    │   ├── services/\n    │   └── routes/\n    └── users/           # Another feature\n        ├── ...\n```\n\nTest structure mirrors the feature-based organization:\n```\nsrc/test/kotlin/com/company/app/\n├── common/\n└── features/\n    ├── auth/\n    │   ├── models/\n    │   ├── repositories/\n    │   ├── services/\n    │   └── routes/\n    └── users/\n        ├── ...\n```\n\n## Application Logic Design\n1. Route handlers: Handle requests/responses only\n2. Services: Contain business logic, call repositories\n3. Repositories: Handle database operations\n4. Entity classes: Data classes for database models\n5. DTOs: Data transfer between layers\n\n## Entities & Data Classes\n- Use Kotlin data classes with proper validation\n- Define Table objects when using Exposed ORM\n- Use UUID or auto-incrementing integers for IDs\n\n## Repository Pattern\n```kotlin\ninterface UserRepository {\n    suspend fun findById(id: UUID): UserDTO?\n    suspend fun create(user: CreateUserRequest): UserDTO\n    suspend fun update(id: UUID, user: UpdateUserRequest): UserDTO?\n    suspend fun delete(id: UUID): Boolean\n}\n\nclass UserRepositoryImpl : UserRepository {\n    override suspend fun findById(id: UUID): UserDTO? = withContext(Dispatchers.IO) {\n        transaction {\n            Users.select { Users.id eq id }\n                .mapNotNull { it.toUserDTO() }\n                .singleOrNull()\n        }\n    }\n    // Other implementations...\n}\n```\n\n## Service Layer\n```kotlin\ninterface UserService {\n    suspend fun getUserById(id: UUID): UserDTO\n    suspend fun createUser(request: CreateUserRequest): UserDTO\n    suspend fun updateUser(id: UUID, request: UpdateUserRequest): UserDTO\n    suspend fun deleteUser(id: UUID)\n}\n\nclass UserServiceImpl(\n    private val userRepository: UserRepository\n) : UserService {\n    override suspend fun getUserById(id: UUID): UserDTO {\n        return userRepository.findById(id) ?: throw ResourceNotFoundException(\"User\", id.toString())\n    }\n    // Other implementations...\n}\n```\n\n## Route Handlers\n```kotlin\nfun Application.configureUserRoutes(userService: UserService) {\n    routing {\n        route(\"/api/users\") {\n            get(\"/{id}\") {\n                val id = call.parameters[\"id\"]?.let { UUID.fromString(it) }\n                    ?: throw ValidationException(\"Invalid ID format\")\n                val user = userService.getUserById(id)\n                call.respond(ApiResponse(\"SUCCESS\", \"User retrieved\", user))\n            }\n            // Other routes...\n        }\n    }\n}\n```\n\n## Error Handling\n```kotlin\nopen class ApplicationException(\n    message: String,\n    val statusCode: HttpStatusCode = HttpStatusCode.InternalServerError\n) : RuntimeException(message)\n\nclass ResourceNotFoundException(resource: String, id: String) :\n    ApplicationException(\"$resource with ID $id not found\", HttpStatusCode.NotFound)\n\nfun Application.configureExceptions() {\n    install(StatusPages) {\n        exception<ResourceNotFoundException> { call, cause ->\n            call.respond(cause.statusCode, ApiResponse(\"ERROR\", cause.message ?: \"Resource not found\"))\n        }\n        exception<Throwable> { call, cause ->\n            call.respond(HttpStatusCode.InternalServerError, ApiResponse(\"ERROR\", \"An internal error occurred\"))\n        }\n    }\n}\n```\n\n## Testing Strategies and Coverage Requirements\n\n### Test Coverage Requirements\n- **Minimum coverage**: 80% overall code coverage required\n- **Critical components**: 90%+ coverage for repositories, services, and validation\n- **Test all edge cases**: Empty collections, null values, boundary conditions\n- **Test failure paths**: Exception handling, validation errors, timeouts\n- **All public APIs**: Must have integration tests\n- **Performance-critical paths**: Must have benchmarking tests\n\n### Unit Testing with Kotest\n```kotlin\nclass UserServiceTest : DescribeSpec({\n    describe(\"UserService\") {\n        val mockRepository = mockk<UserRepository>()\n        val userService = UserServiceImpl(mockRepository)\n\n        it(\"should return user when exists\") {\n            val userId = UUID.randomUUID()\n            val user = UserDTO(userId.toString(), \"Test User\", \"test@example.com\")\n            coEvery { mockRepository.findById(userId) } returns user\n\n            val result = runBlocking { userService.getUserById(userId) }\n\n            result shouldBe user\n        }\n\n        it(\"should throw exception when user not found\") {\n            val userId = UUID.randomUUID()\n            coEvery { mockRepository.findById(userId) } returns null\n\n            shouldThrow<ResourceNotFoundException> {\n                runBlocking { userService.getUserById(userId) }\n            }\n        }\n    }\n})\n```\n\n## Route Testing with Ktor 3.x\n```kotlin\nclass UserRoutesTest : FunSpec({\n    test(\"GET /api/users/{id} returns 200 when user exists\") {\n        val mockService = mockk<UserService>()\n        val userId = UUID.randomUUID()\n        val user = UserDTO(userId.toString(), \"Test User\", \"test@example.com\")\n\n        coEvery { mockService.getUserById(userId) } returns user\n\n        testApplication {\n            application {\n                configureRouting()\n                configureDI { single { mockService } }\n            }\n\n            client.get(\"/api/users/$userId\").apply {\n                status shouldBe HttpStatusCode.OK\n                bodyAsText().let {\n                    Json.decodeFromString<ApiResponse<UserDTO>>(it)\n                }.data shouldBe user\n            }\n        }\n    }\n})\n```\n\n## Key Principles for Testable Code\n1. **Single Responsibility**: Each method should do one thing well\n2. **Pure Functions**: Same input always produces same output\n3. **Dependency Injection**: Constructor injection for testable components\n4. **Clear Boundaries**: Well-defined inputs and outputs\n5. **Small Methods**: Extract complex logic into testable helper functions\n\n## Configuration Management\n```kotlin\n// Type-safe configuration\ninterface AppConfig {\n    val database: DatabaseConfig\n    val security: SecurityConfig\n}\n\ndata class DatabaseConfig(\n    val driver: String,\n    val url: String,\n    val user: String,\n    val password: String\n)\n\n// Access in application\nfun Application.configureDI() {\n    val appConfig = HoconAppConfig(environment.config)\n\n    install(Koin) {\n        modules(module {\n            single<AppConfig> { appConfig }\n            single { appConfig.database }\n        })\n    }\n}\n```\n\n## Security Best Practices\n```kotlin\nfun Application.configureSecurity() {\n    install(Authentication) {\n        jwt(\"auth-jwt\") {\n            // JWT configuration\n        }\n    }\n\n    install(DefaultHeaders) {\n        header(HttpHeaders.XContentTypeOptions, \"nosniff\")\n        header(HttpHeaders.XFrameOptions, \"DENY\")\n        header(HttpHeaders.ContentSecurityPolicy, \"default-src 'self'\")\n        header(\"Strict-Transport-Security\", \"max-age=31536000; includeSubDomains\")\n    }\n}\n```\n\n## Health Checks & Monitoring\n```kotlin\nfun Application.configureMonitoring() {\n    val startTime = System.currentTimeMillis()\n\n    routing {\n        get(\"/health\") {\n            call.respond(mapOf(\"status\" to \"UP\", \"uptime\" to \"${(System.currentTimeMillis() - startTime) / 1000}s\"))\n        }\n\n        get(\"/metrics\") {\n            call.respond(prometheusRegistry.scrape())\n        }\n    }\n\n    install(MicrometerMetrics) {\n        registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)\n        meterBinders = listOf(\n            JvmMemoryMetrics(),\n            JvmGcMetrics(),\n            ProcessorMetrics(),\n            JvmThreadMetrics()\n        )\n    }\n}\n```\n\n## Performance Tuning\n- **JVM Settings**: `-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:MaxRAMPercentage=75.0`\n- **Connection Pooling**: Configure HikariCP with proper sizing based on workload\n- **Caching**: Use Caffeine for in-memory caching of frequently accessed data\n- **Coroutines**: Use structured concurrency for asynchronous processing\n- **Database Queries**: Optimize with proper indexing, batch operations, pagination\n"}