Skip to content

Commit e46a91a

Browse files
committed
feat(plugin): implement client generation pipeline for AWS Custom SDK Build plugin
Complete implementation of the client generation functionality: ## Core Features - **SmithyBuildConfigurator**: Creates Smithy build configurations with operation filtering - **GenerateCustomClientsTask**: Orchestrates the complete generation process - **DependencyManager**: Automatic protocol-specific dependency resolution - **Comprehensive Validation**: Configuration validation with detailed error messages - **Usage Examples**: Automatic generation of usage examples and documentation ## Implementation Details - **Smithy Integration**: Uses awsSmithyKotlinIncludeOperations transformer for filtering - **JSON Configuration**: Dynamic smithy-build.json generation with proper projections - **Service Model Discovery**: Flexible model file discovery with fallback approaches - **Protocol Dependencies**: Automatic dependency resolution (JSON, XML, Query, REST, CBOR) - **Output Management**: Structured output with examples and README generation ## Generated Artifacts - **Custom Service Clients**: Filtered clients with only selected operations - **Usage Examples**: Complete Kotlin examples showing client usage - **README Documentation**: Comprehensive documentation with service summaries - **Dependency Configuration**: Automatic protocol-specific dependencies ## Task Features - **Configuration Validation**: Validates services and operations before generation - **Validation Summary**: Detailed logging of configuration and validation results - **Error Handling**: Graceful handling of missing models and invalid configurations - **Progress Reporting**: Comprehensive logging throughout the generation process ## Testing - **Comprehensive Test Suite**: 6 test scenarios covering all major functionality - **Mock Service Models**: Graceful handling when service models are not available - **Validation Testing**: Tests for both valid and invalid configurations - **Output Verification**: Verification of generated files and directory structure ## Usage Example 🤖 Assisted by Amazon Q Developer
1 parent 55aed5b commit e46a91a

File tree

5 files changed

+866
-64
lines changed

5 files changed

+866
-64
lines changed

