From f23d0ae328538e9252ce6fee7e2b643fc8e17ed9 Mon Sep 17 00:00:00 2001 From: wty-Bryant Date: Wed, 21 Oct 2026 17:13:24 -0400 Subject: [PATCH 1/6] resolve smithy ServiceNameUtil conflict --- tools/code-generation/generator/pom.xml | 14 ++ .../C2jModelToGeneratorModelTransformer.java | 127 ++++++++---------- .../transform/ServiceNamespaceTable.java | 70 ++++++++++ ...jModelToGeneratorModelTransformerTest.java | 7 + .../shared/service-namespace-table.json | 45 +++++++ .../smithy-cpp-codegen/build.gradle.kts | 8 ++ .../generators/ServiceNameUtil.java | 122 +++++------------ .../generators/ServiceNamespaceTable.java | 84 ++++++++++++ 8 files changed, 313 insertions(+), 164 deletions(-) create mode 100644 tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/ServiceNamespaceTable.java create mode 100644 tools/code-generation/shared/service-namespace-table.json create mode 100644 tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNamespaceTable.java diff --git a/tools/code-generation/generator/pom.xml b/tools/code-generation/generator/pom.xml index 6b0b5ac63d31..4346ef0c3f7a 100644 --- a/tools/code-generation/generator/pom.xml +++ b/tools/code-generation/generator/pom.xml @@ -16,6 +16,20 @@ + + + + src/main/resources + + + + ../shared + + service-namespace-table.json + + + org.apache.maven.plugins diff --git a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java index 6543427066c8..1e1555aa0bc4 100644 --- a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java +++ b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java @@ -52,63 +52,15 @@ public class C2jModelToGeneratorModelTransformer { UNSUPPORTEDHTMLTAGS.add(""); } - // A list of non-compliant service models that are already released, so we keep them backward-compatible. - public static final HashSet LEGACY_SERVICE_IDS = new HashSet<>(); + // SEP exception: a few ancient models carry no "serviceId" at all, so their namespace cannot + // be keyed by one (the Service ID SEP forbids keying on a docgen id, endpoint prefix, or model + // file name). Their already-released namespaces are therefore preserved by model (service) + // name. Every other preserved namespace lives in the shared, serviceId-keyed identifier table + // (service-namespace-table.json via ServiceNamespaceTable). + private static final Map SERVICE_ID_LESS_NAMESPACES = new HashMap<>(); static { - LEGACY_SERVICE_IDS.add("amp"); - LEGACY_SERVICE_IDS.add("appintegrations"); - LEGACY_SERVICE_IDS.add("billingconductor"); - LEGACY_SERVICE_IDS.add("clouddirectory"); - LEGACY_SERVICE_IDS.add("cloudfront"); - LEGACY_SERVICE_IDS.add("cloudsearch"); - LEGACY_SERVICE_IDS.add("cloudsearchdomain"); - LEGACY_SERVICE_IDS.add("codeartifact"); - LEGACY_SERVICE_IDS.add("codestar-notifications"); - LEGACY_SERVICE_IDS.add("config"); - LEGACY_SERVICE_IDS.add("databrew"); - LEGACY_SERVICE_IDS.add("ec2"); - LEGACY_SERVICE_IDS.add("elasticache"); - LEGACY_SERVICE_IDS.add("emr-containers"); - LEGACY_SERVICE_IDS.add("entitlement.marketplace"); - LEGACY_SERVICE_IDS.add("events"); - LEGACY_SERVICE_IDS.add("evidently"); - LEGACY_SERVICE_IDS.add("forecast"); - LEGACY_SERVICE_IDS.add("forecastquery"); - LEGACY_SERVICE_IDS.add("grafana"); - LEGACY_SERVICE_IDS.add("importexport"); - LEGACY_SERVICE_IDS.add("inspector"); - LEGACY_SERVICE_IDS.add("lambda"); - LEGACY_SERVICE_IDS.add("location"); - LEGACY_SERVICE_IDS.add("lookoutvision"); - LEGACY_SERVICE_IDS.add("m2"); - LEGACY_SERVICE_IDS.add("migrationhubstrategy"); - LEGACY_SERVICE_IDS.add("mobile"); - LEGACY_SERVICE_IDS.add("mobileanalytics"); - LEGACY_SERVICE_IDS.add("mq"); - LEGACY_SERVICE_IDS.add("nimble"); - LEGACY_SERVICE_IDS.add("opensearch"); - LEGACY_SERVICE_IDS.add("rbin"); - LEGACY_SERVICE_IDS.add("rds-data"); - LEGACY_SERVICE_IDS.add("redshift-data"); - LEGACY_SERVICE_IDS.add("resiliencehub"); - LEGACY_SERVICE_IDS.add("rum"); - LEGACY_SERVICE_IDS.add("sagemaker-a2i-runtime"); - LEGACY_SERVICE_IDS.add("sagemaker-edge"); - LEGACY_SERVICE_IDS.add("schemas"); - LEGACY_SERVICE_IDS.add("sdb"); - LEGACY_SERVICE_IDS.add("transcribe"); - LEGACY_SERVICE_IDS.add("transcribe-streaming"); - LEGACY_SERVICE_IDS.add("wisdom"); - - // remaps - LEGACY_SERVICE_IDS.add("lex"); - LEGACY_SERVICE_IDS.add("lexv2-runtime"); - LEGACY_SERVICE_IDS.add("lexv2-models"); - LEGACY_SERVICE_IDS.add("marketplace-entitlement"); - LEGACY_SERVICE_IDS.add("sagemaker-runtime"); - LEGACY_SERVICE_IDS.add("awstransfer"); - LEGACY_SERVICE_IDS.add("transcribestreaming"); - LEGACY_SERVICE_IDS.add("dynamodbstreams"); + SERVICE_ID_LESS_NAMESPACES.put("importexport", "ImportExport"); + SERVICE_ID_LESS_NAMESPACES.put("sdb", "SimpleDB"); } /** @@ -310,30 +262,19 @@ Metadata convertMetadata() { } metadata.setProtocols(c2jMetadata.getProtocols()); metadata.setAuth(c2jMetadata.getAuth()); - metadata.setNamespace(c2jMetadata.getServiceAbbreviation()); metadata.setServiceFullName(c2jMetadata.getServiceFullName()); metadata.setSignatureVersion(c2jMetadata.getSignatureVersion()); metadata.setTargetPrefix(c2jMetadata.getTargetPrefix()); metadata.setGlobalEndpoint(c2jMetadata.getGlobalEndpoint()); metadata.setTimestampFormat(c2jMetadata.getTimestampFormat()); - if (metadata.getNamespace() == null || metadata.getNamespace().isEmpty()) { - String serviceId = c2jMetadata.getServiceId(); - if(LEGACY_SERVICE_IDS.contains(this.c2jServiceModel.getServiceName())) { - serviceId = c2jMetadata.getServiceFullName(); // fallback to full name for legacy already released models - } - if(serviceId == null || serviceId.isEmpty()) { - throw new SourceGenerationFailedException(String.format("Service model file %s must have metadata.serviceId present", c2jServiceModel.getServiceName())); - } - metadata.setNamespace(sanitizeServiceAbbreviation(serviceId)); - } else { - metadata.setNamespace(sanitizeServiceAbbreviation(metadata.getNamespace())); - } - metadata.setServiceId(c2jMetadata.getServiceId() != null ? c2jMetadata.getServiceId() : c2jMetadata.getEndpointPrefix()); + // Service namespace is derived from the codegen id ("serviceId") ONLY, per Revision 2 of + // the Service ID SEP. Docgen ids (serviceAbbreviation / serviceFullName) MUST NOT be read + // to derive it. Names released before that revision that diverge from the serviceId-derived + // name are preserved via the static, serviceId-keyed identifier table (ServiceNamespaceTable). + metadata.setNamespace(deriveNamespace(c2jMetadata, this.c2jServiceModel.getServiceName())); - if ("S3-CRT".equalsIgnoreCase(c2jServiceModel.getServiceName())) { - metadata.setNamespace("S3Crt"); - } + metadata.setServiceId(c2jMetadata.getServiceId() != null ? c2jMetadata.getServiceId() : c2jMetadata.getEndpointPrefix()); metadata.setClassNamePrefix(CppViewHelper.convertToUpperCamel(ifNotNullOrEmpty(c2jMetadata.getClientClassNamePrefix(), metadata.getNamespace()))); @@ -1037,6 +978,46 @@ Error convertError(C2jError c2jError, String operationName) { return error; } + /** + * Derive the service namespace from the codegen id ("serviceId") only, consulting the frozen, + * serviceId-keyed identifier table (ServiceNamespaceTable) for already-released names that + * diverge from it. See the Service ID SEP (seps/accepted/shared/service-id.md). + * + * @param c2jMetadata the raw C2J metadata for the service + * @param serviceModelName the model (service) name, used only for S3-CRT and for the two + * serviceId-less SEP exceptions; never used to derive a tabled value + */ + String deriveNamespace(C2jMetadata c2jMetadata, String serviceModelName) { + // S3-CRT is a projection of s3 with no model of its own. + if ("S3-CRT".equalsIgnoreCase(serviceModelName)) { + return "S3Crt"; + } + // SEP exception: pre-serviceId models (see SERVICE_ID_LESS_NAMESPACES). + String serviceIdLess = SERVICE_ID_LESS_NAMESPACES.get(serviceModelName); + if (serviceIdLess != null) { + return serviceIdLess; + } + String serviceId = c2jMetadata.getServiceId(); + if (serviceId == null || serviceId.isEmpty()) { + throw new SourceGenerationFailedException(String.format( + "Service model file %s must have metadata.serviceId present", serviceModelName)); + } + // Preserved, already-released name takes precedence over derivation. + String preserved = ServiceNamespaceTable.getNamespace(serviceId); + if (preserved != null) { + return preserved; + } + String derived = sanitizeServiceAbbreviation(serviceId); + // SEP uniqueness guard: a serviceId-derived name must never collide with a preserved one. + if (ServiceNamespaceTable.isReservedNamespace(derived)) { + throw new SourceGenerationFailedException(String.format( + "Derived namespace '%s' for serviceId '%s' collides with a reserved namespace " + + "in the identifier table; the serviceId or the table must be reconciled", + derived, serviceId)); + } + return derived; + } + String sanitizeServiceAbbreviation(String serviceAbbreviation) { return serviceAbbreviation.replace(" ", "").replace("-", "").replace("_", "").replace("Amazon", "").replace("AWS", "").replace("/", ""); } diff --git a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/ServiceNamespaceTable.java b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/ServiceNamespaceTable.java new file mode 100644 index 000000000000..4100a63d0b61 --- /dev/null +++ b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/ServiceNamespaceTable.java @@ -0,0 +1,70 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +package com.amazonaws.util.awsclientgenerator.transform; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; + +/** + * Static, checked-in table that preserves the C++ SDK service namespaces released before + * Revision 2 of the Service ID SEP (seps/accepted/shared/service-id.md) that diverge from the + * namespace derived from the service's codegen id ("serviceId"). + * + *

