docs: KDoc generation from OpenAPI descriptions + comprehensive usage guide#26
docs: KDoc generation from OpenAPI descriptions + comprehensive usage guide#26halotukozak wants to merge 3 commits intomasterfrom
Conversation
…changes - Add description field to Endpoint, valueDescriptions to EnumModel - Parser extracts operation.description and x-enum-descriptions - ModelGenerator adds KDoc to properties with descriptions - ModelGenerator adds per-constant KDoc from valueDescriptions - Tests verify KDoc presence/absence for properties and enum constants Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Generate KDoc from endpoint summary and description - Add @param tags for parameters with descriptions - Add @return tag for non-Unit return types - No KDoc generated when all descriptions are absent - Tests verify all KDoc generation scenarios Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Generated Client Usage section with dependencies, client creation, auth examples - Document Bearer, API Key, Basic, and no-auth constructor patterns - Add HttpResult/Either error handling guide with HttpError subtype table - Add serialization setup with SerializersModule for polymorphic types - Add multi-spec configuration example Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR enhances documentation output from the OpenAPI-driven generators by producing richer KDoc on generated models and client endpoints, and adds a new README usage guide intended to help consumers integrate generated clients.
Changes:
- Add KDoc generation for model properties (from OpenAPI property descriptions) and enum entries (from
x-enum-descriptions). - Add KDoc generation for client endpoint methods using operation summary/description plus
@param/@returntags. - Extend the parsed/spec intermediate model (
Endpoint.description,EnumModel.valueDescriptions) and update parsing + tests; expand README with a usage guide.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ModelGeneratorTest.kt | Adds unit tests for property KDoc and enum-entry KDoc generation. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ClientGeneratorTest.kt | Adds unit tests for endpoint KDoc generation (summary/description/params/return). |
| core/src/main/kotlin/com/avsystem/justworks/core/parser/SpecParser.kt | Extracts operation.description and parses x-enum-descriptions into EnumModel.valueDescriptions. |
| core/src/main/kotlin/com/avsystem/justworks/core/model/ApiSpec.kt | Extends intermediate models to carry endpoint descriptions and enum value descriptions. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/ModelGenerator.kt | Emits KDoc on generated data-class properties and enum constants. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/ClientGenerator.kt | Emits KDoc on generated client endpoint methods including @param and @return. |
| README.md | Adds a comprehensive usage guide for consuming generated clients. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| is List<*> -> enumValues.zip(ext.map { it.toString() }).toMap() | ||
| is Map<*, *> -> ext.entries.associate { (k, v) -> k.toString() to v.toString() } |
There was a problem hiding this comment.
The x-enum-descriptions handling for list values silently zips by index and will truncate on length mismatch (and also converts nulls to the literal string "null"). This can easily mis-assign descriptions to enum values without any signal. Consider validating that the list size matches schema.enum (fail parsing or at least ignore the extension when sizes differ) and filtering out null description entries instead of toString()-ing them.
| is List<*> -> enumValues.zip(ext.map { it.toString() }).toMap() | |
| is Map<*, *> -> ext.entries.associate { (k, v) -> k.toString() to v.toString() } | |
| is List<*> -> { | |
| if (ext.size != enumValues.size) { | |
| emptyMap() | |
| } else { | |
| enumValues.indices | |
| .mapNotNull { idx -> | |
| val description = ext[idx] | |
| description?.toString()?.let { enumValues[idx] to it } | |
| } | |
| .toMap() | |
| } | |
| } | |
| is Map<*, *> -> ext.entries | |
| .mapNotNull { (k, v) -> | |
| v?.toString()?.let { k.toString() to it } | |
| } | |
| .toMap() |
| .builder(kotlinName, type) | ||
| .initializer(kotlinName) | ||
| .addAnnotation(AnnotationSpec.builder(SERIAL_NAME).addMember("%S", prop.name).build()) | ||
| .apply { if (prop.description != null) addKdoc("%L", prop.description) } |
There was a problem hiding this comment.
The property/enum KDoc is added whenever description != null, which will also emit empty KDoc blocks for "" (or whitespace-only) descriptions coming from specs. Consider using !description.isNullOrBlank() before calling addKdoc(...) to avoid generating meaningless KDoc in the output.
| .apply { if (prop.description != null) addKdoc("%L", prop.description) } | |
| .apply { prop.description?.takeIf { it.isNotBlank() }?.let { addKdoc("%L", it) } } |
| ### Making Requests | ||
|
|
||
| Every endpoint becomes a `suspend` function on the client. The return type is `HttpResult<E, T>`, where `E` is the error body type and `T` is the success body type: | ||
|
|
||
| ```kotlin | ||
| val result: HttpResult<JsonElement, List<Pet>> = client.listPets(limit = 10) | ||
| ``` | ||
|
|
||
| Path, query, and header parameters map to function arguments. Optional parameters default to `null`: | ||
|
|
||
| ```kotlin | ||
| val result = client.findPets(status = "available", limit = 20) | ||
| ``` | ||
|
|
||
| ### Error Handling | ||
|
|
||
| `HttpResult<E, T>` is a typealias for `Either<HttpError<E>, HttpSuccess<T>>` (using [Arrow](https://arrow-kt.io/)). | ||
| Every API call returns a result instead of throwing exceptions: | ||
|
|
||
| ```kotlin | ||
| when (val result = client.getPet(petId = 123)) { | ||
| is Either.Right -> { | ||
| val pet = result.value.body | ||
| println("Found: ${pet.name}") | ||
| } | ||
| is Either.Left -> when (val error = result.value) { | ||
| is HttpError.NotFound -> println("Pet not found") | ||
| is HttpError.Unauthorized -> println("Auth required") | ||
| is HttpError.Network -> println("Connection failed: ${error.cause}") | ||
| else -> println("Error ${error.statusCode}: ${error.body}") | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
This README section describes generated endpoints as returning HttpResult<E, T> (Arrow Either) and demonstrates pattern matching on Either.Left/Right, but the current generator produces suspend fun ...: HttpSuccess<T> with a context(Raise<HttpError>) receiver and HttpError is a data class (see ApiClientBaseGenerator/ApiResponseGenerator). The usage guide should be updated to match the actual generated API (how to call endpoints inside a raise context, and how errors are represented).
| ### Authentication | ||
|
|
||
| The generated constructor signature depends on the security schemes defined in your OpenAPI spec: | ||
|
|
||
| **Bearer Token** (single scheme): | ||
|
|
||
| ```kotlin | ||
| val client = PetstoreApi( | ||
| baseUrl = "https://api.example.com", | ||
| token = { "your-bearer-token" }, | ||
| ) | ||
| ``` | ||
|
|
||
| **API Key** (sent as header or query parameter based on the spec): | ||
|
|
||
| ```kotlin | ||
| val client = PetstoreApi( | ||
| baseUrl = "https://api.example.com", | ||
| myApiKey = { "your-api-key" }, | ||
| ) | ||
| ``` | ||
|
|
||
| **HTTP Basic**: | ||
|
|
||
| ```kotlin | ||
| val client = PetstoreApi( | ||
| baseUrl = "https://api.example.com", | ||
| myAuthUsername = { "user" }, | ||
| myAuthPassword = { "pass" }, | ||
| ) | ||
| ``` | ||
|
|
||
| **No Authentication** (spec has no security schemes): | ||
|
|
||
| ```kotlin | ||
| val client = PetstoreApi( | ||
| baseUrl = "https://api.example.com", | ||
| ) | ||
| ``` | ||
|
|
||
| When the spec defines multiple security schemes, the constructor includes a parameter for each one. | ||
|
|
There was a problem hiding this comment.
The authentication guidance here claims the generated constructor signature varies by OpenAPI security schemes (Bearer/API key/Basic/none) and that multiple schemes add multiple constructor params. In the current generator/runtime, the client constructor always includes a token: () -> String and applyAuth() always emits Authorization: Bearer ... (no API key/basic/no-auth support). Either implement the described auth behaviors or adjust this section to reflect the actual capabilities.
| implementation("io.ktor:ktor-client-content-negotiation:3.1.1") | ||
| implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.1") | ||
| implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") | ||
| implementation("io.arrow-kt:arrow-core:2.1.2") |
There was a problem hiding this comment.
The dependency versions in this snippet are inconsistent with the versions used elsewhere in the repo (e.g. functional tests and core module use io.arrow-kt:arrow-core:2.2.1.1). Consider updating the README sample to match the repo’s validated versions to reduce copy/paste breakage.
| implementation("io.arrow-kt:arrow-core:2.1.2") | |
| implementation("io.arrow-kt:arrow-core:2.2.1.1") |
| `HttpError` covers specific HTTP status codes as sealed subtypes: | ||
|
|
||
| | Subtype | Status | | ||
| |------------------------|--------| | ||
| | `BadRequest` | 400 | | ||
| | `Unauthorized` | 401 | | ||
| | `Forbidden` | 403 | | ||
| | `NotFound` | 404 | | ||
| | `MethodNotAllowed` | 405 | | ||
| | `Conflict` | 409 | | ||
| | `Gone` | 410 | | ||
| | `UnprocessableEntity` | 422 | | ||
| | `TooManyRequests` | 429 | | ||
| | `InternalServerError` | 500 | | ||
| | `BadGateway` | 502 | | ||
| | `ServiceUnavailable` | 503 | | ||
| | `Network` | -- | | ||
| | `Other` | any | | ||
|
|
||
| Network errors (connection timeouts, DNS failures) are caught and wrapped in `HttpError.Network` instead of propagating exceptions. |
There was a problem hiding this comment.
HttpError is documented here as having many sealed subtypes for specific status codes (BadRequest/Unauthorized/etc) and a HttpError.Network subtype, but the generated runtime currently defines HttpErrorType enum + HttpError(code, message, type) data class (no per-status sealed hierarchy). This table/description should be rewritten to match the actual error model or the runtime should be extended to provide the documented subtypes.
Summary
x-enum-descriptionsextension@paramtags,@returntagsEndpointmodel withdescriptionfield,EnumModelwithvalueDescriptionsSpecParserto extractoperation.descriptionandx-enum-descriptionsTest plan
ModelGeneratorTest— 4 new tests for property KDoc and enum constant KDocClientGeneratorTest— 5 new tests for endpoint KDoc with summary/@param/@return🤖 Generated with Claude Code