diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/README.md b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/README.md new file mode 100644 index 000000000..18832f7e6 --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/README.md @@ -0,0 +1,43 @@ +# Smithy Kotlin Service Codegen (SKSC) + +## Overview + +This project generate **service-side code** from Smithy models, producing **complete service stubs**, including routing, serialization/deserialization, authentication, and validation, so developers can focus entirely on implementing business logic. + +While Ktor is the default backend, the architecture is framework-agnostic, allowing future support for other server frameworks. + + +## Getting Started + +- Get an [introduction to Smithy](https://smithy.io/2.0/index.html) +- Follow [Smithy's quickstart guide](https://smithy.io/2.0/quickstart.html) +- See the [Guide](docs/GettingStarted.md) to learn how to use SKSC to generate service. +- See a [Summary of Service Support](docs/FEATURES.md) to learn which features are supported + + +## Development + +### Module Structure + +- `constraints` – directory that contains the constraints validation generator. + - `ConstraintsGenerator.kt` - main generator for constraints. + - `ConstraintUtilsGenerator` - generator for constraint utilities. + - For each constraint trait, there is a dedicated file. +- `ktor` – directory that stores all features generators specific to Ktor. + - `ktorStubGenerator.kt` – main generator for ktor framework service stub generator. +- `ServiceStubConfiguration.kt` – configuration file for the service stub generator. +- `ServiceStubGenerator.kt` – abstract service stub generator file. +- `ServiceTypes.kt` – file that includes service component symbols. +- `utils.kt` – utilities file. + +### Testing + +The **service code generation tests** are located in `tests/codegen/service-codegen-tests`. These end-to-end tests generate the service, launch the server, send HTTP requests to validate functionality, and then shut down the service once testing is complete. This process typically takes around 2 minutes. To run tests specifically for SKSC, use the following command: +```bash + ./gradlew :tests:codegen:service-codegen-tests:test +``` + +## Feedback + +You can provide feedback or report a bug by submitting an [issue](https://github.com/smithy-lang/smithy-kotlin/issues/new/choose). +This is the preferred mechanism to give feedback so that other users can engage in the conversation, +1 issues, etc. \ No newline at end of file diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/docs/FEATURES.md b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/docs/FEATURES.md new file mode 100644 index 000000000..03784e96b --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/docs/FEATURES.md @@ -0,0 +1,59 @@ +# Summary + +--- + +### Features Support + +| **Features** | **Description** | +|-----------------------------------|-------------------------------------------------------------------------------------------------| +| Service Framework | Abstracted service framework interface and base implementation with Ktor as the default backend | +| CBOR Protocol | Support for CBOR serialization / deserialization and CBOR protocol traits | +| Json Protocol | Support for Json serialization / deserialization and Json protocol traits | +| Routing | Per-operation routing generation with Ktor DSL; ties to handler and validation | +| Error Handler | Unified exception handling logic mapped to HTTP status codes and support for error trait | +| Authentication (bearer) | Bearer token authentication middleware with model-driven configuration | +| Authentication (SigV4 and SigV4A) | SigV4 and SigV4A authentication middleware with model-driven configuration | +| Logging | Structured logging setup | +| Constraints Checker | Validation logic generated from Smithy traits and invoked pre-handler | +| Unit Test | Covers serialization/deserialization, routing, validation, and integration tests | + +### Smithy Protocol Traits Support + +| **Traits** | **CBOR Protocol** | **Json Protocol** | +|--------------------------|-------------------|-------------------| +| http | Yes | Yes | +| httpError | Yes | Yes | +| httpHeader | Not supported | Yes | +| httpPrefixHeader | Not supported | Yes | +| httpLabel | Not supported | Yes | +| httpQuery | Not supported | Yes | +| httpQueryParams | Not supported | Yes | +| httpPayload | Not supported | Yes | +| jsonName | Not supported | Yes | +| timestampFormat | Not supported | Yes | +| httpChecksumRequired | Not supported | Not implemented yet | +| requestCompression | Not implemented yet | Not implemented yet | + +### Constraint Traits Support + +| **Traits** | **CBOR Protocol** | **Json Protocol** | +|-----------------|------------------------------|------------------------------| +| required | Yes | Yes | +| length | Yes | Yes | +| pattern | Yes | Yes | +| private | Yes (handled by Smithy) | Yes (handled by Smithy) | +| range | Yes | Yes | +| uniqueItems | Yes | Yes | +| idRef | Not implemented yet | Not implemented yet | + + +### Future Features + +| Feature | Description | +|-----------------------------------|-------------------------------------------------------------------------------------------------| +| Additional Protocols | XML, Ec2Query, AWSQuery protocols | +| Middleware / Interceptors | Cross-cutting logic support (e.g., metrics, headers, rate limiting) via middleware architecture | +| API Versioning | Built-in support for versioned APIs to maintain backward compatibility | +| gRPC / WebSocket Protocol Support | High-performance binary RPC and real-time bidirectional communication | +| Metrics & Tracing | Observability support with metrics, logs, and distributed tracing for debugging and monitoring | +| Caching Middleware | Per-route or global cache support to improve response times and reduce backend load | diff --git a/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/docs/GettingStarted.md b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/docs/GettingStarted.md new file mode 100644 index 000000000..1f6d8c9bd --- /dev/null +++ b/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/docs/GettingStarted.md @@ -0,0 +1,165 @@ +# Getting Started + +### Step 1: Build & Publish Codegen to Local Maven +First, in **this repository**, build and publish the code generator locally: +```bash + ./gradlew :codegen:smithy-kotlin-codegen:build + ./gradlew publishToMavenLocal +``` + +### Step 2: Create a New Kotlin Project +Now, create a **new Kotlin project** where you will use the Smithy Kotlin service code generator. You can find a full example demo project [here](../../../../../../../../../../../../examples/service-codegen) + +From this point forward, **all steps apply to the new Kotlin project** you just created. + + +### Step 3: Configure `build.gradle.kts` in the New Project + +```kotlin +plugins { + alias(libs.plugins.kotlin.jvm) + id("software.amazon.smithy.gradle.smithy-jar") version "1.3.0" // check for latest version + application +} + +repositories { + mavenLocal() + mavenCentral() +} + +val codegenVersion = "0.35.2-SNAPSHOT" +val smithyVersion = "1.60.2" + +dependencies { + smithyBuild("software.amazon.smithy.kotlin:smithy-kotlin-codegen:$codegenVersion") + implementation("software.amazon.smithy.kotlin:smithy-aws-kotlin-codegen:$codegenVersion") + implementation("software.amazon.smithy:smithy-model:$smithyVersion") + implementation("software.amazon.smithy:smithy-build:$smithyVersion") + implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion") + ... +} +``` + + +### Step 4: Create `smithy-build.json` in the New Project +This is an example of smithy-build.json. +```json +{ + "version": "1.0", + "outputDirectory": "build/generated-src", + "plugins": { + "kotlin-codegen": { + "service": "com.demo#DemoService", + "package": { + "name": "com.demo.server", + "version": "1.0.0" + }, + "build": { + "rootProject": true, + "generateServiceProject": true, + "optInAnnotations": [ + "aws.smithy.kotlin.runtime.InternalApi", + "kotlinx.serialization.ExperimentalSerializationApi" + ] + }, + "serviceStub": { + "framework": "ktor" + } + } + } +} +``` + +**Notes:** +- The most important fields are: + - **`outputDirectory`** — defines where the generated service code will be placed in your new project. + - **`service`** — must match your Smithy model’s `#`. + - **`serviceStub.framework`** — defines the server framework for generated code. Currently only `"ktor"` is supported. + +### Step 5: Define Your Smithy Model in the New Project + +Create a `model` directory and add your `.smithy` files. +Example `model/greeter.smithy`: + +```smithy +$version: "2.0" +namespace com.demo + +use aws.protocols#restJson1 +use smithy.api#httpBearerAuth + +@restJson1 +@httpBearerAuth +service DemoService { + version: "1.0.0" + operations: [ + SayHello + ] +} + +@http(method: "POST", uri: "/greet", code: 201) +operation SayHello { + input: SayHelloInput + output: SayHelloOutput + errors: [ + CustomError + ] +} + +@input +structure SayHelloInput { + @required + @length(min: 3, max: 10) + name: String + @httpHeader("X-User-ID") + id: Integer +} + +@output +structure SayHelloOutput { + greeting: String +} + +@error("server") +@httpError(500) +structure CustomError { + msg: String + @httpHeader("X-User-error") + err: String +} +``` + +### Step 6: Generate the Service in the New Project + +Run: +```bash + gradle build +``` + +If you want to clean previously generated code: +```bash + gradle clean +``` + +### Step 7: Run the Generated Service + +The generated service will be in the directory specified in `smithy-build.json` (`outputDirectory`). +You can start it by running: +```bash + gradle run +``` +By default, it listens on port **8080**. + +### Step 8: Adjust Service Configuration + +You can override runtime settings (such as port or HTTP engine) using command-line arguments: +```bash + gradle run --args="port 8000 engineFactory cio" +``` +You can find all available settings [here](https://github.com/smithy-lang/smithy-kotlin/blob/16bd523e2ccd6177dcc662466107189b013a818d/codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/ServiceStubGenerator.kt#L179C1-L186C38) + +--- + +## Notes +- **Business Logic**: Implement your own logic in the generated operation handler interfaces. +- **Configuration**: Adjust port, engine, auth, and other settings via `ServiceFrameworkConfig` or CLI args. diff --git a/examples/service-codegen/build.gradle.kts b/examples/service-codegen/build.gradle.kts new file mode 100644 index 000000000..28003a6fe --- /dev/null +++ b/examples/service-codegen/build.gradle.kts @@ -0,0 +1,54 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Kotlin application project to get you started. + * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.14.2/userguide/building_java_projects.html in the Gradle documentation. + */ + +plugins { + // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + alias(libs.plugins.kotlin.jvm) + + id("software.amazon.smithy.gradle.smithy-jar") version "1.3.0" + // Apply the application plugin to add support for building a CLI application in Java. + application +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() + mavenLocal() +} + +val codegenVersion = "0.35.2-SNAPSHOT" +val smithyVersion = "1.60.2" + +dependencies { + smithyBuild("software.amazon.smithy.kotlin:smithy-kotlin-codegen:$codegenVersion") + implementation("software.amazon.smithy.kotlin:smithy-aws-kotlin-codegen:$codegenVersion") + implementation("software.amazon.smithy:smithy-model:$smithyVersion") + implementation("software.amazon.smithy:smithy-build:$smithyVersion") + implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion") + // Use the Kotlin JUnit 5 integration. + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + + // Use the JUnit 5 integration. + testImplementation(libs.junit.jupiter.engine) + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + // This dependency is used by the application. + implementation(libs.guava) +} + +// Apply a specific Java toolchain to ease working on different environments. +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/examples/service-codegen/model/demo.smithy b/examples/service-codegen/model/demo.smithy new file mode 100644 index 000000000..b887aa4e2 --- /dev/null +++ b/examples/service-codegen/model/demo.smithy @@ -0,0 +1,49 @@ +// model/greeter.smithy +$version: "2.0" + +namespace com.demo + +use aws.protocols#restJson1 +use smithy.api#httpBearerAuth + +@restJson1 +@httpBearerAuth +service DemoService { + version: "1.0.0" + operations: [ + SayHello + ] +} + +@http(method: "POST", uri: "/greet", code: 201) +operation SayHello { + input: SayHelloInput + output: SayHelloOutput + errors: [ + CustomError + ] +} + +@input +structure SayHelloInput { + @required + @length(min: 3, max: 10) + name: String + + @httpHeader("X-User-ID") + id: Integer +} + +@output +structure SayHelloOutput { + greeting: String +} + +@error("server") +@httpError(500) +structure CustomError { + msg: String + + @httpHeader("X-User-error") + err: String +} diff --git a/examples/service-codegen/smithy-build.json b/examples/service-codegen/smithy-build.json new file mode 100644 index 000000000..7b1948b71 --- /dev/null +++ b/examples/service-codegen/smithy-build.json @@ -0,0 +1,30 @@ +{ + "version": "1.0", + "outputDirectory": "build/generated-src-test", + "plugins": { + "kotlin-codegen": { + "service": "com.demo#DemoService", + + "package": { + "name": "com.demo.server", + "version": "1.0.0" + }, + + "build": { + "rootProject": true, + "generateServiceProject": true, + "optInAnnotations": [ + "aws.smithy.kotlin.runtime.InternalApi", + "kotlinx.serialization.ExperimentalSerializationApi" + ] + }, + + "serviceStub": { + "framework": "ktor" + } + } + } +} + + +