aws-custom-sdk-build-plugin/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ dependencies {
2525
// Kotlin reflection for constants registry
2626
implementation(kotlin("reflect", version = "2.1.0"))
2727

28+
// JSON processing for Smithy build configuration
29+
implementation("com.fasterxml.jackson.core:jackson-databind:2.18.2")
30+
2831
// Smithy dependencies for model processing - use versions matching parent project
2932
implementation("software.amazon.smithy:smithy-model:1.60.2")
3033
implementation("software.amazon.smithy:smithy-aws-traits:1.60.2")

aws-custom-sdk-build-plugin/src/main/kotlin/aws/sdk/kotlin/gradle/customsdk/DependencyManager.kt

Lines changed: 130 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,60 @@ import org.gradle.api.artifacts.Configuration
1717
class DependencyManager(private val project: Project) {
1818

1919
companion object {
20+
// Core AWS SDK dependencies that are always needed
21+
private val CORE_DEPENDENCIES = listOf(
22+
"aws.sdk.kotlin:aws-core",
23+
"aws.sdk.kotlin:aws-config",
24+
"aws.sdk.kotlin:aws-http",
25+
"aws.sdk.kotlin:aws-endpoint",
26+
"software.amazon.smithy.kotlin:http-client-engine-default",
27+
"software.amazon.smithy.kotlin:runtime-core"
28+
)
29+
2030
// Protocol to dependency mapping
2131
private val PROTOCOL_DEPENDENCIES = mapOf(
2232
"json" to listOf(
23-
"aws.sdk.kotlin:aws-json",
24-
"software.amazon.smithy.kotlin:http-client-engine-default"
33+
"software.amazon.smithy.kotlin:serde-json"
2534
),
2635
"xml" to listOf(
27-
"aws.sdk.kotlin:aws-xml",
28-
"software.amazon.smithy.kotlin:http-client-engine-default"
36+
"software.amazon.smithy.kotlin:serde-xml"
2937
),
3038
"query" to listOf(
31-
"aws.sdk.kotlin:aws-query",
32-
"software.amazon.smithy.kotlin:http-client-engine-default"
39+
"software.amazon.smithy.kotlin:serde-form-url"
3340
),
3441
"rest" to listOf(
35-
"aws.sdk.kotlin:aws-core",
36-
"software.amazon.smithy.kotlin:http-client-engine-default"
42+
"software.amazon.smithy.kotlin:http"
43+
),
44+
"cbor" to listOf(
45+
"software.amazon.smithy.kotlin:serde-cbor"
3746
)
3847
)
3948

40-
// Service to protocol mapping (will be populated from service models in later prompts)
49+
// Service to protocol mapping based on AWS service protocols
4150
private val SERVICE_PROTOCOLS = mapOf(
42-
"s3" to "rest",
43-
"dynamodb" to "json",
44-
"ec2" to "query",
45-
"lambda" to "json",
46-
"sns" to "query",
47-
"sqs" to "query"
48-
// TODO: Complete mapping will be loaded from service models
51+
"s3" to setOf("rest", "xml"),
52+
"dynamodb" to setOf("json"),
53+
"ec2" to setOf("query"),
54+
"lambda" to setOf("json"),
55+
"sns" to setOf("query"),
56+
"sqs" to setOf("query"),
57+
"iam" to setOf("query"),
58+
"cloudformation" to setOf("query"),
59+
"rds" to setOf("query"),
60+
"apigateway" to setOf("rest", "json"),
61+
"cloudwatch" to setOf("query"),
62+
"sts" to setOf("query"),
63+
"secretsmanager" to setOf("json"),
64+
"ssm" to setOf("json"),
65+
"kinesis" to setOf("json"),
66+
"firehose" to setOf("json"),
67+
"cognito-identity" to setOf("json"),
68+
"cognito-idp" to setOf("json")
4969
)
70+
71+
// AWS SDK version - should match the parent project
72+
private const val AWS_SDK_VERSION = "1.4.119-SNAPSHOT"
73+
private const val SMITHY_KOTLIN_VERSION = "0.34.21"
5074
}
5175

5276
/**
@@ -55,65 +79,123 @@ class DependencyManager(private val project: Project) {
5579
fun configureDependencies(selectedServices: Map<String, Set<String>>) {
5680
project.logger.info("Configuring dependencies for ${selectedServices.size} services...")
5781

82+
// Get or create the implementation configuration
83+
val implementationConfig = project.configurations.findByName("implementation")
84+
?: project.configurations.create("implementation")
85+
86+
// Add core dependencies
87+
addCoreDependencies(implementationConfig)
88+
89+
// Add protocol-specific dependencies based on selected services
5890
val requiredProtocols = determineRequiredProtocols(selectedServices.keys)
59-
val dependencies = resolveDependencies(requiredProtocols)
91+
addProtocolDependencies(implementationConfig, requiredProtocols)
6092

61-
addDependenciesToProject(dependencies)
93+
// Add service-specific dependencies if needed
94+
addServiceSpecificDependencies(implementationConfig, selectedServices.keys)
6295

63-
project.logger.info("Added ${dependencies.size} dependencies for protocols: ${requiredProtocols.joinToString(", ")}")
96+
project.logger.info("Dependencies configured successfully")
97+
}
98+
99+
/**
100+
* Add core AWS SDK dependencies that are always required
101+
*/
102+
private fun addCoreDependencies(config: Configuration) {
103+
project.logger.info("Adding core AWS SDK dependencies...")
104+
105+
CORE_DEPENDENCIES.forEach { dependency ->
106+
val dependencyNotation = if (dependency.startsWith("aws.sdk.kotlin:")) {
107+
"$dependency:$AWS_SDK_VERSION"
108+
} else {
109+
"$dependency:$SMITHY_KOTLIN_VERSION"
110+
}
111+
112+
project.dependencies.add(config.name, dependencyNotation)
113+
project.logger.debug("Added core dependency: $dependencyNotation")
114+
}
64115
}
65116

66117
/**
67-
* Determine which protocols are needed based on selected services
118+
* Determine which protocols are required based on the selected services
68119
*/
69120
private fun determineRequiredProtocols(serviceNames: Set<String>): Set<String> {
70-
val protocols = mutableSetOf<String>()
121+
val requiredProtocols = mutableSetOf<String>()
71122

72123
serviceNames.forEach { serviceName ->
73-
val protocol = SERVICE_PROTOCOLS[serviceName]
74-
if (protocol != null) {
75-
protocols.add(protocol)
124+
val protocols = SERVICE_PROTOCOLS[serviceName.lowercase()]
125+
if (protocols != null) {
126+
requiredProtocols.addAll(protocols)
127+
project.logger.debug("Service $serviceName requires protocols: ${protocols.joinToString(", ")}")
76128
} else {
77-
project.logger.warn("Unknown protocol for service: $serviceName, defaulting to 'rest'")
78-
protocols.add("rest")
129+
// Default to JSON and REST for unknown services
130+
requiredProtocols.addAll(setOf("json", "rest"))
131+
project.logger.warn("Unknown service $serviceName, using default protocols: json, rest")
79132
}
80133
}
81134

82-
return protocols
135+
project.logger.info("Required protocols: ${requiredProtocols.joinToString(", ")}")
136+
return requiredProtocols
83137
}
84138

85139
/**
86-
* Resolve dependencies for the required protocols
140+
* Add protocol-specific dependencies
87141
*/
88-
private fun resolveDependencies(protocols: Set<String>): Set<String> {
89-
val dependencies = mutableSetOf<String>()
142+
private fun addProtocolDependencies(config: Configuration, requiredProtocols: Set<String>) {
143+
project.logger.info("Adding protocol-specific dependencies...")
90144

91-
protocols.forEach { protocol ->
92-
val protocolDeps = PROTOCOL_DEPENDENCIES[protocol]
93-
if (protocolDeps != null) {
94-
dependencies.addAll(protocolDeps)
145+
requiredProtocols.forEach { protocol ->
146+
val dependencies = PROTOCOL_DEPENDENCIES[protocol]
147+
if (dependencies != null) {
148+
dependencies.forEach { dependency ->
149+
val dependencyNotation = "$dependency:$SMITHY_KOTLIN_VERSION"
150+
project.dependencies.add(config.name, dependencyNotation)
151+
project.logger.debug("Added protocol dependency for $protocol: $dependencyNotation")
152+
}
95153
}
96154
}
155+
}
156+
157+
/**
158+
* Add service-specific dependencies if needed
159+
*/
160+
private fun addServiceSpecificDependencies(config: Configuration, serviceNames: Set<String>) {
161+
project.logger.info("Checking for service-specific dependencies...")
97162

98-
// Always add core AWS runtime dependencies
99-
dependencies.addAll(listOf(
100-
"aws.sdk.kotlin:aws-core",
101-
"aws.sdk.kotlin:aws-config",
102-
"software.amazon.smithy.kotlin:smithy-client"
103-
))
104-
105-
return dependencies
163+
serviceNames.forEach { serviceName ->
164+
when (serviceName.lowercase()) {
165+
"s3" -> {
166+
// S3 might need additional dependencies for multipart uploads, etc.
167+
project.logger.debug("S3 service detected - using standard dependencies")
168+
}
169+
"dynamodb" -> {
170+
// DynamoDB might need additional dependencies for enhanced client features
171+
project.logger.debug("DynamoDB service detected - using standard dependencies")
172+
}
173+
"lambda" -> {
174+
// Lambda might need additional dependencies for async invocation
175+
project.logger.debug("Lambda service detected - using standard dependencies")
176+
}
177+
else -> {
178+
project.logger.debug("Service $serviceName - using standard dependencies")
179+
}
180+
}
181+
}
106182
}
107183

108184
/**
109-
* Add dependencies to the project configuration
185+
* Get a summary of configured dependencies
110186
*/
111-
private fun addDependenciesToProject(dependencies: Set<String>) {
112-
val implementation = project.configurations.getByName("implementation")
187+
fun getDependencySummary(selectedServices: Map<String, Set<String>>): String {
188+
val summary = StringBuilder()
189+
summary.append("Dependency Configuration Summary:\n")
190+
summary.append("- Core Dependencies: ${CORE_DEPENDENCIES.size}\n")
113191

114-
dependencies.forEach { dependency ->
115-
project.logger.debug("Adding dependency: $dependency")
116-
project.dependencies.add("implementation", dependency)
192+
val requiredProtocols = determineRequiredProtocols(selectedServices.keys)
193+
val protocolDependencies = requiredProtocols.flatMap { protocol ->
194+
PROTOCOL_DEPENDENCIES[protocol] ?: emptyList()
117195
}
196+
summary.append("- Protocol Dependencies: ${protocolDependencies.size} (${requiredProtocols.joinToString(", ")})\n")
197+
summary.append("- Services: ${selectedServices.keys.joinToString(", ")}\n")
198+
199+
return summary.toString()
118200
}
119201
}

0 commit comments

Comments
 (0)