feat(core): security scheme extraction and auth-aware code generation#31
feat(core): security scheme extraction and auth-aware code generation#31halotukozak wants to merge 1 commit intorefactor/issue-modelfrom
Conversation
…tion Parse security schemes (Bearer, Basic, ApiKey) from OpenAPI specs and generate auth-aware ApiClientBase with corresponding constructor parameters and header/query injection. Wire spec files into JustworksSharedTypesTask for security scheme extraction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds OpenAPI security scheme awareness to the parsing + codegen pipeline so generated clients/base classes can inject auth (Bearer, Basic, ApiKey) based on the spec’s security configuration, and wires this through the Gradle plugin and tests.
Changes:
- Parse referenced security schemes from OpenAPI specs into
ApiSpec.securitySchemes. - Generate auth-aware
ApiClientBaseand propagate matching constructor parameters into generated clients. - Wire spec files into the shared-types Gradle task and add/adjust unit + functional tests for scheme extraction and auth codegen.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| plugin/src/main/kotlin/com/avsystem/justworks/gradle/JustworksSharedTypesTask.kt | Adds specFiles inputs and parses specs to feed security schemes into shared-types generation. |
| plugin/src/main/kotlin/com/avsystem/justworks/gradle/JustworksPlugin.kt | Wires each spec file into the shared-types task for scheme extraction. |
| plugin/src/functionalTest/kotlin/com/avsystem/justworks/gradle/JustworksPluginFunctionalTest.kt | Adds end-to-end assertions for auth-aware ApiClientBase generation with/without security. |
| core/src/test/resources/security-schemes-spec.yaml | New fixture spec containing Bearer/Basic/ApiKey schemes and an unused OAuth2 scheme. |
| core/src/test/kotlin/com/avsystem/justworks/core/parser/SpecParserSecurityTest.kt | New tests validating scheme extraction behavior. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ModelGeneratorTest.kt | Updates ApiSpec construction to include securitySchemes. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ModelGeneratorPolymorphicTest.kt | Updates ApiSpec construction to include securitySchemes. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/IntegrationTest.kt | Updates ApiClientBaseGenerator.generate(...) calls to pass spec.securitySchemes. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ClientGeneratorTest.kt | Adds/updates tests for security-aware client constructors. |
| core/src/test/kotlin/com/avsystem/justworks/core/gen/ApiClientBaseGeneratorTest.kt | Adds/updates tests for auth params and applyAuth body generation. |
| core/src/main/kotlin/com/avsystem/justworks/core/parser/SpecParser.kt | Extracts referenced security schemes into ApiSpec. |
| core/src/main/kotlin/com/avsystem/justworks/core/model/ApiSpec.kt | Introduces SecurityScheme model + ApiKeyLocation and adds securitySchemes to ApiSpec. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/Names.kt | Adds BASE64_CLASS for Basic auth header generation. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/CodeGenerator.kt | Updates shared-types generation to accept specs and pass combined schemes to ApiClientBaseGenerator. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/ClientGenerator.kt | Generates client constructors based on spec.securitySchemes and forwards params to ApiClientBase. |
| core/src/main/kotlin/com/avsystem/justworks/core/gen/ApiClientBaseGenerator.kt | Generates auth-aware constructor params/properties and applyAuth logic for Bearer/Basic/ApiKey. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }.toList() | ||
|
|
||
| context(_: ComponentSchemaIdentity, _: ComponentSchemas) | ||
| context (_: ComponentSchemaIdentity, _: ComponentSchemas) |
There was a problem hiding this comment.
Formatting is inconsistent with the rest of this file: context (_: ...) includes a space after context, while other declarations use context(_:). With ktlint enabled this may be flagged; please align to the existing style (no space).
| context (_: ComponentSchemaIdentity, _: ComponentSchemas) | |
| context(_: ComponentSchemaIdentity, _: ComponentSchemas) |
| if (headerSchemes.isNotEmpty()) { | ||
| builder.beginControlFlow("%M", HEADERS_FUN) | ||
| for (scheme in headerSchemes) { | ||
| when (scheme) { | ||
| is SecurityScheme.Bearer -> { | ||
| val isSingleBearer = | ||
| securitySchemes.size == 1 && securitySchemes.first() is SecurityScheme.Bearer | ||
|
|
||
| val paramName = if (isSingleBearer) TOKEN else "${scheme.name.toCamelCase()}Token" | ||
| builder.addStatement( | ||
| "append(%T.Authorization, %P)", | ||
| HTTP_HEADERS, | ||
| CodeBlock.of("Bearer \${$paramName()}"), | ||
| ) | ||
| } | ||
|
|
||
| is SecurityScheme.Basic -> { | ||
| val usernameParam = "${scheme.name.toCamelCase()}Username" | ||
| val passwordParam = "${scheme.name.toCamelCase()}Password" | ||
| builder.addStatement( | ||
| "append(%T.Authorization, %P)", | ||
| HTTP_HEADERS, | ||
| CodeBlock.of( | ||
| "Basic \${%T.getEncoder().encodeToString(\"${'$'}{$usernameParam()}:${'$'}{$passwordParam()}\".toByteArray())}", | ||
| BASE64_CLASS, | ||
| ), | ||
| ) |
There was a problem hiding this comment.
When multiple Bearer/Basic schemes are present, this implementation will append(HttpHeaders.Authorization, ...) multiple times, producing multiple Authorization headers on a single request. Many servers reject or mis-handle that header; consider enforcing at most one Authorization-based scheme, or generating a single chosen header based on security requirements rather than applying all schemes unconditionally.
| val securitySchemes = specs.flatMap { it.securitySchemes } | ||
|
|
||
| val files = ApiResponseGenerator.generate() + ApiClientBaseGenerator.generate(securitySchemes) |
There was a problem hiding this comment.
generateSharedTypes builds ApiClientBase from the combined securitySchemes of all specs, but ClientGenerator builds each client constructor from its own spec’s schemes. In a multi-spec project where scheme sets differ, generated clients won’t match the shared ApiClientBase constructor and compilation will fail. Consider keeping ApiClientBase constructor stable (e.g., defaults/optional providers), generating ApiClientBase per spec instead of as a shared type, or ensuring both shared types and clients are generated from the same unified scheme set.
| val securitySchemes = specs.flatMap { it.securitySchemes } | |
| val files = ApiResponseGenerator.generate() + ApiClientBaseGenerator.generate(securitySchemes) | |
| val files = ApiResponseGenerator.generate() + ApiClientBaseGenerator.generate() |
| import kotlin.apply | ||
| import kotlin.collections.map |
There was a problem hiding this comment.
These imports appear unused (apply and map are already available without explicit imports). With ktlint enabled, unused imports can fail the build; please remove them.
| import kotlin.apply | |
| import kotlin.collections.map |
| * ```kotlin | ||
| * when (val result = SpecParser.parse(file)) { | ||
| * is ParseResult.Success -> result.apiSpec | ||
| * is ParseResult.Failure -> handleErrors(result.error) | ||
| * is ParseResult.Failure -> handleErrors(result.errors) | ||
| * } |
There was a problem hiding this comment.
KDoc example refers to result.errors, but ParseResult.Failure exposes error (singular). Update the snippet to match the actual API so callers don’t copy/paste broken code.
Summary
ApiClientBasewith constructor parameters and header/query injectionJustworksSharedTypesTaskfor security scheme extractionClientGeneratorDepends on: #30
Test plan
🤖 Generated with Claude Code