diff --git a/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java b/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java
index b9a1608d9079..5aa8a61376eb 100644
--- a/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java
+++ b/codegen-maven-plugin/src/main/java/software/amazon/awssdk/codegen/maven/plugin/GenerationMojo.java
@@ -180,8 +180,6 @@ private GenerationParams smithyGenerationParams(ModelRoot r) {
SmithyModels smithyModels = SmithyModels.builder()
.model(model)
.customizationConfig(r.customizationConfig)
- .endpointRuleSetModel(loadEndpointRuleSetModel(modelRootPath))
- .endpointTestSuiteModel(loadEndpointTestSuiteModel(modelRootPath))
.build();
IntermediateModel intermediateModel = new SmithyIntermediateModelBuilder(smithyModels).build();
return new GenerationParams().withIntermediateModel(intermediateModel)
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/AddSmithyEndpoints.java b/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/AddSmithyEndpoints.java
new file mode 100644
index 000000000000..54483a85f4b0
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/AddSmithyEndpoints.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.codegen.smithy;
+
+import java.io.IOException;
+import software.amazon.awssdk.codegen.internal.Jackson;
+import software.amazon.awssdk.codegen.model.rules.endpoints.EndpointTestSuiteModel;
+import software.amazon.awssdk.codegen.model.service.EndpointRuleSetModel;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
+import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait;
+
+/**
+ * Reads the endpoint rule-set and endpoint tests from the service's Smithy traits, where C2J reads
+ * them from the {@code endpoint-rule-set.json} and {@code endpoint-tests.json} sidecar files.
+ *
+ *
The trait content already uses the schema these POJOs expect, so this is a parse rather than a
+ * translation. It goes through the same {@link Jackson} mapper the sidecar path uses, so the two
+ * sources produce the same concrete types and tolerate unknown keys the same way.
+ *
+ *
The {@code endpointBdd} trait encodes the same rules as a binary decision diagram. Every
+ * service also carries {@code endpointRuleSet} in tree form, so it is ignored.
+ */
+final class AddSmithyEndpoints {
+
+ private AddSmithyEndpoints() {
+ }
+
+ /**
+ * Returns null when the service has no rule-set trait. {@code IntermediateModel} then
+ * substitutes the default rules.
+ */
+ static EndpointRuleSetModel endpointRuleSet(ServiceShape service) {
+ return service.getTrait(EndpointRuleSetTrait.class)
+ .map(trait -> parse(EndpointRuleSetModel.class, trait.getRuleSet(), service))
+ .orElse(null);
+ }
+
+ /**
+ * Returns null when the service has no endpoint-tests trait. {@code IntermediateModel} then
+ * substitutes an empty test suite. Uses {@code toNode()} rather than {@code getTestCases()}
+ * because the POJO expects the whole {@code {version, testCases}} object.
+ */
+ static EndpointTestSuiteModel endpointTests(ServiceShape service) {
+ return service.getTrait(EndpointTestsTrait.class)
+ .map(trait -> parse(EndpointTestSuiteModel.class, trait.toNode(), service))
+ .orElse(null);
+ }
+
+ private static T parse(Class clazz, Node node, ServiceShape service) {
+ try {
+ return Jackson.load(clazz, Node.printJson(node));
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to read " + clazz.getSimpleName() + " from a Smithy trait on "
+ + service.getId(), e);
+ }
+ }
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/SmithyIntermediateModelBuilder.java b/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/SmithyIntermediateModelBuilder.java
index e005fb948968..19960805dae9 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/SmithyIntermediateModelBuilder.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/SmithyIntermediateModelBuilder.java
@@ -83,8 +83,8 @@ public SmithyIntermediateModelBuilder(SmithyModels models) {
this.typeUtils = new TypeUtils(namingStrategy);
this.serviceIndex = ServiceIndex.of(model);
this.protocol = ProtocolUtils.resolveProtocol(serviceIndex, service);
- this.endpointRuleSet = models.endpointRuleSetModel();
- this.endpointTestSuiteModel = models.endpointTestSuiteModel();
+ this.endpointRuleSet = AddSmithyEndpoints.endpointRuleSet(service);
+ this.endpointTestSuiteModel = AddSmithyEndpoints.endpointTests(service);
this.shapeProcessors = createShapeProcessors();
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/SmithyModels.java b/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/SmithyModels.java
index ead0508e8572..076bec36b705 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/SmithyModels.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/smithy/SmithyModels.java
@@ -16,8 +16,6 @@
package software.amazon.awssdk.codegen.smithy;
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
-import software.amazon.awssdk.codegen.model.rules.endpoints.EndpointTestSuiteModel;
-import software.amazon.awssdk.codegen.model.service.EndpointRuleSetModel;
import software.amazon.awssdk.utils.builder.SdkBuilder;
import software.amazon.smithy.model.Model;
@@ -31,17 +29,10 @@ public final class SmithyModels {
private final Model model;
private final CustomizationConfig customizationConfig;
- private final EndpointRuleSetModel endpointRuleSetModel;
- private final EndpointTestSuiteModel endpointTestSuiteModel;
- private SmithyModels(Model model,
- CustomizationConfig customizationConfig,
- EndpointRuleSetModel endpointRuleSetModel,
- EndpointTestSuiteModel endpointTestSuiteModel) {
+ private SmithyModels(Model model, CustomizationConfig customizationConfig) {
this.model = model;
this.customizationConfig = customizationConfig;
- this.endpointRuleSetModel = endpointRuleSetModel;
- this.endpointTestSuiteModel = endpointTestSuiteModel;
}
public static Builder builder() {
@@ -56,20 +47,10 @@ public CustomizationConfig customizationConfig() {
return customizationConfig;
}
- public EndpointRuleSetModel endpointRuleSetModel() {
- return endpointRuleSetModel;
- }
-
- public EndpointTestSuiteModel endpointTestSuiteModel() {
- return endpointTestSuiteModel;
- }
-
public static final class Builder implements SdkBuilder {
private Model model;
private CustomizationConfig customizationConfig;
- private EndpointRuleSetModel endpointRuleSetModel;
- private EndpointTestSuiteModel endpointTestSuiteModel;
private Builder() {
}
@@ -84,20 +65,10 @@ public Builder customizationConfig(CustomizationConfig customizationConfig) {
return this;
}
- public Builder endpointRuleSetModel(EndpointRuleSetModel endpointRuleSetModel) {
- this.endpointRuleSetModel = endpointRuleSetModel;
- return this;
- }
-
- public Builder endpointTestSuiteModel(EndpointTestSuiteModel endpointTestSuiteModel) {
- this.endpointTestSuiteModel = endpointTestSuiteModel;
- return this;
- }
-
@Override
public SmithyModels build() {
CustomizationConfig config = customizationConfig != null ? customizationConfig : CustomizationConfig.create();
- return new SmithyModels(model, config, endpointRuleSetModel, endpointTestSuiteModel);
+ return new SmithyModels(model, config);
}
}
}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/smithy/AddSmithyEndpointsTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/smithy/AddSmithyEndpointsTest.java
new file mode 100644
index 000000000000..5665b4a681e7
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/smithy/AddSmithyEndpointsTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.codegen.smithy;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.codegen.model.rules.endpoints.EndpointTestSuiteModel;
+import software.amazon.awssdk.codegen.model.service.EndpointRuleSetModel;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
+
+/**
+ * Unit tests for {@link AddSmithyEndpoints}, covering trait present, trait absent, and the
+ * lower-case parameter {@code type} the traits use where the sidecar files capitalise it.
+ */
+class AddSmithyEndpointsTest {
+
+ /**
+ * Rule-set parameters must be bound in the service model, and AWS built-ins like
+ * {@code AWS::Region} are registered by smithy-aws-endpoints, which codegen does not depend on.
+ * Binding through {@code clientContextParams} keeps the model self-contained.
+ */
+ private static final String RULE_SET =
+ "@smithy.rules#clientContextParams(\n"
+ + " Region: { type: \"string\", documentation: \"The region\" }\n"
+ + " UseFIPS: { type: \"boolean\", documentation: \"Use FIPS endpoints\" }\n"
+ + ")\n"
+ + "@smithy.rules#endpointRuleSet({\n"
+ + " version: \"1.0\"\n"
+ + " parameters: {\n"
+ + " Region: { required: false, documentation: \"The region\", type: \"string\" }\n"
+ + " UseFIPS: { required: true, default: false, type: \"boolean\" }\n"
+ + " }\n"
+ + " rules: [\n"
+ + " { conditions: [], endpoint: { url: \"https://example.amazonaws.com\" }, type: \"endpoint\" }\n"
+ + " ]\n"
+ + "})\n";
+
+ private static final String TESTS =
+ "@smithy.rules#endpointTests({\n"
+ + " version: \"1.0\"\n"
+ + " testCases: [\n"
+ + " {\n"
+ + " documentation: \"basic\"\n"
+ + " params: { Region: \"us-east-1\", UseFIPS: false }\n"
+ + " expect: { endpoint: { url: \"https://example.amazonaws.com\" } }\n"
+ + " }\n"
+ + " ]\n"
+ + "})\n";
+
+ private static ServiceShape serviceOf(String serviceTraits) {
+ String src =
+ "$version: \"2.0\"\nnamespace demo\n\n"
+ + "use aws.api#service\n"
+ + "use aws.auth#sigv4\n"
+ + "use aws.protocols#restJson1\n"
+ + "@service(sdkId: \"Demo\", arnNamespace: \"demo\")\n"
+ + "@sigv4(name: \"demo\")\n"
+ + "@restJson1\n"
+ + serviceTraits
+ + "service DemoService { version: \"2024-01-01\", operations: [Op] }\n\n"
+ + "@http(method: \"POST\", uri: \"/op\")\n"
+ + "operation Op { input: OpRequest, output: OpResponse }\n"
+ + "structure OpRequest {}\n"
+ + "structure OpResponse {}\n";
+ Model model = Model.assembler()
+ .discoverModels(Model.class.getClassLoader())
+ .addUnparsedModel("test.smithy", src)
+ .assemble()
+ .unwrap();
+ return model.getServiceShapes().iterator().next();
+ }
+
+ @Test
+ void ruleSetTraitPresent_isTranslated() {
+ EndpointRuleSetModel ruleSet = AddSmithyEndpoints.endpointRuleSet(serviceOf(RULE_SET));
+
+ assertThat(ruleSet).isNotNull();
+ assertThat(ruleSet.getVersion()).isEqualTo("1.0");
+ assertThat(ruleSet.getParameters()).containsOnlyKeys("Region", "UseFIPS");
+ assertThat(ruleSet.getRules()).hasSize(1);
+ }
+
+ @Test
+ void ruleSetTraitAbsent_isNull() {
+ assertThat(AddSmithyEndpoints.endpointRuleSet(serviceOf(""))).isNull();
+ }
+
+ @Test
+ void endpointTestsTraitPresent_isTranslated() {
+ EndpointTestSuiteModel tests = AddSmithyEndpoints.endpointTests(serviceOf(RULE_SET + TESTS));
+
+ assertThat(tests).isNotNull();
+ assertThat(tests.getTestCases()).hasSize(1);
+ }
+
+ @Test
+ void endpointTestsTraitAbsent_isNull() {
+ assertThat(AddSmithyEndpoints.endpointTests(serviceOf(""))).isNull();
+ }
+
+ /**
+ * The traits write {@code "string"} where the sidecar files write {@code "String"}. The value is
+ * carried through verbatim; every consumer lower-cases before switching on it.
+ */
+ @Test
+ void parameterType_keepsTheTraitsLowerCaseForm() {
+ EndpointRuleSetModel ruleSet = AddSmithyEndpoints.endpointRuleSet(serviceOf(RULE_SET));
+
+ assertThat(ruleSet.getParameters().get("Region").getType()).isEqualTo("string");
+ assertThat(ruleSet.getParameters().get("UseFIPS").getType()).isEqualTo("boolean");
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/smithy/SmithyIntermediateModelBuilderTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/smithy/SmithyIntermediateModelBuilderTest.java
new file mode 100644
index 000000000000..8307ae52d4eb
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/smithy/SmithyIntermediateModelBuilderTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.codegen.smithy;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
+import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
+import software.amazon.awssdk.codegen.model.service.EndpointRuleSetModel;
+import software.amazon.smithy.model.Model;
+
+/**
+ * Covers the endpoint models reaching the intermediate model from the service's traits. The Smithy
+ * path takes no sidecar input, so a service without the traits lands on the same defaults the C2J
+ * path uses when the sidecar files are absent.
+ */
+class SmithyIntermediateModelBuilderTest {
+
+ /**
+ * {@code clientContextParams} binds the rule-set parameter to the service model, which the
+ * rules-engine validator requires for any parameter that is not a registered built-in.
+ */
+ private static final String RULE_SET =
+ "@smithy.rules#clientContextParams(\n"
+ + " Region: { type: \"string\", documentation: \"The region\" }\n"
+ + ")\n"
+ + "@smithy.rules#endpointRuleSet({\n"
+ + " version: \"1.0\"\n"
+ + " parameters: {\n"
+ + " Region: { required: false, type: \"string\" }\n"
+ + " }\n"
+ + " rules: [\n"
+ + " { conditions: [], endpoint: { url: \"https://example.amazonaws.com\" }, type: \"endpoint\" }\n"
+ + " ]\n"
+ + "})\n";
+
+ private static final String TESTS =
+ "@smithy.rules#endpointTests({\n"
+ + " version: \"1.0\"\n"
+ + " testCases: [\n"
+ + " {\n"
+ + " documentation: \"from the trait\"\n"
+ + " params: { Region: \"us-east-1\" }\n"
+ + " expect: { endpoint: { url: \"https://example.amazonaws.com\" } }\n"
+ + " }\n"
+ + " ]\n"
+ + "})\n";
+
+ private static IntermediateModel build(String serviceTraits) {
+ String src =
+ "$version: \"2.0\"\nnamespace demo\n\n"
+ + "use aws.api#service\n"
+ + "use aws.auth#sigv4\n"
+ + "use aws.protocols#restJson1\n"
+ + "@service(sdkId: \"Demo\", arnNamespace: \"demo\")\n"
+ + "@sigv4(name: \"demo\")\n"
+ + "@restJson1\n"
+ + serviceTraits
+ + "service DemoService { version: \"2024-01-01\", operations: [Op] }\n\n"
+ + "@http(method: \"POST\", uri: \"/op\")\n"
+ + "operation Op { input: OpRequest, output: OpResponse }\n"
+ + "structure OpRequest {}\n"
+ + "structure OpResponse {}\n";
+ Model model = Model.assembler()
+ .discoverModels(Model.class.getClassLoader())
+ .addUnparsedModel("test.smithy", src)
+ .assemble()
+ .unwrap();
+ return new SmithyIntermediateModelBuilder(
+ SmithyModels.builder()
+ .model(model)
+ .customizationConfig(CustomizationConfig.create())
+ .build()).build();
+ }
+
+ @Test
+ void endpointTraitsPresent_reachTheIntermediateModel() {
+ IntermediateModel model = build(RULE_SET + TESTS);
+
+ assertThat(model.getEndpointRuleSetModel().getVersion()).isEqualTo("1.0");
+ assertThat(model.getEndpointRuleSetModel().getParameters()).containsOnlyKeys("Region");
+ assertThat(model.getEndpointTestSuiteModel().getTestCases()).hasSize(1);
+ }
+
+ /**
+ * Neither getter returns null. {@code IntermediateModel} substitutes an empty test suite, and
+ * {@code EndpointRuleSetModel.defaultRules(endpointPrefix)} for the rule-set, so a service
+ * without the traits degrades to the generic rules exactly as a C2J service with no sidecar does.
+ */
+ @Test
+ void endpointTraitsAbsent_fallBackToTheDefaultRules() {
+ IntermediateModel model = build("");
+
+ assertThat(model.getEndpointRuleSetModel())
+ .usingRecursiveComparison()
+ .isEqualTo(EndpointRuleSetModel.defaultRules(model.getMetadata().getEndpointPrefix()));
+ assertThat(model.getEndpointTestSuiteModel().getTestCases()).isEmpty();
+ }
+}