diff --git a/custom-trait-examples/integ/custom-trait-with-java-validator-test/model/main.smithy b/custom-trait-examples/integ/custom-trait-with-java-validator-test/model/main.smithy index 1ef1b5b..69a3c02 100644 --- a/custom-trait-examples/integ/custom-trait-with-java-validator-test/model/main.smithy +++ b/custom-trait-examples/integ/custom-trait-with-java-validator-test/model/main.smithy @@ -10,7 +10,9 @@ use example.traits#resourceMetadata associatedStructures: [ForecastStruct] ) resource Forecast { - identifiers: { forecastId: ForecastId } + identifiers: { + forecastId: ForecastId + } } string ForecastId diff --git a/gradle-plugin-examples/tutorial/service/model/service.smithy b/gradle-plugin-examples/tutorial/service/model/service.smithy index 25db1f4..bb6dc30 100644 --- a/gradle-plugin-examples/tutorial/service/model/service.smithy +++ b/gradle-plugin-examples/tutorial/service/model/service.smithy @@ -26,8 +26,14 @@ service DrinkService { @externalDocumentation(wikipedia: "https://en.wikipedia.org/wiki/Order") resource Order { - identifiers: { id: MenuItemId } - properties: { price: Price, style: Style, type: Type } + identifiers: { + id: MenuItemId + } + properties: { + price: Price + style: Style + type: Type + } create: CreateOrder read: GetOrder } diff --git a/gradle.properties b/gradle.properties index 352fd85..1a53326 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ smithyVersion=1.51.0 smithyGradleVersion=1.1.0 +smithyJavaVersion=0.0.1 diff --git a/settings.gradle.kts b/settings.gradle.kts index 2f1ef0e..348dc6e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -53,3 +53,10 @@ includeBuild("gradle-plugin-examples/tutorial") // Integ test include(":gradle-plugin-examples:integ:tutorial") + +// ---- Smithy-Java examples ---- +// templates +includeBuild("smithy-java-examples/quickstart-java") + +// integration tests +include(":smithy-java-examples:integ") diff --git a/smithy-java-examples/README.md b/smithy-java-examples/README.md new file mode 100644 index 0000000..bc5a454 --- /dev/null +++ b/smithy-java-examples/README.md @@ -0,0 +1,4 @@ +# Linting and Validation +The examples in this directory demonstrate the use of the [Smithy Java](https://github.com/smithy-lang/smithy-java) code generator. + +Additional examples can be found [in the Smithy Java repository](https://github.com/smithy-lang/smithy-java/tree/main/examples). diff --git a/smithy-java-examples/quickstart-java/README.md b/smithy-java-examples/quickstart-java/README.md new file mode 100644 index 0000000..d7c4055 --- /dev/null +++ b/smithy-java-examples/quickstart-java/README.md @@ -0,0 +1,30 @@ +## Smithy-Java Quickstart + +This project provides a template to get started using [Smithy Java](https://github.com/smithy-lang/smithy-java/) +to create Java clients and servers. + +For more information on this example, see the [Smithy Java Quickstart Guide](https://smithy.io/2.0/java/quickstart.html). + +### Layout +- `lib/`: Common package for the service API model. Shared by both client and server. +- `server/`: Code generated Server that implements stubbed operations code-generated from the service model. +- `client/`: Code generated client that can call the server. +- `plugins/`: A package defining client plugins. + +### 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-java-quickstart +``` + +### Running and testing server + +To run and test the server, run `./gradlew 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 call the server using curl or by executing the integration tests in `client` subproject: +- `curl`: `curl -H "content-type: application/json" -d '{"coffeeType": "LATTE"}' -X POST localhost:8888/order` +- integration tests: `./gradlew :client:integ` diff --git a/smithy-java-examples/quickstart-java/build.gradle.kts b/smithy-java-examples/quickstart-java/build.gradle.kts new file mode 100644 index 0000000..fca222d --- /dev/null +++ b/smithy-java-examples/quickstart-java/build.gradle.kts @@ -0,0 +1,8 @@ + +// Add repositories for all subprojects to resolve dependencies. +allprojects { + repositories { + mavenLocal() + mavenCentral() + } +} diff --git a/smithy-java-examples/quickstart-java/client/build.gradle.kts b/smithy-java-examples/quickstart-java/client/build.gradle.kts new file mode 100644 index 0000000..2f8d063 --- /dev/null +++ b/smithy-java-examples/quickstart-java/client/build.gradle.kts @@ -0,0 +1,65 @@ +description = "Cafe service client" + +plugins { + `java-library` + // Executes smithy-build process to generate client code + id("software.amazon.smithy.gradle.smithy-base") +} + +dependencies { + val smithyJavaVersion: String by project + + // === Code generators === + smithyBuild("software.amazon.smithy.java.codegen:plugins:$smithyJavaVersion") + + // === Service model === + implementation(project(":lib")) + + // === Client Plugins === + implementation(project(":plugins")) + + // === Client Dependencies === + // Core client dependency required by generated client code. + implementation("software.amazon.smithy.java:client-core:$smithyJavaVersion") + // Add client implementation of `RestJson1` protocol + implementation("software.amazon.smithy.java:aws-client-restjson:$smithyJavaVersion") + + // Test dependencies + testImplementation("org.junit.jupiter:junit-jupiter:5.7.1") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +afterEvaluate { + val clientPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-client-codegen") + sourceSets { + // Add generated client source code to the main sourceSet + main { + java { + srcDir(clientPath) + } + } + // Set up integration testing tasks source set + create("it") { + compileClasspath += main.get().output + configurations["testRuntimeClasspath"] + configurations["testCompileClasspath"] + runtimeClasspath += output + compileClasspath + test.get().runtimeClasspath + test.get().output + } + } +} + +tasks { + // Ensure compilation happens after source-code generation + val smithyBuild by getting + compileJava { + dependsOn(smithyBuild) + } + + // This is set up integ tests separate from the `test` task to + // avoid automatically running tests as part of build. + val integ by registering(Test::class) { + useJUnitPlatform() + testClassesDirs = sourceSets["it"].output.classesDirs + classpath = sourceSets["it"].runtimeClasspath + // Allow the integ tests to print to stdout/stderr + testLogging.showStandardStreams = true + } +} diff --git a/smithy-java-examples/quickstart-java/client/smithy-build.json b/smithy-java-examples/quickstart-java/client/smithy-build.json new file mode 100644 index 0000000..32409c1 --- /dev/null +++ b/smithy-java-examples/quickstart-java/client/smithy-build.json @@ -0,0 +1,12 @@ +{ + "version": "1.0", + "plugins": { + "java-client-codegen": { + "service": "com.example#CoffeeShop", + "namespace": "io.smithy.java.client.example", + "headerFile": "../license.txt", + "protocol": "aws.protocols#restJson1", + "defaultPlugins": ["io.smithy.java.example.plugins.ExamplePlugin"] + } + } +} diff --git a/smithy-java-examples/quickstart-java/client/src/it/java/io/smithy/java/client/example/TestRunner.java b/smithy-java-examples/quickstart-java/client/src/it/java/io/smithy/java/client/example/TestRunner.java new file mode 100644 index 0000000..171aecb --- /dev/null +++ b/smithy-java-examples/quickstart-java/client/src/it/java/io/smithy/java/client/example/TestRunner.java @@ -0,0 +1,58 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + */ + +package io.smithy.java.client.example; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.smithy.java.client.example.client.CoffeeShopClient; +import io.smithy.java.client.example.model.CoffeeType; +import io.smithy.java.client.example.model.CreateOrderInput; +import io.smithy.java.client.example.model.GetMenuInput; +import io.smithy.java.client.example.model.GetOrderInput; +import io.smithy.java.client.example.model.OrderNotFound; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + + +public class TestRunner { + private final CoffeeShopClient client = CoffeeShopClient.builder().build(); + + @Test + public void getMenu() { + var menu = client.getMenu(GetMenuInput.builder().build()); + System.out.println(menu); + } + + @Test + public void createOrder() throws InterruptedException { + // Create an order + var createRequest = CreateOrderInput.builder().coffeeType(CoffeeType.COLD_BREW).build(); + var createResponse = client.createOrder(createRequest); + System.out.println("Created request with id = " + createResponse.id()); + + // Get the order. Should still be in progress. + var getRequest = GetOrderInput.builder().id(createResponse.id()).build(); + var getResponse1 = client.getOrder(getRequest); + System.out.println("Got order with id = " + getResponse1.id()); + + // Give order some time to complete + System.out.println("Waiting for order to complete...."); + TimeUnit.SECONDS.sleep(5); + + // Get the order again. + var getResponse2 = client.getOrder(getRequest); + System.out.println("Completed Order :" + getResponse2); + } + + @Test + void errorsOutIfOrderDoesNotExist() throws InterruptedException { + var getRequest = GetOrderInput.builder().id(UUID.randomUUID().toString()).build(); + var orderNotFound = assertThrows(OrderNotFound.class, () -> client.getOrder(getRequest)); + assertEquals(orderNotFound.orderId(), getRequest.id()); + } +} diff --git a/smithy-java-examples/quickstart-java/gradle.properties b/smithy-java-examples/quickstart-java/gradle.properties new file mode 100644 index 0000000..1a53326 --- /dev/null +++ b/smithy-java-examples/quickstart-java/gradle.properties @@ -0,0 +1,3 @@ +smithyVersion=1.51.0 +smithyGradleVersion=1.1.0 +smithyJavaVersion=0.0.1 diff --git a/smithy-java-examples/quickstart-java/lib/build.gradle.kts b/smithy-java-examples/quickstart-java/lib/build.gradle.kts new file mode 100644 index 0000000..3cbc116 --- /dev/null +++ b/smithy-java-examples/quickstart-java/lib/build.gradle.kts @@ -0,0 +1,23 @@ +description = "Smithy definition of a Cafe service." + +plugins { + `java-library` + // Packages the models in this package into a jar for sharing/distribution by other packages + id("software.amazon.smithy.gradle.smithy-jar") +} + +dependencies { + val smithyVersion: String by project + + // Adds the `@restJson1` protocol trait + api("software.amazon.smithy:smithy-aws-traits:$smithyVersion") +} + +// Helps the Smithy IntelliJ plugin identify models +sourceSets { + main { + java { + srcDir("model") + } + } +} diff --git a/smithy-java-examples/quickstart-java/lib/model/common.smithy b/smithy-java-examples/quickstart-java/lib/model/common.smithy new file mode 100644 index 0000000..192c4f5 --- /dev/null +++ b/smithy-java-examples/quickstart-java/lib/model/common.smithy @@ -0,0 +1,27 @@ +$version: "2" + +namespace com.example + +/// An enum describing the types of coffees available +enum CoffeeType { + DRIP + POUR_OVER + LATTE + ESPRESSO + COLD_BREW +} + +/// A structure which defines a coffee item which can be ordered +structure CoffeeItem { + /// A type of coffee + @required + type: CoffeeType + + @required + description: String +} + +/// A list of coffee items +list CoffeeItems { + member: CoffeeItem +} diff --git a/smithy-java-examples/quickstart-java/lib/model/main.smithy b/smithy-java-examples/quickstart-java/lib/model/main.smithy new file mode 100644 index 0000000..92bc2d6 --- /dev/null +++ b/smithy-java-examples/quickstart-java/lib/model/main.smithy @@ -0,0 +1,28 @@ +$version: "2" + +namespace com.example + +use aws.protocols#restJson1 + +/// Allows users to retrieve a menu, create a coffee order, and +/// and to view the status of their orders +@title("Coffee Shop Service") +@restJson1 +service CoffeeShop { + version: "2024-08-23" + operations: [ + GetMenu + ] + resources: [ + Order + ] +} + +/// Retrieve the menu +@http(method: "GET", uri: "/menu") +@readonly +operation GetMenu { + output := { + items: CoffeeItems + } +} diff --git a/smithy-java-examples/quickstart-java/lib/model/order.smithy b/smithy-java-examples/quickstart-java/lib/model/order.smithy new file mode 100644 index 0000000..45d6264 --- /dev/null +++ b/smithy-java-examples/quickstart-java/lib/model/order.smithy @@ -0,0 +1,83 @@ +$version: "2.0" + +namespace com.example + +/// An Order resource, which has an id and describes an order by the type of coffee +/// and the order's status +resource Order { + identifiers: { + id: Uuid + } + properties: { + coffeeType: CoffeeType + status: OrderStatus + } + read: GetOrder + create: CreateOrder +} + +/// Create an order +@idempotent +@http(method: "POST", uri: "/order") +operation CreateOrder { + input := for Order { + @required + $coffeeType + } + + output := for Order { + @required + $id + + @required + $coffeeType + + @required + $status + } +} + +/// Retrieve an order +@readonly +@http(method: "GET", uri: "/order/{id}") +operation GetOrder { + input := for Order { + @httpLabel + @required + $id + } + + output := for Order { + @required + $id + + @required + $coffeeType + + @required + $status + } + + errors: [ + OrderNotFound + ] +} + +/// An error indicating an order could not be found +@httpError(404) +@error("client") +structure OrderNotFound { + message: String + orderId: Uuid +} + +/// An identifier to describe a unique order +@length(min: 1, max: 128) +@pattern("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$") +string Uuid + +/// An enum describing the status of an order +enum OrderStatus { + IN_PROGRESS + COMPLETED +} diff --git a/smithy-java-examples/quickstart-java/lib/smithy-build.json b/smithy-java-examples/quickstart-java/lib/smithy-build.json new file mode 100644 index 0000000..703ffb7 --- /dev/null +++ b/smithy-java-examples/quickstart-java/lib/smithy-build.json @@ -0,0 +1,3 @@ +{ + "version": "1.0" +} diff --git a/smithy-java-examples/quickstart-java/license.txt b/smithy-java-examples/quickstart-java/license.txt new file mode 100644 index 0000000..edaafd8 --- /dev/null +++ b/smithy-java-examples/quickstart-java/license.txt @@ -0,0 +1,4 @@ +/* + * Example file license header. + * File header line two + */ diff --git a/smithy-java-examples/quickstart-java/plugins/build.gradle.kts b/smithy-java-examples/quickstart-java/plugins/build.gradle.kts new file mode 100644 index 0000000..8106986 --- /dev/null +++ b/smithy-java-examples/quickstart-java/plugins/build.gradle.kts @@ -0,0 +1,11 @@ +description = "Defines plugins to configure generated clients" + +plugins { + `java-library` +} + +dependencies { + val smithyJavaVersion: String by project + + implementation("software.amazon.smithy.java:client-core:$smithyJavaVersion") +} diff --git a/smithy-java-examples/quickstart-java/plugins/src/main/java/io/smithy/java/example/plugins/ExamplePlugin.java b/smithy-java-examples/quickstart-java/plugins/src/main/java/io/smithy/java/example/plugins/ExamplePlugin.java new file mode 100644 index 0000000..ab1b5f9 --- /dev/null +++ b/smithy-java-examples/quickstart-java/plugins/src/main/java/io/smithy/java/example/plugins/ExamplePlugin.java @@ -0,0 +1,24 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + */ + +package io.smithy.java.example.plugins; + +import software.amazon.smithy.java.client.core.ClientConfig; +import software.amazon.smithy.java.client.core.ClientPlugin; +import software.amazon.smithy.java.client.core.endpoint.EndpointResolver; + +/** + * Example plugin that sets the static endpoint for the generated client. + * + *
Plugins allow clients to be configured in a repeatable way.
+ */
+public class ExamplePlugin implements ClientPlugin {
+ private static final EndpointResolver STATIC_ENDPOINT = EndpointResolver.staticEndpoint("http://localhost:8888");
+
+ @Override
+ public void configureClient(ClientConfig.Builder builder) {
+ builder.endpointResolver(STATIC_ENDPOINT);
+ }
+}
diff --git a/smithy-java-examples/quickstart-java/server/build.gradle.kts b/smithy-java-examples/quickstart-java/server/build.gradle.kts
new file mode 100644
index 0000000..2cffec4
--- /dev/null
+++ b/smithy-java-examples/quickstart-java/server/build.gradle.kts
@@ -0,0 +1,39 @@
+description = "Cafe service server implementation"
+
+plugins {
+ `java-library`
+ application
+ // Executes smithy-build process to generate server stubs
+ id("software.amazon.smithy.gradle.smithy-base")
+}
+
+dependencies {
+ val smithyJavaVersion: String by project
+
+ // === Code generators ===
+ smithyBuild("software.amazon.smithy.java.codegen:plugins:$smithyJavaVersion")
+
+ // === Service model ===
+ implementation(project(":lib"))
+
+ // === Server dependencies ===
+ // Adds an HTTP server implementation based on netty
+ implementation("software.amazon.smithy.java:server-netty:$smithyJavaVersion")
+ // Adds the server implementation of the `RestJson1` protocol
+ implementation("software.amazon.smithy.java:aws-server-restjson:$smithyJavaVersion")
+}
+
+// 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.java.server.example.CafeService"
+}
diff --git a/smithy-java-examples/quickstart-java/server/smithy-build.json b/smithy-java-examples/quickstart-java/server/smithy-build.json
new file mode 100644
index 0000000..e0ce858
--- /dev/null
+++ b/smithy-java-examples/quickstart-java/server/smithy-build.json
@@ -0,0 +1,10 @@
+{
+ "version": "1.0",
+ "plugins": {
+ "java-server-codegen": {
+ "service": "com.example#CoffeeShop",
+ "namespace": "io.smithy.java.server.example",
+ "headerFile": "../license.txt"
+ }
+ }
+}
diff --git a/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/CafeService.java b/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/CafeService.java
new file mode 100644
index 0000000..d75eecb
--- /dev/null
+++ b/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/CafeService.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: MIT-0
+ */
+
+package io.smithy.java.server.example;
+
+import io.smithy.java.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 CafeService implements Runnable {
+ private static final Logger LOGGER = Logger.getLogger(CafeService.class.getName());
+
+ public static void main(String... args) throws InterruptedException, ExecutionException {
+ new CafeService().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);
+ }
+ }
+ }
+}
diff --git a/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/CreateOrder.java b/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/CreateOrder.java
new file mode 100644
index 0000000..e561a4e
--- /dev/null
+++ b/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/CreateOrder.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: MIT-0
+ */
+
+package io.smithy.java.server.example;
+
+import io.smithy.java.server.example.model.CreateOrderInput;
+import io.smithy.java.server.example.model.CreateOrderOutput;
+import io.smithy.java.server.example.model.OrderStatus;
+import io.smithy.java.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();
+ }
+}
diff --git a/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/GetMenu.java b/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/GetMenu.java
new file mode 100644
index 0000000..b7278af
--- /dev/null
+++ b/smithy-java-examples/quickstart-java/server/src/main/java/io/smithy/java/server/example/GetMenu.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: MIT-0
+ */
+
+package io.smithy.java.server.example;
+
+import io.smithy.java.server.example.model.CoffeeItem;
+import io.smithy.java.server.example.model.CoffeeType;
+import io.smithy.java.server.example.model.GetMenuInput;
+import io.smithy.java.server.example.model.GetMenuOutput;
+import io.smithy.java.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