@@ -45,20 +45,297 @@ Each spec gets its own Gradle task (`justworksGenerate<Name>`) and output direct
4545./gradlew justworksGenerateAll
4646```
4747
48- Generated sources are automatically wired into Kotlin source sets, so ` compileKotlin ` depends on code generation -- no extra configuration needed.
48+ Generated sources are automatically wired into Kotlin source sets, so ` compileKotlin ` depends on code generation -- no
49+ extra configuration needed.
4950
5051### Configuration options
5152
52- | Property | Required | Default | Description |
53- | ----------------| ----------| --------------------------| --------------------------------------|
54- | ` specFile ` | Yes | -- | Path to the OpenAPI spec (.yaml/.json) |
55- | ` packageName ` | Yes | -- | Base package for generated code |
56- | ` apiPackage ` | No | ` $packageName.api ` | Package for API client classes |
57- | ` modelPackage ` | No | ` $packageName.model ` | Package for model/data classes |
53+ | Property | Required | Default | Description |
54+ | ----------------| ----------| ----------------------| ----------------------------------------|
55+ | ` specFile ` | Yes | -- | Path to the OpenAPI spec (.yaml/.json) |
56+ | ` packageName ` | Yes | -- | Base package for generated code |
57+ | ` apiPackage ` | No | ` $packageName.api ` | Package for API client classes |
58+ | ` modelPackage ` | No | ` $packageName.model ` | Package for model/data classes |
59+
60+ ## Supported OpenAPI Features
61+
62+ ### Schema Types
63+
64+ | OpenAPI type | Format | Kotlin type |
65+ | ------------------------| -------------| ------------------|
66+ | ` string ` | * (default)* | ` String ` |
67+ | ` string ` | ` date ` | ` LocalDate ` |
68+ | ` string ` | ` date-time ` | ` Instant ` |
69+ | ` string ` | ` uuid ` | ` Uuid ` |
70+ | ` string ` | ` byte ` | ` ByteArray ` |
71+ | ` string ` | ` binary ` | ` ByteArray ` |
72+ | ` integer ` | ` int32 ` | ` Int ` |
73+ | ` integer ` | ` int64 ` | ` Long ` |
74+ | ` number ` | ` float ` | ` Float ` |
75+ | ` number ` | ` double ` | ` Double ` |
76+ | ` boolean ` | -- | ` Boolean ` |
77+ | ` array ` | -- | ` List<T> ` |
78+ | ` object ` | -- | data class |
79+ | ` additionalProperties ` | -- | ` Map<String, T> ` |
80+
81+ Other string formats (` email ` , ` uri ` , ` hostname ` , etc.) are kept as ` String ` .
82+
83+ ### Composition & Polymorphism
84+
85+ | Feature | Support | Generated Kotlin |
86+ | -------------------------------| ---------| ------------------------------------------------------------------------------------|
87+ | ` allOf ` | Full | Merged data class (properties from all schemas) |
88+ | ` oneOf ` with discriminator | Full | ` sealed interface ` + variant data classes with ` @JsonClassDiscriminator ` |
89+ | ` anyOf ` with discriminator | Full | Same as ` oneOf ` |
90+ | ` anyOf ` without discriminator | Partial | ` sealed interface ` + ` JsonContentPolymorphicSerializer ` (field-presence heuristic) |
91+ | Discriminator mapping | Full | ` @SerialName ` on variants |
92+
93+ A ` SerializersModule ` is auto-generated when discriminated polymorphic types are present.
94+
95+ ### Enums, Nullability, Defaults
96+
97+ - ** Enums** -- generated as ` enum class ` with ` @SerialName ` per constant. String and integer backing types supported.
98+ - ** Required properties** -- non-nullable constructor parameters.
99+ - ** Optional properties** -- nullable with ` = null ` default.
100+ - ** Default values** -- supported for primitives, dates, and enum references.
101+ - ** Inline schemas** -- auto-named from context (e.g. ` CreatePetRequest ` ) and deduplicated by structure.
102+
103+ ### Parameters & Request Bodies
104+
105+ | Feature | Status |
106+ | ---------------------------------| -------------------|
107+ | Path parameters | Supported |
108+ | Query parameters | Supported |
109+ | Header parameters | Supported |
110+ | Cookie parameters | Not yet supported |
111+ | ` application/json ` request body | Supported |
112+ | Form data / multipart | Not supported |
113+
114+ ### Not Supported
115+
116+ Callbacks, links, webhooks, XML content types, and OpenAPI vendor extensions (` x-* ` ) are not processed. The plugin logs
117+ warnings for callbacks and links found in a spec.
118+
119+ ## Generated Code Structure
120+
121+ The plugin produces two categories of output: ** shared types** (generated once) and ** per-spec types** (generated per
122+ registered spec).
123+
124+ ### Output Layout
125+
126+ ```
127+ build/generated/justworks/
128+ ├── shared/kotlin/
129+ │ └── com/avsystem/justworks/
130+ │ ├── ApiClientBase.kt # Abstract base class + helper extensions
131+ │ ├── HttpError.kt # HttpErrorType enum + HttpError data class
132+ │ └── HttpSuccess.kt # HttpSuccess<T> data class
133+ │
134+ └── specName/
135+ └── com/example/
136+ ├── model/
137+ │ ├── Pet.kt # @Serializable data class
138+ │ ├── PetStatus.kt # @Serializable enum class
139+ │ ├── Shape.kt # sealed interface (oneOf/anyOf)
140+ │ ├── Circle.kt # variant data class : Shape
141+ │ ├── UuidSerializer.kt # (if spec uses UUID fields)
142+ │ └── SerializersModule.kt # (if spec has polymorphic types)
143+ └── api/
144+ └── PetsApi.kt # Client class per OpenAPI tag
145+ ```
146+
147+ ### Model Package
148+
149+ - ** Data classes** -- one per named schema. Properties annotated with ` @SerialName ` , sorted required-first.
150+ - ** Enums** -- constants in ` UPPER_SNAKE_CASE ` with ` @SerialName ` for the wire value.
151+ - ** Sealed interfaces** -- for ` oneOf ` /` anyOf ` schemas. Variants are separate data classes implementing the interface.
152+ - ** SerializersModule** -- top-level ` val generatedSerializersModule ` registering all polymorphic hierarchies. Only
153+ generated when needed.
154+
155+ ### API Package
156+
157+ One client class per OpenAPI tag (e.g. ` pets ` tag -> ` PetsApi ` ). Untagged endpoints go to ` DefaultApi ` .
158+
159+ Each endpoint becomes a ` suspend ` function with ` context(Raise<HttpError>) ` that returns ` HttpSuccess<T> ` .
160+
161+ ### Gradle Tasks
162+
163+ | Task | Description |
164+ | ---------------------------| -------------------------------------------------------|
165+ | ` justworksSharedTypes ` | Generates shared types (once per build) |
166+ | ` justworksGenerate<Name> ` | Generates code for one spec (depends on shared types) |
167+ | ` justworksGenerateAll ` | Aggregate -- triggers all spec tasks |
168+
169+ ` compileKotlin ` depends on ` justworksGenerateAll ` , so generation runs automatically.
170+
171+ ## Customization
172+
173+ ### Package Overrides
174+
175+ Override the default ` api ` / ` model ` sub-packages per spec:
176+
177+ ``` kotlin
178+ justworks {
179+ specs {
180+ register(" petstore" ) {
181+ specFile = file(" api/petstore.yaml" )
182+ packageName = " com.example.petstore"
183+ apiPackage = " com.example.petstore.client" // default: packageName.api
184+ modelPackage = " com.example.petstore.dto" // default: packageName.model
185+ }
186+ }
187+ }
188+ ```
189+
190+ ### Ktor Engine
191+
192+ The generated ` HttpClient ` is created without an explicit engine, so Ktor uses whichever engine is on the classpath.
193+ Switch engines by changing your dependency:
194+
195+ ``` kotlin
196+ dependencies {
197+ // Pick one:
198+ implementation(" io.ktor:ktor-client-cio:3.1.1" ) // CIO (default, pure Kotlin)
199+ implementation(" io.ktor:ktor-client-okhttp:3.1.1" ) // OkHttp
200+ implementation(" io.ktor:ktor-client-apache:3.1.1" ) // Apache
201+ }
202+ ```
203+
204+ ### JSON Configuration
205+
206+ The internal ` Json ` instance uses default settings (` ignoreUnknownKeys = false ` , ` isLenient = false ` ). If you need a
207+ custom ` Json ` for use outside the client, configure it yourself and include the generated ` SerializersModule ` when your
208+ spec has polymorphic types:
209+
210+ ``` kotlin
211+ val json = Json {
212+ ignoreUnknownKeys = true
213+ serializersModule = com.example.petstore.model.generatedSerializersModule
214+ }
215+ ```
216+
217+ ## Generated Client Usage
218+
219+ After running code generation, the plugin produces type-safe Kotlin client classes.
220+ Here is how to use them.
221+
222+ ### Dependencies
223+
224+ Add the required runtime dependencies and enable the experimental context parameters compiler flag:
225+
226+ ``` kotlin
227+ kotlin {
228+ compilerOptions {
229+ freeCompilerArgs.add(" -Xcontext-parameters" )
230+ }
231+ }
232+
233+ dependencies {
234+ implementation(" io.ktor:ktor-client-core:3.1.1" )
235+ implementation(" io.ktor:ktor-client-cio:3.1.1" ) // or another engine (OkHttp, Apache, etc.)
236+ implementation(" io.ktor:ktor-client-content-negotiation:3.1.1" )
237+ implementation(" io.ktor:ktor-serialization-kotlinx-json:3.1.1" )
238+ implementation(" org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1" )
239+ implementation(" io.arrow-kt:arrow-core:2.2.1.1" )
240+ }
241+ ```
242+
243+ ### Creating the Client
244+
245+ Each generated client extends ` ApiClientBase ` and creates its own pre-configured ` HttpClient ` internally.
246+ You only need to provide the base URL and authentication credentials.
247+
248+ Class names are derived from OpenAPI tags as ` <Tag>Api ` (e.g., a ` pets ` tag produces ` PetsApi ` ). Untagged endpoints go
249+ to ` DefaultApi ` .
250+
251+ ``` kotlin
252+ val client = PetsApi (
253+ baseUrl = " https://api.example.com" ,
254+ token = { " your-bearer-token" },
255+ )
256+ ```
257+
258+ The ` token ` parameter is a ` () -> String ` lambda called on every request and sent as a ` Bearer ` token in the
259+ ` Authorization ` header. This lets you supply a provider that refreshes automatically:
260+
261+ ``` kotlin
262+ val client = PetsApi (
263+ baseUrl = " https://api.example.com" ,
264+ token = { tokenStore.getAccessToken() },
265+ )
266+ ```
267+
268+ The client implements ` Closeable ` -- call ` client.close() ` when done to release HTTP resources.
269+
270+ ### Making Requests
271+
272+ Every endpoint becomes a ` suspend ` function on the client. Functions use
273+ Arrow's [ Raise] ( https://arrow-kt.io/docs/typed-errors/ ) for structured error handling -- they require a
274+ ` context(Raise<HttpError>) ` and return ` HttpSuccess<T> ` on success:
275+
276+ ``` kotlin
277+ // Inside a Raise<HttpError> context (e.g., within either { ... })
278+ val result: HttpSuccess <List <Pet >> = client.listPets(limit = 10 )
279+ println (result.body) // the deserialized response body
280+ println (result.code) // the HTTP status code
281+ ```
282+
283+ Path, query, and header parameters map to function arguments. Optional parameters default to ` null ` :
284+
285+ ``` kotlin
286+ val result = client.findPets(status = " available" , limit = 20 )
287+ ```
288+
289+ ### Error Handling
290+
291+ Generated endpoints use [ Arrow's Raise] ( https://arrow-kt.io/docs/typed-errors/ ) -- errors are raised, not returned as
292+ ` Either ` . Use Arrow's ` either { ... } ` block to obtain an ` Either<HttpError, HttpSuccess<T>> ` :
293+
294+ ``` kotlin
295+ val result: Either <HttpError , HttpSuccess <Pet >> = either {
296+ client.getPet(petId = 123 )
297+ }
298+
299+ result.fold(
300+ ifLeft = { error ->
301+ when (error.type) {
302+ HttpErrorType .Client -> println (" Client error ${error.code} : ${error.message} " )
303+ HttpErrorType .Server -> println (" Server error ${error.code} : ${error.message} " )
304+ HttpErrorType .Redirect -> println (" Redirect ${error.code} " )
305+ HttpErrorType .Network -> println (" Connection failed: ${error.message} " )
306+ }
307+ },
308+ ifRight = { success ->
309+ println (" Found: ${success.body.name} " )
310+ }
311+ )
312+ ```
313+
314+ ` HttpError ` is a data class with the following fields:
315+
316+ | Field | Type | Description |
317+ | -----------| -----------------| ----------------------------------------------|
318+ | ` code ` | ` Int ` | HTTP status code (or ` 0 ` for network errors) |
319+ | ` message ` | ` String ` | Response body text or exception message |
320+ | ` type ` | ` HttpErrorType ` | Category of the error |
321+
322+ ` HttpErrorType ` categorizes errors:
323+
324+ | ` HttpErrorType ` value | Covered statuses / scenario |
325+ | -----------------------| ------------------------------------|
326+ | ` Client ` | HTTP 4xx client errors |
327+ | ` Server ` | HTTP 5xx server errors |
328+ | ` Redirect ` | HTTP 3xx redirect responses |
329+ | ` Network ` | I/O failures, timeouts, DNS issues |
330+
331+ Network errors (connection timeouts, DNS failures) are caught and reported as
332+ ` HttpError(code = 0, ..., type = HttpErrorType.Network) ` instead of propagating exceptions.
58333
59334## Publishing
60335
61- Releases are published to [ Maven Central] ( https://central.sonatype.com/ ) automatically when a version tag (` v* ` ) is pushed. The CD pipeline runs CI checks first, then publishes signed artifacts via the [ vanniktech maven-publish] ( https://github.com/vanniktech/gradle-maven-publish-plugin ) plugin.
336+ Releases are published to [ Maven Central] ( https://central.sonatype.com/ ) automatically when a version tag (` v* ` ) is
337+ pushed. The CD pipeline runs CI checks first, then publishes signed artifacts via
338+ the [ vanniktech maven-publish] ( https://github.com/vanniktech/gradle-maven-publish-plugin ) plugin.
62339
63340To trigger a release:
64341
0 commit comments