From swagger-android
Rules for generating Android Kotlin models from Swagger/OpenAPI data.
How this skill is triggered — by the user, by Claude, or both
Slash command
/swagger-android:swagger-kotlin-conventionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Rules for generating Android Kotlin models from Swagger/OpenAPI data.
Rules for generating Android Kotlin models from Swagger/OpenAPI data.
This skill is used by the swagger-model-generator agent.
| Layer | Rule | Example |
|---|---|---|
| Data model | Ktor prefix + Swagger name | KtorProduct, KtorCreateProductRequest |
| Domain model | Swagger name, no changes | Product |
| Data enum | Ktor prefix + Swagger name | KtorProductStatus |
| Domain enum | Swagger name, no changes | ProductStatus |
| File type | Name | Location |
|---|---|---|
| Data response model | KtorProduct.kt | feature/{name}/data/model/ |
| Data request model | KtorCreateProductRequest.kt | feature/{name}/data/model/ |
| Data enum | KtorProductStatus.kt | feature/{name}/data/model/ |
| Domain model | Product.kt | feature/{name}/domain/model/ |
| Domain enum | ProductStatus.kt | feature/{name}/domain/model/ |
| Mapper | KtorProductMapper.kt | feature/{name}/data/model/ |
| Enum mapper | KtorProductStatusMapper.kt | feature/{name}/data/model/ |
{base_package}.feature.{feature_name}.data.model
{base_package}.feature.{feature_name}.domain.model
Example: com.company.app.feature.catalog.data.model
package com.company.app.feature.catalog.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Product entity from the catalog
*/
@Serializable
data class KtorProduct(
/** Unique product identifier */
@SerialName("id") val id: Int,
/** Display name of the product */
@SerialName("product_name") val productName: String,
/** Optional product description */
@SerialName("description") val description: String? = null,
@SerialName("price") val price: Double,
/** Product category */
@SerialName("category") val category: KtorCategory,
/** Search tags */
@SerialName("tags") val tags: List<String> = emptyList(),
@SerialName("images") val images: List<KtorProductImage> = emptyList(),
/** Creation timestamp in ISO 8601 */
@SerialName("created_at") val createdAt: String,
/** Current product status */
@SerialName("status") val status: KtorProductStatus
)
Rules:
@Serializable on the class@SerialName("jsonKey") on EVERY field — even when jsonKey == kotlinNamenullable=true → Type? = nullnullable=false + List type → List<Type> = emptyList()nullable=false + non-list → Type (no default)description != null → /** description */ comment before fielddescription == null → no commentdescription != null → /** description */ KDoc before classpackage com.company.app.feature.catalog.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class KtorCreateProductRequest(
@SerialName("name") val name: String,
@SerialName("price") val price: Double,
@SerialName("category_id") val categoryId: Int
)
Rules (differences from response):
package com.company.app.feature.catalog.domain.model
/**
* Product entity from the catalog
*/
data class Product(
/** Unique product identifier */
val id: Int,
/** Display name of the product */
val productName: String,
/** Optional product description */
val description: String?,
val price: Double,
/** Product category */
val category: Category,
/** Search tags */
val tags: List<String>,
val images: List<ProductImage>,
/** Creation timestamp in ISO 8601 */
val createdAt: String,
/** Current product status */
val status: ProductStatus
)
Rules:
@Serializable, no @SerialName — pure domain classKtor prefix)nullable=true → Type? (no = null default)List<Type> (no = emptyList() default)package com.company.app.feature.catalog.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Possible product statuses
*/
@Serializable
enum class KtorProductStatus {
@SerialName("active") ACTIVE,
@SerialName("archived") ARCHIVED,
@SerialName("draft") DRAFT,
UNKNOWN
}
Rules:
@Serializable on the class@SerialName("originalJsonValue") on every value EXCEPT UNKNOWNUNKNOWN is always the last value — no @SerialNamedescription != nullpackage com.company.app.feature.catalog.domain.model
enum class ProductStatus {
ACTIVE,
ARCHIVED,
DRAFT,
UNKNOWN
}
Rules:
UNKNOWN always presentpackage com.company.app.feature.catalog.data.model
fun KtorProduct.toDomain(): Product = Product(
id = id,
productName = productName,
description = description,
price = price,
category = category.toDomain(),
tags = tags,
images = images.map { it.toDomain() },
createdAt = createdAt,
status = status.toDomain()
)
Rules:
fun KtorModel.toDomain(): Model = Model(.toDomain() — no variationsfield = fieldref: types → field = field.toDomain()List<ref:> → field = field.map { it.toDomain() }enum: → field = field.toDomain()ref: → field = field?.toDomain()List<ref:> → field = field?.map { it.toDomain() }KtorProductMapper.ktdata/model/ folder as the data modelpackage com.company.app.feature.catalog.data.model
fun KtorProductStatus.toDomain(): ProductStatus = when (this) {
KtorProductStatus.ACTIVE -> ProductStatus.ACTIVE
KtorProductStatus.ARCHIVED -> ProductStatus.ARCHIVED
KtorProductStatus.DRAFT -> ProductStatus.DRAFT
KtorProductStatus.UNKNOWN -> ProductStatus.UNKNOWN
}
Rules:
when expression with exhaustive mappingUNKNOWN)KtorProductStatusMapper.ktThe script outputs kotlinType values with these formats. Apply prefix rules when generating code:
Script kotlinType | Data layer | Domain layer |
|---|---|---|
"Int" | Int | Int |
"Long" | Long | Long |
"Double" | Double | Double |
"Float" | Float | Float |
"Boolean" | Boolean | Boolean |
"String" | String | String |
"ref:Product" | KtorProduct | Product |
"enum:ProductStatus" | KtorProductStatus | ProductStatus |
"List<String>" | List<String> | List<String> |
"List<ref:Product>" | List<KtorProduct> | List<Product> |
"List<enum:Status>" | List<KtorStatus> | List<Status> |
"Map<String, JsonElement>" | Map<String, JsonElement> | Map<String, Any?> |
"Map<String, String>" | Map<String, String> | Map<String, String> |
"JsonElement" | JsonElement | Any? |
"sealed:Name" | sealed class KtorName | sealed class Name |
"Any" | Any | Any |
See references/edge-cases.md for detailed guidance on:
For each model, verify before writing:
Data model:
@Serializable annotation on class@SerialName on every single field (no exceptions)Ktor prefix on all reference typeskotlinx.serialization.SerialName, kotlinx.serialization.Serializable= null default= emptyList() defaultDomain model:
Ktor prefix on any typeType? without default valueList<Type> without default valueMapper:
.toDomain().toDomain().map { it.toDomain() }Enum:
UNKNOWN value present in both Data and Domain@SerialName on all enum values except UNKNOWNnpx claudepluginhub gorban-dev/gor-dev-plugins --plugin swagger-androidImplements shared data models for Kotlin Multiplatform using kotlinx.serialization: immutable data classes, sealed UiState/Result hierarchies, pagination, and auth requests.
Teaches how to author a kubb.config.ts and choose @kubb/plugin-* packages for TypeScript generation from OpenAPI/Swagger specs. Activate when setting up Kubb, adding generators, or debugging codegen output.
Generates OpenAPI 3.0+ specs from existing API code, enhances with schemas/examples/errors, creates interactive docs/SDKs, and validates compliance.