Repository: smithy-lang/smithy
Pull request #2014 (2023-10-20 17:33:02)

Add automatic integration configuration
This adds a configuration step for integrations, allowing them to use `smithy-build.json` settings that aren't used by the generator that they target.

Generators using the node mapper helper for their settings will have this set for free. Others will need to grab it themselves.


### FAQ

#### Why not just have integration drive configuration through traits

Yes that's the *ideal* but some things just don't make sense as traits. For example, a trait that controls whether a documentation generator scans your system for the presence of a build tool and all the other dependencies you need doesn't make sense as a trait.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/docs/source-2.0/aws/aws-endpoints-region.rst b/docs/source-2.0/aws/aws-endpoints-region.rst
new file mode 100644
index 000000000..10c0db91f
--- /dev/null
+++ b/docs/source-2.0/aws/aws-endpoints-region.rst
@@ -0,0 +1,414 @@
+.. _aws-endpoints:
+
+===============================
+AWS Declarative Endpoint Traits
+===============================
+
+This document defines AWS declarative endpoint traits.
+
+.. _aws-endpoints-overview:
+
+----------------------
+AWS Endpoints Overview
+----------------------
+
+An endpoint is used to connect programmatically to an AWS service. An endpoint is the URL of the
+entry point for an AWS web service.
+
+Most AWS services are regional: they offer regional endpoints and the service's resources are independent
+of similar resources in other regions.
+
+.. _aws-endpoints-region:
+
+Region
+    Each `region <https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/regions.html>`_
+    consists of multiple availability zones within a single geographic area. Regions themselves are isolated
+    and independent from other regions.
+
+.. _aws-partition:
+
+Partition
+    AWS groups regions into
+    `partitions <https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/partitions.html>`_.
+    Every region is in exactly one partition and each partition has one or more regions.
+    AWS commercial Regions are in the ``aws`` partition, Regions in China are in the ``aws-cn`` partition,
+    and AWS GovCloud Regions are in the ``aws-us-gov`` partition.
+
+.. _fips-endpoints:
+
+FIPS Endpoints
+    Some AWS services have endpoints that support Federal Information Processing Standard (FIPS) 140-2.
+    For a list of FIPS endpoints, see
+    `FIPS endpoints by Service
+    <http://aws.amazon.com/compliance/fips/#FIPS_Endpoints_by_Service>`_.
+
+.. _dualstack-endpoints:
+
+Dual stack Endpoints
+    Some AWS services offer dual stack endpoints, so that you can access them using either IPv4 or IPv6 requests.
+    For a list of services that support dual stack endpoints, see `AWS services that support IPv6
+    <https://docs.aws.amazon.com/vpc/latest/userguide/aws-ipv6-support.html>`_.
+
+.. smithy-trait:: aws.endpoints#endpointsModifier
+.. _aws.endpoints#endpointsModifier-trait:
+
+-----------------------------------------
+``aws.endpoints#endpointsModifier`` trait
+-----------------------------------------
+
+Summary
+    A meta-trait that marks a trait as an endpoint modifier.  Traits that are marked with this trait are
+    applied to service shapes or operation shapes to indicate how a client can resolve
+    endpoints for that service or operation.
+Trait selector
+    ``[trait|trait]``
+Value type
+    Annotation trait
+
+The following example defines a service with ``standardRegionalEndpoints`` modified by
+the hypothetical ``fooExample`` endpoint modifier.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use aws.endpoints#endpointsModifier
+    use aws.endpoints#standardRegionalEndpoints
+
+    @endpointsModifier
+    @trait(selector: "service")
+    structure fooExample {}
+
+    @standardRegionalEndpoints
+    @fooExample
+    service MyService {
+        version: "2020-04-02"
+    }
+
+Because endpoint modification definitions are just specialized shapes, they
+can also support configuration settings.
+
+.. code-block:: smithy
+
+    $version: "2"
+    namespace smithy.example
+
+    use aws.endpoints#endpointsModifier
+    use aws.endpoints#standardRegionalEndpoints
+
+    @endpointsModifier
+    @trait(selector: "service")
+    structure endpointSuffix {
+        suffix: String
+    }
+
+    @standardRegionalEndpoints
+    @endpointSuffix(suffix="-suffix")
+    service MyService {
+        version: "2020-04-02"
+    }
+
+
+
+.. smithy-trait:: aws.endpoints#standardRegionalEndpoints
+.. _aws.endpoints#standardRegionalEndpoints-trait:
+
+-------------------------------------------------
+``aws.endpoints#standardRegionalEndpoints`` trait
+-------------------------------------------------
+
+Summary
+    An :ref:`endpoints modifier trait <aws.endpoints#endpointsModifier-trait>`
+    that indicates that a service's endpoints should be resolved using the standard AWS regional
+    patterns:
+
+    - Default: ``{service}.{region}.{dnsSuffix}``
+    - Fips: ``{service}-fips.{region}.{dnsSuffix}``
+    - Dualstack: ``{service}.{region}.{dualStackDnsSuffix}``
+    - Fips/Dualstack: ``{service}-fips.{region}.{dualStackDnsSuffix}``
+
+Trait selector
+    ``service``
+Trait value
+    A ``structure`` with the following properties:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 10 30 60
+
+    * - Property
+      - Type
+      - Description
+    * - partitionSpecialCases
+      - ``map`` of partition to `PartitionSpecialCase object`_
+      - A map of partition to partition special cases - endpoints for a partition that do not follow the
+        standard patterns.
+    * - regionSpecialCases
+      - ``map`` of region to `RegionSpecialCase object`_
+      - A map of region to regional special cases - endpoints for a region that do not follow the
+        standard patterns.
+
+Conflicts with
+    :ref:`aws.endpoints#standardPartitionalEndpoints-trait`
+
+Most AWS services are regionalized and are strongly encouraged to follow
+the standard endpoint patterns defined above for consistency, and to
+ensure that endpoints are forwards compatible, and that SDK updates are
+not required when the service launches in a new region or partition.
+
+The following example defines a service that uses the standard regional endpoints:
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use aws.endpoints#standardRegionalEndpoints
+
+    @standardRegionalEndpoints
+    service MyService {
+        version: "2020-04-02"
+    }
+
+While services are strongly encouraged to follow standard endpoint patterns,
+there are occasional exceptions and special cases.  The following example defines
+a service that uses standard regional endpoints, but uses a non-standard pattern for
+FIPS endpoints in US GovCloud:
+
+.. code-block:: smithy
+
+    @standardRegionalEndpoints(
+        partitionSpecialCases: {
+            aws-us-gov: [
+                {
+                    endpoint: "myservice.{region}.{dnsSuffix}",
+                    fips: true
+                }
+            ]
+        }
+    )
+    service MyService {
+        version: "2020-04-02"
+    }
+
+``PartitionSpecialCase`` object
+-------------------------------
+
+A ``PartitionSpecialCase`` defines the endpoint pattern to apply for all regional endpoints
+in the given partition. A PartitionSpecialCase object contains the following properties:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 10 30 60
+
+    * - Property name
+      - Type
+      - Description
+    * - endpoint
+      - ``string``
+      - **Required**. The special-cased endpoint template.
+    * - dualStack
+      - ``boolean``
+      - When ``true`` the special case will apply to dualstack endpoint variants.
+    * - fips
+      - ``boolean``
+      - When ``true`` the special case will apply to fips endpoint variants.
+
+
+``RegionSpecialCase`` object
+----------------------------
+
+A ``RegionSpecialCase`` object contains the following properties:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 10 30 60
+
+    * - Property name
+      - Type
+      - Description
+    * - endpoint
+      - ``string``
+      - **Required**. The special-cased endpoint template.
+    * - dualStack
+      - ``boolean``
+      - When ``true`` the special case will apply to dualstack endpoint variants.
+    * - fips
+      - ``boolean``
+      - When ``true`` the special case will apply to fips endpoint variants.
+    * - signingRegion
+      - ``string``
+      - Overrides the signingRegion used for this region.
+
+.. smithy-trait:: aws.endpoints#standardPartitionalEndpoints
+.. _aws.endpoints#standardPartitionalEndpoints-trait:
+
+----------------------------------------------------
+``aws.endpoints#standardPartitionalEndpoints`` trait
+----------------------------------------------------
+
+Summary
+    An :ref:`endpoints modifier trait <aws.endpoints#endpointsModifier-trait>`
+    that indicates that a service is
+    `partitional <https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html#global-services-that-are-unique-by-partition>`_
+    and a single endpoint should be resolved per partition.
+Trait selector
+    ``service``
+Trait value
+    A ``structure`` with the following properties:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 10 30 60
+
+    * - Property
+      - Type
+      - Description
+    * - endpointPatternType
+      - ``string``
+      - **Required** The pattern type to use for the partition endpoint.  This value can be set to ``service_dnsSuffix`` to
+        use the ``{service}.{dnsSuffix}`` pattern or ``service_region_dnsSuffix`` to use
+        ``{service}.{region}.{dnsSuffix}``.
+    * - partitionEndpointSpecialCases
+      - ``map`` of partition to `PartitionEndpointSpecialCase object`_
+      - A map of partition to partition endpoint special cases - partitions that do not follow the
+        service's standard patterns or are located in a region other than the partition's
+        ``defaultGlobalRegion``.
+
+Conflicts with
+    :ref:`aws.endpoints#standardRegionalEndpoints-trait`
+
+Partitional services (also known as "global" services) resolve a single endpoint per partition.
+That single endpoint is located in the partition's ``defaultGlobalRegion``. Partitional
+services should follow one of two standard patterns:
+
+- ``service_dnsSuffix``: ``{service}.{dnsSuffix}``
+- ``service_region_dnsSuffix``: ``{service}.{region}.{dnsSuffix}``
+
+The following example defines a partitional service that uses ``{service}.{dnsSuffix}``:
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use aws.endpoints#standardPartitionalEndpoints
+
+    @standardPartitionalEndpoints(endpointPatternType: "service_dnsSuffix")
+    service MyService {
+        version: "2020-04-02"
+    }
+
+Services should follow the standard patterns; however, occasionally there are special cases.
+The following example defines a partitional service that uses a special case pattern in
+the ``aws`` partition and uses a non-standard global region in the ``aws-cn`` partition:
+
+.. code-block:: smithy
+
+    @standardPartitionalEndpoints(
+        endpointPatternType: "service_dnsSuffix",
+        partitionEndpointSpecialCases: {
+            aws: [{endpoint: "myservice.global.amazonaws.com"}],
+            aws-cn: [{region: "cn-north-1"}]
+        }
+    )
+    service MyService {
+        version: "2020-04-02"
+    }
+
+``PartitionEndpointSpecialCase`` object
+---------------------------------------
+
+A ``PartitionEndpointSpecialCase`` object contains the following properties:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 10 30 60
+
+    * - Property name
+      - Type
+      - Description
+    * - endpoint
+      - ``string``
+      - The special-cased endpoint template.
+    * - region
+      - ``string``
+      - Override the ``defaultGlobalRegion`` used in this partition.
+    * - dualStack
+      - ``boolean``
+      - When ``true`` the special case will apply to dualstack endpoint variants.
+    * - fips
+      - ``boolean``
+      - When ``true`` the special case will apply to fips endpoint variants.
+
+.. smithy-trait:: aws.endpoints#dualStackOnlyEndpoints
+.. _aws.endpoints#dualStackOnlyEndpoints-trait:
+
+----------------------------------------------
+``aws.endpoints#dualStackOnlyEndpoints`` trait
+----------------------------------------------
+
+Summary
+    An :ref:`endpoints modifier trait <aws.endpoints#endpointsModifier-trait>`
+    that indicates that a service has only
+    `dual stack endpoints <https://docs.aws.amazon.com/general/latest/gr/rande.html#dual-stack-endpoints>`_,
+    does not support IPV4 only endpoints, and should not have the ``useDualStackEndpoint`` endpoint parameter.
+    Dual stack endpoints support IPV4 and IPV6.
+Trait selector
+    ``service``
+Trait value
+    Annotation trait
+
+Adding the ``dualStackOnlyEndpoints`` trait to a service modifies the generation of endpoints from
+:ref:`aws.endpoints#standardRegionalEndpoints-trait` or :ref:`aws.endpoints#standardPartitionalEndpoints-trait`,
+removes the ``useDualStackEndpoint`` parameter, and defaults the behavior to dual stack for
+all partitions that support it.
+
+The following example specifies a service that uses standard regional endpoint patterns and
+is dual stack only:
+
+.. code-block:: smithy
+
+     @standardRegionalEndpoints
+     @dualStackOnlyEndpoints
+     service MyService {
+         version: "2020-04-02"
+     }
+
+.. smithy-trait:: aws.endpoints#rulesBasedEndpoints
+.. _aws.endpoints#rulesBasedEndpoints-trait:
+
+-------------------------------------------
+``aws.endpoints#rulesBasedEndpoints`` trait
+-------------------------------------------
+
+Summary
+    An :ref:`endpoints modifier trait <aws.endpoints#endpointsModifier-trait>`
+    that indicates that a service has hand written endpoint rules.
+Trait selector
+    ``service``
+Trait value
+    Annotation trait
+
+Services marked with the ``rulesBasedEndpoints`` trait have hand written endpoint rules that
+extend or replace their standard generated endpoint rules through an external mechanism.
+This trait marks the presence of hand written rules, which are added to the model by a transformer,
+but does not specify their behavior.
+
+A service with ``rulesBasedEndpoints`` may extend the functionality of
+endpoint behavior described in the model through other
+:ref:`endpoints modifier traits <aws.endpoints#endpointsModifier-trait>`
+by modifying the generated :ref:`EndpointRuleSet <smithy.rules#endpointRuleSet-trait>`.
+The following example specifies a service that has standard regional endpoints extended with hand written rules:
+
+.. code-block:: smithy
+
+     @standardRegionalEndpoints
+     @rulesBasedEndpoints
+     service MyService {
+         version: "2020-04-02"
+     }
diff --git a/docs/source-2.0/aws/index.rst b/docs/source-2.0/aws/index.rst
index d8c49590d..ae9e5711c 100644
--- a/docs/source-2.0/aws/index.rst
+++ b/docs/source-2.0/aws/index.rst
@@ -7,6 +7,7 @@ AWS integrations
 
     aws-core
     aws-auth
