Complete examples
Full API definition
// ApiSpec.kt
package dev.kolibrium.api.example
import dev.kolibrium.api.core.ApiSpec
import dev.kolibrium.api.ksp.annotations.GenerateApi
@GenerateApi
object VinylStoreApiSpec : ApiSpec(baseUrl = "https://api.example.com")
// models/Vinyl.kt
package dev.kolibrium.api.example.models
import dev.kolibrium.api.ksp.annotations.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
// Request models
@POST("/api/vinyls")
@Returns(Vinyl::class)
@Serializable
data class CreateVinylRequest(
var artist: String? = null,
var album: String? = null,
var year: Int? = null,
var genre: String? = null,
var price: Double? = null,
var stock: Int? = null
)
@GET("/api/vinyls/{id}")
@Returns(Vinyl::class)
@Serializable
data class GetVinylRequest(
@Path @Transient val id: Int = 0
)
@GET("/api/vinyls")
@Returns(VinylList::class)
@Serializable
data class ListVinylsRequest(
@Query @Transient val genre: String? = null,
@Query @Transient val artist: String? = null
)
@PUT("/api/vinyls/{id}")
@Returns(Vinyl::class)
@Serializable
data class UpdateVinylRequest(
@Path @Transient val id: Int = 0,
var artist: String? = null,
var album: String? = null,
var year: Int? = null,
var genre: String? = null,
var price: Double? = null,
var stock: Int? = null
)
@DELETE("/api/vinyls/{id}")
@Returns(Unit::class)
@Serializable
data class DeleteVinylRequest(
@Path @Transient val id: Int = 0
)
// Response models
@Serializable
data class Vinyl(
val id: Int,
val artist: String,
val album: String,
val year: Int,
val genre: String,
val price: Double,
val stock: Int
)
@Serializable
data class VinylList(
val vinyls: List<Vinyl>,
val total: Int
)
Using the generated client
import dev.kolibrium.api.example.generated.VinylStoreClient
import dev.kolibrium.api.example.generated.vinylStoreApiTest
import dev.kolibrium.api.core.defaultHttpClient
// Direct client usage
suspend fun main() {
val client = VinylStoreClient(defaultHttpClient, "http://localhost:8080")
// Create a vinyl using DSL
val created = client.createVinyl {
artist = "Pink Floyd"
album = "The Dark Side of the Moon"
year = 1973
genre = "Progressive Rock"
price = 29.99
stock = 10
}
println("Created: ${created.body}")
// Get by ID
val vinyl = client.getVinyl(created.body.id)
println("Retrieved: ${vinyl.body}")
// List with filters
val rockVinyls = client.listVinyls(genre = "Progressive Rock")
println("Found ${rockVinyls.body.total} rock vinyls")
// Update
val updated = client.updateVinyl(created.body.id) {
price = 24.99
}
println("Updated price: ${updated.body.price}")
// Delete
client.deleteVinyl(created.body.id)
}
// Test usage
class VinylStoreTest {
@Test
fun `create and retrieve vinyl`() = vinylStoreApiTest {
val created = createVinyl {
artist = "Test Artist"
album = "Test Album"
year = 2024
genre = "Test"
price = 19.99
stock = 5
}
assertTrue(created.isSuccess)
val retrieved = getVinyl(created.body.id)
assertEquals(created.body, retrieved.body)
// Cleanup
deleteVinyl(created.body.id)
}
@Test
fun `test with setup and teardown`() = vinylStoreApiTest(
setUp = {
createVinyl {
artist = "Setup Artist"
album = "Setup Album"
year = 2024
genre = "Test"
price = 9.99
stock = 1
}.body.id
},
tearDown = { vinylId ->
deleteVinyl(vinylId)
}
) { vinylId ->
val response = getVinyl(vinylId)
assertEquals("Setup Artist", response.body.artist)
}
}
Best practices
Organize request models
Keep request and response models in the default models subpackage:
dev.kolibrium.api.example/
├── MyApiSpec.kt
└── models/
├── User.kt # User requests and response
├── Order.kt # Order requests and response
└── Product.kt # Product requests and response
Use descriptive names
// Good
data class GetUserByIdRequest(...)
data class ListActiveOrdersRequest(...)
data class CreateProductRequest(...)
// Avoid
data class UserRequest(...) // Ambiguous
data class GetRequest(...) // Too generic
Handle errors gracefully
val response = client.getUser(id)
when {
response.isSuccess -> handleUser(response.body)
response.isClientError -> handleNotFound()
response.isServerError -> handleServerError()
}
// Or use requireSuccess() for fail-fast
val user = client.getUser(id).requireSuccess().body
Use test harness for integration tests
@Test
fun `integration test`() = myApiTest(
setUp = { /* create test data */ },
tearDown = { /* cleanup */ }
) { testData ->
// Test with guaranteed cleanup
}