Skip to content
Merged
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
4 changes: 4 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,7 @@ include(":smithy-java-examples:integ")
// ---- Smithy-Rust examples ----
// templates
includeBuild("smithy-rs-examples/quickstart-rust")

// ---- Smithy-Kotlin examples ----
// templates
includeBuild("smithy-kotlin-examples/quickstart-kotlin")
2 changes: 2 additions & 0 deletions smithy-kotlin-examples/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Smithy Kotlin
The examples in this directory demonstrate the use of the [Smithy Kotlin](https://github.com/smithy-lang/smithy-kotlin) code generator for clients.
30 changes: 30 additions & 0 deletions smithy-kotlin-examples/quickstart-kotlin/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## Smithy-Kotlin Quickstart

This project provides a template to get started using [Smithy Kotlin](https://github.com/smithy-lang/smithy-kotlin)
to create Kotlin clients.

### Layout

- `client/`: Code generated Kotlin client that can call the server.
- `smithy/`: Common package for the service API model. Shared by both client and server.
- `server/`: Code generated Java Server that implements stubbed operations code-generated from the service model.

### Usage

To create a new project from this template, use the [Smithy CLI](https://smithy.io/2.0/guides/smithy-cli/index.html)
`init` command as follows:

```console
smithy init -t smithy-kotlin-quickstart
```

### Running and testing client

To run and test the client, first start a server by running `./gradlew :server:run` from the root of a project created from this
template. That will start the server running on port `8888`.

Once the server is running you can run the client application in the `client` subproject

```console
./gradlew :client:run
```
8 changes: 8 additions & 0 deletions smithy-kotlin-examples/quickstart-kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

// Add repositories for all subprojects to resolve dependencies.
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
description = "Coffee shop service client"

plugins {
application
kotlin("jvm") version "2.3.0"
// Executes smithy-build process to generate client code
id("software.amazon.smithy.gradle.smithy-base")
}

// Add generated client source code to the main sourceSet
afterEvaluate {
val clientPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "kotlin-codegen")
sourceSets.main.get().kotlin.srcDir(clientPath)
}

tasks.named("compileKotlin") {
dependsOn("smithyBuild")
}

dependencies {
// Code generators
compileOnly(libs.smithy.kotlin.aws.codegen)

// Service model
implementation(project(":smithy"))

// Client Dependencies
implementation(libs.smithy.kotlin.runtime.core)
implementation(libs.smithy.kotlin.smithy.client)
implementation(libs.smithy.kotlin.http.client)
implementation(libs.smithy.kotlin.telemetry.api)
implementation(libs.smithy.kotlin.telemetry.defaults)
implementation(libs.smithy.kotlin.rpcv2.protocol)
implementation(libs.smithy.kotlin.aws.protocol.core)
implementation(libs.smithy.kotlin.aws.signing.common)
implementation(libs.smithy.kotlin.serde)
implementation(libs.smithy.kotlin.serde.cbor)
implementation(libs.smithy.kotlin.http.client.engine.default)
implementation(libs.kotlinx.coroutines.core)
}

val optinAnnotations = listOf("kotlin.RequiresOptIn", "aws.smithy.kotlin.runtime.InternalApi")
kotlin.sourceSets.all {
optinAnnotations.forEach { languageSettings.optIn(it) }
}

application {
mainClass.set("io.smithy.kotlin.client.example.MainKt")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "1.0",
"plugins": {
"kotlin-codegen": {
"service": "com.example#CoffeeShop",
"sdkId": "CoffeeShop",
"package": {
"name": "io.smithy.kotlin.client.example",
"version": "0.0.1"
},
"build": {
"rootProject": false,
"generateDefaultBuildFiles": false
},
"api": {
"nullabilityCheckMode": "client"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.smithy.kotlin.client.example

import aws.smithy.kotlin.runtime.client.endpoints.Endpoint
import io.smithy.kotlin.client.example.endpoints.CoffeeShopEndpointProvider
import io.smithy.kotlin.client.example.model.CoffeeType
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.util.logging.Logger
import kotlin.time.Duration.Companion.seconds

fun main(): Unit = runBlocking {
val logger: Logger = Logger.getLogger("SmithyKotlinClient")

CoffeeShopClient {
endpointProvider = CoffeeShopEndpointProvider {
Endpoint("http://localhost:8888")
}
}.use { client ->
logger.info("Creating coffee order")

val createOrderResponse = client.createOrder {
coffeeType = CoffeeType.Latte
}

logger.info("Created order with ID: ${createOrderResponse.id}")

logger.info("Waiting for order to complete.")
delay(5.seconds)

logger.info("Checking order status")
val orderStatus = client.getOrder {
id = createOrderResponse.id
}.status

logger.info("Order status: $orderStatus")
Comment on lines +27 to +35
Copy link

Choose a reason for hiding this comment

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

Maybe we can make this a little more comprehensive example by explicitly waiting until the order status is OrderStatus.COMPLETED

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[versions]
smithy-version="1.63.0"
smithy-java-version="0.0.1"
smithy-kotlin-codegen-version="0.35.25"
smithy-kotlin-runtime-version="1.5.25"
coroutines-core-version="1.10.2"

[libraries]
smithy-aws-traits = { module="software.amazon.smithy:smithy-aws-traits", version.ref = "smithy-version" }

smithy-java-plugins = { module="software.amazon.smithy.java.codegen:plugins", version.ref = "smithy-java-version" }
smithy-java-server-netty = { module="software.amazon.smithy.java:server-netty", version.ref = "smithy-java-version" }
smithy-java-aws-server-restjson = { module="software.amazon.smithy.java:aws-server-restjson", version.ref = "smithy-java-version" }
smithy-java-aws-server-rpcv2-cbor = { module="software.amazon.smithy.java:server-rpcv2-cbor", version.ref = "smithy-java-version" }

smithy-kotlin-aws-codegen = { module = "software.amazon.smithy.kotlin:smithy-aws-kotlin-codegen", version.ref = "smithy-kotlin-codegen-version" }
smithy-kotlin-runtime-core = { module = "aws.smithy.kotlin:runtime-core", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-smithy-client = { module = "aws.smithy.kotlin:smithy-client", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-http-client = { module = "aws.smithy.kotlin:http-client", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-telemetry-api = { module = "aws.smithy.kotlin:telemetry-api", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-telemetry-defaults = { module = "aws.smithy.kotlin:telemetry-defaults", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-rpcv2-protocol = { module = "aws.smithy.kotlin:smithy-rpcv2-protocols", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-aws-protocol-core = { module = "aws.smithy.kotlin:aws-protocol-core", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-aws-signing-common = { module = "aws.smithy.kotlin:aws-signing-common", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-serde = { module = "aws.smithy.kotlin:serde", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-serde-cbor = { module = "aws.smithy.kotlin:serde-cbor", version.ref = "smithy-kotlin-runtime-version" }
smithy-kotlin-http-client-engine-default = { module = "aws.smithy.kotlin:http-client-engine-default", version.ref = "smithy-kotlin-runtime-version" }

kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines-core-version" }
4 changes: 4 additions & 0 deletions smithy-kotlin-examples/quickstart-kotlin/license.txt
Copy link

Choose a reason for hiding this comment

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

Do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we need it for the Java Kotlin server to work. When I remove it I get:

Projection source failed: software.amazon.smithy.codegen.core.CodegenException: Header file /Users/aoperez/Documents/kotlinSdkWork/smithyExamples/smithy-examples/smithy-kotlin-examples/quickstart-kotlin/server/../license.txt does not exist.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
* Example file license header.
* File header line two
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
description = "Coffee shop service server implementation"

plugins {
`java-library`
application
// Executes smithy-build process to generate server stubs
id("software.amazon.smithy.gradle.smithy-base")
}

dependencies {
// Code generators
smithyBuild(libs.smithy.java.plugins)

// Service model
implementation(project(":smithy"))

// Server dependencies
// Adds an HTTP server implementation based on netty
implementation(libs.smithy.java.server.netty)
// Adds a server implementation of the `RestJson1` protocol
implementation(libs.smithy.java.aws.server.restjson)
// Adds a server implementation of the `Rpcv2Cbor` protocol
implementation(libs.smithy.java.aws.server.rpcv2.cbor)
}

// Add generated source code to the compilation sourceSet
afterEvaluate {
val serverPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-server-codegen")
sourceSets.main.get().java.srcDir(serverPath)
}

tasks.named("compileJava") {
dependsOn("smithyBuild")
}

// Use the application plugin to start the service via the `run` task.
application {
mainClass = "io.smithy.kotlin.server.example.CoffeeShopService"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": "1.0",
"plugins": {
"java-server-codegen": {
"service": "com.example#CoffeeShop",
"namespace": "io.smithy.kotlin.server.example",
"headerFile": "../license.txt"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/

package io.smithy.kotlin.server.example;

import io.smithy.kotlin.server.example.service.CoffeeShop;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import software.amazon.smithy.java.server.Server;

public class CoffeeShopService implements Runnable {
private static final Logger LOGGER = Logger.getLogger(CoffeeShopService.class.getName());

public static void main(String... args) throws InterruptedException, ExecutionException {
new CoffeeShopService().run();
}

@Override
public void run() {
Server server = Server.builder()
.endpoints(URI.create("http://localhost:8888"))
.addService(
CoffeeShop.builder()
.addCreateOrderOperation(new CreateOrder())
.addGetMenuOperation(new GetMenu())
.addGetOrderOperation(new GetOrder())
.build()
)
.build();
LOGGER.info("Starting server...");
server.start();
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
LOGGER.info("Stopping server...");
try {
server.shutdown().get();
} catch (InterruptedException | ExecutionException ex) {
throw new RuntimeException(ex);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/

package io.smithy.kotlin.server.example;

import io.smithy.kotlin.server.example.model.CreateOrderInput;
import io.smithy.kotlin.server.example.model.CreateOrderOutput;
import io.smithy.kotlin.server.example.model.OrderStatus;
import io.smithy.kotlin.server.example.service.CreateOrderOperation;
import java.util.UUID;

import java.util.logging.Logger;
import software.amazon.smithy.java.server.RequestContext;

/**
* Create an order for a coffee.
*/
final class CreateOrder implements CreateOrderOperation {
private static final Logger LOGGER = Logger.getLogger(CreateOrder.class.getName());

@Override
public CreateOrderOutput createOrder(CreateOrderInput input, RequestContext context) {
var id = UUID.randomUUID();
OrderTracker.putOrder(new Order(id, input.coffeeType(), OrderStatus.IN_PROGRESS));

LOGGER.info("Created order " + id + " for a " + input.coffeeType());

return CreateOrderOutput.builder()
.id(id.toString())
.coffeeType(input.coffeeType())
.status(OrderStatus.IN_PROGRESS)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/

package io.smithy.kotlin.server.example;

import io.smithy.kotlin.server.example.model.CoffeeItem;
import io.smithy.kotlin.server.example.model.CoffeeType;
import io.smithy.kotlin.server.example.model.GetMenuInput;
import io.smithy.kotlin.server.example.model.GetMenuOutput;
import io.smithy.kotlin.server.example.service.GetMenuOperation;
import java.util.List;

import software.amazon.smithy.java.server.RequestContext;

/**
* Returns the menu for the coffee shop
*/
final class GetMenu implements GetMenuOperation {
private static final List<CoffeeItem> MENU = List.of(
CoffeeItem.builder()
.typeMember(CoffeeType.DRIP)
.description("""
A clean-bodied, rounder, and more simplistic flavour profile.
Often praised for mellow and less intense notes.
Far less concentrated than espresso.
""")
.build(),
CoffeeItem.builder()
.typeMember(CoffeeType.POUR_OVER)
.description("""
Similar to drip coffee, but with a process that brings out more subtle nuances in flavor.
More concentrated than drip, but less than espresso.
""")
.build(),
CoffeeItem.builder()
.typeMember(CoffeeType.LATTE)
.description("""
A creamier, milk-based drink made with espresso.
A subtle coffee taste, with smooth texture.
High milk-to-coffee ratio.
""")
.build(),
CoffeeItem.builder()
.typeMember(CoffeeType.ESPRESSO)
.description("""
A highly concentrated form of coffee, brewed under high pressure.
Syrupy, thick liquid in a small serving size.
Full bodied and intensely aromatic.
""")
.build()
);

@Override
public GetMenuOutput getMenu(GetMenuInput input, RequestContext context) {
return GetMenuOutput.builder().items(MENU).build();
}
}
Loading
Loading