diff --git a/docs/source-2.0/java/client/codegen-integrations.rst b/docs/source-2.0/java/client/codegen-integrations.rst new file mode 100644 index 00000000000..ddd7e0bf94e --- /dev/null +++ b/docs/source-2.0/java/client/codegen-integrations.rst @@ -0,0 +1,83 @@ +==================== +Codegen Integrations +==================== + +Smithy Java provides a number of client codegen integrations that modify the code +generated by the client codegen plugin. + +-------------- +Waiter codegen +-------------- + +.. warning:: + + Only synchronous waiters are supported at this time. + +:ref:`Waiters ` are a client-side abstraction used to poll a resource until a +desired state is reached, or until it is determined that the resource will never enter +a desirable end state. Waiters can be defined in the Smithy model using the +``smithy.waiters#waitable`` trait. + +For example: + +.. code-block:: smithy + :caption: model.smithy + + use smithy.waiters#waitable + + @waitable( + BucketExists: { + documentation: "Wait until a bucket exists" + acceptors: [ + { + state: "success" + matcher: { + success: true + } + } + { + state: "retry" + matcher: { + errorType: "NotFound" + } + } + ] + } + ) + operation HeadBucket { + input: HeadBucketInput + output: HeadBucketOutput + errors: [NotFound] + } + +Using the waiters integration, you can automatically generate waiters from instances +of the ``waitable`` trait in your Smithy model. If you are using the Smithy Gradle plugins, +you can add this integration to your project like so: + +.. code-block:: kotlin + :caption: build.gradle.kts + + dependencies { + // Add codegen integration as a smithy-build dependency so it can be + // discovered by the client codegen plugin + smithyBuild("software.amazon.smithy.java.codegen:waiters:__smithy_java_version__") + + // Add waiters core package as a runtime dependency + implementation("software.amazon.smithy.java:waiters:__smithy_java_version__") + } + +The code generator will create a class named ``Waiters`` in the client package. +This class provides a method per waiter defined in your Smithy model. The methods return ``Waiter``` instances based on the configuration set in your Smithy model. + +To get an instance of a waiter, call the ``waiters()`` method on the client object: + +.. code-block:: java + + // Get the generated waiter container + var waiters = client.waiters(); + + // Get the configurable waiter from the container + var orderCompletedWaiter = waiters.orderCompleted(); + + // Wait for up to 2 seconds for the waiter to complete. + orderCompletedWaiter.wait(input, Duration.ofSeconds(2)); diff --git a/docs/source-2.0/java/client/configuration.rst b/docs/source-2.0/java/client/configuration.rst new file mode 100644 index 00000000000..b9ff5c7a1e8 --- /dev/null +++ b/docs/source-2.0/java/client/configuration.rst @@ -0,0 +1,506 @@ +=================== +Configuring Clients +=================== + +Smithy Java clients are composed of several components which are configurable at runtime: + +* :ref:`Protocols ` - Defines how to serialize and deserialize a client request. +* :ref:`Transports ` - Manages connections and the sending of data on the wire. +* :ref:`Auth Schemes ` - Adds authentication/authorization information to a client request. +* :ref:`Endpoint Resolvers ` - Resolves the service endpoint to call. +* :ref:`Context Properties ` - Sets typed properties on the client. + +.. _java-client-protocols: + +--------- +Protocols +--------- + +A protocol defines how to (de)serialize and bind data into a request. For example, an HTTP+JSON protocol +would handle the serialization of data into the HTTP message body as JSON and might bind some data to the +HTTP message headers. + +Configure a protocol +^^^^^^^^^^^^^^^^^^^^ + +The Smithy IDL is protocol-agnostic and allows a service to support any number of protocols for transport and +(de)serialization of data. Like the Smithy IDL, Smithy Java clients are also protocol-agnostic and allow users +to configure a protocol at runtime. + +To set a protocol at runtime, add the protocol to the client builder: + +.. code-block:: java + :caption: Set protocol at runtime + + var client = MyGeneratedClient.builder() + .protocol(new MyProtocol()) + .build(); + +.. admonition:: Important + :class: note + + The input/output types of the configured protocol must be compatible with the configured transport. + +Set a default protocol +^^^^^^^^^^^^^^^^^^^^^^ + +While users can set protocols at runtime, a default protocol should be set when generating the client. + +To configure the client plugin with a default protocol, add the protocol’s fully qualified Smithy ID to the protocol setting in the smithy-build.json configuration file: + +.. code-block:: json + :caption: smithy-build.json + + "plugins": { + "java-client-codegen": { + // Set the default protocol for the client. + "protocol": "smithy.protocols#rpcv2Cbor", + // ... additional settings + } + } + + +A service declares compatible protocols by applying the corresponding protocol trait on the service shape. +For example, if a service supports the rpcV2 protocol, it MUST have the protocol trait applied: + +.. code-block:: smithy + :caption: model.smithy + + @rpcV2Cbor // <- Protocol trait + service MyService() + +.. tip:: + + The rpcv2-cbor protocol is a generic binary protocol and is good choice for services that want a fast, compact data format. + +Provided protocols +^^^^^^^^^^^^^^^^^^ + +The Smithy Java framework provides the following protocols: + +.. list-table:: + :header-rows: 1 + :widths: 20 5 30 35 10 + + * - Name + - Type + - Trait + - Description + - Package + * - rpcv2Cbor + - Smithy + - ``smithy.protocols#rpcv2Cbor`` + - HTTP RPC protocol that sends requests and responses with CBOR payloads. + - ``client-rpcv2-cbor`` + * - AWS JSON 1.1 + - AWS + - ``aws.protocols#awsJson1_1`` + - HTTP protocol that sends "POST" requests and responses with JSON payloads. + - ``aws-client-awsjson`` + * - AWS Rest JSON 1.0 + - AWS + - ``aws.protocols#restJson1`` + - HTTP protocol that sends requests and responses with JSON payloads + - ``aws-client-awsjson`` + * - AWS Rest XML + - AWS + - ``aws.protocols#restXml`` + - HTTP protocol that sends requests and responses with XML payloads. + - ``aws-client-restxml`` + +Writing custom protocols +^^^^^^^^^^^^^^^^^^^^^^^^^ + +To create a custom protocol, implement the ``ClientProtocol`` interface from the ``client-core`` package. + +.. tip:: + + If you are writing a service that uses a custom HTTP protocol, you may extend the ``HttpClientProtocol`` + and use one of the codecs provided by Smithy Java to get started. + +Protocols are discovered via Service Provider Interface (SPI). To use a custom protocol, you +must implement a protocol factory that implements ``ClientProtocolFactory``. +Once you have defined your factory, add it’s fully qualified name to the service provider file +(``META-INF/services/software.amazon.smithy.java.runtime.client.core.ClientProtocolFactory``). +As a reminder, make sure the custom protocol trait is applied to the service shape. + +Codec‘s are used by client and server protocols for generic (de)serialization of types into wire data, such as JSON +Protocols SHOULD use an appropriate codec for (de)serialization where possible. +Smithy Java provides XML, JSON, and CBOR codecs. + +When writing a custom protocol, we recommend writing compliance tests, which are used to validate the protocol across +multiple language implementations. The ``protocol-test-harness`` package provides a `JUnit5 `_ +test harness for running protocol compliance tests with Smithy Java. + +.. _java-client-transports: + +---------- +Transports +---------- + +Transports manage connections, and handle the sending/receiving of serialized requests/responses. + +``ClientTransport``'s can also configure default functionality like adding a user-agent header for an HTTP request +by modifying the client builder using the ``configureClient`` method. + +.. admonition:: Important + :class: note + + When overriding the ``configureClient`` method of a ``ClientTransport``, you need to also call the ``configureClient`` + method of the ``MessageExchange``, if you want it to take effect. This allows for transports to override + or even completely remove ``MessageExchange``-wide functionality. + +Transport discovery +^^^^^^^^^^^^^^^^^^^ + +Transport implementations can be discovered by client code generators and dynamic clients via SPI. +To make a transport implementation discoverable, implement the ``ClientTransportFactory`` service provider. + +If no transport is set on a client, the client will attempt to resolve a transport compatible with the current protocol +from the discoverable transport implementations. + +Setting a default transport +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To set a default transport, add the following to your :ref:`smithy-build.json `: + +.. code-block:: json + :caption: smithy-build.json + + "java-client-codegen": { + //... + "transport": { + "http-java": {} + } + } + +.. admonition:: Important + :class: note + + Transports MUST implement the ``ClientTransportFactory`` service provider to + be discoverable by the code generation plugin. + +Provided transports +^^^^^^^^^^^^^^^^^^^ + +* **http-java** - Uses the ``java.net.http.HttpClient`` to send and receive HTTP messages. + This transport is provided by the ``client-http`` module. + + +.. _java-client-authSchemes: + +------------ +Auth schemes +------------ + +Auth schemes add authentication/authorization information to a client request. An auth scheme is composed of: + +1. Scheme ID - A unique identifier for the auth scheme. It SHOULD correspond to the ID of a Smithy trait +defining an auth scheme (see: https://smithy.io/2.0/spec/authentication-traits.html#smithy-api-authdefinition-trait). +2. Identity resolver - An API to acquire the customer's identity. +3. Signer - An API to sign requests using the resolved identity. + +Auth Schemes may be registered by the client at runtime. To register an auth scheme, +use the ``putSupportedAuthSchemes`` method: + +.. code-block:: java + + var client = MyClient.builder() + .putSupportedAuthSchemes(new MyAuthScheme()) + .build() + +Automatic registration +^^^^^^^^^^^^^^^^^^^^^^ + +The Client code generation plugin can discover auth schemes on the classpath. To be discoverable, the auth scheme +must provide an ``AuthSchemeFactory`` implementation and register that implementation via SPI. Smithy Java client +codegen plugin will automatically search the classpath for relevant ``AuthSchemeFactory``` implementations and attempt +to match those with a corresponding trait in the model. + +Effective auth schemes +^^^^^^^^^^^^^^^^^^^^^^ + +Operations may have one or more “effective auth schemes” that could be used to authenticate a request. +Auth scheme traits applied to the service shape are inherited by all service operations unless those +operations have the auth trait applied. + +The ``@auth`` trait define a priority-ordered list of authentication schemes supported by a service or operation. +When applied to a service, it defines the default authentication schemes of every operation in the service. +When applied to an operation, it defines the list of all authentication schemes supported by the operation, +overriding any auth trait specified on a service. + +.. code-block:: smithy + :caption: model.smithy + + @httpBasicAuth + @httpDigestAuth + @httpBearerAuth + service MyService { + version: "2020-01-29" + operations: [ + OperationA + OperationB + ] + } + + // This operation does not have the @auth trait and is bound to a service + // without the @auth trait. The effective set of authentication schemes it + // supports are: httpBasicAuth, httpDigestAuth and httpBearerAuth + operation OperationA {} + + // This operation does have the @auth trait and is bound to a service + // without the @auth trait. The effective set of authentication schemes it + // supports are: httpDigestAuth. + @auth([httpDigestAuth]) + operation OperationB {} + +https://smithy.io/2.0/spec/authentication-traits.html#smithy-api-auth-trait +See :ref:`@auth ` trait for a more thorough discussion on how auth schemes are resolved. + +Identity resolution +^^^^^^^^^^^^^^^^^^^ + +To use an auth scheme in a client, the client must register a corresponding identity resolver +that provides a compatible identity class. Auth schemes can provide a default resolver themselves +or clients can register resolvers via the client builder or via a client plugin. + +.. tip:: + + Multiple identity resolvers can be chained together using the ``IdentityResolver.chain`` method. + +Provided auth schemes +^^^^^^^^^^^^^^^^^^^^^ + +A number of auth schemes are provided by default in the ``client-http`` package. These include: + +* :ref:`httpBearerAuth ` - Supports HTTP Bearer authentication as defined in RFC 6750. +* :ref:`httpApiKeyAuth ` - Supports HTTP authentication using an API key sent in a header or query string parameter. +* :ref:`httpBasicAuth ` - Supports HTTP Basic authentication as defined in RFC 2617. + +Add the ``client-http`` package as a dependency of your project to make these auth schemes available in your service. + +Worked example: Adding HTTP API key authentication +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Consider a Smithy modeled API for a service, ``ExampleService``. We would like to enable users of our generated SDK +to authenticate to the API using an API key sent via an ``x-api-key`` HTTP header. +Smithy Java already provides an ``httpApiKeyAuth`` auth scheme that we can use to allow +this API key authentication. + +Before we can add any auth scheme implementations to our generated client we must first add the associated +:ref:`@httpApiKeyAuth ` scheme trait to our service model: + +.. code-block:: smithy + :caption: model.smithy + + namespace com.example + + @httpApiKeyAuth(name: "X-Api-Key", in: "header") // <- Add auth scheme trait + service ExampleService { + version: "2025-05-05" + // ... + } + +Authentication schemes are effectively part of your services interface and so (outside of testing) +SHOULD always be modeled in your Smithy model using a trait. See the :ref:`@authDefinition ` +trait for more information on how to define a custom auth scheme in your Smithy model. + +Now that we have added our auth trait to the Smithy model we need to add a corresponding auth scheme implementation +to our client’s dependencies. The ``client-http package`` provides an ``HttpApiKeyAuthScheme`` implementation corresponding +to the ``@httpApiKeyAuth`` trait: + +.. code-block:: kotlin + :caption: build.gradle.kts + + dependencies { + // Add the HTTP auth scheme to the classpath + implementation("software.amazon.smithy.java:client-http:__smithy_java_version__") + // ... + } + +The ``HttpApiKeyAuthScheme`` class implements the ``AuthSchemeFactory`` service provider, making the auth scheme +discoverable by the client codegen plugin. + +The built-in Smithy Java HTTP auth schemes all require one or more compatible ``IdentityResolver`` +to be register with the client. This resolver will handle actually fetching the clients credentials. +The ``HttpApiKeyAuthScheme`` scheme needs an identity resolver that returns an ``ApiKeyIdentity``. +For testing purposes we will provide a static resolver as follows: + +.. code-block:: java + + var client = ExampleService.builder() + .addIdentityResolver( + IdentityResolver.of(new ApiKeyIdentity.create("example-api-key")) + ) + .build() + +Or, we could create a custom resolver that resolves the ``ApiKeyIdentity`` from an environment variable, ``EXAMPLE_API_KEY``: + +.. code-block:: java + :caption: Environment variable identity resolver implementation + + public final class EnvironmentVariableIdentityResolver implements IdentityResolver { + private static final String API_KEY_PROPERTY = "EXAMPLE_API_KEY" + + @Override + public Class identityType() { + return ApiKeyIdentity.class; + } + + @Override + public CompletableFuture> resolveIdentity(AuthProperties requestProperties) { + String apiKey = System.getenv(API_KEY_PROPERTY); + + if (apiKey == null || apiKey.isEmpty()) + return CompletableFuture.completedFuture( + IdentityResult.ofError(getClass(), "Could not find API KEY") + ); + } + + return CompletableFuture.completedFuture(IdentityResult.of(ApiKeyIdentity.create(apiKey))); + } + +Smithy Java also allows identity resolvers to be chained together if we want to check multiple locations for the client: + +.. code-block:: java + :caption: Chaining identity resolvers + + IdentityResolver.chain(List.of(new FirstResolver(), new SecondResolver()) + +.. _java-client-endpoints: + +----------------- +Endpoint resolver +----------------- + +Endpoint resolvers determine the endpoint to use for an operation. For example, an endpoint resolver could +determine what subdomain to use, i.e. ``us-east-2.myservice.com`` based on a region setting on the client. + +To set a static endpoint for a client use the following client builder setter: + +.. code-block:: java + + client.builder() + .endpointResolver(EndpointResolver.staticResolver("https://example.com")) + .build() + +.. tip:: + + Create a common endpoint resolver for your organization that can be shared across clients. + +.. _java-client-context: + +------------------- +Context properties +------------------- + +Smithy Java clients allow users to add any configurations to a typed property bag, via the putConfig method. +The properties are tied to a typed key and used by client pipeline components such as request signers. + +For example, a ``REGION`` property might need to be set on the client in order for a ``Sigv4`` request signer to correctly function. +Configuration parameters can be set on a client using a typed property key via the putConfig method: + +.. code-block:: java + :caption: Setting REGION context property + + static Context.Key MY_PROPERTY = Context.key("a config property"); + + ... + + var client = MyClient.builder() + .putContext(MY_PROPERTY, "value") + .build(); + +Custom builder setters +^^^^^^^^^^^^^^^^^^^^^^ + +For common settings on a client, it is often desirable to use specifically-named setter methods such as ``.region("us-east-1")`` +rather than requiring users to know the specific context property to use for a configuration parameter. +The ``ClientSetting`` interface can be used to create a custom setter that for client builders. + +For example we would write a custom setting as: + +.. code-block:: java + :caption: custom client setting implementation + + public interface CustomSetting> extends ClientSetting { + Context.Key MY_PROPERTY = Context.key("A custom string configuration property"); + + default B custom(String custom) { + return putConfig(MY_PROPERTY, custom); + } + } + +.. tip:: + + If a config property is required, make sure to validate that it exists using a default plugin (see below) + +A client setting can then be added to our generated clients using the defaultSettings setting in the smithy-build.json file: + +.. code-block:: json + :caption: smithy-build.json + + "java-client-codegen": { + //... + "defaultSettings": [ + "com.example.settings.CustomSetting" + ], + //... + } + +Now we can use our new setting as follows: + +.. code-block:: java + + var client = MyClient.builder() + .custom("that was easy!") + .build(); + +.. tip:: + + Default settings are typically paired with a default plugin to add the configuration and behavior of a feature + (respectively) to a client by default. + +Composing settings +^^^^^^^^^^^^^^^^^^ + +Some features require multiple custom settings. Because custom settings are simply Java interfaces, we can compose them. + +For example, the SigV4 auth scheme requires that region, clock, and signing name settings be configured on a client. We can define the ``SigV4Settings`` interface as follows: + +.. code-block:: java + :caption: composed setting + + public interface SigV4Settings> + extends ClockSetting, RegionSetting { + + /** + * Service name to use for signing. For example {@code lambda}. + */ + Context.Key SIGNING_NAME = Context.key("Signing name to use for computing SigV4 signatures."); + + /** + * Signing name to use for the SigV4 signing process. + * + *

