Skip to content

Commit

Permalink
Add automatic integration configuration
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JordonPhillips committed Oct 20, 2023
1 parent 32ff2c3 commit 389eb37
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand Down Expand Up @@ -70,6 +71,50 @@ default byte priority() {
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 all integrations.
* Integrations SHOULD put all of their settings inside a nested object so
* that they don't experience conflicts with other integrations.
*
* <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, everything under the key {@code integrations} will be
* provided as the {@code rawSettings} value and the {@code my-integration} key
* represents the settings for a particular integration.
*
* <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.
*
Expand Down Expand Up @@ -103,7 +148,7 @@ default List<String> runAfter() {
* <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) {
Expand All @@ -122,7 +167,7 @@ default Model preprocessModel(Model model, S settings) {
* <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}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -159,6 +161,9 @@ public void settings(S settings) {
* 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.
Expand All @@ -167,9 +172,47 @@ public S settings(Class<S> settingsType, Node settingsNode) {
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.
*
* @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.
*
Expand Down Expand Up @@ -366,7 +409,10 @@ private void performModelTransforms() {
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);
});
return integrations;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 void configure(TestSettings settings, ObjectNode integrationSettings) {
this.integrationSettings = integrationSettings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
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;
Expand All @@ -33,6 +36,7 @@
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 {
Expand All @@ -42,6 +46,8 @@ private static final class TestDirected implements DirectedCodegen<TestContext,
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()
Expand All @@ -52,6 +58,8 @@ public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective<TestSet

@Override
public TestContext createContext(CreateContextDirective<TestSettings, TestIntegration> directive) {
integrations.clear();
integrations.addAll(directive.integrations());
WriterDelegator<TestWriter> delegator = new WriterDelegator<>(
directive.fileManifest(),
directive.symbolProvider(),
Expand Down Expand Up @@ -339,4 +347,40 @@ public void testShapesGenerationWithoutOrder() {
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 settings = Node.objectNode()
.withMember("foo", "hi")
.withMember("integrations", integrationSettings);
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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
software.amazon.smithy.codegen.core.directed.CapturingIntegration

0 comments on commit 389eb37

Please sign in to comment.