-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
Open
Labels
Description
The Problem:
Currently, when a Kotlin Spring endpoint returns different response types for different status codes, there's no type-safe way to represent this in the generated interface. Controllers must use unsafe casts or return a generic supertype, losing compile-time type safety.
Current Behavior:
Given an OpenAPI spec with multiple response types:
paths:
/users:
post:
operationId: createUser
responses:
'200':
description: User created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'409':
description: Conflict - User already exists
content:
application/json:
schema:
$ref: '#/components/schemas/ConflictResponse'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
The generated interface returns only the 200 response type:
interface DefaultApi {
fun createUser(@Valid @RequestBody request: CreateUserRequest): ResponseEntity<User>
}
This forces implementations to use unsafe casts when returning non 2xx responses in controllers.
A Possible Solution:
Kotlin provides sealed interfaces, which feel like a good solution to this:
// Generated sealed interface
sealed interface CreateUserResponse
// Generated models implement the sealed interface
data class User(...) : CreateUserResponse
data class ConflictResponse(...) : CreateUserResponse
data class ErrorResponse(...) : CreateUserResponse
// Generated API uses sealed interface
interface DefaultApi {
fun createUser(@Valid @RequestBody request: CreateUserRequest): ResponseEntity<CreateUserResponse>
}
This way our controllers can return any response code while maintaining compile time safety
@RestController
class UserController : DefaultApi {
override fun createUser(request: CreateUserRequest): ResponseEntity<CreateUserResponse> {
if (userExists(request.email)) {
val response: CreateUserResponse = ConflictResponse(
reason = ConflictReason.EMAIL_CONFLICT,
message = "User already exists"
)
return ResponseEntity.status(409).body(response)
}
if (invalid(request)) {
val response: CreateUserResponse = ErrorResponse(
code = "INVALID_INPUT",
message = "Invalid request"
)
return ResponseEntity.status(400).body(response)
}
val response: CreateUserResponse = User(...)
return ResponseEntity.ok(response)
}
}
Reactions are currently unavailable