The signing name is typically the name of the service. For example {@code "lambda"}. + * + * @param signingName signing name. + */ + default B signingName(String signingName) { + // Validation of the signing name + if (signingName == null || signingName.isEmpty()) { + throw new IllegalArgumentException("signingName cannot be null or empty"); + } + return putConfig(SIGNING_NAME, signingName); + } + } + +When the ``SigV4Settings`` interface is added to the codegen configuration as a default setting, the +``ClockSetting`` and ``RegionSetting`` setters will be included in the generated client’s builder. + +.. tip:: + + Create a custom setting class for your organization that aggregates all common settings for your clients. + This minimizes the number of code generation configurations you need to provide to create a functional client. + Add new settings to the aggregate setting to add them to clients without changing the codegen configuration. + diff --git a/docs/source-2.0/java/client/customization.rst b/docs/source-2.0/java/client/customization.rst new file mode 100644 index 00000000000..6aaa1155f88 --- /dev/null +++ b/docs/source-2.0/java/client/customization.rst @@ -0,0 +1,129 @@ +=========================== +Customizing Client Behavior +=========================== + +Request-level overrides +----------------------- + +Smithy Java supports client configuration overrides that are applied to a single request. +Create and use an override as follows: + +.. code-block:: java + + var requestOverride = RequestOverrideConfig.builder() + .protocol(...) + .addIdentityResolver(...) + // etc... + .build() + + var fooOutput = client.callFoo(fooInput, requestOverride); + +Each generated client will contain a client-specific ``RequestOverride`` class that includes any custom +configuration settings. To get a client-specific override builder, use the ``.requestOverrideBuilder`` method on the +generated client: + +.. code-block:: java + + var overrideBuilder = MyClient.requestOverrideBuilder(); + var override = overrideBuilder.customSetting("foo").build(); + var output = client.createFoo(input, override); + +Interceptors +------------ + +Interceptors allow “injecting” logic into specific stages of the request execution pipeline. +Logic injection is done with hooks that the interceptor implements. + +The following hooks are supported: + +* **readBeforeExecution** - called at the start of an execution, before the client does anything else +* **modifyBeforeSerialization** - called before the input message is serialized into a transport message +* **readBeforeSerialization** - called before the input is serialized into a transport request +* **readAfterSerialization** - called after the input message is marshalled into a protocol-specific request +* **modifyBeforeRetryLoop** - called before the retry loop is entered that can be used to modify and return a new request +* **readBeforeAttempt** - called before each attempt at sending the transmission * request message to the service. +* **modifyBeforeSigning** - called before the request is signed; this method can modify and return a new request +* **readBeforeSigning** - called before the transport request message is signed +* **readAfterSigning** - called after the transport request message is signed +* **modifyBeforeTransmit** - called before the transport request message is sent to the service +* **readBeforeTransmit** - called before the transport request message is sent to the * service +* **readAfterTransmit** - called after the transport request message is sent to the service and a transport response message is received +* **modifyBeforeDeserialization** - called before the response is deserialized +* **readBeforeDeserialization** - called before the response is deserialized +* **readAfterDeserialization** - called after the transport response message is deserialized +* **modifyBeforeAttemptCompletion** - called when an attempt is completed. This method can + modify and return a new output or error matching the currently executing operation +* **readAfterAttempt** - called when an attempt is completed +* **modifyBeforeCompletion** - called when an execution is completed +* **readAfterExecution** - called when an execution is completed + +Interceptors implement the ``ClientInterceptor`` interface and override one or more hook methods. + +Interceptors can be registered for all calls on a client: + +.. code-block:: java + + var client = MyClient.builder() + .addInterceptor(new MyInterceptor()) + .build(); + +Or for a single request: + +.. code-block:: java + + var requestOverride = RequestOverrideConfig.builder() + .addInterceptor(new MyInterceptor()) + .build() + + var fooOutput = client.callFoo(fooInput, requestOverride); + +Plugins +------- + +Plugins implement the ``ClientPlugin`` interface to modify client configuration when the client is created or when +an operation is called (if added to a ``RequestOverrideConfig``). + +Plugins may set ``IdentityResolvers``, ``EndpointResolvers``, ``Interceptors``, ``AuthSchemeResolvers``, +and other client configuration in a repeatable way. + +.. tip:: + + Create one or more common plugins for your organization to apply a standard configuration to generated clients. + + +To apply a plugins to a client at runtime, use the ``addPlugin`` method on the client builder: + +.. code-block:: java + + var client = MyClient.builder() + .addPlugin(new MyPlugin()) + .build(); + +.. admonition:: Important + :class: note + + Plugins are run once at client build time if added to the client builder, or each time a request is made if + added through a ``RequestOverrideConfig``. + +Default plugins +^^^^^^^^^^^^^^^ + +Plugins can be applied by default at client instantiation. To apply a plugin by default, add the plugin’s +fully qualified name to the ``defaultPlugins``` setting to your :ref`smithy-build ` configuration: + +.. code-block:: json + :caption: smithy-build.json + + "java-client-codegen": { + // ... + "defaultPlugins": [ + "fully.qualified.plugin.name.MyPlugin" + ] + } + +.. admonition:: Important + :class: note + + Plugins must have a public, zero-arg constructor defined. The code generator will check for an + empty constructor when resolving default plugins and fail if one is not found. + diff --git a/docs/source-2.0/java/client/dynamic-client.rst b/docs/source-2.0/java/client/dynamic-client.rst new file mode 100644 index 00000000000..0346dd15958 --- /dev/null +++ b/docs/source-2.0/java/client/dynamic-client.rst @@ -0,0 +1,69 @@ +============== +Dynamic Client +============== + +The dynamic client is used to interact with services without a code-generated client. +The dynamic client loads Smithy models at runtime, converting them to a schema-based client. +Users can call a modeled service using document types as input and output. + +.. warning:: + + The dynamic client does not currently support streaming or event streaming. + +Usage +----- + +Add the dynamic-client module as a dependency of your project: + +.. code-block:: kotlin + :caption: build.gradle.kts + + dependencies { + implementation("software.amazon.smithy.java:dynamicclient:__smithy_java_version__") + } + +Then, load a Smithy model: + +.. code-block:: java + + import software.amazon.smithy.java.dynamicclient + import software.amazon.smithy.model.Model; + ... + + var model = Model.assembler() + .addImport("/path/to/model.json") + .assemble() + .unwrap(); + +Then, select a service shape in the loaded model to call. In this case, the ``CoffeeShop`` service: + +.. code-block:: java + + var shapeId = ShapeId.from("com.example#CoffeeShop"); + +Now, create the ``DynamicClient`` instance for this model and service: + +.. code-block:: java + + var client = DynamicClient.builder() + .service(shapeId) + .model(model) + .protocol(new RestJsonClientProtocol(shapeId)) + .transport(new JavaHttpClientTransport()) + .endpointResolver(EndpointResolver.staticEndpoint("https://api.cafe.example.com")) + .build(); + +.. admonition:: Important + :class: note + + If an explicit protocol and transport are not provided to the builder, the builder will attempt to find protocol + and transport implementations on the classpath that match the protocol traits attached to the service. + +To call the service, create an input using a ``Document`` that mirrors what's defined in the Smithy model. +The ``Document.createFromObject`` method can create a ``Document`` from a map: + +.. code-block:: java + + var input = Document.createFromObject(Map.of("coffeeType", "COLD_BREW")); + var result = client.call("CreateOrder", input).get(); + System.out.println(result); diff --git a/docs/source-2.0/java/client/generating-clients.rst b/docs/source-2.0/java/client/generating-clients.rst new file mode 100644 index 00000000000..cdd37047e23 --- /dev/null +++ b/docs/source-2.0/java/client/generating-clients.rst @@ -0,0 +1,216 @@ +================== +Generating Clients +================== + +The Smithy Java :ref:`build plugin `, ``java-client-codegen``, generates Java clients from Smithy models, +and can be executed with `Gradle `_ (recommended) or the :ref:`Smithy CLI `. + +.. admonition:: Important + :class: note + + The Smithy CLI is a prerequisite for this guide. + See the :doc:`Smithy CLI installation guide <../../guides/smithy-cli/cli_installation>` + if you do not already have the CLI installed. + +----------------------------------- +Initial setup: Gradle (recommended) +----------------------------------- + +To generate a Java client for a service, start by creating a new Smithy Gradle project. + +The ``smithy init``` CLI command can be used to create a new Smithy Gradle project: + +.. code-block:: sh + + smithy init -t quickstart-gradle + +A Smithy Gradle project should contain a Gradle settings file, a Gradle build script, +a :ref:`smithy-build.json ` configuration file, and a `model/` directory +containing Smithy models. For example: + +.. code-block:: sh + + my-project/ + ├── model/ + │ ├── ... + ├── smithy-build.json + ├── build.gradle.kts + └── settings.gradle + +Apply the `smithy-base`_ plugin to your Gradle build script to build your Smithy model +and execute build plugins: + +.. code-block:: diff + :caption: build.gradle.kts + + plugins { + `java-library` + + id("software.amazon.smithy.gradle.smithy-base") version "__smithy_gradle_version__" + } + +Add the following dependencies to your project: + + 1. Add the codegen `plugins`_ package as a ``smithyBuild`` dependency of your project. This makes the codegen + plugins discoverable by the Smithy build task. + 2. Add the `client-core`_ package as a runtime dependency of your package. + The ``client-core``` package is the only required dependency for all generated clients. + 3. Add any additional dependencies used by your client, such as protocols or auth schemes. + +.. code-block:: kotlin + :caption: build.gradle.kts + + dependencies { + // Add the code generation plugins to the smithy build classpath + smithyBuild("software.amazon.smithy.java.codegen:plugins:__smithy_java_version__") + + // Add the client-core dependency needed by the generated code + implementation("software.amazon.smithy.java:client-core:__smithy_java_version__") + + // Protocol implementations and auth schemes used by client + implementation("com.example:my-protocol:1.0.0") + implementation("com.example:my-auth-scheme:1.0.0") + } + +Now, define a service model in a ``model/`` directory at the root of your project. +The ``smithy-base``` Gradle plugin will automatically discover any models added to that directory. + +--------------------------- +Configuring code generation +--------------------------- + +In order to execute code generation, the ``java-client-codegen`` plugin must be added to +your :ref:`smithy-build ` config: + +.. code-block:: diff + :caption: smithy-build.json + + { + "version": "1.0", + "plugins": { + + "java-client-codegen": { + + "service": "com.example#CoffeeShop", // <- Replace with your service's ID + + // Generated Java code will use this as the root package namespace + + "namespace": "com.example.cafe" + + } + } + } + +---------------------------------------- +Add generated code to the Java sourceSet +---------------------------------------- + +Your package is now configured to generate Java client source code. However, the generated code must be +added to a `sourceSet `_ to be +compiled by Gradle. To add the generated code to the ``main`` sourceSet, add the following to your +Gradle build script: + +.. code-block:: kotlin + :caption: build.gradle.kts + + // Add generated Java sources to the main sourceSet so they are compiled alongside + // any other Java code in your package + afterEvaluate { + val clientPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-client-codegen") + sourceSets { + main { + java { + srcDir(clientPath) + } + } + } + } + + // Ensure client files are generated before java compilation is executed. + tasks.named("compileJava") { + dependsOn("smithyBuild") + } + +--------------- +Generating code +--------------- + +To generate and compile your client code, run a build from the root of your Gradle project: + +.. code-block:: sh + + ./gradlew clean build + +Building the project will generate code in the +``build/smithy-projections//source/java-client-codegen/`` directory. + +---------------- +Complete example +---------------- + +The following Gradle build script and ``smithy-build.json`` files provide a complete example of how to configure a +Gradle project to generate a Smithy Java client: + +.. code-block:: kotlin + :caption: build.gradle.kts + + plugins { + `java-library` + id("software.amazon.smithy.gradle.smithy-base") version "__smithy_gradle_version__" + } + + dependencies { + // Add the code generation plugin to the smithy build dependencies + smithyBuild("software.amazon.smithy.java.codegen:client:__smithy_java_version__") + + // Add any smithy model dependencies as `implementation` dependencies here. + // For example, you might add additional trait packages here. + implementation("...") + + // Add the client-core dependency needed by the generated code + implementation("software.amazon.smithy.java:client-core:__smithy_java_version__"") + + // Also add your protocol implementations or auth schemes as dependencies + implementation("com.example:my-protocol:1.0.0") + implementation("com.example:my-auth-scheme:1.0.0") + } + + // Add generated Java sources to the main sourceSet so they are compiled alongside + // any other java code in your package + afterEvaluate { + val clientPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-client-codegen") + sourceSets { + main { + java { + srcDir(clientPath) + } + } + } + } + + // Ensure client files are generated before java compilation is executed. + tasks.named("compileJava") { + dependsOn("smithyBuild") + } + + repositories { + mavenLocal() + mavenCentral() + } + +.. code-block:: json + :caption: smithy-build.json + + { + "version": "1.0", + "plugins": { + "java-client-codegen": { + "service": "com.example#CoffeeShop", + "namespace": "com.example.cafe", + // Default protocol for the client. Must have a corresponding trait in the + // model and implementation discoverable via SPI (see section on protocols below) + "protocol": "aws.protocols#restJson1", + // Adds a common header to all generated files + "headerFile": "license.txt" + } + } + } + + +.. _smithy-base: https://github.com/smithy-lang/smithy-gradle-plugin#smithy-base-plugin +.. _client-core: https://mvnrepository.com/artifact/software.amazon.smithy.java/client-core +.. _plugins: https://mvnrepository.com/artifact/software.amazon.smithy.java.codegen/plugins diff --git a/docs/source-2.0/java/client/index.rst b/docs/source-2.0/java/client/index.rst new file mode 100644 index 00000000000..149990d4198 --- /dev/null +++ b/docs/source-2.0/java/client/index.rst @@ -0,0 +1,25 @@ +================= +Client User Guide +================= + +This guide walks through how to use `Smithy Java `_ to generate +Java clients from a Smithy model of a service. + +.. warning:: + + Smithy Java is currently in Developer Preview. All interfaces are subject to change. + +For a general overview of the Smithy IDL, see the Smithy :doc:`../../quickstart` Guide + +For a general introduction the the Smithy Java framework, see the Smithy :doc:`../quickstart` Guide. + +.. toctree:: + :maxdepth: 1 + + generating-clients + codegen-integrations + dynamic-client + configuration + customization + plugins + diff --git a/docs/source-2.0/java/client/plugins.rst b/docs/source-2.0/java/client/plugins.rst new file mode 100644 index 00000000000..0456f14e8bd --- /dev/null +++ b/docs/source-2.0/java/client/plugins.rst @@ -0,0 +1,120 @@ +============== +Client Plugins +============== + +Smithy Java provides a number of client plugins that add functionality to generated clients. + +---------- +MockPlugin +---------- + +The ``MockPlugin`` intercepts client requests and returns pre-defined mock responses, shapes, or exceptions. +The plugin facilitates testing client request/responses without the need to set up a mock server. + +Usage +^^^^^ + +Add the ``mock-client-plugin`` package as a dependency: + +.. code-block:: java + :caption: build.gradle.kts + + dependencies { + implementation("software.amazon.smithy.java.client.http.mock:mock-client-plugin:__smithy_java_version__") + } + +Use the plugin to return canned responses from the http client: + +.. code-block:: java + + // (1) Create a response queue and add a set of canned responses that will be returned + // from client in the order in which they were added to the queue. + var mockQueue = new MockQueue(); + mockQueue.enqueue( + HttpResponse.builder() + .statusCode(200) + .body(DataStream.ofString("{\"id\":\"1\"}")) + .build() + ); + mockQueue.enqueue( + HttpResponse.builder() + .statusCode(429) + .body(DataStream.ofString("{\"__type\":\"InvalidSprocketId\"}")) + .build() + ); + + // (2) Create a MockPlugin instance using the request queue created above. + var mockPlugin = MockPlugin.builder().addQueue(mockQueue).build(); + + // (3) Create a client instance that uses the MockPlugin. + var client = SprocketClient.builder() + .addPlugin(mockPlugin) + .build(); + + // (4) Call client to get the first canned response from the queue. + var response = client.createSprocket(CreateSprocketRequest.builder().id(2).build()); + + // (5) Inspect the HTTP requests that were sent to the client. + var requests = mock.getRequests(); + +--------------- +UserAgentPlugin +--------------- + +The ``UserAgentPlugin``adds a default ``User-Agent`` header to an HTTP request if none is set. + +.. note:: + + This plugin is applied by default by all built-in HTTP transports. + +The added agent header has the form: + +.. code-block:: + + smithy-java/ ua/ os/# lang/java# m/ + +.. list-table:: + :header-rows: 1 + :widths: 20 10 70 + + * - Property + - Example + - Description + * - ``smithy`` + - ``0.0.1`` + - Smithy Java version in use by client in SemVer format. + * - ``ua`` + - ``2.1`` + - Version of the ``User-Agent`` metadata + * - ``os-family`` + - ``macos`` + - Operating system client is running on + * - ``version`` + - ``14.6.1`` + - version of OS or Language the client is running on. + * - ``features`` + - ``a,b`` + - Comma-separated list of feature Ids + +Feature IDs +^^^^^^^^^^^ + +Feature ID’s are set via the ``CallContext#FEATURE_IDS`` context key. +To add a new feature ID, update the FEATURE_IDS context key within an interceptor or in the client builder + +.. code-block:: java + + // (1) Get the existing feature ids + Set features = context.get(CallContext.FEATURE_IDS); + + // (2) Update with a new feature + features.add(new FeatureId() { + @Override + public String getShortName() { + return "short-name"; + } + }); + +A pair of ``app/{id}`` is added if ``CallContext#APPLICATION_ID`` is set, or a value is set in +the ``aws.userAgentAppId`` system property, or the value set in the ``AWS_SDK_UA_APP_ID`` environment variable. +See the `App ID `_ guide for more information. diff --git a/docs/source-2.0/java/index.rst b/docs/source-2.0/java/index.rst index daa88aff8d1..8b5bc501525 100644 --- a/docs/source-2.0/java/index.rst +++ b/docs/source-2.0/java/index.rst @@ -6,6 +6,7 @@ Java :maxdepth: 1 quickstart + client/index .. toctree:: :caption: References