+    aws-endpoints-region
     aws-iam
     amazon-apigateway
     aws-cloudformation
diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/SmithyIntegration.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/SmithyIntegration.java
index 5ab5ddad2..a0a5ff9bf 100644
--- a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/SmithyIntegration.java
+++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/SmithyIntegration.java
@@ -19,6 +19,7 @@ import java.util.Collections;
 import java.util.List;
 import software.amazon.smithy.build.FileManifest;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.utils.AbstractCodeWriter;
 import software.amazon.smithy.utils.CodeInterceptor;
 import software.amazon.smithy.utils.CodeSection;
@@ -70,6 +71,49 @@ public interface SmithyIntegration<S, W extends SymbolWriter<W, ?>, C extends Co
         return 0;
     }
 
+    /**
+     * Configures the integration.
+     *
+     * <p>This provides access to both the parsed settings for the generator and
+     * an unparsed {@link ObjectNode} containing settings for this particular
+     * integration.
+     *
+     * <p>The following {@code smithy-build.json} file contains an example of how
+     * this configuration will be set.
+     *
+     * <pre>{@code
+     * {
+     *     "version": "1.0",
+     *     "projections": {
+     *         "codegen-projection": {
+     *             "plugins": {
+     *                 "code-generator": {
+     *                     "service": "com.example#DocumentedService",
+     *                     "integrations": {
+     *                         "my-integration": {
+     *                             "example-setting": "foo"
+     *                         }
+     *                     }
+     *                 }
+     *             }
+     *         }
+     *     }
+     * }
+     * }</pre>
+     *
+     * <p>In this example, an integration whose {@link #name} is {@code my-integration}
+     * Would receive the extra settings from the key of the same name within the
+     * {@code integrations} node.
+     *
+     * <p>Integrations SHOULD use modeled traits as much as possible to drive
+     * configuration. This is intended for configuration that doesn't make sense
+     * as a trait, such as configuring a documentation theme.
+     *
+     * @param settings Settings used to generate code.
+     * @param integrationSettings Settings used to configure integrations.
+     */
+    default void configure(S settings, ObjectNode integrationSettings) {}
+
     /**
      * Gets the names of integrations that this integration must come before.
      *
@@ -103,7 +147,7 @@ public interface SmithyIntegration<S, W extends SymbolWriter<W, ?>, C extends Co
      * <p>By default, this method will return the given {@code model} as-is.
      *
      * @param model Model being generated.
-     * @param settings Setting used to generate code.
+     * @param settings Settings used to generate code.
      * @return Returns the updated model.
      */
     default Model preprocessModel(Model model, S settings) {
@@ -122,7 +166,7 @@ public interface SmithyIntegration<S, W extends SymbolWriter<W, ?>, C extends Co
      * <p>This integration method should be called only after {@link #preprocessModel}.
      *
      * @param model Model being generated.
-     * @param settings Setting used to generate.
+     * @param settings Settings used to generate.
      * @param symbolProvider The original {@code SymbolProvider}.
      * @return The decorated {@code SymbolProvider}.
      */
diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java
index 127f746f9..81e024138 100644
--- a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java
+++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java
@@ -36,6 +36,7 @@ import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.neighbor.Walker;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.NodeMapper;
+import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.shapes.EnumShape;
 import software.amazon.smithy.model.shapes.IntEnumShape;
 import software.amazon.smithy.model.shapes.OperationShape;
@@ -74,6 +75,7 @@ public final class CodegenDirector<
     private ShapeId service;
     private Model model;
     private S settings;
+    private ObjectNode integrationSettings = Node.objectNode();
     private FileManifest fileManifest;
     private Supplier<Iterable<I>> integrationFinder;
     private DirectedCodegen<C, S, I> directedCodegen;
@@ -143,6 +145,8 @@ public final class CodegenDirector<
     /**
      * Sets the required settings object used for code generation.
      *
+     * <p>{@link #integrationSettings} MUST also be set.
+     *
      * @param settings Settings object.
      */
     public void settings(S settings) {
@@ -159,6 +163,9 @@ public final class CodegenDirector<
      * You will need to manually deserialize your settings if using types that
      * are not supported by Smithy's {@link NodeMapper}.
      *
+     * <p>This will also set {@link #integrationSettings} if the {@code integrations}
+     * key is present.
+     *
      * @param settingsType Settings type to deserialize into.
      * @param settingsNode Settings node value to deserialize.
      * @return Returns the deserialized settings as this is needed to provide a service shape ID.
@@ -167,9 +174,48 @@ public final class CodegenDirector<
         LOGGER.fine(() -> "Loading codegen settings from node value: " + settingsNode.getSourceLocation());
         S deserialized = new NodeMapper().deserialize(settingsNode, settingsType);
         settings(deserialized);
+        settingsNode.asObjectNode()
+            .flatMap(node -> node.getObjectMember("integrations"))
+            .ifPresent(this::integrationSettings);
         return deserialized;
     }
 
+    /**
+     * Sets the settings node to be passed to integrations.
+     *
+     * <p>Generators MUST set this with the {@code integrations} key from their
+     * plugin settings.
+     *
+     * <pre>{@code
+     * {
+     *     "version": "1.0",
+     *     "projections": {
+     *         "codegen-projection": {
+     *             "plugins": {
+     *                 "code-generator": {
+     *                     "service": "com.example#DocumentedService",
+     *                     "integrations": {
+     *                         "my-integration": {
+     *                             "example-setting": "foo"
+     *                         }
+     *                     }
+     *                 }
+     *             }
+     *         }
+     *     }
+     * }
+     * }</pre>
+     *
+     * <p>In this example, the value of the {@code integrations} key is what must
+     * be passed to this method. The value of the {@code my-integration} key will
+     * then be provided to an integration with the name {@code my-integration}.
+     *
+     * @param integrationSettings Settings used to configure integrations.
+     */
+    public void integrationSettings(ObjectNode integrationSettings) {
+        this.integrationSettings = Objects.requireNonNull(integrationSettings);
+    }
+
     /**
      * Sets the required file manifest used to write files to disk.
      *
@@ -366,7 +412,10 @@ public final class CodegenDirector<
     private List<I> findIntegrations() {
         LOGGER.fine(() -> "Finding integration implementations of " + integrationClass.getName());
         List<I> integrations = SmithyIntegration.sort(integrationFinder.get());
-        integrations.forEach(i -> LOGGER.finest(() -> "Found integration " + i.getClass().getCanonicalName()));
+        integrations.forEach(i -> {
+            LOGGER.finest(() -> "Found integration " + i.getClass().getCanonicalName());
+            i.configure(settings, integrationSettings.getObjectMember(i.name()).orElse(Node.objectNode()));
+        });
         return integrations;
     }
 


Test patch:
diff --git a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CapturingIntegration.java b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CapturingIntegration.java
new file mode 100644
index 000000000..73fe5997d
--- /dev/null
+++ b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CapturingIntegration.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.codegen.core.directed;
+
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.ObjectNode;
+
+public class CapturingIntegration implements TestIntegration {
+    public ObjectNode integrationSettings = Node.objectNode();
+
+    @Override
+    public String name() {
+        return "capturing-integration";
+    }
+
+    @Override
+    public void configure(TestSettings settings, ObjectNode integrationSettings) {
+        this.integrationSettings = integrationSettings;
+    }
+}
diff --git a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java
index 295633630..df320005b 100644
--- a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java
+++ b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java
@@ -19,6 +19,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -33,6 +36,7 @@ import software.amazon.smithy.codegen.core.WriterDelegator;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.node.ExpectationNotMetException;
 import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.shapes.ShapeId;
 
 public class CodegenDirectorTest {
@@ -42,6 +46,8 @@ public class CodegenDirectorTest {
         public final List<ShapeId> generatedEnumTypeEnums = new ArrayList<>();
         public final List<ShapeId> generatedStringTypeEnums = new ArrayList<>();
 
+        public final List<TestIntegration> integrations = new ArrayList<>();
+
         @Override
         public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective<TestSettings> directive) {
             return shape -> Symbol.builder()
@@ -52,6 +58,8 @@ public class CodegenDirectorTest {
 
         @Override
         public TestContext createContext(CreateContextDirective<TestSettings, TestIntegration> directive) {
+            integrations.clear();
+            integrations.addAll(directive.integrations());
             WriterDelegator<TestWriter> delegator = new WriterDelegator<>(
                     directive.fileManifest(),
                     directive.symbolProvider(),
@@ -339,4 +347,42 @@ public class CodegenDirectorTest {
                 ShapeId.from("smithy.example#Foo")
         ));
     }
+
+    @Test
+    public void testConfiguresIntegrations() {
+        TestDirected testDirected = new TestDirected();
+        CodegenDirector<TestWriter, TestIntegration, TestContext, TestSettings> runner
+            = new CodegenDirector<>();
+        FileManifest manifest = new MockManifest();
+        Model model = Model.assembler()
+            .addImport(getClass().getResource("needs-sorting.smithy"))
+            .assemble()
+            .unwrap();
+
+        ObjectNode integrationSettings = Node.objectNode().withMember("spam", "eggs");
+        ObjectNode allIntegrationSettings = Node.objectNode()
+                .withMember("capturing-integration", integrationSettings);
+        ObjectNode settings = Node.objectNode()
+            .withMember("foo", "hi")
+            .withMember("integrations", allIntegrationSettings);
+        runner.settings(TestSettings.class, settings);
+        runner.directedCodegen(testDirected);
+        runner.fileManifest(manifest);
+        runner.service(ShapeId.from("smithy.example#Foo"));
+        runner.model(model);
+        runner.integrationClass(TestIntegration.class);
+        runner.performDefaultCodegenTransforms();
+        runner.shapeGenerationOrder(ShapeGenerationOrder.NONE);
+        runner.run();
+
+        assertThat(testDirected.integrations, not(empty()));
+        CapturingIntegration capturingIntegration = null;
+        for (TestIntegration integration : testDirected.integrations) {
+            if (integration instanceof CapturingIntegration) {
+                capturingIntegration = (CapturingIntegration) integration;
+            }
+        }
+        assertThat(capturingIntegration, notNullValue());
+        assertThat(capturingIntegration.integrationSettings, equalTo(integrationSettings));
+    }
 }
diff --git a/smithy-codegen-core/src/test/resources/META-INF/services/software.amazon.smithy.codegen.core.directed.TestIntegration b/smithy-codegen-core/src/test/resources/META-INF/services/software.amazon.smithy.codegen.core.directed.TestIntegration
new file mode 100644
index 000000000..7b3cf821d
--- /dev/null
+++ b/smithy-codegen-core/src/test/resources/META-INF/services/software.amazon.smithy.codegen.core.directed.TestIntegration
@@ -0,0 +1,1 @@
+software.amazon.smithy.codegen.core.directed.CapturingIntegration