Skip to content

Service sdk comments #1394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: server-sdk-main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ import software.amazon.smithy.model.traits.HttpPayloadTrait
import software.amazon.smithy.model.traits.MediaTypeTrait
import software.amazon.smithy.protocol.traits.Rpcv2CborTrait

/**
* Enumeration of supported media types used by the generated service.
*
* These values define how payloads are encoded/decoded:
* - `CBOR` → Concise Binary Object Representation
* - `JSON` → JSON encoding
* - `PLAIN_TEXT` → Text/plain
* - `OCTET_STREAM` → Binary data
* - `ANY` → Fallback for arbitrary content types
*/
enum class MediaType(val value: String) {
CBOR("CBOR"),
JSON("JSON"),
Expand Down Expand Up @@ -57,6 +67,12 @@ enum class MediaType(val value: String) {
}
}

/**
* Enumeration of supported service frameworks for generated stubs.
*
* Currently only supports:
* - `KTOR`: Generates Ktor-based service stubs
*/
enum class ServiceFramework(val value: String) {
KTOR("ktor"),
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,29 @@ import software.amazon.smithy.kotlin.codegen.core.withBlock
import software.amazon.smithy.kotlin.codegen.core.withInlineBlock
import software.amazon.smithy.model.knowledge.TopDownIndex

/**
* Interface representing a generator that produces service stubs (boilerplate code) for a Smithy service.
*/
internal interface ServiceStubGenerator {
/**
* Render the stub code into the target files.
*/
fun render()
}

/**
* Abstract base class for generating service stubs.
*
* Provides a framework for generating common service artifacts such as:
* - Configuration (`ServiceFrameworkConfig.kt`)
* - Framework bootstrap (`ServiceFramework.kt`)
* - Plugins, utils, authentication, validators
* - Operation handlers and routing
* - Main launcher (`Main.kt`)
*
* Concrete subclasses must implement abstract methods for framework-specific
* code (e.g., Ktor).
*/
internal abstract class AbstractStubGenerator(
val ctx: GenerationContext,
val delegator: KotlinDelegator,
Expand All @@ -27,6 +46,10 @@ internal abstract class AbstractStubGenerator(

val pkgName = ctx.settings.pkg.name

/**
* Render all service stub files by invoking the component renderers.
* This acts as the main entrypoint for code generation.
*/
final override fun render() {
renderServiceFrameworkConfig()
renderServiceFramework()
Expand All @@ -39,7 +62,16 @@ internal abstract class AbstractStubGenerator(
renderMainFile()
}

/** Emits the `ServiceFrameworkConfig.kt` file. */
/**
* Generate the service configuration file (`ServiceFrameworkConfig.kt`).
*
* Defines enums for:
* - `LogLevel`: Logging verbosity levels
* - `ServiceEngine`: Available server engines (Netty, CIO, Jetty)
*
* Provides a singleton `ServiceFrameworkConfig` object that stores runtime
* settings such as port, engine, region, timeouts, and log level.
*/
protected fun renderServiceFrameworkConfig() {
delegator.useFileWriter("ServiceFrameworkConfig.kt", "${ctx.settings.pkg.name}.config") { writer ->
writer.withBlock("internal enum class LogLevel(val value: String) {", "}") {
Expand Down Expand Up @@ -143,7 +175,12 @@ internal abstract class AbstractStubGenerator(
}
}

/** Emits ServiceFramework.kt and other engine bootstrap code. */
/**
* Generate the service framework interface and bootstrap (`ServiceFramework.kt`).
*
* Declares a common `ServiceFramework` interface with lifecycle methods and
* delegates framework-specific implementation details to subclasses.
*/
protected fun renderServiceFramework() {
delegator.useFileWriter("ServiceFramework.kt", "${ctx.settings.pkg.name}.framework") { writer ->

Expand All @@ -157,27 +194,36 @@ internal abstract class AbstractStubGenerator(
}
}

/** Render the specific server framework implementation (e.g., Ktor). */
protected abstract fun renderServerFrameworkImplementation(writer: KotlinWriter)

/** Emits content-type guards, error handler plugins, … */
/** Generate service plugins such as content-type guards, error handlers, etc. */
protected abstract fun renderPlugins()

/** Emits utils. */
/** Generate supporting utility classes and functions. */
protected abstract fun renderUtils()

/** Auth interfaces & installers (bearer, IAM, …). */
/** Generate authentication module interfaces and installers (e.g., bearer auth, SigV4, SigV4A). */
protected abstract fun renderAuthModule()

/** Request-level Smithy constraint validators. */
/** Generate request-level constraint validators for Smithy model constraints. */
protected abstract fun renderConstraintValidators()

/** One handler file per Smithy operation. */
/** Generate a request handler for each Smithy operation. */
protected abstract fun renderPerOperationHandlers()

/** Route table that maps operations runtime endpoints. */
/** Generate the route table that maps Smithy operations to runtime endpoints. */
protected abstract fun renderRouting()

/** Writes the top-level `Main.kt` launcher. */
/**
* Generate the top-level `Main.kt` launcher file.
*
* This file provides the `main()` entrypoint:
* - Parses command-line arguments
* - Applies defaults for configuration values
* - Initializes the `ServiceFrameworkConfig`
* - Starts the appropriate service framework
*/
protected fun renderMainFile() {
val portName = "port"
val engineFactoryName = "engineFactory"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

internal abstract class AbstractConstraintTraitGenerator {
abstract fun render()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

import software.amazon.smithy.kotlin.codegen.core.GenerationContext
import software.amazon.smithy.kotlin.codegen.core.KotlinDelegator
Expand All @@ -10,6 +10,16 @@ import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.RequiredTrait
import kotlin.collections.iterator

/**
* Generates validation code for request constraints on Smithy operation inputs.
*
* For a given [operation], this generator traverses the input structure and:
* - Recursively inspects members of structures and lists.
* - Applies trait-based validations (e.g., required, length, range).
* - Emits Kotlin validation functions that check constraints at runtime.
*
* Output is written into a `<Operation>RequestConstraints.kt` file in the generated `constraints` package.
*/
internal class ConstraintGenerator(
val ctx: GenerationContext,
val operation: OperationShape,
Expand All @@ -21,9 +31,23 @@ internal class ConstraintGenerator(
val opName = operation.id.name
val pkgName = ctx.settings.pkg.name

/**
* Entry point for emitting validation code for the operation’s request type.
* Delegates to [renderRequestConstraintsValidation].
*/
fun render() {
renderRequestConstraintsValidation()
}

/**
* Recursively generates validation code for a given [memberShape].
*
* - If the target is a list, iterates over elements and validates them.
* - If the target is a structure, recursively validates its members.
* - For each trait (on the member or its target), invokes the matching trait generator.
* - `@required` traits are always enforced.
* - Other traits are wrapped in a null check before validation.
*/
private fun generateConstraintValidations(prefix: String, memberShape: MemberShape, writer: KotlinWriter) {
val targetShape = ctx.model.expectShape(memberShape.target)

Expand Down Expand Up @@ -59,6 +83,12 @@ internal class ConstraintGenerator(
}
}

/**
* Writes the top-level validation function for the operation’s input type.
*
* Inside, it calls [generateConstraintValidations] for each input member,
* ensuring all modeled constraints are enforced.
*/
private fun renderRequestConstraintsValidation() {
delegator.useFileWriter("${opName}RequestConstraints.kt", "$pkgName.constraints") { writer ->
val inputShape = ctx.model.expectShape(operation.input.get())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

import software.amazon.smithy.kotlin.codegen.core.GenerationContext
import software.amazon.smithy.kotlin.codegen.core.KotlinDelegator
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.core.withBlock

/**
* Generates utility functions to support constraint validation for Smithy models.
*
* This generator emits reusable helpers that can be called from operation-specific
* validation code (e.g., generated by [ConstraintGenerator]). These helpers enforce
* common traits like `@length` and `@uniqueItems`.
*
* Output is written into a `utils.kt` file under the generated `constraints` package.
*/
internal class ConstraintUtilsGenerator(
val ctx: GenerationContext,
val delegator: KotlinDelegator,
Expand All @@ -20,6 +29,16 @@ internal class ConstraintUtilsGenerator(
}
}

/**
* Emits the `sizeOf()` function.
*
* This utility computes a generalized "size" for multiple types:
* - Collections, arrays, maps → `size`
* - Strings → Unicode code point count
* - Byte arrays → length
*
* Any unsupported type will throw an `IllegalArgumentException`.
*/
private fun renderLengthTraitUtils(writer: KotlinWriter) {
writer.withBlock("internal fun sizeOf(value: Any?): Long = when (value) {", "}") {
write("is Collection<*> -> value.size.toLong()")
Expand All @@ -34,6 +53,16 @@ internal class ConstraintUtilsGenerator(
}
}

/**
* Emits the `hasAllUniqueElements()` function.
*
* This utility checks if a list contains only unique elements, where uniqueness
* is defined by deep structural equality:
* - Primitive wrappers (String, Boolean, Number, Instant) → compared by value
* - Byte arrays → compared by contents
* - Lists → recursively compared element by element
* - Maps → recursively compared entries by key/value
*/
private fun renderUniqueItemsTraitUtils(writer: KotlinWriter) {
writer.withBlock("internal fun hasAllUniqueElements(elements: List<Any?>): Boolean {", "}") {
withBlock("class Wrapped(private val v: Any?) {", "}") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.service.ServiceTypes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.model.traits.PatternTrait
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.model.traits.RangeTrait
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.model.traits.RequiredTrait
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.service.ServiceTypes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package software.amazon.smithy.kotlin.codegen.service.contraints
package software.amazon.smithy.kotlin.codegen.service.constraints

import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.model.traits.LengthTrait
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,22 @@ Run:
gradle build
```

⚠️ Running gradle build will delete the previous build output before creating a new one.

If you want to prevent accidentally losing previous build, use the provided scripts instead:

You can find script for Linux / macOS [here](../../../../../../../../../../../../examples/service-codegen/build.sh):
```bash
chmod +x build.sh
./build.sh
```

You can find script for Windows [here](../../../../../../../../../../../../examples/service-codegen/build.bat):
```bash
icacls build.bat /grant %USERNAME%:RX
.\build.bat
```

If you want to clean previously generated code:
```bash
gradle clean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import software.amazon.smithy.kotlin.codegen.core.withBlock
import software.amazon.smithy.kotlin.codegen.model.getTrait
import software.amazon.smithy.kotlin.codegen.service.ServiceTypes

/**
* Writes Ktor-based authentication support classes and configuration
* for a generated service.
*
* This generates three files:
* 1. UserPrincipal.kt → Represents the authenticated user.
* 2. Validation.kt → Provides bearer token validation logic.
* 3. Authentication.kt → Configures authentication providers in Ktor.
*/
internal fun KtorStubGenerator.writeAuthentication() {
delegator.useFileWriter("UserPrincipal.kt", "$pkgName.auth") { writer ->
writer.withBlock("public data class UserPrincipal(", ")") {
Expand Down
Loading
Loading