Skip to content

Parser enhancements: anyOf, inline schemas, defaults#7

Open
halotukozak wants to merge 11 commits intofeat/pluginfrom
feat/parser-enhancements
Open

Parser enhancements: anyOf, inline schemas, defaults#7
halotukozak wants to merge 11 commits intofeat/pluginfrom
feat/parser-enhancements

Conversation

@halotukozak
Copy link
Member

Summary

  • anyOf support — parsed and generated identically to oneOf (sealed hierarchies)
  • Inline schema detection — with structural deduplication via InlineSchemaDeduplicator
  • Default parameter values — extracted from OpenAPI spec, constructor params ordered (required first)
  • Date/time typeskotlin.time.Instant and kotlinx.datetime.LocalDate mapping
  • Wrapper pattern unwrapping — oneOf schemas with single-property wrappers collapsed
  • Primitive type aliases — schemas with only primitive constraints generate typealias
  • allOf reference resolution — properly resolves $ref inside allOf compositions
  • Kotlin keyword escaping — backtick-escapes reserved words in generated identifiers
  • HttpError refactor — flat data class with HttpErrorType enum
  • Conditional SerializersModule — only wired when polymorphic types are present
  • Removed TypeRef.Unknown — dead code cleanup
  • ktlint fixes across codebase

Stacked on feat/plugin (#5).

Test plan

  • All existing unit tests pass
  • ktlintCheck passes
  • Review parser changes in SpecParser.kt
  • Verify generated code for anyOf schemas

🤖 Generated with Claude Code

…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>
Copilot AI review requested due to automatic review settings March 11, 2026 20:21
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
import com.avsystem.justworks.core.gen.toPascalCase

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +90
// Check if this is a nested inline (contains dot)
val isNested = name.contains(".")

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// Check if this is a nested inline (contains dot)
val isNested = name.contains(".")

Copilot uses AI. Check for mistakes.
code.addStatement(
"append(%T.Authorization, %P)",
HTTP_HEADERS,
CodeBlock.of($$"Bearer ${'$'}{$tokenProvider}"),
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
CodeBlock.of($$"Bearer ${'$'}{$tokenProvider}"),
"Bearer \${'$'}tokenProvider",

Copilot uses AI. Check for mistakes.
Comment on lines +287 to +295
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)
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 *.

Copilot uses AI. Check for mistakes.
Comment on lines +338 to +348
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(
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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(

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +165
// 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))
}

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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)))
}

Copilot uses AI. Check for mistakes.
Comment on lines +470 to +494
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,
)
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +37
// 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:") }
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
halotukozak and others added 5 commits March 12, 2026 10:44
…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
Copilot AI review requested due to automatic review settings March 12, 2026 12:16
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}"),
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
CodeBlock.of($$"Bearer ${'$'}{$tokenProvider}"),
CodeBlock.of("\"Bearer ${'$'}tokenProvider\""),

Copilot uses AI. Check for mistakes.
Comment on lines +338 to +352
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: ",
)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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,
)

Copilot uses AI. Check for mistakes.
Comment on lines +287 to +291
val deserializationStrategy = ClassName("kotlinx.serialization", "DeserializationStrategy")
.parameterizedBy(com.squareup.kotlinpoet.STAR)

val selectFun = FunSpec
.builder("selectDeserializer")
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +21
* register("petstore") {
* specFile = file("api/petstore.yaml")
* packageName = "com.example.petstore"
* }
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +23
* register("petstore") {
* specFile = file("api/petstore.yaml")
* packageName = "com.example.petstore"
* }
* register("payments") {
* specFile = file("api/payments.yaml")
* packageName = "com.example.payments"
* }
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +120
justworks {
specs {
register("main") {
specFile = file("api/petstore.yaml")
packageName = "com.example"
$extraDsl
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
errors.add("[JUSTWORKS] Spec is missing required 'info' section")
}

// Missing paths is a warning logged but not an error
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// Missing paths is a warning logged but not an error
// Missing paths is treated as a non-blocking warning, not an error

Copilot uses AI. Check for mistakes.
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
Copilot AI review requested due to automatic review settings March 12, 2026 13:04
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +120 to +145
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("}")
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +327 to +332
builder.addStatement(
"%S·in·element.%M -> %T.serializer()",
uniqueField,
JSON_OBJECT_EXT,
variantClassName,
)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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...").

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +121
justworks {
specs {
register("main") {
specFile = file("api/petstore.yaml")
packageName = "com.example"
$extraDsl
}
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
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>
@halotukozak halotukozak changed the base branch from master to feat/plugin March 12, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants