Parser enhancements: anyOf, inline schemas, defaults#7
Parser enhancements: anyOf, inline schemas, defaults#7halotukozak wants to merge 11 commits intofeat/pluginfrom
Conversation
…mprovements - Add anyOf support in SpecParser and ModelGenerator (identical to oneOf handling) - Add inline schema detection, deduplication, and generation - Add default parameter values with constructor ordering - Add date/time type support (kotlin.time.Instant, kotlinx.datetime.LocalDate) - Add oneOf wrapper pattern detection and unwrapping - Add primitive-only type alias generation - Add allOf reference resolution in schemaToTypeRef - Add Kotlin keyword escaping and visited-set guard in parser - Refactor HttpError to data class with HttpErrorType enum - Wire SerializersModule conditionally based on polymorphic types - Remove unused TypeRef.Unknown variant - Fix ktlint violations across codebase Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR expands the OpenAPI parsing + Kotlin code generation capabilities in core (anyOf, inline schemas, defaults, improved ref handling) and introduces a new Gradle plugin module to run generation from consumer builds (including functional tests via TestKit).
Changes:
- Add Gradle plugin module with multi-spec DSL, shared-types generation task, and functional tests.
- Enhance parser/model/generators: anyOf + wrapper unwrapping, inline schema detection/dedup, default values + constructor ordering, and additional type mappings.
- Add/expand unit test coverage and test resources for new parsing/generation behaviors.
Reviewed changes
Copilot reviewed 42 out of 43 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle.kts | Includes the new plugin module in the build. |
| .gitignore | Ignores Gradle/Kotlin/IDE build artifacts. |
| plugin/build.gradle.kts | Configures Gradle plugin publishing + functional test source set. |
| plugin/src/main/kotlin/com/avsystem/justworks/gradle/JustworksExtension.kt | Adds the justworks { specs { ... } } multi-spec DSL container. |
| plugin/src/main/kotlin/com/avsystem/justworks/gradle/JustworksSpecConfiguration.kt | Defines per-spec configuration (spec file + packages). |
| plugin/src/main/kotlin/com/avsystem/justworks/gradle/JustworksGenerateTask.kt | Adds cacheable generation task (parse spec + generate model/client). |
| plugin/src/main/kotlin/com/avsystem/justworks/gradle/JustworksSharedTypesTask.kt | Adds shared-types generation task used by all specs. |
| plugin/src/main/kotlin/com/avsystem/justworks/gradle/JustworksPlugin.kt | Wires DSL -> tasks -> source sets -> compileKotlin dependency. |
| plugin/src/functionalTest/kotlin/com/avsystem/justworks/gradle/JustworksPluginFunctionalTest.kt | TestKit functional coverage for plugin behavior and multi-spec support. |
| core/build.gradle.kts | Adds Arrow dependency and compiler/ktlint configuration. |
| core/src/main/kotlin/com/avsystem/justworks/core/Generator.kt | Keeps generator entry point (placeholder). |
| core/src/main/kotlin/com/avsystem/justworks/core/model/ApiSpec.kt | Updates core domain model (schemas, refs, discriminator, defaults). |
| core/src/main/kotlin/com/avsystem/justworks/core/model/TypeRef.kt | Adds inline type refs and expands primitive set (date/time). |
| core/src/main/kotlin/com/avsystem/justworks/core/parser/ParseResult.kt | Adds structured parse result with warnings/errors. |
| core/src/main/kotlin/com/avsystem/justworks/core/parser/SpecParser.kt | Major parser enhancements: anyOf, inline schemas, wrapper unwrapping, ref preservation. |
| core/src/main/kotlin/com/avsystem/justworks/core/parser/SpecValidator.kt | Adds basic spec validation for required sections + unsupported constructs. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/ApiResponseGenerator.kt | Generates shared HTTP result/error types. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/ClientGenerator.kt | Generates Ktor-based API clients (+ conditional serializersModule wiring). |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/InlineSchemaDeduplicator.kt | Structural deduplication for inline schemas. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/ModelGenerator.kt | Generates models (sealed hierarchies, defaults, type aliases, inline models, serializers). |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/NameUtils.kt | Adds consistent identifier transformations + Kotlin keyword escaping. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/Names.kt | Centralizes KotlinPoet ClassName/MemberName constants. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/SerializersModuleGenerator.kt | Generates polymorphic SerializersModule when needed. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/TypeMapping.kt | Maps parsed TypeRef to KotlinPoet types (incl. date/time). |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ApiResponseGeneratorTest.kt | Unit tests for shared types generation. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ClientGeneratorTest.kt | Unit tests for client generation behavior. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/InlineSchemaDedupTest.kt | Unit tests for inline schema deduplication. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ModelGeneratorPolymorphicTest.kt | Unit tests for polymorphic model generation (oneOf/anyOf/allOf). |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ModelGeneratorTest.kt | Unit tests for defaults, ordering, keyword escaping, aliases, etc. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/NameUtilsTest.kt | Unit tests for naming/escaping utilities. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/SerializersModuleGeneratorTest.kt | Unit tests for serializers module generation conditions/output. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/TypeMappingTest.kt | Unit tests for type mapping, incl. date/time mappings. |
| core/src/test/kotlin/com/avsystem/justworks/core/parser/SpecParserPolymorphicTest.kt | Parser tests for oneOf/allOf/discriminator. |
| core/src/test/kotlin/com/avsystem/justworks/core/parser/SpecParserTest.kt | Parser tests for refs, anyOf, mixed combinators, errors, etc. |
| core/src/test/kotlin/com/avsystem/justworks/core/parser/SpecValidatorTest.kt | Validator tests for missing required sections. |
| core/src/test/resources/anyof-spec.yaml | Test spec for anyOf scenarios. |
| core/src/test/resources/anyof-valid-spec.yaml | Test spec for anyOf + discriminator. |
| core/src/test/resources/invalid-spec.yaml | Invalid spec used for error reporting tests. |
| core/src/test/resources/mixed-combinator-spec.yaml | Spec testing mixed anyOf + oneOf rejection. |
| core/src/test/resources/petstore-v2.json | Swagger v2 conversion test resource. |
| core/src/test/resources/petstore.yaml | OpenAPI v3 test resource. |
| core/src/test/resources/polymorphic-spec.yaml | Polymorphic schema test resource (oneOf/allOf). |
| core/src/test/resources/refs-spec.yaml | Ref-resolution test resource. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| package com.avsystem.justworks.core.parser | ||
|
|
||
| import com.avsystem.justworks.core.gen.operationNameFromPath | ||
| import com.avsystem.justworks.core.gen.toPascalCase |
There was a problem hiding this comment.
import com.avsystem.justworks.core.gen.toPascalCase appears unused because this file also defines its own private fun String.toPascalCase() at the bottom. This will likely fail ktlint for unused imports; remove the import or delete the local extension and use the shared toPascalCase implementation to avoid duplication.
| import com.avsystem.justworks.core.gen.toPascalCase |
| // Check if this is a nested inline (contains dot) | ||
| val isNested = name.contains(".") | ||
|
|
There was a problem hiding this comment.
isNested is computed but never used. Please remove it (and the associated comment) or use it to drive the nested-inline handling logic so the intent is reflected in code.
| // Check if this is a nested inline (contains dot) | |
| val isNested = name.contains(".") |
| code.addStatement( | ||
| "append(%T.Authorization, %P)", | ||
| HTTP_HEADERS, | ||
| CodeBlock.of($$"Bearer ${'$'}{$tokenProvider}"), |
There was a problem hiding this comment.
CodeBlock.of($$"Bearer …") is not valid Kotlin syntax and will prevent the generator itself from compiling. Build the string literal using KotlinPoet placeholders (e.g., %P/CodeBlock.of("Bearer \${'$'}tokenProvider") or equivalent) rather than a $$-prefixed string.
| CodeBlock.of($$"Bearer ${'$'}{$tokenProvider}"), | |
| "Bearer \${'$'}tokenProvider", |
| val deserializationStrategy = ClassName("kotlinx.serialization", "DeserializationStrategy") | ||
| .parameterizedBy(com.squareup.kotlinpoet.STAR) | ||
|
|
||
| val selectFun = FunSpec | ||
| .builder("selectDeserializer") | ||
| .addModifiers(KModifier.OVERRIDE) | ||
| .addParameter(ParameterSpec.builder("element", JSON_ELEMENT).build()) | ||
| .returns(deserializationStrategy) | ||
| .addCode(selectDeserializerBody) |
There was a problem hiding this comment.
selectDeserializer overrides JsonContentPolymorphicSerializer.selectDeserializer, which returns DeserializationStrategy<out T>. Returning DeserializationStrategy<*> here is likely not a valid override (it’s a wider type than DeserializationStrategy<out <sealedType>>) and can fail compilation of generated code. Use a covariant return like DeserializationStrategy<out <sealedType>> (e.g., producer wildcard of the sealed interface) instead of *.
| builder.addStatement( | ||
| "else -> TODO(%S)", | ||
| "No unique discriminating fields found for variant '$variantName' of anyOf '$parentName' - manual selectDeserializer required", | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Add a final else clause with SerializationException (only if all variants had unique fields) | ||
| val allHaveUniqueFields = uniqueFieldsPerVariant.all { it.second != null } | ||
| if (allHaveUniqueFields) { | ||
| builder.addStatement( |
There was a problem hiding this comment.
buildSelectDeserializerBody emits else -> TODO(...) inside the loop, which can produce multiple else branches and/or place else before other branches. In Kotlin when { ... }, else must appear exactly once and must be last; the current generation can yield uncompilable serializers (e.g., when 2+ variants lack unique fields). Consider generating a single fallback else at the end (TODO/throw), or short-circuit to a stub implementation when any variant lacks a unique field.
| builder.addStatement( | |
| "else -> TODO(%S)", | |
| "No unique discriminating fields found for variant '$variantName' of anyOf '$parentName' - manual selectDeserializer required", | |
| ) | |
| } | |
| } | |
| // Add a final else clause with SerializationException (only if all variants had unique fields) | |
| val allHaveUniqueFields = uniqueFieldsPerVariant.all { it.second != null } | |
| if (allHaveUniqueFields) { | |
| builder.addStatement( | |
| } | |
| } | |
| // Add a final else clause: | |
| // - if any variant lacks a unique field, generate a TODO to signal manual implementation is required | |
| // - otherwise, throw SerializationException for unknown variants | |
| val anyMissingUniqueField = uniqueFieldsPerVariant.any { it.second == null } | |
| if (anyMissingUniqueField) { | |
| builder.addStatement( | |
| "else -> TODO(%S)", | |
| "No unique discriminating fields found for some variants of anyOf '$parentName' - manual selectDeserializer required", | |
| ) | |
| } else { | |
| builder.addStatement( |
| // For primitive-only schemas, generate type alias | ||
| // TODO: Extend SchemaModel to include primitiveType field for primitive-only schemas | ||
| // For now, defaulting to String as the most common case | ||
| listOf(generateTypeAlias(schema, STRING)) | ||
| } | ||
|
|
There was a problem hiding this comment.
Primitive-only component schemas are currently always emitted as typealias <Name> = String (generateTypeAlias(schema, STRING)). This will generate incorrect types for primitive-only schemas that are integer, number, boolean, date, etc. Either capture the underlying primitive kind in SchemaModel during parsing (or reuse schemaToTypeRef for the schema itself) and map it via TypeMapping, or avoid generating a typealias unless the primitive type is known.
| // For primitive-only schemas, generate type alias | |
| // TODO: Extend SchemaModel to include primitiveType field for primitive-only schemas | |
| // For now, defaulting to String as the most common case | |
| listOf(generateTypeAlias(schema, STRING)) | |
| } | |
| // For primitive-only schemas, generate type alias using the resolved primitive type | |
| // The underlying primitive kind is obtained via schemaToTypeRef and mapped to a Kotlin type | |
| listOf(generateTypeAlias(schema, schemaToTypeRef(schema, schemasById).toTypeName(modelPackage))) | |
| } |
| resolvedSchema.properties.orEmpty().forEach { (propName, propSchema) -> | ||
| mergedProperties[propName] = | ||
| PropertyModel( | ||
| name = propName, | ||
| type = schemaToTypeRef(propSchema), | ||
| description = propSchema.description, | ||
| nullable = propName !in mergedRequired, | ||
| defaultValue = propSchema.default, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Add top-level properties (inline alongside allOf) | ||
| val topRequired = schema.required.orEmpty().toSet() | ||
| mergedRequired.addAll(topRequired) | ||
|
|
||
| schema.properties.orEmpty().forEach { (propName, propSchema) -> | ||
| mergedProperties[propName] = | ||
| PropertyModel( | ||
| name = propName, | ||
| type = schemaToTypeRef(propSchema), | ||
| description = propSchema.description, | ||
| nullable = propName !in mergedRequired, | ||
| defaultValue = propSchema.default, | ||
| ) |
There was a problem hiding this comment.
extractAllOfProperties calls schemaToTypeRef(propSchema) directly. Unlike the non-allOf path, this does not detect inline object schemas, so nested inline objects inside allOf compositions will be collapsed to TypeRef.Reference(schema.title ?: "Unknown") (and may reference a non-existent model type). Mirror the inline-object handling used in extractSchemaModel (use isInlineObjectSchema + createInlineTypeRef with a context name) when extracting allOf properties.
| // Missing paths is a warning logged but not an error | ||
| if (openApi.paths.isNullOrEmpty()) { | ||
| errors.add("[JUSTWORKS] Warning: Spec has no paths defined") | ||
| } | ||
|
|
||
| // Detect unsupported constructs for v1 | ||
| openApi.paths?.values?.forEach { pathItem -> | ||
| pathItem.readOperationsMap()?.values?.forEach { operation -> | ||
| if (!operation.callbacks.isNullOrEmpty()) { | ||
| errors.add("[JUSTWORKS] Warning: Callbacks are not supported in v1 and will be ignored") | ||
| return@forEach | ||
| } | ||
| } | ||
| } | ||
|
|
||
| openApi.components?.links?.let { links -> | ||
| if (links.isNotEmpty()) { | ||
| errors.add("[JUSTWORKS] Warning: Links are not supported in v1 and will be ignored") | ||
| } | ||
| } | ||
|
|
||
| // Only return actual errors (non-warnings) as blockers | ||
| return errors.filter { !it.contains("Warning:") } | ||
| } |
There was a problem hiding this comment.
The comments describe warnings being logged, but SpecValidator.validate currently only accumulates strings and then filters out any entry containing Warning: (so warnings are neither returned nor logged). Either actually log these warnings (via a logger passed in / returned separately) or adjust the comments/structure (e.g., return a structured result with errors + warnings).
…ator files - Replace TODO comments in ModelGenerator.kt with explicit DEFERRED(Phase N) annotations - Add descriptive comment block to Names.kt explaining its purpose as centralized KotlinPoet constants repository - ktlintCheck and :core:test pass with zero violations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ktlint { version.set("1.8.0") } matching core/build.gradle.kts
- Consistent ktlint version across core and plugin modules
- Extract duplicate task name computation to specTaskName() helper in JustworksPlugin - Add KDoc to generate() TaskAction methods in JustworksGenerateTask and JustworksSharedTypesTask - JustworksExtension, JustworksSpecConfiguration already fully documented — no changes needed - All 5 plugin source files pass ktlint with zero violations
…receivers - Switch from ContextParameter (new API) to contextReceivers (old API) in ClientGenerator to avoid import of arrow.core.raise.context package where 'context' is a keyword when -Xcontext-parameters is active - Change RAISE_FUN from MemberName to const String so raise() is emitted as a plain call resolved via context receiver, without generating a broken import path - Update test consumer builds to use -Xcontext-receivers matching generated context(Raise<HttpError>) syntax - Update ClientGeneratorTest to assert contextReceiverTypes (not contextParameters) since we now use the old context receivers API
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 50 out of 51 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| code.addStatement( | ||
| "append(%T.Authorization, %P)", | ||
| HTTP_HEADERS, | ||
| CodeBlock.of($$"Bearer ${'$'}{$tokenProvider}"), |
There was a problem hiding this comment.
This uses CodeBlock.of($$"..."), which is not valid Kotlin syntax and will fail to compile the generator itself. It also references tokenProvider as if it were in scope (it isn't here) and should instead emit a Kotlin string template for the generated client code (e.g., "Bearer $tokenProvider") using KotlinPoet escaping (%P/%S) so the $tokenProvider is evaluated in the generated code at runtime.
| CodeBlock.of($$"Bearer ${'$'}{$tokenProvider}"), | |
| CodeBlock.of("\"Bearer ${'$'}tokenProvider\""), |
| builder.addStatement( | ||
| "else -> TODO(%S)", | ||
| "No unique discriminating fields found for variant '$variantName' of anyOf '$parentName' - manual selectDeserializer required", | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Add a final else clause with SerializationException (only if all variants had unique fields) | ||
| val allHaveUniqueFields = uniqueFieldsPerVariant.all { it.second != null } | ||
| if (allHaveUniqueFields) { | ||
| builder.addStatement( | ||
| "else -> throw %T(%S + element)", | ||
| SERIALIZATION_EXCEPTION, | ||
| "Unknown $parentName variant: ", | ||
| ) |
There was a problem hiding this comment.
This generates an else -> TODO(...) branch inside the loop. In Kotlin, else must be the last branch of a when, so as soon as one variant has no unique field the generated serializer won't compile (and any subsequent branches are unreachable). Consider emitting a single final else that TODOs/throws, or switching to an if/else chain that checks each unique field condition.
| builder.addStatement( | |
| "else -> TODO(%S)", | |
| "No unique discriminating fields found for variant '$variantName' of anyOf '$parentName' - manual selectDeserializer required", | |
| ) | |
| } | |
| } | |
| // Add a final else clause with SerializationException (only if all variants had unique fields) | |
| val allHaveUniqueFields = uniqueFieldsPerVariant.all { it.second != null } | |
| if (allHaveUniqueFields) { | |
| builder.addStatement( | |
| "else -> throw %T(%S + element)", | |
| SERIALIZATION_EXCEPTION, | |
| "Unknown $parentName variant: ", | |
| ) | |
| } | |
| } | |
| // Determine whether all variants have unique fields | |
| val allHaveUniqueFields = uniqueFieldsPerVariant.all { it.second != null } | |
| if (allHaveUniqueFields) { | |
| // All variants have unique fields: unknown variant is an error | |
| builder.addStatement( | |
| "else -> throw %T(%S + element)", | |
| SERIALIZATION_EXCEPTION, | |
| "Unknown $parentName variant: ", | |
| ) | |
| } else { | |
| // At least one variant has no unique field: require manual selectDeserializer | |
| val variantsWithoutUniqueFields = uniqueFieldsPerVariant | |
| .filter { it.second == null } | |
| .map { it.first } | |
| val message = if (variantsWithoutUniqueFields.isNotEmpty()) { | |
| "No unique discriminating fields found for variant(s) " + | |
| variantsWithoutUniqueFields.joinToString(prefix = "'", postfix = "'") + | |
| " of anyOf '$parentName' - manual selectDeserializer required" | |
| } else { | |
| "No unique discriminating fields found for some variants of anyOf '$parentName' - manual selectDeserializer required" | |
| } | |
| builder.addStatement( | |
| "else -> TODO(%S)", | |
| message, | |
| ) |
| val deserializationStrategy = ClassName("kotlinx.serialization", "DeserializationStrategy") | ||
| .parameterizedBy(com.squareup.kotlinpoet.STAR) | ||
|
|
||
| val selectFun = FunSpec | ||
| .builder("selectDeserializer") |
There was a problem hiding this comment.
selectDeserializer in JsonContentPolymorphicSerializer<T> must return DeserializationStrategy<out T>. Using DeserializationStrategy<*> here is not a covariant override and will not match the superclass signature, so the generated serializer is likely to fail compilation. Return DeserializationStrategy<out <sealed type>> (e.g., DeserializationStrategy<out Payment>) instead of a star-projection.
| * register("petstore") { | ||
| * specFile = file("api/petstore.yaml") | ||
| * packageName = "com.example.petstore" | ||
| * } |
There was a problem hiding this comment.
The KDoc example shows specFile = file(...) and packageName = "...", but specFile is a RegularFileProperty and packageName is a Property<String>, so this DSL will not compile for plugin consumers as written. Either update the docs/examples/tests to use .set(...), or add Kotlin-friendly var setters (e.g., var packageName: String) that delegate to the underlying Property objects.
| * register("petstore") { | ||
| * specFile = file("api/petstore.yaml") | ||
| * packageName = "com.example.petstore" | ||
| * } | ||
| * register("payments") { | ||
| * specFile = file("api/payments.yaml") | ||
| * packageName = "com.example.payments" | ||
| * } |
There was a problem hiding this comment.
Same issue as in JustworksSpecConfiguration KDoc: these examples use assignment (specFile = ..., packageName = ...) but the backing types are RegularFileProperty/Property<String>, so the DSL shown here will not compile unless you add dedicated var setters. Update the example to .set(...) (or adjust the extension/configuration API to support assignment) so users can copy/paste it successfully.
| justworks { | ||
| specs { | ||
| register("main") { | ||
| specFile = file("api/petstore.yaml") | ||
| packageName = "com.example" | ||
| $extraDsl | ||
| } |
There was a problem hiding this comment.
The generated build.gradle.kts in these functional tests configures the DSL using specFile = file(...) and packageName = "...", but specFile/packageName are declared as RegularFileProperty/Property<String> (vals) in the extension types, so this build script will not compile when executed by TestKit. Update the test build script strings to use .set(...), or change the DSL types to expose assignable var setters that delegate to the underlying properties.
| errors.add("[JUSTWORKS] Spec is missing required 'info' section") | ||
| } | ||
|
|
||
| // Missing paths is a warning logged but not an error |
There was a problem hiding this comment.
This comment says missing paths is a "warning logged" but nothing is actually logged here; the warning is just added to errors and then filtered out. Either log it (and the other warnings) or adjust the comment to reflect the current behavior to avoid misleading future changes.
| // Missing paths is a warning logged but not an error | |
| // Missing paths is treated as a non-blocking warning, not an error |
Planning documentation is generated locally and should not be tracked in git.
- selectDeserializer return type: STAR -> WildcardTypeName.producerOf(sealedClassName) - buildSelectDeserializerBody: move else branch outside loop (single trailing else) - extractAllOfProperties: detect inline schemas in allOf sub-schema properties - extractAllOfProperties: detect inline schemas in top-level properties block
- Remove unused isNested variable from ModelGenerator - Remove duplicate toPascalCase import from SpecParser (shadowed by private extension) - Fix SpecValidator comment to accurately describe warning-filter behavior - Update plugin KDoc examples to use .set() syntax for Gradle property types
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 50 out of 51 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| private fun buildClientInitializer(hasPolymorphicTypes: Boolean): CodeBlock { | ||
| val builder = CodeBlock | ||
| .builder() | ||
| .add("%T·{\n", HTTP_CLIENT) | ||
| .indent() | ||
| .add("install(%T)·{\n", CONTENT_NEGOTIATION) | ||
| .indent() | ||
|
|
||
| if (hasPolymorphicTypes) { | ||
| val generatedSerializersModule = MemberName(modelPackage, "generatedSerializersModule") | ||
| builder | ||
| .add("%M(%T·{\n", JSON_FUN, JSON_CLASS) | ||
| .indent() | ||
| .add("serializersModule·=·%M\n", generatedSerializersModule) | ||
| .unindent() | ||
| .add("})\n") | ||
| } else { | ||
| builder.add("%M()\n", JSON_FUN) | ||
| } | ||
|
|
||
| builder | ||
| .unindent() | ||
| .add("}\n") | ||
| .add("expectSuccess·=·false\n") | ||
| .unindent() | ||
| .add("}") |
There was a problem hiding this comment.
The format strings here contain the literal middle-dot character · (e.g., "%T·{\n", "expectSuccess·=·false"). KotlinPoet will emit these characters into the generated Kotlin source, producing invalid Kotlin syntax. Replace these with normal spaces so the generated client code compiles.
| builder.addStatement( | ||
| "%S·in·element.%M -> %T.serializer()", | ||
| uniqueField, | ||
| JSON_OBJECT_EXT, | ||
| variantClassName, | ||
| ) |
There was a problem hiding this comment.
This generated when branch format string contains the literal · character between tokens ("%S·in·element..."). KotlinPoet will output that verbatim, making the generated serializer code invalid Kotlin. Use regular spaces in the emitted code (e.g., "%S in element...").
| justworks { | ||
| specs { | ||
| register("main") { | ||
| specFile = file("api/petstore.yaml") | ||
| packageName = "com.example" | ||
| $extraDsl | ||
| } | ||
| } |
There was a problem hiding this comment.
The Gradle build script strings in this test assign to specFile/packageName using =. In the plugin DSL these are RegularFileProperty/Property<String> vals, so consumer build scripts should call .set(...) instead; as written, these TestKit projects will fail to compile their build.gradle.kts (and the same pattern repeats throughout this file). Update the test build script snippets to use .set(...) (and adjust any other property assignments similarly).
Configure vanniktech maven-publish plugin with Central Portal, GPG signing, and GitHub Actions workflow triggered on version tags. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
InlineSchemaDeduplicatorkotlin.time.Instantandkotlinx.datetime.LocalDatemapping$refinside allOf compositionsHttpErrorTypeenumTypeRef.Unknown— dead code cleanupStacked on
feat/plugin(#5).Test plan
SpecParser.kt🤖 Generated with Claude Code