The table is keyed by the exact, untransformed "serviceId" and is loaded from the shared + * data file {@code service-namespace-table.json}, which is the single source of truth shared + * with the Smithy generator. The SEP forbids keying on a docgen id, an endpoint prefix, or a + * model file name, and forbids deriving the preserved value from any of those; the values here + * are frozen literals, not computed from model metadata at generation time. + */ +public final class ServiceNamespaceTable { + + private static final String RESOURCE = "/service-namespace-table.json"; + private static final Map TABLE = load(); + + private ServiceNamespaceTable() { + } + + private static Map load() { + try (InputStream in = ServiceNamespaceTable.class.getResourceAsStream(RESOURCE)) { + if (in == null) { + throw new IllegalStateException("Missing classpath resource " + RESOURCE); + } + Type mapType = new TypeToken>() { + }.getType(); + Map parsed = new Gson().fromJson( + new InputStreamReader(in, StandardCharsets.UTF_8), mapType); + if (parsed == null) { + throw new IllegalStateException("Empty or invalid " + RESOURCE); + } + return Collections.unmodifiableMap(parsed); + } catch (Exception e) { + throw new IllegalStateException("Failed to load " + RESOURCE, e); + } + } + + /** + * @return the frozen namespace preserved for this serviceId, or {@code null} if the service + * is not in the table (in which case the caller derives the namespace from the serviceId). + */ + public static String getNamespace(String serviceId) { + return TABLE.get(serviceId); + } + + /** + * @return {@code true} if the given namespace is already reserved by a preserved (tabled) + * service. Used to fail generation when a serviceId-derived name would collide with one. + */ + public static boolean isReservedNamespace(String namespace) { + return TABLE.containsValue(namespace); + } +} diff --git a/tools/code-generation/generator/src/test/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformerTest.java b/tools/code-generation/generator/src/test/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformerTest.java index 3eaac10aecae..78e089d32dcb 100644 --- a/tools/code-generation/generator/src/test/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformerTest.java +++ b/tools/code-generation/generator/src/test/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformerTest.java @@ -77,12 +77,19 @@ public void testMetadataConversationWithStandalonePackages() { c2jMetadata.setServiceFullName("Amazon AWS Service Abbr Full Name"); c2jMetadata.setSignatureVersion("v4"); c2jMetadata.setTargetPrefix("ServiceAbbr."); + // The namespace is derived from serviceId only (serviceAbbreviation / serviceFullName are + // no longer read), so the model must carry a serviceId. A deliberately "dirty" serviceId + // (distinct from the serviceAbbreviation above) verifies sanitizeServiceAbbreviation runs + // AND that the transformer reads serviceId, not the abbreviation: + // "AWS My Cool-Service_X" -> strip " -_/", "Amazon", "AWS" -> "MyCoolServiceX". + c2jMetadata.setServiceId("AWS My Cool-Service_X"); c2jMetadata.setUid("service-9089-34-54"); c2jServiceModel.setMetadata(c2jMetadata); C2jModelToGeneratorModelTransformer c2jModelToGeneratorModelTransformer = new C2jModelToGeneratorModelTransformer(c2jServiceModel, true); Metadata metadata = c2jModelToGeneratorModelTransformer.convertMetadata(); assertTrue(metadata.isStandalone()); assertTrue(metadata.isApigateway()); + assertEquals("MyCoolServiceX", metadata.getNamespace()); assertEquals("service-abbr.execute-api", metadata.getEndpointPrefix()); assertEquals("application-json", metadata.findFirstSupportedProtocol()); } diff --git a/tools/code-generation/shared/service-namespace-table.json b/tools/code-generation/shared/service-namespace-table.json new file mode 100644 index 000000000000..702a0b6bb1dc --- /dev/null +++ b/tools/code-generation/shared/service-namespace-table.json @@ -0,0 +1,45 @@ +{ + "AppIntegrations": "AppIntegrationsService", + "CloudControl": "CloudControlApi", + "DataBrew": "GlueDataBrew", + "ECR PUBLIC": "ECRPublic", + "EMR containers": "EMRContainers", + "IVS RealTime": "ivsrealtime", + "Inspector Scan": "inspectorscan", + "IotDeviceAdvisor": "IoTDeviceAdvisor", + "Kinesis Video Signaling": "KinesisVideoSignalingChannels", + "Location": "LocationService", + "Marketplace Agreement": "AgreementService", + "MediaPackageV2": "mediapackagev2", + "MigrationHubStrategy": "MigrationHubStrategyRecommendations", + "OpenSearch": "OpenSearchService", + "RDS Data": "RDSDataService", + "RUM": "CloudWatchRUM", + "Redshift Data": "RedshiftDataAPIService", + "SESv2": "SESV2", + "SageMaker A2I Runtime": "AugmentedAIRuntime", + "Sagemaker Edge": "SagemakerEdgeManager", + "Service Catalog AppRegistry": "AppRegistry", + "Transcribe": "TranscribeService", + "Transcribe Streaming": "TranscribeStreamingService", + "Wisdom": "ConnectWisdomService", + "amp": "PrometheusService", + "b2bi": "B2BI", + "billingconductor": "BillingConductor", + "codeartifact": "CodeArtifact", + "codestar notifications": "CodeStarNotifications", + "evs": "EVS", + "fis": "FIS", + "forecast": "ForecastService", + "forecastquery": "ForecastQueryService", + "grafana": "ManagedGrafana", + "identitystore": "IdentityStore", + "ivs": "IVS", + "m2": "MainframeModernization", + "mq": "MQ", + "rbin": "RecycleBin", + "resiliencehub": "ResilienceHub", + "savingsplans": "SavingsPlans", + "schemas": "Schemas", + "synthetics": "Synthetics" +} diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/build.gradle.kts b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/build.gradle.kts index 53e0d723ed5c..c2318532f97a 100644 --- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/build.gradle.kts +++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/build.gradle.kts @@ -31,3 +31,11 @@ dependencies { tasks.test { useJUnitPlatform() } + +// Bundle the shared Service ID SEP identifier table (single source of truth, also consumed by the +// C2J generator) onto the classpath so ServiceNamespaceTable can load it as a resource. +tasks.named("processResources") { + from("$projectDir/../../../shared") { + include("service-namespace-table.json") + } +} diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNameUtil.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNameUtil.java index ed39edb69614..e1577880dcc6 100644 --- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNameUtil.java +++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNameUtil.java @@ -16,110 +16,50 @@ public final class ServiceNameUtil { private static final String S3_CRT_PROJECTION_PREFIX = "s3-crt"; private static final String S3_CRT_NAMESPACE = "S3Crt"; - // Legacy service IDs that need special handling to match what the legacy c2j code generator produced - // This logic is copied from C2jModelToGeneratorModelTransformer.convertMetadata() method - // Hardcoded mappings for legacy services where C2J used fullServiceName for file naming - // Maps serviceId (lowercase, sanitized) to the sanitized fullServiceName from C2J models - private static final Map LEGACY_FULL_SERVICE_NAME_MAP = Map.ofEntries( - Map.entry("amp", "PrometheusService"), - Map.entry("appintegrations", "AppIntegrationsService"), - Map.entry("billingconductor", "BillingConductor"), - Map.entry("chatbot", "chatbot"), - Map.entry("clouddirectory", "CloudDirectory"), - Map.entry("cloudfront", "CloudFront"), - Map.entry("cloudsearch", "CloudSearch"), - Map.entry("cloudsearchdomain", "CloudSearchDomain"), - Map.entry("codeartifact", "CodeArtifact"), - Map.entry("codestarnotifications", "CodeStarNotifications"), - Map.entry("configservice", "ConfigService"), - Map.entry("databrew", "GlueDataBrew"), - Map.entry("deadline", "deadline"), - Map.entry("elasticache", "ElastiCache"), - Map.entry("emrcontainers", "EMRContainers"), - Map.entry("marketplaceentitlementservice", "MarketplaceEntitlementService"), - Map.entry("cloudwatchevents", "CloudWatchEvents"), - Map.entry("evidently", "CloudWatchEvidently"), - Map.entry("forecast", "ForecastService"), - Map.entry("forecastquery", "ForecastQueryService"), - Map.entry("grafana", "ManagedGrafana"), + // SEP exception: legacy services with NO serviceId in their C2J model (generated as Smithy + // "mock" projections). They cannot be keyed by serviceId, so their already-released namespaces + // are preserved here, keyed by the normalized (sanitized, lower-cased) sdkId. Every other + // preserved namespace lives in the shared, serviceId-keyed table (ServiceNamespaceTable). + private static final Map SERVICE_ID_LESS_NAMESPACES = Map.ofEntries( Map.entry("importexport", "ImportExport"), - Map.entry("inspector", "Inspector"), - Map.entry("lambda", "Lambda"), - Map.entry("location", "LocationService"), - Map.entry("m2", "MainframeModernization"), - Map.entry("migrationhubstrategy", "MigrationHubStrategyRecommendations"), - Map.entry("mq", "MQ"), - Map.entry("opensearch", "OpenSearchService"), - Map.entry("rbin", "RecycleBin"), - Map.entry("rdsdata", "RDSDataService"), - Map.entry("redshiftdata", "RedshiftDataAPIService"), - Map.entry("resiliencehub", "ResilienceHub"), - Map.entry("rum", "CloudWatchRUM"), - Map.entry("lexruntimeservice", "LexRuntimeService"), - Map.entry("lexruntimev2", "LexRuntimeV2"), - Map.entry("lexmodelsv2", "LexModelsV2"), - Map.entry("sagemakerruntime", "SageMakerRuntime"), - Map.entry("sagemakera2iruntime", "AugmentedAIRuntime"), - Map.entry("sagemakeredge", "SagemakerEdgeManager"), - Map.entry("schemas", "Schemas"), - Map.entry("simpledb", "SimpleDB"), - Map.entry("dynamodbstreams", "DynamoDBStreams"), - Map.entry("transcribe", "TranscribeService"), - Map.entry("transcribestreaming", "TranscribeStreamingService"), - Map.entry("transfer", "TransferFamily"), - Map.entry("wisdom", "ConnectWisdomService"), - Map.entry("marketplaceagreement", "MarketplaceAgreementService") + Map.entry("simpledb", "SimpleDB") ); - - // TODO: Remove hardcoded mappings once Smithy models include serviceAbbreviation trait - // Smithy sdkId matches C2J serviceId, but C2J prioritizes serviceAbbreviation (which Smithy lacks). - // 18 of 66 services where C2J abbrev != serviceId require these mappings to match C2J output. - // Hardcoded mappings where Smithy sdkId doesn't match C2J serviceAbbreviation - private static final Map SMITHY_TO_C2J_NAMESPACE = Map.ofEntries( - Map.entry("b2bi", "B2BI"), - Map.entry("cloudcontrol", "CloudControlApi"), - Map.entry("ecrpublic", "ECRPublic"), - Map.entry("evs", "EVS"), - Map.entry("fis", "FIS"), - Map.entry("identitystore", "IdentityStore"), - Map.entry("inspectorscan", "inspectorscan"), - Map.entry("iotdeviceadvisor", "IoTDeviceAdvisor"), - Map.entry("ivs", "IVS"), - Map.entry("ivsrealtime", "ivsrealtime"), - Map.entry("kinesisvideosignaling", "KinesisVideoSignalingChannels"), - Map.entry("marketplaceagreement", "AgreementService"), - Map.entry("mediapackagev2", "mediapackagev2"), - Map.entry("savingsplans", "SavingsPlans"), - Map.entry("servicecatalogappregistry", "AppRegistry"), - Map.entry("sesv2", "SESV2"), - Map.entry("synthetics", "Synthetics"), - Map.entry("transfer", "Transfer") - ); - + public static String getServiceName(ServiceShape service) { // S3-CRT override: when service is S3-CRT, use S3Crt namespace if (isS3CrtProjection(service)) { return S3_CRT_NAMESPACE; } - + + // Namespace is derived from the codegen id (sdkId == C2J serviceId) ONLY, per Revision 2 of + // the Service ID SEP. Docgen ids (smithy.api#title) MUST NOT be read. Pre-rev2 names that + // diverge from the sdkId-derived name are preserved via the shared identifier table. String serviceId = service.getTrait(ServiceTrait.class) .map(ServiceTrait::getSdkId) .orElse(service.getId().getName()); - + String sanitized = sanitizeServiceAbbreviation(serviceId); - - // Check serviceAbbreviation mappings first (highest priority in C2J) - String mapped = SMITHY_TO_C2J_NAMESPACE.get(sanitized.toLowerCase()); - if (mapped != null) { - return mapped; + String key = sanitized.toLowerCase(); + + // SEP exception: serviceId-less legacy (mock) services. + String exception = SERVICE_ID_LESS_NAMESPACES.get(key); + if (exception != null) { + return exception; } - - // Check legacy fullServiceName mappings (fallback when serviceAbbreviation missing) - String legacyName = LEGACY_FULL_SERVICE_NAME_MAP.get(sanitized.toLowerCase()); - if (legacyName != null) { - return legacyName; + + // Preserved divergent name from the shared Service ID SEP table (keyed by serviceId). + String preserved = ServiceNamespaceTable.getByNormalizedKey(key); + if (preserved != null) { + return preserved; } - + + // SEP uniqueness guard: a sdkId-derived name must never collide with a preserved one. + if (ServiceNamespaceTable.isReservedNamespace(sanitized)) { + throw new IllegalStateException(String.format( + "Derived namespace '%s' for serviceId '%s' collides with a reserved namespace in %s", + sanitized, serviceId, "service-namespace-table.json")); + } + return sanitized; } diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNamespaceTable.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNamespaceTable.java new file mode 100644 index 000000000000..916ae9e1e127 --- /dev/null +++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNamespaceTable.java @@ -0,0 +1,84 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +package com.amazonaws.util.awsclientsmithygenerator.generators; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.StringNode; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Static, checked-in table preserving the C++ SDK service namespaces released before Revision 2 + * of the Service ID SEP (seps/accepted/shared/service-id.md) that diverge from the namespace + * derived from the service's codegen id ("serviceId" == Smithy sdkId). + * + *

This loads the SAME shared data file as the C2J generator + * ({@code tools/code-generation/shared/service-namespace-table.json}), the single source of truth. + * The file is keyed by the exact, untransformed serviceId per the SEP. Because the Smithy + * generator queries with a sanitized, lower-cased serviceId, this class builds a lookup index + * keyed by {@code sanitizeServiceAbbreviation(serviceId).toLowerCase()} (the same normalization + * {@link ServiceNameUtil#getServiceName} applies). The stored values are frozen literals, never + * derived from a docgen id, endpoint prefix, or model file name. + */ +public final class ServiceNamespaceTable { + + private static final String RESOURCE = "/service-namespace-table.json"; + private static final Map BY_NORMALIZED_KEY = load(); + + private ServiceNamespaceTable() { + } + + private static Map load() { + try (InputStream in = ServiceNamespaceTable.class.getResourceAsStream(RESOURCE)) { + if (in == null) { + throw new IllegalStateException("Missing classpath resource " + RESOURCE); + } + String json = new String(in.readAllBytes(), StandardCharsets.UTF_8); + ObjectNode obj = Node.parse(json).expectObjectNode(); + Map map = new HashMap<>(); + for (Map.Entry entry : obj.getMembers().entrySet()) { + String serviceId = entry.getKey().getValue(); + String namespace = entry.getValue().expectStringNode().getValue(); + String key = normalize(serviceId); + String prev = map.put(key, namespace); + if (prev != null && !prev.equals(namespace)) { + throw new IllegalStateException(String.format( + "Normalized serviceId collision '%s' in %s: '%s' vs '%s'", + key, RESOURCE, prev, namespace)); + } + } + return Collections.unmodifiableMap(map); + } catch (IOException e) { + throw new IllegalStateException("Failed to load " + RESOURCE, e); + } + } + + /** Normalize a serviceId to the lookup key the Smithy generator queries with. */ + static String normalize(String serviceId) { + return ServiceNameUtil.sanitizeServiceAbbreviation(serviceId).toLowerCase(); + } + + /** + * @param normalizedKey {@code sanitizeServiceAbbreviation(serviceId).toLowerCase()} + * @return the frozen namespace preserved for this serviceId, or {@code null} if not tabled. + */ + public static String getByNormalizedKey(String normalizedKey) { + return BY_NORMALIZED_KEY.get(normalizedKey); + } + + /** + * @return {@code true} if the namespace is already reserved by a preserved (tabled) service. + * Used to fail generation if a serviceId-derived name would collide with a preserved one. + */ + public static boolean isReservedNamespace(String namespace) { + return BY_NORMALIZED_KEY.containsValue(namespace); + } +} From 540149af678550a2c1ec407da352ce1793f6ea5e Mon Sep 17 00:00:00 2001 From: wty-Bryant Date: Thu, 22 Oct 2026 01:31:16 -0400 Subject: [PATCH 2/6] add changelog --- .changelog/bugfix-fix-legacy-id-table.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changelog/bugfix-fix-legacy-id-table.json diff --git a/.changelog/bugfix-fix-legacy-id-table.json b/.changelog/bugfix-fix-legacy-id-table.json new file mode 100644 index 000000000000..ca907b254a08 --- /dev/null +++ b/.changelog/bugfix-fix-legacy-id-table.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "tools", + "contributor": "", + "description": "Derive generated service client namespaces from the service ID (codegen ID) in both the C2J and Smithy code generators. Generated client output is unchanged." +} From 33a5241bd0b4c5f814a9d94f63a3d8937dbf0188 Mon Sep 17 00:00:00 2001 From: wty-Bryant Date: Sat, 7 Nov 2026 14:32:05 -0500 Subject: [PATCH 3/6] rebase from main --- .changelog/bugfix-fix-legacy-id-table.json | 2 +- crt/aws-crt-cpp | 2 +- tools/code-generation/generator/pom.xml | 4 +- .../domainmodels/codegeneration/Metadata.java | 8 +- .../C2jModelToGeneratorModelTransformer.java | 26 ++-- .../transform/ServiceNamespaceTable.java | 17 +- .../smithy-cpp-codegen/build.gradle.kts | 4 +- .../generators/ServiceNameUtil.java | 20 +-- .../generators/ServiceNamespaceTable.java | 14 +- .../mapping/smithy-namespace-mapping.json | 147 ------------------ tools/scripts/codegen/legacy_c2j_cpp_gen.py | 19 --- tools/scripts/codegen/smithy_cpp_gen.py | 6 +- 12 files changed, 51 insertions(+), 218 deletions(-) delete mode 100644 tools/code-generation/smithy/mapping/smithy-namespace-mapping.json diff --git a/.changelog/bugfix-fix-legacy-id-table.json b/.changelog/bugfix-fix-legacy-id-table.json index ca907b254a08..70189570958a 100644 --- a/.changelog/bugfix-fix-legacy-id-table.json +++ b/.changelog/bugfix-fix-legacy-id-table.json @@ -2,5 +2,5 @@ "type": "bugfix", "category": "tools", "contributor": "", - "description": "Derive generated service client namespaces from the service ID (codegen ID) in both the C2J and Smithy code generators. Generated client output is unchanged." + "description": "Derive generated service client namespaces from the service ID in both the C2J and Smithy code generators. Generated client output is unchanged." } diff --git a/crt/aws-crt-cpp b/crt/aws-crt-cpp index 58bca57cbc8b..9c319786d1d8 160000 --- a/crt/aws-crt-cpp +++ b/crt/aws-crt-cpp @@ -1 +1 @@ -Subproject commit 58bca57cbc8b242468414972a6a35f92676b164d +Subproject commit 9c319786d1d83d5ace6a9de817dcfe1861003609 diff --git a/tools/code-generation/generator/pom.xml b/tools/code-generation/generator/pom.xml index 4346ef0c3f7a..937cf4e233db 100644 --- a/tools/code-generation/generator/pom.xml +++ b/tools/code-generation/generator/pom.xml @@ -21,8 +21,8 @@ src/main/resources - + ../shared diff --git a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Metadata.java b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Metadata.java index 13de1f104b4e..01a19b6b7794 100644 --- a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Metadata.java +++ b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/domainmodels/codegeneration/Metadata.java @@ -72,13 +72,15 @@ public String findFirstSupportedProtocol() { return protocol; } - //We're releasing CBOR so current services (only arc-region-switch) need to opt into the new protocol priority - if (serviceFullName != null && serviceFullName.equalsIgnoreCase("ARC - Region switch")){ + //We're releasing CBOR so current services (only arc-region-switch) need to opt into the new protocol priority. + //Match on the service id (serviceId), NOT the display name (serviceFullName), so a + //display-name change can never silently flip the wire protocol (JSON <-> CBOR). + if (serviceId != null && serviceId.equalsIgnoreCase("ARC Region switch")){ return "json"; } return protocols.stream().filter(supportedProtocols::contains) .min(Comparator.comparingInt(protocolName -> supportedProtocols.indexOf(protocolName))) - .orElseThrow(() -> new RuntimeException(String.format("No supported protocol found for %s", serviceFullName))); + .orElseThrow(() -> new RuntimeException(String.format("No supported protocol found for %s", serviceId))); } } diff --git a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java index 1e1555aa0bc4..497d716d3781 100644 --- a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java +++ b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformer.java @@ -52,10 +52,10 @@ public class C2jModelToGeneratorModelTransformer { UNSUPPORTEDHTMLTAGS.add(""); } - // SEP exception: a few ancient models carry no "serviceId" at all, so their namespace cannot - // be keyed by one (the Service ID SEP forbids keying on a docgen id, endpoint prefix, or model - // file name). Their already-released namespaces are therefore preserved by model (service) - // name. Every other preserved namespace lives in the shared, serviceId-keyed identifier table + // A few ancient models carry no "serviceId" at all, so their namespace cannot be keyed by one + // (identifiers must not be keyed on a display name, endpoint prefix, or model file name). Their + // already-released namespaces are therefore preserved by model (service) name. Every other + // preserved namespace lives in the shared, serviceId-keyed identifier table // (service-namespace-table.json via ServiceNamespaceTable). private static final Map SERVICE_ID_LESS_NAMESPACES = new HashMap<>(); static { @@ -268,10 +268,10 @@ Metadata convertMetadata() { metadata.setGlobalEndpoint(c2jMetadata.getGlobalEndpoint()); metadata.setTimestampFormat(c2jMetadata.getTimestampFormat()); - // Service namespace is derived from the codegen id ("serviceId") ONLY, per Revision 2 of - // the Service ID SEP. Docgen ids (serviceAbbreviation / serviceFullName) MUST NOT be read - // to derive it. Names released before that revision that diverge from the serviceId-derived - // name are preserved via the static, serviceId-keyed identifier table (ServiceNamespaceTable). + // Service namespace is derived from the service id ("serviceId") ONLY. The display-name + // fields (serviceAbbreviation / serviceFullName) MUST NOT be read to derive it. Names that + // diverge from the serviceId-derived name are preserved via the static, serviceId-keyed + // identifier table (ServiceNamespaceTable). metadata.setNamespace(deriveNamespace(c2jMetadata, this.c2jServiceModel.getServiceName())); metadata.setServiceId(c2jMetadata.getServiceId() != null ? c2jMetadata.getServiceId() : c2jMetadata.getEndpointPrefix()); @@ -979,20 +979,20 @@ Error convertError(C2jError c2jError, String operationName) { } /** - * Derive the service namespace from the codegen id ("serviceId") only, consulting the frozen, + * Derive the service namespace from the service id ("serviceId") only, consulting the frozen, * serviceId-keyed identifier table (ServiceNamespaceTable) for already-released names that - * diverge from it. See the Service ID SEP (seps/accepted/shared/service-id.md). + * diverge from it. * * @param c2jMetadata the raw C2J metadata for the service * @param serviceModelName the model (service) name, used only for S3-CRT and for the two - * serviceId-less SEP exceptions; never used to derive a tabled value + * serviceId-less exceptions; never used to derive a tabled value */ String deriveNamespace(C2jMetadata c2jMetadata, String serviceModelName) { // S3-CRT is a projection of s3 with no model of its own. if ("S3-CRT".equalsIgnoreCase(serviceModelName)) { return "S3Crt"; } - // SEP exception: pre-serviceId models (see SERVICE_ID_LESS_NAMESPACES). + // Exception: pre-serviceId models (see SERVICE_ID_LESS_NAMESPACES). String serviceIdLess = SERVICE_ID_LESS_NAMESPACES.get(serviceModelName); if (serviceIdLess != null) { return serviceIdLess; @@ -1008,7 +1008,7 @@ String deriveNamespace(C2jMetadata c2jMetadata, String serviceModelName) { return preserved; } String derived = sanitizeServiceAbbreviation(serviceId); - // SEP uniqueness guard: a serviceId-derived name must never collide with a preserved one. + // Uniqueness guard: a serviceId-derived name must never collide with a preserved one. if (ServiceNamespaceTable.isReservedNamespace(derived)) { throw new SourceGenerationFailedException(String.format( "Derived namespace '%s' for serviceId '%s' collides with a reserved namespace " diff --git a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/ServiceNamespaceTable.java b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/ServiceNamespaceTable.java index 4100a63d0b61..09507b858241 100644 --- a/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/ServiceNamespaceTable.java +++ b/tools/code-generation/generator/src/main/java/com/amazonaws/util/awsclientgenerator/transform/ServiceNamespaceTable.java @@ -16,15 +16,16 @@ import java.util.Map; /** - * Static, checked-in table that preserves the C++ SDK service namespaces released before - * Revision 2 of the Service ID SEP (seps/accepted/shared/service-id.md) that diverge from the - * namespace derived from the service's codegen id ("serviceId"). + * Static, checked-in table that preserves the C++ SDK service namespaces that diverge from the + * namespace derived from the service's {@code "serviceId"} (so previously released names stay + * stable). * - *

The table is keyed by the exact, untransformed "serviceId" and is loaded from the shared - * data file {@code service-namespace-table.json}, which is the single source of truth shared - * with the Smithy generator. The SEP forbids keying on a docgen id, an endpoint prefix, or a - * model file name, and forbids deriving the preserved value from any of those; the values here - * are frozen literals, not computed from model metadata at generation time. + *

The table is keyed by the exact, untransformed {@code "serviceId"} and is loaded from the + * shared data file {@code service-namespace-table.json}, which is the single source of truth + * shared with the Smithy generator. Identifiers are derived from {@code "serviceId"} only, never + * from the display-name fields ({@code serviceAbbreviation}/{@code serviceFullName}), the endpoint + * prefix, or the model file name; the values here are frozen literals, not computed from model + * metadata at generation time. */ public final class ServiceNamespaceTable { diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/build.gradle.kts b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/build.gradle.kts index c2318532f97a..a4a4594d2e3b 100644 --- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/build.gradle.kts +++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/build.gradle.kts @@ -32,8 +32,8 @@ tasks.test { useJUnitPlatform() } -// Bundle the shared Service ID SEP identifier table (single source of truth, also consumed by the -// C2J generator) onto the classpath so ServiceNamespaceTable can load it as a resource. +// Bundle the shared service-id -> namespace identifier table (single source of truth, also consumed +// by the C2J generator) onto the classpath so ServiceNamespaceTable can load it as a resource. tasks.named("processResources") { from("$projectDir/../../../shared") { include("service-namespace-table.json") diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNameUtil.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNameUtil.java index e1577880dcc6..970060b86592 100644 --- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNameUtil.java +++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNameUtil.java @@ -16,10 +16,10 @@ public final class ServiceNameUtil { private static final String S3_CRT_PROJECTION_PREFIX = "s3-crt"; private static final String S3_CRT_NAMESPACE = "S3Crt"; - // SEP exception: legacy services with NO serviceId in their C2J model (generated as Smithy - // "mock" projections). They cannot be keyed by serviceId, so their already-released namespaces - // are preserved here, keyed by the normalized (sanitized, lower-cased) sdkId. Every other - // preserved namespace lives in the shared, serviceId-keyed table (ServiceNamespaceTable). + // Legacy services with NO serviceId in their C2J model (generated as Smithy "mock" projections). + // They cannot be keyed by serviceId, so their already-released namespaces are preserved here, + // keyed by the normalized (sanitized, lower-cased) sdkId. Every other preserved namespace lives + // in the shared, serviceId-keyed table (ServiceNamespaceTable). private static final Map SERVICE_ID_LESS_NAMESPACES = Map.ofEntries( Map.entry("importexport", "ImportExport"), Map.entry("simpledb", "SimpleDB") @@ -31,9 +31,9 @@ public static String getServiceName(ServiceShape service) { return S3_CRT_NAMESPACE; } - // Namespace is derived from the codegen id (sdkId == C2J serviceId) ONLY, per Revision 2 of - // the Service ID SEP. Docgen ids (smithy.api#title) MUST NOT be read. Pre-rev2 names that - // diverge from the sdkId-derived name are preserved via the shared identifier table. + // Namespace is derived from the service id (sdkId == C2J serviceId) ONLY. The display name + // (smithy.api#title) MUST NOT be read. Names that diverge from the sdkId-derived name are + // preserved via the shared identifier table. String serviceId = service.getTrait(ServiceTrait.class) .map(ServiceTrait::getSdkId) .orElse(service.getId().getName()); @@ -41,19 +41,19 @@ public static String getServiceName(ServiceShape service) { String sanitized = sanitizeServiceAbbreviation(serviceId); String key = sanitized.toLowerCase(); - // SEP exception: serviceId-less legacy (mock) services. + // Exception: serviceId-less legacy (mock) services. String exception = SERVICE_ID_LESS_NAMESPACES.get(key); if (exception != null) { return exception; } - // Preserved divergent name from the shared Service ID SEP table (keyed by serviceId). + // Preserved divergent name from the shared, serviceId-keyed table. String preserved = ServiceNamespaceTable.getByNormalizedKey(key); if (preserved != null) { return preserved; } - // SEP uniqueness guard: a sdkId-derived name must never collide with a preserved one. + // Uniqueness guard: a sdkId-derived name must never collide with a preserved one. if (ServiceNamespaceTable.isReservedNamespace(sanitized)) { throw new IllegalStateException(String.format( "Derived namespace '%s' for serviceId '%s' collides with a reserved namespace in %s", diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNamespaceTable.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNamespaceTable.java index 916ae9e1e127..27d62c0e56d6 100644 --- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNamespaceTable.java +++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/ServiceNamespaceTable.java @@ -16,17 +16,17 @@ import java.util.Map; /** - * Static, checked-in table preserving the C++ SDK service namespaces released before Revision 2 - * of the Service ID SEP (seps/accepted/shared/service-id.md) that diverge from the namespace - * derived from the service's codegen id ("serviceId" == Smithy sdkId). + * Static, checked-in table preserving the C++ SDK service namespaces that diverge from the + * namespace derived from the service's {@code "serviceId"} (== Smithy sdkId), so previously + * released names stay stable. * *

This loads the SAME shared data file as the C2J generator * ({@code tools/code-generation/shared/service-namespace-table.json}), the single source of truth. - * The file is keyed by the exact, untransformed serviceId per the SEP. Because the Smithy - * generator queries with a sanitized, lower-cased serviceId, this class builds a lookup index - * keyed by {@code sanitizeServiceAbbreviation(serviceId).toLowerCase()} (the same normalization + * The file is keyed by the exact, untransformed serviceId. Because the Smithy generator queries + * with a sanitized, lower-cased serviceId, this class builds a lookup index keyed by + * {@code sanitizeServiceAbbreviation(serviceId).toLowerCase()} (the same normalization * {@link ServiceNameUtil#getServiceName} applies). The stored values are frozen literals, never - * derived from a docgen id, endpoint prefix, or model file name. + * derived from the display-name fields, endpoint prefix, or model file name. */ public final class ServiceNamespaceTable { diff --git a/tools/code-generation/smithy/mapping/smithy-namespace-mapping.json b/tools/code-generation/smithy/mapping/smithy-namespace-mapping.json deleted file mode 100644 index 32a24d05b900..000000000000 --- a/tools/code-generation/smithy/mapping/smithy-namespace-mapping.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "neptune-graph": "Neptune Graph", - "rtbfabric": "RTBFabric", - "dax": "Amazon DAX", - "signer": "signer", - "entityresolution": "AWSEntityResolution", - "s3": "Amazon S3", - "s3-crt": "S3Crt", - "ram": "RAM", - "kafkaconnect": "Kafka Connect", - "connectparticipant": "Amazon Connect Participant", - "kinesis-video-media": "Kinesis Video Media", - "sesv2": "Amazon SES V2", - "pricing": "AWS Pricing", - "ssm-incidents": "SSM Incidents", - "inspector2": "Inspector2", - "mediastore": "MediaStore", - "docdb-elastic": "DocDB Elastic", - "kinesis-video-archived-media": "Kinesis Video Archived Media", - "elasticloadbalancingv2": "Elastic Load Balancing v2", - "drs": "drs", - "savingsplans": "AWSSavingsPlans", - "cloudfront": "CloudFront", - "rds": "Amazon RDS", - "comprehendmedical": "ComprehendMedical", - "ec2": "Amazon EC2", - "mediapackage-vod": "MediaPackage Vod", - "snowball": "Amazon Snowball", - "elasticmapreduce": "Amazon EMR", - "appsync": "AWSAppSync", - "kinesisvideo": "Kinesis Video", - "partnercentral-benefits": "PartnerCentral Benefits", - "ce": "AWS Cost Explorer", - "mediapackage": "MediaPackage", - "sagemaker-metrics": "SageMaker Metrics", - "lookoutequipment": "LookoutEquipment", - "kendra": "kendra", - "ebs": "Amazon EBS", - "iotdeviceadvisor": "AWSIoTDeviceAdvisor", - "ecs": "Amazon ECS", - "support-app": "SupportApp", - "pi": "AWS PI", - "dynamodb": "DynamoDB", - "states": "AWS SFN", - "wafv2": "WAFV2", - "connect": "Amazon Connect", - "sso-admin": "SSO Admin", - "outposts": "Outposts", - "ssm": "Amazon SSM", - "mgn": "mgn", - "ivs-realtime": "ivsrealtime", - "customer-profiles": "Customer Profiles", - "sagemaker": "SageMaker", - "kms": "KMS", - "iam": "IAM", - "timestream-write": "Timestream Write", - "marketplace-discovery": "Marketplace Discovery", - "ds": "Directory Service", - "kinesis-video-signaling": "Amazon Kinesis Video Signaling Channels", - "evs": "EVS", - "codepipeline": "CodePipeline", - "appconfig": "AppConfig", - "identitystore": "IdentityStore", - "cloudhsmv2": "CloudHSM V2", - "resource-groups": "Resource Groups", - "sms-voice": "Pinpoint SMS Voice", - "timestream-query": "Timestream Query", - "memorydb": "Amazon MemoryDB", - "acm": "ACM", - "synthetics": "Synthetics", - "monitoring": "CloudWatch", - "pinpoint-email": "Pinpoint Email", - "fis": "FIS", - "waf": "WAF", - "ecr-public": "Amazon ECR Public", - "health": "AWSHealth", - "managedblockchain": "ManagedBlockchain", - "awstransfer": "AWS Transfer", - "batch": "AWS Batch", - "inspector-scan": "inspector-scan", - "sso": "SSO", - "cloudcontrol": "CloudControlApi", - "mediapackagev2": "mediapackagev2", - "config": "Config Service", - "kafka": "Kafka", - "waf-regional": "WAF Regional", - "dlm": "Amazon DLM", - "servicecatalog-appregistry": "AppRegistry", - "mediastore-data": "MediaStore Data", - "elasticfilesystem": "EFS", - "kinesis": "Kinesis", - "mediaconvert": "MediaConvert", - "network-firewall": "Network Firewall", - "budgets": "AWSBudgets", - "servicediscovery": "ServiceDiscovery", - "application-insights": "Application Insights", - "marketplace-agreement": "Agreement Service", - "mediatailor": "MediaTailor", - "kinesisanalytics": "Kinesis Analytics", - "swf": "Amazon SWF", - "organizations": "Organizations", - "networkmanager": "NetworkManager", - "sns": "Amazon SNS", - "kendra-ranking": "Kendra Ranking", - "s3outposts": "Amazon S3 Outposts", - "connect-contact-lens": "Amazon Connect Contact Lens", - "neptune": "Amazon Neptune", - "healthlake": "HealthLake", - "email": "Amazon SES", - "codecommit": "CodeCommit", - "route53resolver": "Route53Resolver", - "sqs": "Amazon SQS", - "fms": "FMS", - "sts": "AWS STS", - "ssm-contacts": "SSM Contacts", - "ssm-sap": "SsmSap", - "kinesisanalyticsv2": "Kinesis Analytics V2", - "mturk-requester": "Amazon MTurk", - "route53": "Route 53", - "datasync": "DataSync", - "greengrassv2": "AWS GreengrassV2", - "codedeploy": "CodeDeploy", - "timestream-influxdb": "Timestream InfluxDB", - "firehose": "Firehose", - "acm-pca": "ACM-PCA", - "imagebuilder": "imagebuilder", - "connectcases": "ConnectCases", - "lexv2-models": "Lex Models V2", - "ecr": "Amazon ECR", - "elasticbeanstalk": "Elastic Beanstalk", - "docdb": "Amazon DocDB", - "sso-oidc": "SSO OIDC", - "marketplace-catalog": "AWS Marketplace Catalog", - "codeguru-reviewer": "CodeGuruReviewer", - "b2bi": "AWS B2BI", - "ivs": "Amazon IVS", - "ivschat": "ivschat", - "amplify": "Amplify", - "ec2-instance-connect": "EC2 Instance Connect", - "lexv2-runtime": "Lex Runtime V2", - "cloudhsm": "CloudHSM", - "eks": "Amazon EKS", - "cloudtrail": "CloudTrail", - "shield": "AWS Shield", - "medialive": "MediaLive", - "wellarchitected": "Well-Architected" -} \ No newline at end of file diff --git a/tools/scripts/codegen/legacy_c2j_cpp_gen.py b/tools/scripts/codegen/legacy_c2j_cpp_gen.py index bb5bccf518b5..b9e5fdf6c631 100644 --- a/tools/scripts/codegen/legacy_c2j_cpp_gen.py +++ b/tools/scripts/codegen/legacy_c2j_cpp_gen.py @@ -153,8 +153,6 @@ def generate(self, executor: ProcessPoolExecutor, max_workers: int, args: dict) if len(failures): return -1 - self._create_smithy_namespace_mapping(self.c2j_models) - print(f"Code generation done, (re)generated {len(done)} packages.") # Including defaults and partitions return 0 @@ -376,20 +374,3 @@ def _generate_core_component(self, output_zip_file = self.run_generator_once(f"core/{component_name}", run_command, output_filename) return self.extract_zip(output_zip_file, f"core/{component_name}", output_dir, None) - - def _create_smithy_namespace_mapping(self, c2j_models: dict): - mapping = {} - for service, model_files in c2j_models.items(): - full_model_file_path = f"{self.path_to_api_definitions}/{model_files.c2j_model}" - with open(full_model_file_path, 'r') as f: - model = json.load(f) - abbreviation = model.get("metadata", {}).get("serviceAbbreviation") - if abbreviation: - mapping[service] = abbreviation - # add customization for s3-crt - mapping["s3-crt"] = "S3Crt" - output_path = f"{self.path_to_api_definitions}/../smithy/mapping/smithy-namespace-mapping.json" - tmp_path = output_path + ".tmp" - with open(tmp_path, 'w') as f: - json.dump(mapping, f, indent=2) - os.replace(tmp_path, output_path) diff --git a/tools/scripts/codegen/smithy_cpp_gen.py b/tools/scripts/codegen/smithy_cpp_gen.py index 29e0fdc3b318..7652ed92e09b 100644 --- a/tools/scripts/codegen/smithy_cpp_gen.py +++ b/tools/scripts/codegen/smithy_cpp_gen.py @@ -16,7 +16,6 @@ SMITHY_GENERATOR_LOCATION = "tools/code-generation/smithy/cpp-codegen" SMITHY_TO_C2J_MAP_FILE = "tools/code-generation/smithy/cpp-codegen/smithy2c2j_service_map.json" -SMITHY_NAMPESPACE_MAPPING_FILE = "tools/code-generation/smithy/mapping/smithy-namespace-mapping.json" SMITHY_API_DESCRIPTIONS_DIR = "tools/code-generation/smithy/api-descriptions" BDD_BYTECODER_FILE = "crt/aws-crt-cpp/crt/aws-c-sdkutils/compiler/bdd-bytecoder.py" ENDPOINT_BDD_TRAIT = "smithy.rules#endpointBdd" @@ -63,8 +62,6 @@ def __init__(self, debug: bool, use_smithy_models: bool = False, with open(os.path.abspath(SMITHY_TO_C2J_MAP_FILE), 'r') as file: self.smithy_c2j_data = json.load(file) self.c2j_smithy_data = {value: key for key, value in self.smithy_c2j_data.items()} - with open(os.path.abspath(SMITHY_NAMPESPACE_MAPPING_FILE), 'r') as file: - self.smithy_namespace_mappings = json.load(file) def generate(self, clients_to_build: set): """Generate Smithy-based code for SDK clients""" @@ -96,8 +93,7 @@ def _generate_pagination(self, smithy_services: List[str], smithy_c2j_data: str) "./gradlew", "build", "-PservicesFilter=" + ",".join(smithy_services), - "-Pc2jMap=" + smithy_c2j_data, - "-PnamespaceMappings=" + json.dumps(self.smithy_namespace_mappings) + "-Pc2jMap=" + smithy_c2j_data ] if self.use_smithy_models: smithy_codegen_command.append("-PgenerateModels=true") From eae5f3a969e450618bea8d54c8bc0417a61d670b Mon Sep 17 00:00:00 2001 From: wty-Bryant Date: Thu, 5 Nov 2026 18:32:41 -0500 Subject: [PATCH 4/6] add codegen namespace test for c2j service metadata and smithy service shape --- ...jModelToGeneratorModelTransformerTest.java | 44 +++++++++++++++++++ .../generators/model/ServiceNameUtilTest.java | 39 ++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/tools/code-generation/generator/src/test/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformerTest.java b/tools/code-generation/generator/src/test/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformerTest.java index 78e089d32dcb..83c791bf09e3 100644 --- a/tools/code-generation/generator/src/test/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformerTest.java +++ b/tools/code-generation/generator/src/test/java/com/amazonaws/util/awsclientgenerator/transform/C2jModelToGeneratorModelTransformerTest.java @@ -94,6 +94,50 @@ public void testMetadataConversationWithStandalonePackages() { assertEquals("application-json", metadata.findFirstSupportedProtocol()); } + // A token that cannot appear in a serviceId-derived namespace. If the namespace derivation ever + // read a display-name field, this would leak into the generated identifier. + private static final String DISPLAY_NAME_SENTINEL = "DisplayNameSentinelZZZ"; + + private static C2jServiceModel modelWithIds(String serviceId, String serviceFullName, String serviceAbbreviation) { + C2jServiceModel model = new C2jServiceModel(); + C2jMetadata c2jMetadata = new C2jMetadata(); + c2jMetadata.setServiceId(serviceId); + c2jMetadata.setServiceFullName(serviceFullName); + c2jMetadata.setServiceAbbreviation(serviceAbbreviation); + c2jMetadata.setEndpointPrefix("endpoint"); + c2jMetadata.setProtocol("json"); + c2jMetadata.setApiVersion("2021-01-01"); + c2jMetadata.setUid(serviceId + "-2021-01-01"); + model.setMetadata(c2jMetadata); + return model; + } + + // Guard: the service namespace MUST derive from serviceId only. Swapping the display-name fields + // (serviceFullName / serviceAbbreviation) for a sentinel must not change the derived namespace, + // nor leak the sentinel into it. serviceId NOT in the identifier table. + @Test + public void testNamespaceDerivesFromServiceIdOnly_derivedService() { + Metadata clean = new C2jModelToGeneratorModelTransformer( + modelWithIds("GuardTestService", "AWS Guard Test Service", "GTS"), false).convertMetadata(); + Metadata sentineled = new C2jModelToGeneratorModelTransformer( + modelWithIds("GuardTestService", DISPLAY_NAME_SENTINEL + " Full Name", DISPLAY_NAME_SENTINEL), false).convertMetadata(); + + assertEquals("GuardTestService", clean.getNamespace()); + assertEquals(clean.getNamespace(), sentineled.getNamespace()); + assertFalse(sentineled.getNamespace().contains(DISPLAY_NAME_SENTINEL)); + } + + // Same guard for a serviceId that is present in the identifier table (amp -> PrometheusService): + // the preserved name must come from the serviceId-keyed table, never from the display-name fields. + @Test + public void testNamespaceDerivesFromServiceIdOnly_tableService() { + Metadata sentineled = new C2jModelToGeneratorModelTransformer( + modelWithIds("amp", DISPLAY_NAME_SENTINEL + " Full Name", DISPLAY_NAME_SENTINEL), false).convertMetadata(); + + assertEquals("PrometheusService", sentineled.getNamespace()); + assertFalse(sentineled.getNamespace().contains(DISPLAY_NAME_SENTINEL)); + } + @Test public void testStructureShapeConversion() { C2jServiceModel c2jServiceModel = new C2jServiceModel(); diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ServiceNameUtilTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ServiceNameUtilTest.java index 9d8e5ba74de0..7618f36c9d45 100644 --- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ServiceNameUtilTest.java +++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ServiceNameUtilTest.java @@ -6,10 +6,31 @@ import com.amazonaws.util.awsclientsmithygenerator.generators.ServiceNameUtil; import org.junit.jupiter.api.Test; +import software.amazon.smithy.aws.traits.ServiceTrait; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.traits.TitleTrait; import static org.junit.jupiter.api.Assertions.*; class ServiceNameUtilTest { + // A token that cannot appear in a sdkId-derived namespace. If the namespace derivation ever read + // the display name (smithy.api#title), this would leak into the generated identifier. + private static final String DISPLAY_NAME_SENTINEL = "DisplayNameSentinelZZZ"; + + private static ServiceShape serviceWith(String sdkId, String title) { + return ServiceShape.builder() + .id("com.example#Service") + .version("2021-01-01") + .addTrait(ServiceTrait.builder() + .sdkId(sdkId) + .arnNamespace("guardtest") + .cloudFormationName("GuardTest") + .cloudTrailEventSource("guardtest.amazonaws.com") + .build()) + .addTrait(new TitleTrait(title)) + .build(); + } + @Test void capitalize_lowercaseString() { assertEquals("Kinesis", ServiceNameUtil.capitalize("kinesis")); @@ -40,4 +61,22 @@ void getExportMacro_standardService() { void getProjectName_standardService() { // Project name format (lowercase hyphenated) needs a full ServiceShape; covered separately. } + + // Guard: the namespace MUST derive from the sdkId (== C2J serviceId) only. A sentinel title must + // not change the derived namespace, nor leak into it. sdkId NOT in the identifier table. + @Test + void getServiceName_derivesFromSdkIdOnly_derivedService() { + String name = ServiceNameUtil.getServiceName(serviceWith("GuardTestService", DISPLAY_NAME_SENTINEL + " Title")); + assertEquals("GuardTestService", name); + assertFalse(name.contains(DISPLAY_NAME_SENTINEL)); + } + + // Same guard for a sdkId present in the identifier table (amp -> PrometheusService): the preserved + // name must come from the sdkId-keyed table, never from the display name. + @Test + void getServiceName_derivesFromSdkIdOnly_tableService() { + String name = ServiceNameUtil.getServiceName(serviceWith("amp", DISPLAY_NAME_SENTINEL + " Title")); + assertEquals("PrometheusService", name); + assertFalse(name.contains(DISPLAY_NAME_SENTINEL)); + } } From a91bef40447f5f863ac96be62dc024efa6217dcc Mon Sep 17 00:00:00 2001 From: wty-Bryant Date: Thu, 5 Nov 2026 23:43:21 -0500 Subject: [PATCH 5/6] Restore aws-crt-cpp submodule to mainline (v0.44.2) --- crt/aws-crt-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crt/aws-crt-cpp b/crt/aws-crt-cpp index 9c319786d1d8..58bca57cbc8b 160000 --- a/crt/aws-crt-cpp +++ b/crt/aws-crt-cpp @@ -1 +1 @@ -Subproject commit 9c319786d1d83d5ace6a9de817dcfe1861003609 +Subproject commit 58bca57cbc8b242468414972a6a35f92676b164d From 2589c2969ea3989b91d3ea3b9697a9a7ed324b6c Mon Sep 17 00:00:00 2001 From: wty-Bryant Date: Fri, 6 Nov 2026 00:15:21 -0500 Subject: [PATCH 6/6] include dualstack in client config to resolve cspeel error --- .cspell.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.cspell.json b/.cspell.json index 07d085df6ce9..34b701bfb9b1 100644 --- a/.cspell.json +++ b/.cspell.json @@ -315,7 +315,8 @@ "eusc", "EUSC", "CBOR", - "cbor" + "cbor", + "dualstack" ], "ignoreWords": [ // variables defined in ConcurrentStreamBuf