Skip to content

feat(core): security scheme extraction and auth-aware code generation#31

Open
halotukozak wants to merge 1 commit intorefactor/issue-modelfrom
feat/security-schemes
Open

feat(core): security scheme extraction and auth-aware code generation#31
halotukozak wants to merge 1 commit intorefactor/issue-modelfrom
feat/security-schemes

Conversation

@halotukozak
Copy link
Member

Summary

  • Parse security schemes (Bearer, Basic, ApiKey) from OpenAPI specs
  • Generate auth-aware ApiClientBase with constructor parameters and header/query injection
  • Wire spec files into JustworksSharedTypesTask for security scheme extraction
  • Security-aware constructor generation in ClientGenerator

Depends on: #30

Test plan

  • All 250 tests pass
  • SpecParserSecurityTest covers scheme extraction from spec
  • ApiClientBaseGeneratorTest covers auth code generation
  • ClientGeneratorTest covers security-aware constructors
  • JustworksPluginFunctionalTest covers end-to-end plugin flow

🤖 Generated with Claude Code

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

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

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 ApiClientBase and 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)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

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

Suggested change
context (_: ComponentSchemaIdentity, _: ComponentSchemas)
context(_: ComponentSchemaIdentity, _: ComponentSchemas)

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

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +33
val securitySchemes = specs.flatMap { it.securitySchemes }

val files = ApiResponseGenerator.generate() + ApiClientBaseGenerator.generate(securitySchemes)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
val securitySchemes = specs.flatMap { it.securitySchemes }
val files = ApiResponseGenerator.generate() + ApiClientBaseGenerator.generate(securitySchemes)
val files = ApiResponseGenerator.generate() + ApiClientBaseGenerator.generate()

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +41
import kotlin.apply
import kotlin.collections.map
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
import kotlin.apply
import kotlin.collections.map

Copilot uses AI. Check for mistakes.
Comment on lines 49 to 53
* ```kotlin
* when (val result = SpecParser.parse(file)) {
* is ParseResult.Success -> result.apiSpec
* is ParseResult.Failure -> handleErrors(result.error)
* is ParseResult.Failure -> handleErrors(result.errors)
* }
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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