Restlet is a registered trademark of QlikTech International AB. */ +/* + * The server classes use the smithy-java server, which splits a package in 1.8.0 + * (smithy-java#1437) and can't be required: they read it on the class path, with --add-reads + * org.restlet.ext.smithy=ALL-UNNAMED, and its provider is only declared in META-INF/services. + */ @SuppressWarnings({"requires-automatic", "requires-transitive-automatic"}) module org.restlet.ext.smithy { requires transitive org.restlet; diff --git a/org.restlet.ext.smithy/src/main/java/org/restlet/ext/smithy/RestletSmithyServer.java b/org.restlet.ext.smithy/src/main/java/org/restlet/ext/smithy/RestletSmithyServer.java new file mode 100644 index 0000000000..f1a12b7520 --- /dev/null +++ b/org.restlet.ext.smithy/src/main/java/org/restlet/ext/smithy/RestletSmithyServer.java @@ -0,0 +1,211 @@ +/** + * Copyright 2005-2026 Qlik + *
+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.smithy;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.Restlet;
+import org.restlet.data.Protocol;
+import org.restlet.data.Reference;
+import org.restlet.data.Status;
+import software.amazon.smithy.java.server.Route;
+import software.amazon.smithy.java.server.Server;
+
+/**
+ * A smithy-java server built by {@link SmithyServerBuilder}: a Restlet server connector per
+ * endpoint, dispatching each call to the adapter of its route. Single-use.
+ */
+final class RestletSmithyServer implements Server {
+ /** A route, and the adapter hosting its services. */
+ record Hosted(Route route, SmithyServerAdapter adapter) {}
+
+ private final List {@link SmithyServerBuilder} builds SDK servers of such adapters, listening on Restlet server
+ * connectors.
+ *
* The smithy-java server API is unstable ({@code @SmithyUnstableApi}) in 1.8.0, and its modules
- * split a package, so that it only runs on the class path. This artifact shares the packages of
- * {@code org.restlet.ext.smithy}, which must then be on the class path too. Event streams are not
- * supported.
+ * split a package, so that they only run on the class path. The optional {@code server-core}
+ * dependency of this extension must then be added to the class path. A named module using the
+ * server reads it, as this module does, with {@code --add-reads
+ * org.restlet.ext.smithy=ALL-UNNAMED}, when compiled and run. Event streams are not supported.
*/
public final class SmithyServerAdapter extends Restlet {
private final List
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.smithy;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import org.restlet.Context;
+import software.amazon.smithy.java.server.Route;
+import software.amazon.smithy.java.server.Server;
+import software.amazon.smithy.java.server.ServerBuilder;
+
+/**
+ * Experimental builder of smithy-java servers listening on Restlet HTTP server connectors, one per
+ * endpoint, each route of services hosted by a {@link SmithyServerAdapter}.
+ *
+ * On the class path, {@code Server.builder("restlet")} finds it, and so does {@code
+ * Server.builder()} when the SDK's Netty server isn't there too. It can also be created directly,
+ * to set the Restlet settings that the SDK's builder lacks: the connectors' context, for example
+ * with an {@code http.transport.protocol} parameter set to {@code HTTP2} or the TLS parameters of
+ * {@code https} endpoints, and the adapters' limits. {@link #numberOfWorkers(int)} sets the number
+ * of calls each route handles at once.
+ *
+ * A call goes to the first route whose path prefix, host name, and connector port and scheme,
+ * when set, match it, and that resolves its operation. When the routes only set path prefixes, as
+ * with services added under paths, whose order the SDK doesn't keep, the longest matching prefix
+ * wins. Operations are resolved on the full path, as with the SDK's Netty server.
+ */
+// The SDK server types come from the class path, which a named module can't export.
+@SuppressWarnings("exports")
+public final class SmithyServerBuilder extends ServerBuilder
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.smithy.internal;
+
+import org.restlet.ext.smithy.SmithyServerBuilder;
+import software.amazon.smithy.java.server.ServerBuilder;
+import software.amazon.smithy.java.server.ServerProvider;
+
+/**
+ * Provider discovered by {@code Server.builder()} on the class path, under the name {@code
+ * restlet}. A named module can't declare it: the SDK server isn't in the module graph.
+ */
+public final class RestletServerProvider implements ServerProvider {
+ @Override
+ public String name() {
+ return "restlet";
+ }
+
+ @Override
+ public ServerBuilder> serverBuilder() {
+ try {
+ Class.forName(
+ "software.amazon.smithy.java.server.core.HandlerAssembler",
+ false,
+ RestletServerProvider.class.getClassLoader());
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(
+ "The Restlet Smithy server needs smithy-java's server-core on the class path",
+ e);
+ }
+ return new SmithyServerBuilder();
+ }
+
+ @Override
+ public int priority() {
+ // The highest priority is picked first: the SDK's Netty server (0) stays the default when
+ // present, so that adding this extension for its client doesn't change an application's
+ // server. Name it "restlet" to pick it then.
+ return -1;
+ }
+}
diff --git a/org.restlet.ext.smithy.server/src/main/java/org/restlet/ext/smithy/internal/SmithyServerHost.java b/org.restlet.ext.smithy/src/main/java/org/restlet/ext/smithy/internal/SmithyServerHost.java
similarity index 100%
rename from org.restlet.ext.smithy.server/src/main/java/org/restlet/ext/smithy/internal/SmithyServerHost.java
rename to org.restlet.ext.smithy/src/main/java/org/restlet/ext/smithy/internal/SmithyServerHost.java
diff --git a/org.restlet.ext.smithy/src/main/resources/META-INF/services/software.amazon.smithy.java.server.ServerProvider b/org.restlet.ext.smithy/src/main/resources/META-INF/services/software.amazon.smithy.java.server.ServerProvider
new file mode 100644
index 0000000000..f584d79eaa
--- /dev/null
+++ b/org.restlet.ext.smithy/src/main/resources/META-INF/services/software.amazon.smithy.java.server.ServerProvider
@@ -0,0 +1 @@
+org.restlet.ext.smithy.internal.RestletServerProvider
diff --git a/org.restlet.ext.smithy/src/test/java/org/restlet/ext/smithy/SmithyModuleTestCase.java b/org.restlet.ext.smithy/src/test/java/org/restlet/ext/smithy/SmithyModuleTestCase.java
index b96dabe3ef..25690113a5 100644
--- a/org.restlet.ext.smithy/src/test/java/org/restlet/ext/smithy/SmithyModuleTestCase.java
+++ b/org.restlet.ext.smithy/src/test/java/org/restlet/ext/smithy/SmithyModuleTestCase.java
@@ -10,32 +10,41 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.spi.ToolProvider;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.restlet.Client;
/**
- * A named module calls a restJson1 service through the transport, set explicitly or discovered.
- * RPCv2 CBOR is left out: in smithy-java 1.8.0, its client modules split a package
- * (smithy-java#1437) and only work on the class path.
+ * Consumers in child JVMs. A named module calls a restJson1 service through the transport, set
+ * explicitly or discovered, and hosts a generated service with the SDK server on the class path. A
+ * class-path consumer discovers both the server and the transport, over RPCv2 CBOR. In smithy-java
+ * 1.8.0, the RPCv2 CBOR modules and the server modules split packages (smithy-java#1437), so they
+ * stay off the module path.
*/
class SmithyModuleTestCase {
/**
- * Jars left off the module path: the RPCv2 CBOR client modules, and the code generator of the
- * tests, used at build time only, whose modules also split packages.
+ * Jars left off the module path: the RPCv2 CBOR modules and the SDK server, and the code
+ * generator of the tests, used at build time only, whose modules also split packages.
*/
private static final List
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.smithy;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.restlet.Client;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.MediaType;
+import org.restlet.data.Method;
+import org.restlet.data.Protocol;
+import org.restlet.data.Reference;
+import org.restlet.ext.smithy.generated.counter.client.CounterClient;
+import org.restlet.ext.smithy.generated.counter.model.AddInput;
+import org.restlet.ext.smithy.generated.counter.model.AddOutput;
+import org.restlet.ext.smithy.generated.counter.model.Overflow;
+import org.restlet.ext.smithy.generated.counter.service.AddOperation;
+import org.restlet.ext.smithy.generated.counter.service.Counter;
+import org.restlet.ext.smithy.generated.notes.model.GetNoteOutput;
+import org.restlet.ext.smithy.generated.notes.model.PutNoteOutput;
+import org.restlet.ext.smithy.generated.notes.model.RequiredUploadOutput;
+import org.restlet.ext.smithy.generated.notes.model.UploadOutput;
+import org.restlet.ext.smithy.generated.notes.model.WaitOutput;
+import org.restlet.ext.smithy.generated.notes.service.GetNoteOperation;
+import org.restlet.ext.smithy.generated.notes.service.Notes;
+import org.restlet.ext.smithy.generated.notes.service.PutNoteOperation;
+import org.restlet.ext.smithy.generated.notes.service.RequiredUploadOperation;
+import org.restlet.ext.smithy.generated.notes.service.UploadOperation;
+import org.restlet.ext.smithy.generated.notes.service.WaitOperation;
+import org.restlet.ext.smithy.internal.RestletServerProvider;
+import org.restlet.representation.ByteArrayRepresentation;
+import software.amazon.smithy.java.client.core.error.TransportException;
+import software.amazon.smithy.java.core.error.CallException;
+import software.amazon.smithy.java.server.Route;
+import software.amazon.smithy.java.server.Server;
+
+/**
+ * An RPCv2 CBOR service generated by smithy-java, hosted by servers of {@link SmithyServerBuilder},
+ * and called by the generated client over {@link SmithyClientAdapter}.
+ */
+class SmithyServerBuilderTestCase {
+ private RestletSmithyServer server;
+ private Client restlet;
+
+ @AfterEach
+ void stop() throws Exception {
+ if (restlet != null) {
+ restlet.stop();
+ }
+ if (server != null) {
+ server.shutdown().get(10, TimeUnit.SECONDS);
+ }
+ }
+
+ private static Counter counter() {
+ return Counter.builder()
+ .addAddOperation(
+ (AddOperation)
+ (input, context) -> {
+ long total = input.getValue() + 1;
+ if (total > 100) {
+ throw Overflow.builder().total(total).build();
+ }
+ return AddOutput.builder()
+ .total(total)
+ .label(input.getLabel())
+ .build();
+ })
+ .build();
+ }
+
+ private void start(SmithyServerBuilder builder, boolean http2) throws Exception {
+ server = (RestletSmithyServer) builder.endpoints(URI.create("http://localhost:0")).build();
+ server.start();
+ Context context = new Context();
+ context.getParameters().set("followRedirects", "false");
+ if (http2) {
+ context.getParameters().set("httpClientTransportMode", "HTTP2");
+ }
+ restlet = new Client(context, Protocol.HTTP);
+ restlet.start();
+ }
+
+ private String uri(String path) {
+ return "http://localhost:" + server.getActualPorts().getFirst() + path;
+ }
+
+ private CounterClient client(String base) {
+ return CounterClient.builder()
+ .transport(new SmithyClientAdapter(restlet))
+ .endpoint(uri(base))
+ .build();
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {false, true})
+ void generatedClientCallsTheCborService(boolean http2) throws Exception {
+ SmithyServerBuilder builder = new SmithyServerBuilder().addService(counter());
+ if (http2) {
+ Context context = new Context();
+ context.getParameters().set("http.transport.protocol", "HTTP2");
+ builder.context(context);
+ }
+ start(builder, http2);
+ AddOutput output = client("").add(AddInput.builder().value(41L).label("ünï").build());
+ assertEquals(42L, output.getTotal());
+ assertEquals("ünï", output.getLabel());
+ }
+
+ /**
+ * Pins a smithy-java 1.8.0 bug: its RPCv2 server serializes a modeled error without its {@code
+ * __type}, so the client can't tell which error it is.
+ */
+ @Test
+ void cborModeledErrorsLoseTheirTypeUpstream() throws Exception {
+ start(new SmithyServerBuilder().addService(counter()), false);
+ AddInput input = AddInput.builder().value(100L).label("x").build();
+ CounterClient client = client("");
+ CallException error = assertThrows(CallException.class, () -> client.add(input));
+ assertFalse(error instanceof Overflow, error.toString());
+ }
+
+ /** The SDK validates the input: a raw CBOR call, which no client validated, gets a 400. */
+ @Test
+ void invalidInputIsRejected() throws Exception {
+ start(new SmithyServerBuilder().addService(counter()), false);
+ Request request = new Request(Method.POST, uri("/service/Counter/operation/Add"));
+ // {"value": -1, "label": "x"}: the value is below its range.
+ request.setEntity(
+ new ByteArrayRepresentation(
+ HexFormat.of().parseHex("a26576616c756520656c6162656c6178"),
+ MediaType.valueOf("application/cbor")));
+ request.getHeaders().add("smithy-protocol", "rpc-v2-cbor");
+ Response response = restlet.handle(request);
+ assertEquals(400, response.getStatus().getCode());
+ assertEquals("rpc-v2-cbor", response.getHeaders().getFirstValue("smithy-protocol", true));
+ }
+
+ /** Services added under a path go to the route of that path prefix, as with the SDK. */
+ @Test
+ void callsGoToTheRouteOfTheirPathPrefix() throws Exception {
+ start(new SmithyServerBuilder().addService("/service", counter()), false);
+ assertEquals(
+ 2L, client("").add(AddInput.builder().value(1L).label("a").build()).getTotal());
+ Response other = restlet.handle(new Request(Method.GET, uri("/other")));
+ assertEquals(404, other.getStatus().getCode());
+ }
+
+ /** The longest matching prefix wins, whatever the order of the SDK's routes. */
+ @Test
+ void longestPathPrefixWins() throws Exception {
+ Counter root =
+ Counter.builder()
+ .addAddOperation(
+ (AddOperation)
+ (input, context) -> AddOutput.builder().total(-1L).build())
+ .build();
+ start(
+ new SmithyServerBuilder()
+ .addService("/", root)
+ .addService("/serv", root)
+ .addService("/service", counter()),
+ false);
+ assertEquals(
+ 2L, client("").add(AddInput.builder().value(1L).label("a").build()).getTotal());
+ }
+
+ @Test
+ void routesMatchTheirConnectorPortAndSchemeAndHostName() {
+ Reference ref = new Reference("http://api.example.com:9000/service/Counter/operation/Add");
+ assertTrue(matches(route().pathPrefix("/service"), ref));
+ assertFalse(matches(route().pathPrefix("/other"), ref));
+ assertTrue(matches(route().port(8080).protocol("http"), ref));
+ // The port and scheme are the connector's, whatever the Host header says.
+ assertFalse(matches(route().port(9000), ref));
+ assertFalse(matches(route().protocol("https"), ref));
+ assertTrue(matches(route().hostName("API.example.com"), ref));
+ assertFalse(matches(route().hostName("other.com"), ref));
+ }
+
+ private static boolean matches(Route.Builder route, Reference ref) {
+ return RestletSmithyServer.matches(route.build(), ref, "http", 8080);
+ }
+
+ @Test
+ void prefixesMatchTheDecodedPath() {
+ Reference ref = new Reference("http://localhost/my%20svc/service/Counter/operation/Add");
+ assertTrue(matches(route().pathPrefix("/my svc"), ref));
+ }
+
+ @Test
+ void ipv6EndpointsLoseTheirBrackets() {
+ assertEquals("::1", RestletSmithyServer.address(URI.create("http://[::1]:8080")));
+ assertEquals("localhost", RestletSmithyServer.address(URI.create("http://localhost:1")));
+ }
+
+ /** A matching route whose services lack the operation lets a later route take the call. */
+ @Test
+ void callsFallThroughToARouteResolvingThem() throws Exception {
+ Notes notes =
+ Notes.builder()
+ .addGetNoteOperation(
+ (GetNoteOperation)
+ (input, context) -> GetNoteOutput.builder().build())
+ .addPutNoteOperation(
+ (PutNoteOperation)
+ (input, context) -> PutNoteOutput.builder().build())
+ .addRequiredUploadOperation(
+ (RequiredUploadOperation)
+ (input, context) -> RequiredUploadOutput.builder().build())
+ .addUploadOperation(
+ (UploadOperation)
+ (input, context) -> UploadOutput.builder().build())
+ .addWaitOperation(
+ (WaitOperation) (input, context) -> WaitOutput.builder().build())
+ .build();
+ start(
+ new SmithyServerBuilder().addService("/", counter()).addService("/service", notes),
+ false);
+ assertEquals(
+ 2L, client("").add(AddInput.builder().value(1L).label("a").build()).getTotal());
+ }
+
+ /** A Host header naming another port doesn't reach the routes of that port. */
+ @Test
+ void hostHeaderPortsDontSelectRoutes() throws Exception {
+ start(new SmithyServerBuilder().addService(counter()), false);
+ int port = server.getActualPorts().getFirst();
+ server.shutdown().get(10, TimeUnit.SECONDS);
+ server =
+ (RestletSmithyServer)
+ new SmithyServerBuilder()
+ .addService(route().port(port + 1).build())
+ .endpoints(URI.create("http://localhost:" + port))
+ .build();
+ server.start();
+ Request request = new Request(Method.POST, uri("/service/Counter/operation/Add"));
+ request.setHostRef(new Reference("http://localhost:" + (port + 1)));
+ assertEquals(404, restlet.handle(request).getStatus().getCode());
+ }
+
+ /** Routes matching hosts keep the order they were added in. */
+ @Test
+ void routesMatchingHostsKeepTheirOrder() throws Exception {
+ Counter root =
+ Counter.builder()
+ .addAddOperation(
+ (AddOperation)
+ (input, context) -> AddOutput.builder().total(-1L).build())
+ .build();
+ start(
+ new SmithyServerBuilder()
+ .addService(
+ Route.builder()
+ .hostName("localhost")
+ .pathPrefix("/")
+ .services(List.of(root))
+ .build())
+ .addService(route().pathPrefix("/service").build()),
+ false);
+ assertEquals(
+ -1L, client("").add(AddInput.builder().value(1L).label("a").build()).getTotal());
+ }
+
+ private static Route.Builder route() {
+ return Route.builder().services(List.of(counter()));
+ }
+
+ @Test
+ void limitsApplyToEachRoute() throws Exception {
+ start(
+ new SmithyServerBuilder()
+ .addService(counter())
+ .numberOfWorkers(1)
+ .maxRequestBytes(16)
+ .maxCallDuration(Duration.ofSeconds(5)),
+ false);
+ Request request = new Request(Method.POST, uri("/service/Counter/operation/Add"));
+ request.setEntity(
+ new ByteArrayRepresentation(new byte[64], MediaType.valueOf("application/cbor")));
+ request.getHeaders().add("smithy-protocol", "rpc-v2-cbor");
+ assertEquals(413, restlet.handle(request).getStatus().getCode());
+ }
+
+ @Test
+ void invalidSettingsAreRejected() {
+ var builder = new SmithyServerBuilder();
+ assertThrows(IllegalArgumentException.class, () -> builder.numberOfWorkers(0));
+ assertThrows(IllegalArgumentException.class, () -> builder.maxRequestBytes(0));
+ assertThrows(IllegalArgumentException.class, () -> builder.maxCallDuration(Duration.ZERO));
+ assertThrows(
+ IllegalArgumentException.class, () -> builder.endpoints(URI.create("ftp://x:1")));
+ assertThrows(IllegalArgumentException.class, () -> builder.endpoints(new URI[0]));
+ for (String endpoint :
+ List.of(
+ "http://localhost:1/api",
+ "http://localhost:1?q",
+ "http://localhost:1#f",
+ "http://u@localhost:1",
+ "http://my_host:1",
+ "http:localhost")) {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> builder.endpoints(URI.create(endpoint)),
+ endpoint);
+ }
+ assertThrows(NullPointerException.class, () -> builder.maxCallDuration(null));
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new SmithyServerBuilder()
+ .addService(Route.builder().pathPrefix("/x").build())
+ .build());
+ // No service.
+ assertThrows(IllegalStateException.class, builder::build);
+ }
+
+ @Test
+ void serverIsSingleUse() throws Exception {
+ start(new SmithyServerBuilder().addService(counter()), false);
+ assertThrows(IllegalStateException.class, server::start);
+ CounterClient client = client("");
+ server.shutdown().get(10, TimeUnit.SECONDS);
+ // Shutting down again returns the same completed future.
+ assertTrue(server.shutdown().isDone());
+ AddInput input = AddInput.builder().value(1L).label("x").build();
+ assertThrows(TransportException.class, () -> client.add(input));
+ }
+
+ @Test
+ void startFailuresStopWhatStarted() throws Exception {
+ start(new SmithyServerBuilder().addService(counter()), false);
+ int taken = server.getActualPorts().getFirst();
+ Server clash =
+ new SmithyServerBuilder()
+ .addService(counter())
+ .endpoints(URI.create("http://localhost:" + taken))
+ .build();
+ IllegalStateException failure = assertThrows(IllegalStateException.class, clash::start);
+ // The bind failure stays the cause.
+ assertTrue(failure.getCause() != null, failure.toString());
+ clash.shutdown().get(10, TimeUnit.SECONDS);
+ }
+
+ @Test
+ void providerIsNamedRestletAndYieldsToNetty() {
+ var provider = new RestletServerProvider();
+ assertEquals("restlet", provider.name());
+ assertTrue(provider.priority() < 0, "After the SDK's Netty server");
+ assertInstanceOf(SmithyServerBuilder.class, provider.serverBuilder());
+ }
+}
diff --git a/org.restlet.ext.smithy/src/test/smithy/counter.smithy b/org.restlet.ext.smithy/src/test/smithy/counter.smithy
new file mode 100644
index 0000000000..cbff93fec2
--- /dev/null
+++ b/org.restlet.ext.smithy/src/test/smithy/counter.smithy
@@ -0,0 +1,34 @@
+$version: "2"
+
+namespace restlet.test.cbor
+
+use smithy.protocols#rpcv2Cbor
+
+/// An RPCv2 CBOR service whose client and server are generated by smithy-java for the tests.
+@rpcv2Cbor
+service Counter {
+ version: "1"
+ operations: [Add]
+ errors: [Overflow]
+}
+
+operation Add {
+ input := {
+ @required
+ @range(min: 0)
+ value: Long
+
+ @required
+ @length(max: 32)
+ label: String
+ }
+ output := {
+ total: Long
+ label: String
+ }
+}
+
+@error("client")
+structure Overflow {
+ total: Long
+}
diff --git a/org.restlet.ext.smithy.server/src/test/smithy/notes.smithy b/org.restlet.ext.smithy/src/test/smithy/notes.smithy
similarity index 100%
rename from org.restlet.ext.smithy.server/src/test/smithy/notes.smithy
rename to org.restlet.ext.smithy/src/test/smithy/notes.smithy
diff --git a/org.restlet.ext.smithy/src/test/smithy/smithy-build.json b/org.restlet.ext.smithy/src/test/smithy/smithy-build.json
index ee4447f4e3..1f33e810ab 100644
--- a/org.restlet.ext.smithy/src/test/smithy/smithy-build.json
+++ b/org.restlet.ext.smithy/src/test/smithy/smithy-build.json
@@ -1,11 +1,35 @@
{
"version": "1.0",
- "plugins": {
- "java-codegen": {
- "service": "restlet.test#Greeter",
- "namespace": "org.restlet.ext.smithy.generated",
- "modes": ["client"],
- "protocol": "aws.protocols#restJson1"
+ "projections": {
+ "greeter": {
+ "plugins": {
+ "java-codegen": {
+ "service": "restlet.test#Greeter",
+ "namespace": "org.restlet.ext.smithy.generated",
+ "modes": ["client"],
+ "protocol": "aws.protocols#restJson1"
+ }
+ }
+ },
+ "notes": {
+ "plugins": {
+ "java-codegen": {
+ "service": "restlet.test.server#Notes",
+ "namespace": "org.restlet.ext.smithy.generated.notes",
+ "modes": ["client", "server"],
+ "protocol": "aws.protocols#restJson1"
+ }
+ }
+ },
+ "counter": {
+ "plugins": {
+ "java-codegen": {
+ "service": "restlet.test.cbor#Counter",
+ "namespace": "org.restlet.ext.smithy.generated.counter",
+ "modes": ["client", "server"],
+ "protocol": "smithy.protocols#rpcv2Cbor"
+ }
+ }
}
}
}
diff --git a/pom.xml b/pom.xml
index dabc01d58f..85c635469d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,7 +91,6 @@