diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml_
similarity index 100%
rename from .github/workflows/ci.yml
rename to .github/workflows/ci.yml_
diff --git a/.github/workflows/release.yml_ b/.github/workflows/release.yml_
new file mode 100644
index 0000000..36b96e4
--- /dev/null
+++ b/.github/workflows/release.yml_
@@ -0,0 +1,90 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+
+ name: Release to Maven Central
+
+ permissions:
+ contents: write
+ packages: write
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Java 21
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+ cache: 'gradle'
+
+ - name: Extract version from tag
+ id: version
+ run: |
+ VERSION=${GITHUB_REF#refs/tags/v}
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "Releasing version: $VERSION"
+
+ - name: Update version in build.gradle.kts
+ run: |
+ sed -i "s/version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" build.gradle.kts
+ cat build.gradle.kts | grep "version ="
+
+ - name: Make Gradle wrapper executable
+ run: chmod +x gradlew
+
+ - name: Build
+ run: ./gradlew clean build
+
+ - name: Import GPG key
+ uses: crazy-max/ghaction-import-gpg@v6
+ with:
+ gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
+ passphrase: ${{ secrets.GPG_PASSPHRASE }}
+
+ - name: Publish to Maven Central
+ run: ./gradlew publish
+ env:
+ MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
+ MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
+ GPG_SIGNING_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+ GPG_SIGNING_PASSWORD: ${{ secrets.GPG_PASSPHRASE }}
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v1
+ with:
+ name: Release ${{ steps.version.outputs.version }}
+ draft: false
+ prerelease: false
+ files: |
+ build/libs/smithy-unison-*.jar
+ body: |
+ ## smithy-unison v${{ steps.version.outputs.version }}
+
+ ### Installation
+
+ Add to your `smithy-build.json`:
+ ```json
+ {
+ "maven": {
+ "dependencies": [
+ "io.smithy.unison:smithy-unison:${{ steps.version.outputs.version }}"
+ ]
+ }
+ }
+ ```
+
+ Or add to your build.gradle.kts:
+ ```kotlin
+ dependencies {
+ implementation("io.smithy.unison:smithy-unison:${{ steps.version.outputs.version }}")
+ }
+ ```
diff --git a/examples/aws-demo/compile.sh b/examples/aws-demo/compile.sh
index 3334c19..72930c2 100755
--- a/examples/aws-demo/compile.sh
+++ b/examples/aws-demo/compile.sh
@@ -108,59 +108,6 @@ scratch/main> load generated/aws_s3_client.u
scratch/main> add
\`\`\`
-Create namespace aliases so main.u can use lib.f34nk_aws_0_1_2 imports:
-
-\`\`\`ucm
-scratch/main> alias.type Config lib.f34nk_aws_0_1_2.Config
-scratch/main> alias.type Credentials lib.f34nk_aws_0_1_2.Credentials
-scratch/main> alias.type ListBucketsRequest lib.f34nk_aws_0_1_2.ListBucketsRequest
-scratch/main> alias.type ListBucketsOutput lib.f34nk_aws_0_1_2.ListBucketsOutput
-scratch/main> alias.type Bucket lib.f34nk_aws_0_1_2.Bucket
-scratch/main> alias.type PutObjectRequest lib.f34nk_aws_0_1_2.PutObjectRequest
-scratch/main> alias.type PutObjectOutput lib.f34nk_aws_0_1_2.PutObjectOutput
-scratch/main> alias.type ListObjectsV2Request lib.f34nk_aws_0_1_2.ListObjectsV2Request
-scratch/main> alias.type ListObjectsV2Output lib.f34nk_aws_0_1_2.ListObjectsV2Output
-scratch/main> alias.type Object lib.f34nk_aws_0_1_2.Object
-scratch/main> alias.type GetObjectRequest lib.f34nk_aws_0_1_2.GetObjectRequest
-scratch/main> alias.type GetObjectOutput lib.f34nk_aws_0_1_2.GetObjectOutput
-scratch/main> alias.type DeleteObjectRequest lib.f34nk_aws_0_1_2.DeleteObjectRequest
-scratch/main> alias.type DeleteObjectOutput lib.f34nk_aws_0_1_2.DeleteObjectOutput
-\`\`\`
-
-\`\`\`ucm
-scratch/main> alias.term objectCannedACLFromText lib.f34nk_aws_0_1_2.objectCannedACLFromText
-scratch/main> alias.term listBuckets lib.f34nk_aws_0_1_2.listBuckets
-scratch/main> alias.term putObject lib.f34nk_aws_0_1_2.putObject
-scratch/main> alias.term listObjectsV2 lib.f34nk_aws_0_1_2.listObjectsV2
-scratch/main> alias.term getObject lib.f34nk_aws_0_1_2.getObject
-scratch/main> alias.term deleteObject lib.f34nk_aws_0_1_2.deleteObject
-\`\`\`
-
-Alias constructors:
-
-\`\`\`ucm
-scratch/main> alias.term Credentials.Credentials lib.f34nk_aws_0_1_2.Credentials.Credentials
-scratch/main> alias.term Config.Config lib.f34nk_aws_0_1_2.Config.Config
-scratch/main> alias.term ListBucketsRequest.ListBucketsRequest lib.f34nk_aws_0_1_2.ListBucketsRequest.ListBucketsRequest
-scratch/main> alias.term PutObjectRequest.PutObjectRequest lib.f34nk_aws_0_1_2.PutObjectRequest.PutObjectRequest
-scratch/main> alias.term ListObjectsV2Request.ListObjectsV2Request lib.f34nk_aws_0_1_2.ListObjectsV2Request.ListObjectsV2Request
-scratch/main> alias.term GetObjectRequest.GetObjectRequest lib.f34nk_aws_0_1_2.GetObjectRequest.GetObjectRequest
-scratch/main> alias.term DeleteObjectRequest.DeleteObjectRequest lib.f34nk_aws_0_1_2.DeleteObjectRequest.DeleteObjectRequest
-\`\`\`
-
-Alias record accessors:
-
-\`\`\`ucm
-scratch/main> alias.term Bucket.name lib.f34nk_aws_0_1_2.Bucket.name
-scratch/main> alias.term Config.endpoint lib.f34nk_aws_0_1_2.Config.endpoint
-scratch/main> alias.term Config.region lib.f34nk_aws_0_1_2.Config.region
-scratch/main> alias.term Object.key lib.f34nk_aws_0_1_2.Object.key
-scratch/main> alias.term Object.size lib.f34nk_aws_0_1_2.Object.size
-scratch/main> alias.term ListBucketsOutput.buckets lib.f34nk_aws_0_1_2.ListBucketsOutput.buckets
-scratch/main> alias.term ListObjectsV2Output.contents lib.f34nk_aws_0_1_2.ListObjectsV2Output.contents
-scratch/main> alias.term GetObjectOutput.body lib.f34nk_aws_0_1_2.GetObjectOutput.body
-\`\`\`
-
Load the main application:
\`\`\`ucm
diff --git a/examples/aws-demo/smithy-build.json b/examples/aws-demo/smithy-build.json
index 7a6d79b..e55a24c 100644
--- a/examples/aws-demo/smithy-build.json
+++ b/examples/aws-demo/smithy-build.json
@@ -1,10 +1,14 @@
{
"version": "1.0",
- "sources": ["model"],
+ "sources": [
+ "model"
+ ],
"maven": {
"dependencies": [
- "software.amazon.smithy:smithy-model:1.64.0",
"software.amazon.smithy:smithy-aws-traits:1.64.0",
+ "software.amazon.smithy:smithy-aws-endpoints:1.64.0",
+ "software.amazon.smithy:smithy-aws-smoke-test-model:1.64.0",
+ "software.amazon.smithy:smithy-aws-iam-traits:1.64.0",
"io.smithy.unison:smithy-unison:0.1.0"
],
"repositories": [
@@ -19,6 +23,7 @@
"plugins": {
"unison-codegen": {
"service": "com.amazonaws.s3#AmazonS3",
+ "name": "s3",
"namespace": "aws.s3",
"outputDir": "generated"
}
diff --git a/examples/aws-demo/src/main.u b/examples/aws-demo/src/main.u
index 7e8c4ca..dffcc08 100644
--- a/examples/aws-demo/src/main.u
+++ b/examples/aws-demo/src/main.u
@@ -8,15 +8,6 @@
-- load aws_http_bridge.u
-- add
--- Import types from @f34nk/aws library (use statements are file-scoped in Unison)
-use lib.f34nk_aws_0_1_2 Config Credentials
-use lib.f34nk_aws_0_1_2 ListBucketsRequest ListBucketsOutput Bucket
-use lib.f34nk_aws_0_1_2 PutObjectRequest PutObjectOutput objectCannedACLFromText
-use lib.f34nk_aws_0_1_2 ListObjectsV2Request ListObjectsV2Output Object
-use lib.f34nk_aws_0_1_2 GetObjectRequest GetObjectOutput
-use lib.f34nk_aws_0_1_2 DeleteObjectRequest DeleteObjectOutput
-use lib.f34nk_aws_0_1_2 listBuckets putObject listObjectsV2 getObject deleteObject
-
-- =============================================================================
-- Helpers
-- =============================================================================
@@ -32,14 +23,14 @@ envOrDefault name default = do
-- =============================================================================
-- Create S3 client configuration for LocalStack
-defaultConfig : '{IO, Exception} Config
+defaultConfig : '{IO, Exception} Aws.S3.Config
defaultConfig = do
key = !(envOrDefault "AWS_ACCESS_KEY_ID" "dummy")
secret = !(envOrDefault "AWS_SECRET_ACCESS_KEY" "dummy")
creds = Credentials.Credentials key secret None
endpoint = !(envOrDefault "AWS_ENDPOINT" "http://localhost:4566")
region = !(envOrDefault "AWS_DEFAULT_REGION" "us-east-1")
- Config.Config endpoint region creds true
+ Aws.S3.Config.Config endpoint region creds true
-- Test bucket name - created by terraform provisioner in docker-compose
testBucket : Text
@@ -134,20 +125,20 @@ runDemo = do
List all buckets in the account.
The terraform provisioner creates 'us-east-1-nonprod-configs' bucket.
}}
-listBucketsDemo : Config -> '{IO, Exception, Threads} ()
+listBucketsDemo : Aws.S3.Config -> '{IO, Exception, Threads} ()
listBucketsDemo config = do
printLine " → Creating ListBucketsRequest..."
-- Create request with all optional fields as None
- request = ListBucketsRequest.ListBucketsRequest None None None None
+ request = Aws.S3.ListBucketsRequest.ListBucketsRequest None None None None
-- Call the S3 client
printLine " → Calling listBuckets..."
- result = !(listBuckets config request)
+ result = !(Aws.S3.listBuckets config request)
-- Extract and print buckets
printLine " ✓ SUCCESS: ListBuckets returned"
- match ListBucketsOutput.buckets result with
+ match Aws.S3.ListBucketsOutput.buckets result with
Some buckets ->
count = List.size buckets
printLine (" → Found " ++ Nat.toText count ++ " bucket(s):")
@@ -158,7 +149,7 @@ listBucketsDemo config = do
{{
Upload a test object to the configs bucket.
}}
-putObjectDemo : Config -> '{IO, Exception, Threads} ()
+putObjectDemo : Aws.S3.Config -> '{IO, Exception, Threads} ()
putObjectDemo config = do
printLine (" → Creating PutObjectRequest...")
printLine (" Bucket: " ++ testBucket)
@@ -170,37 +161,37 @@ putObjectDemo config = do
-- PutObjectRequest with minimal required fields (41 fields)
-- Field order: aCL body bucket bucketKeyEnabled cacheControl checksumAlgorithm ...
-- Set ACL to public-read so we can read the object back
- acl = objectCannedACLFromText "public-read"
+ acl = Aws.S3.objectCannedACLFromText "public-read"
request = PutObjectRequest.PutObjectRequest acl body testBucket None None None None None None None None None None None None None (Some "text/plain") None None None None None None None None testObjectKey None None None None None None None None None None None None None None None
-- Call the S3 client
printLine " → Calling putObject..."
- result = !(putObject config request)
+ result = !(Aws.S3.putObject config request)
printLine " ✓ SUCCESS: PutObject completed"
-- Print ETag if available
- match PutObjectOutput.eTag result with
+ match Aws.S3.PutObjectOutput.eTag result with
Some etag -> printLine (" ETag: " ++ etag)
None -> ()
{{
List objects in the configs prefix.
}}
-listObjectsV2Demo : Config -> '{IO, Exception, Threads} ()
+listObjectsV2Demo : Aws.S3.Config -> '{IO, Exception, Threads} ()
listObjectsV2Demo config = do
printLine " → Creating ListObjectsV2Request..."
printLine (" Bucket: " ++ testBucket)
printLine (" Prefix: configs/")
-- ListObjectsV2Request (11 fields)
- request = ListObjectsV2Request.ListObjectsV2Request testBucket None None None None None (Some +100) None (Some "configs/") None None
+ request = Aws.S3.ListObjectsV2Request.ListObjectsV2Request testBucket None None None None None (Some +100) None (Some "configs/") None None
-- Call the S3 client
printLine " → Calling listObjectsV2..."
- result = !(listObjectsV2 config request)
+ result = !(Aws.S3.listObjectsV2 config request)
printLine " ✓ SUCCESS: ListObjectsV2 returned"
- match ListObjectsV2Output.contents result with
+ match Aws.S3.ListObjectsV2Output.contents result with
Some objects ->
count = List.size objects
printLine (" → Found " ++ Nat.toText count ++ " object(s):")
@@ -211,21 +202,21 @@ listObjectsV2Demo config = do
{{
Get the test object we uploaded.
}}
-getObjectDemo : Config -> '{IO, Exception, Threads} ()
+getObjectDemo : Aws.S3.Config -> '{IO, Exception, Threads} ()
getObjectDemo config = do
printLine " → Creating GetObjectRequest..."
printLine (" Bucket: " ++ testBucket)
printLine (" Key: " ++ testObjectKey)
-- GetObjectRequest (21 fields)
- request = GetObjectRequest.GetObjectRequest testBucket None None None None None None testObjectKey None None None None None None None None None None None None None
+ request = Aws.S3.GetObjectRequest.GetObjectRequest testBucket None None None None None None testObjectKey None None None None None None None None None None None None None
-- Call the S3 client
printLine " → Calling getObject..."
- result = !(getObject config request)
+ result = !(Aws.S3.getObject config request)
-- Extract and print body
- body = GetObjectOutput.body result
+ body = Aws.S3.GetObjectOutput.body result
bodyText = fromUtf8 body
printLine " ✓ SUCCESS: GetObject returned"
@@ -240,42 +231,42 @@ getObjectDemo config = do
{{
Delete the test object (cleanup).
}}
-deleteObjectDemo : Config -> '{IO, Exception, Threads} ()
+deleteObjectDemo : Aws.S3.Config -> '{IO, Exception, Threads} ()
deleteObjectDemo config = do
printLine " → Creating DeleteObjectRequest..."
printLine (" Bucket: " ++ testBucket)
printLine (" Key: " ++ testObjectKey)
-- DeleteObjectRequest (10 fields)
- request = DeleteObjectRequest.DeleteObjectRequest testBucket None None None None None testObjectKey None None None
+ request = Aws.S3.DeleteObjectRequest.DeleteObjectRequest testBucket None None None None None testObjectKey None None None
-- Call the S3 client
printLine " → Calling deleteObject..."
- result = !(deleteObject config request)
+ result = !(Aws.S3.deleteObject config request)
printLine " ✓ SUCCESS: DeleteObject completed"
-- Print delete marker if versioned
- match DeleteObjectOutput.deleteMarker result with
+ match Aws.S3.DeleteObjectOutput.deleteMarker result with
Some true -> printLine " Delete marker created (versioned bucket)"
_ -> ()
{{
Verify the test object was deleted by listing objects.
}}
-verifyDeletionDemo : Config -> '{IO, Exception, Threads} ()
+verifyDeletionDemo : Aws.S3.Config -> '{IO, Exception, Threads} ()
verifyDeletionDemo config = do
printLine " → Listing objects after deletion..."
printLine (" Bucket: " ++ testBucket)
printLine (" Prefix: configs/")
-- ListObjectsV2Request (11 fields)
- request = ListObjectsV2Request.ListObjectsV2Request testBucket None None None None None (Some +100) None (Some "configs/") None None
+ request = Aws.S3.ListObjectsV2Request.ListObjectsV2Request testBucket None None None None None (Some +100) None (Some "configs/") None None
-- Call the S3 client
- result = !(listObjectsV2 config request)
+ result = !(Aws.S3.listObjectsV2 config request)
printLine " ✓ SUCCESS: ListObjectsV2 returned"
- match ListObjectsV2Output.contents result with
+ match Aws.S3.ListObjectsV2Output.contents result with
Some objects ->
count = List.size objects
printLine (" → Found " ++ Nat.toText count ++ " object(s):")
diff --git a/generate-aws-sdk/Makefile b/generate-aws-sdk/Makefile
index 800bb00..26e3e95 100644
--- a/generate-aws-sdk/Makefile
+++ b/generate-aws-sdk/Makefile
@@ -19,7 +19,7 @@ build: init
# Build AWS SDK models
#
./generate.py
- tree -h output/*/src
+ tree -h output/*/generated
.PHONY: clean
clean:
diff --git a/generate-aws-sdk/generate.py b/generate-aws-sdk/generate.py
index 17c5a0e..867a866 100755
--- a/generate-aws-sdk/generate.py
+++ b/generate-aws-sdk/generate.py
@@ -23,7 +23,7 @@
INPUT_PATH = "api-models-aws-main/models"
OUTPUT_PATH = "output"
MODEL_DIRNAME = "model"
-GENERATED_DIRNAME = "src"
+GENERATED_DIRNAME = "generated"
BUILD_DIRNAME = "build"
@@ -74,8 +74,10 @@ def snake_case(x):
result["model"] = source_path
result["key"] = key
- model_name = key.split("#")[1]
+ # model_name = key.split("#")[1].split("_")[0]
+ model_name = key.split("#")[0].split(".")[-1]
module_name = snake_case(model_name)
+ namespace = "aws." + module_name
build_json = {
"version": "1.0",
@@ -96,7 +98,8 @@ def snake_case(x):
"plugins": {
"unison-codegen": {
"service": key,
- "module": module_name,
+ "name": model_name,
+ "namespace": namespace,
"outputDir": GENERATED_DIRNAME,
}
},
diff --git a/src/main/java/io/smithy/unison/codegen/ClientModuleWriter.java b/src/main/java/io/smithy/unison/codegen/ClientModuleWriter.java
index 9280bff..87b2be7 100644
--- a/src/main/java/io/smithy/unison/codegen/ClientModuleWriter.java
+++ b/src/main/java/io/smithy/unison/codegen/ClientModuleWriter.java
@@ -41,6 +41,7 @@ public final class ClientModuleWriter {
private final ServiceShape service;
private final Model model;
private final String namespace;
+ private final String clientNamespace;
private final FileManifest fileManifest;
private final String outputDir;
private final UnisonContext context;
@@ -54,11 +55,41 @@ public ClientModuleWriter(ServiceShape service, Model model, String namespace,
this.service = service;
this.model = model;
this.namespace = namespace;
+ this.clientNamespace = context.settings().getClientNamespace();
this.fileManifest = fileManifest;
this.outputDir = outputDir;
this.context = context;
}
+ /**
+ * Gets the client namespace for prefixing types and functions.
+ *
+ * @return The client namespace (e.g., "Aws.S3")
+ */
+ public String getClientNamespace() {
+ return clientNamespace;
+ }
+
+ /**
+ * Converts a type name to a namespaced type name.
+ *
+ * @param name The base type name
+ * @return The namespaced type name (e.g., "Aws.S3.Config")
+ */
+ private String getNamespacedTypeName(String name) {
+ return UnisonSymbolProvider.toNamespacedTypeName(name, clientNamespace);
+ }
+
+ /**
+ * Converts a function name to a namespaced function name.
+ *
+ * @param name The base function name
+ * @return The namespaced function name (e.g., "Aws.S3.createBucket")
+ */
+ private String getNamespacedFunctionName(String name) {
+ return UnisonSymbolProvider.toNamespacedFunctionName(name, clientNamespace);
+ }
+
/**
* Creates a writer using UnisonContext.
*/
@@ -134,7 +165,7 @@ public void generate() throws IOException {
}
// Generate pagination helpers
- PaginationGenerator paginationGenerator = new PaginationGenerator();
+ PaginationGenerator paginationGenerator = new PaginationGenerator(clientNamespace);
paginationGenerator.generate(service, model, writer);
// Write to file
@@ -156,18 +187,21 @@ public void generate() throws IOException {
*
Used for AWS services that require authentication and S3-style configuration.
*/
private void generateAwsConfigTypes(UnisonWriter writer) {
+ String configType = getNamespacedTypeName("Config");
+ String credentialsType = getNamespacedTypeName("Credentials");
+
writer.writeDocComment("Configuration for the " + service.getId().getName() + " client");
- writer.write("type Config = {");
+ writer.write("type $L = {", configType);
writer.indent();
writer.write("endpoint : Text,");
writer.write("region : Text,");
- writer.write("credentials : Credentials,");
+ writer.write("credentials : $L,", credentialsType);
writer.write("usePathStyle : Boolean");
writer.dedent();
writer.write("}");
writer.writeBlankLine();
- writer.write("type Credentials = {");
+ writer.write("type $L = {", credentialsType);
writer.indent();
writer.write("accessKeyId : Text,");
writer.write("secretAccessKey : Text,");
@@ -183,8 +217,10 @@ private void generateAwsConfigTypes(UnisonWriter writer) {
*
Used for services that don't require AWS authentication.
*/
private void generateGenericConfigType(UnisonWriter writer) {
+ String configType = getNamespacedTypeName("Config");
+
writer.writeDocComment("Configuration for the " + service.getId().getName() + " client");
- writer.write("type Config = {");
+ writer.write("type $L = {", configType);
writer.indent();
writer.write("endpoint : Text,");
writer.write("headers : [(Text, Text)]");
@@ -232,7 +268,7 @@ private void generateModelTypes(UnisonWriter writer) {
for (Shape enumShape : enums) {
if (enumShape instanceof EnumShape) {
- EnumGenerator generator = new EnumGenerator((EnumShape) enumShape, model);
+ EnumGenerator generator = new EnumGenerator((EnumShape) enumShape, model, clientNamespace);
generator.generate(writer);
writer.writeBlankLine();
} else if (enumShape instanceof StringShape && enumShape.hasTrait(software.amazon.smithy.model.traits.EnumTrait.class)) {
@@ -241,7 +277,7 @@ private void generateModelTypes(UnisonWriter writer) {
writer.writeBlankLine();
} else if (enumShape instanceof UnionShape) {
// Generate union types as sum types
- UnionGenerator generator = new UnionGenerator((UnionShape) enumShape, model);
+ UnionGenerator generator = new UnionGenerator((UnionShape) enumShape, model, clientNamespace);
generator.generate(writer);
writer.writeBlankLine();
}
@@ -255,7 +291,7 @@ private void generateModelTypes(UnisonWriter writer) {
for (StructureShape structure : structures) {
StructureGenerator generator = new StructureGenerator(
- structure, model, context.symbolProvider());
+ structure, model, context.symbolProvider(), clientNamespace);
generator.generate(writer);
writer.writeBlankLine();
}
@@ -271,7 +307,7 @@ private void generateModelTypes(UnisonWriter writer) {
for (StructureShape error : errors) {
StructureGenerator generator = new StructureGenerator(
- error, model, context.symbolProvider());
+ error, model, context.symbolProvider(), clientNamespace);
generator.generate(writer);
// Generate toFailure function for errors
@@ -333,7 +369,7 @@ private void collectReferencedShapes(ShapeId shapeId, Set struct
* Generates a toFailure conversion function for an error type.
*/
private void generateErrorToFailure(StructureShape error, UnisonWriter writer) {
- String typeName = UnisonSymbolProvider.toUnisonTypeName(error.getId().getName());
+ String typeName = getNamespacedTypeName(error.getId().getName());
String funcName = typeName + ".toFailure";
writer.writeSignature(funcName, typeName + " -> Failure");
@@ -387,8 +423,9 @@ private void generateXmlParsers(Set structures, UnisonWriter wri
* Generates an XML parser function for a single structure.
*/
private void generateXmlParserForStructure(StructureShape structure, UnisonWriter writer) {
- String typeName = UnisonSymbolProvider.toUnisonTypeName(structure.getId().getName());
- String funcName = "parse" + typeName + "FromXml";
+ String typeName = getNamespacedTypeName(structure.getId().getName());
+ String baseTypeName = UnisonSymbolProvider.toUnisonTypeName(structure.getId().getName());
+ String funcName = getNamespacedFunctionName("parse" + baseTypeName + "FromXml");
// Write doc comment
writer.writeDocComment("Parse " + typeName + " from XML text.");
@@ -401,7 +438,8 @@ private void generateXmlParserForStructure(StructureShape structure, UnisonWrite
writer.indent();
// Write constructor call with field extractions
- writer.write("$L.$L", typeName, typeName);
+ // Use base type name for constructor (Unison namespacing quirk)
+ writer.write("$L", baseTypeName);
writer.indent();
for (MemberShape member : structure.getAllMembers().values()) {
@@ -428,8 +466,8 @@ private String generateFieldExtraction(MemberShape member, Shape targetShape, St
if (targetShape instanceof StructureShape) {
// Nested structure - use parser function
- String nestedTypeName = UnisonSymbolProvider.toUnisonTypeName(targetShape.getId().getName());
- String parserName = "parse" + nestedTypeName + "FromXml";
+ String baseTypeName = UnisonSymbolProvider.toUnisonTypeName(targetShape.getId().getName());
+ String parserName = getNamespacedFunctionName("parse" + baseTypeName + "FromXml");
if (isOptional) {
return "(Aws.Xml.parseNestedFromXml \"" + xmlElementName + "\" " + parserName + " xml)";
} else {
@@ -442,8 +480,8 @@ private String generateFieldExtraction(MemberShape member, Shape targetShape, St
if (memberTarget instanceof StructureShape) {
// List of structures
- String itemTypeName = UnisonSymbolProvider.toUnisonTypeName(memberTarget.getId().getName());
- String parserName = "parse" + itemTypeName + "FromXml";
+ String baseItemTypeName = UnisonSymbolProvider.toUnisonTypeName(memberTarget.getId().getName());
+ String parserName = getNamespacedFunctionName("parse" + baseItemTypeName + "FromXml");
// Get item element name from list member
String itemElementName = Character.toUpperCase(listShape.getMember().getMemberName().charAt(0))
+ listShape.getMember().getMemberName().substring(1);
@@ -457,7 +495,7 @@ private String generateFieldExtraction(MemberShape member, Shape targetShape, St
// List of enums - extract text and convert
String itemElementName = Character.toUpperCase(listShape.getMember().getMemberName().charAt(0))
+ listShape.getMember().getMemberName().substring(1);
- String enumFromText = UnisonSymbolProvider.toUnisonFunctionName(memberTarget.getId().getName()) + "FromText";
+ String enumFromText = getNamespacedFunctionName(memberTarget.getId().getName() + "FromText");
if (isOptional) {
return "(Some (List.filterMap " + enumFromText + " (Aws.Xml.extractAll \"" + itemElementName + "\" xml)))";
} else {
@@ -484,7 +522,7 @@ private String generateFieldExtraction(MemberShape member, Shape targetShape, St
} else if (targetShape instanceof EnumShape ||
targetShape.hasTrait(software.amazon.smithy.model.traits.EnumTrait.class)) {
// Enum - extract text and convert (check before isStringShape since EnumShape extends StringShape)
- String enumFromText = UnisonSymbolProvider.toUnisonFunctionName(targetShape.getId().getName()) + "FromText";
+ String enumFromText = getNamespacedFunctionName(targetShape.getId().getName() + "FromText");
if (isOptional) {
return "(Optional.flatMap " + enumFromText + " (Aws.Xml.extractElementOpt \"" + xmlElementName + "\" xml))";
} else {
@@ -540,21 +578,23 @@ private String generateFieldExtraction(MemberShape member, Shape targetShape, St
*
*/
private void generateOperationStub(OperationShape operation, UnisonWriter writer) {
- String opName = UnisonSymbolProvider.toUnisonFunctionName(operation.getId().getName());
+ String opName = getNamespacedFunctionName(operation.getId().getName());
- // Get input/output type names
+ // Get input/output type names (namespaced)
String inputType = operation.getInput()
- .map(id -> UnisonSymbolProvider.toUnisonTypeName(id.getName()))
+ .map(id -> getNamespacedTypeName(id.getName()))
.orElse("()");
String outputType = operation.getOutput()
- .map(id -> UnisonSymbolProvider.toUnisonTypeName(id.getName()))
+ .map(id -> getNamespacedTypeName(id.getName()))
.orElse("()");
+ String configType = getNamespacedTypeName("Config");
+
writer.writeDocComment(operation.getId().getName() + " operation (NOT IMPLEMENTED)\n\n" +
"Raises exception on error, returns output directly on success.");
// Exception-based signature: returns output directly, raises on error
- String signature = String.format("Config -> %s -> '{IO, Exception, Http} %s", inputType, outputType);
+ String signature = String.format("%s -> %s -> '{IO, Exception, Http} %s", configType, inputType, outputType);
writer.writeSignature(opName, signature);
writer.write("$L config input =", opName);
diff --git a/src/main/java/io/smithy/unison/codegen/UnisonSettings.java b/src/main/java/io/smithy/unison/codegen/UnisonSettings.java
index e4fdda4..81c8f52 100644
--- a/src/main/java/io/smithy/unison/codegen/UnisonSettings.java
+++ b/src/main/java/io/smithy/unison/codegen/UnisonSettings.java
@@ -105,6 +105,44 @@ public Optional getProtocol() {
return Optional.ofNullable(protocol);
}
+ /**
+ * Gets the Unison namespace for client types and operations.
+ *
+ * Converts the dot-separated namespace to PascalCase segments:
+ *
+ * - "aws.s3" → "Aws.S3"
+ * - "aws.dynamodb" → "Aws.DynamoDB"
+ * - "aws.lambda" → "Aws.Lambda"
+ *
+ *
+ * @return The client namespace prefix, or empty string if no namespace configured
+ */
+ public String getClientNamespace() {
+ if (namespace == null || namespace.isEmpty()) {
+ return "";
+ }
+
+ // Split by dot and convert each segment to PascalCase
+ String[] parts = namespace.split("\\.");
+ StringBuilder result = new StringBuilder();
+
+ for (int i = 0; i < parts.length; i++) {
+ String part = parts[i];
+ if (part.isEmpty()) {
+ continue;
+ }
+ // Capitalize first letter of each segment
+ result.append(Character.toUpperCase(part.charAt(0)));
+ result.append(part.substring(1));
+
+ if (i < parts.length - 1) {
+ result.append(".");
+ }
+ }
+
+ return result.toString();
+ }
+
public static Builder builder() {
return new Builder();
}
diff --git a/src/main/java/io/smithy/unison/codegen/UnisonWriter.java b/src/main/java/io/smithy/unison/codegen/UnisonWriter.java
index cc46b58..bca5c44 100644
--- a/src/main/java/io/smithy/unison/codegen/UnisonWriter.java
+++ b/src/main/java/io/smithy/unison/codegen/UnisonWriter.java
@@ -644,9 +644,21 @@ public String apply(Object value, String indent) {
/**
* Converts a PascalCase string to lowerCamelCase.
+ *
+ * For namespaced names like "Aws.S3.RequestPayer", only the last
+ * component is converted to lowerCamelCase, resulting in "Aws.S3.requestPayer".
*/
private String toLowerCamelCase(String s) {
if (s == null || s.isEmpty()) return s;
+
+ // Handle namespaced names (e.g., "Aws.S3.RequestPayer" -> "Aws.S3.requestPayer")
+ int lastDot = s.lastIndexOf('.');
+ if (lastDot >= 0) {
+ String namespace = s.substring(0, lastDot + 1);
+ String typeName = s.substring(lastDot + 1);
+ return namespace + Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
+ }
+
return Character.toLowerCase(s.charAt(0)) + s.substring(1);
}
diff --git a/src/main/java/io/smithy/unison/codegen/generators/EnumGenerator.java b/src/main/java/io/smithy/unison/codegen/generators/EnumGenerator.java
index 1190826..2102150 100644
--- a/src/main/java/io/smithy/unison/codegen/generators/EnumGenerator.java
+++ b/src/main/java/io/smithy/unison/codegen/generators/EnumGenerator.java
@@ -65,6 +65,8 @@ public final class EnumGenerator {
private static final Logger LOGGER = Logger.getLogger(EnumGenerator.class.getName());
private final String typeName;
+ private final String namespacedTypeName;
+ private final String clientNamespace;
private final List enumValues;
private final String documentation;
@@ -91,7 +93,7 @@ public EnumValue(String name, String wireValue) {
* Creates an enum generator from a StringShape with @enum trait (Smithy 1.0).
*
* @param enumShape The string shape with @enum trait
- * @param context The code generation context (unused but kept for API compatibility)
+ * @param context The code generation context
* @throws IllegalArgumentException if the shape doesn't have EnumTrait
*/
public EnumGenerator(StringShape enumShape, UnisonContext context) {
@@ -102,6 +104,9 @@ public EnumGenerator(StringShape enumShape, UnisonContext context) {
}
this.typeName = UnisonSymbolProvider.toUnisonTypeName(enumShape.getId().getName());
+ this.clientNamespace = context.settings().getClientNamespace();
+ this.namespacedTypeName = UnisonSymbolProvider.toNamespacedTypeName(
+ enumShape.getId().getName(), clientNamespace);
this.enumValues = extractEnumTraitValues(enumShape);
this.documentation = enumShape.getTrait(DocumentationTrait.class)
.map(DocumentationTrait::getValue)
@@ -115,10 +120,24 @@ public EnumGenerator(StringShape enumShape, UnisonContext context) {
* @param model The Smithy model
*/
public EnumGenerator(EnumShape enumShape, Model model) {
+ this(enumShape, model, "");
+ }
+
+ /**
+ * Creates an enum generator from a Smithy 2.0 EnumShape with namespace.
+ *
+ * @param enumShape The Smithy 2.0 enum shape
+ * @param model The Smithy model
+ * @param clientNamespace The client namespace for prefixing
+ */
+ public EnumGenerator(EnumShape enumShape, Model model, String clientNamespace) {
Objects.requireNonNull(enumShape, "enumShape is required");
Objects.requireNonNull(model, "model is required");
this.typeName = UnisonSymbolProvider.toUnisonTypeName(enumShape.getId().getName());
+ this.clientNamespace = clientNamespace != null ? clientNamespace : "";
+ this.namespacedTypeName = UnisonSymbolProvider.toNamespacedTypeName(
+ enumShape.getId().getName(), this.clientNamespace);
this.enumValues = extractEnumShapeValues(enumShape);
this.documentation = enumShape.getTrait(DocumentationTrait.class)
.map(DocumentationTrait::getValue)
@@ -133,7 +152,21 @@ public EnumGenerator(EnumShape enumShape, Model model) {
* @param documentation Optional documentation
*/
public EnumGenerator(String typeName, List values, String documentation) {
+ this(typeName, values, documentation, "");
+ }
+
+ /**
+ * Creates an enum generator with explicit values and namespace.
+ *
+ * @param typeName The type name
+ * @param values The enum values
+ * @param documentation Optional documentation
+ * @param clientNamespace The client namespace for prefixing
+ */
+ public EnumGenerator(String typeName, List values, String documentation, String clientNamespace) {
this.typeName = Objects.requireNonNull(typeName, "typeName is required");
+ this.clientNamespace = clientNamespace != null ? clientNamespace : "";
+ this.namespacedTypeName = UnisonSymbolProvider.toNamespacedTypeName(typeName, this.clientNamespace);
this.enumValues = Objects.requireNonNull(values, "values is required");
this.documentation = documentation;
}
@@ -157,13 +190,22 @@ public List getValues() {
}
/**
- * Gets the full variant name for an enum value.
+ * Gets the full variant name for an enum value (namespaced).
*
* @param valueName The value name
- * @return The full variant name (TypeName'ValueName)
+ * @return The full variant name (Namespace.TypeName'ValueName)
*/
public String getVariantName(String valueName) {
- return UnisonSymbolProvider.toUnisonEnumVariant(typeName, valueName);
+ return UnisonSymbolProvider.toUnisonEnumVariant(namespacedTypeName, valueName);
+ }
+
+ /**
+ * Gets the namespaced type name.
+ *
+ * @return The namespaced type name (e.g., "Aws.S3.BucketLocationConstraint")
+ */
+ public String getNamespacedTypeName() {
+ return namespacedTypeName;
}
/**
@@ -190,49 +232,47 @@ public void generateTypeDefinition(UnisonWriter writer) {
writer.writeDocComment(documentation);
}
- // Build variants list
+ // Build variants list using namespaced type name
List variants = new ArrayList<>();
for (EnumValue value : enumValues) {
String variantName = getVariantName(value.name());
variants.add(new UnisonWriter.Variant(variantName, null)); // No payload for enum variants
}
- // Write union type
- writer.writeUnionType(typeName, variants);
+ // Write union type with namespaced name
+ writer.writeUnionType(namespacedTypeName, variants);
}
/**
- * Generates the toText conversion function.
+ * Generates the toText conversion function with namespaced name.
*
* @param writer The writer to output code to
*/
public void generateToTextFunction(UnisonWriter writer) {
- String funcName = UnisonSymbolProvider.toUnisonFunctionName(typeName) + "ToText";
-
// Build match cases
List mappings = new ArrayList<>();
for (EnumValue value : enumValues) {
mappings.add(new UnisonWriter.EnumMapping(value.name(), value.wireValue()));
}
- writer.writeEnumToTextFunction(typeName, mappings);
+ // Use namespaced type name for function generation
+ writer.writeEnumToTextFunction(namespacedTypeName, mappings);
}
/**
- * Generates the fromText conversion function.
+ * Generates the fromText conversion function with namespaced name.
*
* @param writer The writer to output code to
*/
public void generateFromTextFunction(UnisonWriter writer) {
- String funcName = UnisonSymbolProvider.toUnisonFunctionName(typeName) + "FromText";
-
// Build match cases
List mappings = new ArrayList<>();
for (EnumValue value : enumValues) {
mappings.add(new UnisonWriter.EnumMapping(value.name(), value.wireValue()));
}
- writer.writeEnumFromTextFunction(typeName, mappings);
+ // Use namespaced type name for function generation
+ writer.writeEnumFromTextFunction(namespacedTypeName, mappings);
}
/**
diff --git a/src/main/java/io/smithy/unison/codegen/generators/PaginationGenerator.java b/src/main/java/io/smithy/unison/codegen/generators/PaginationGenerator.java
index 23f4517..c601651 100644
--- a/src/main/java/io/smithy/unison/codegen/generators/PaginationGenerator.java
+++ b/src/main/java/io/smithy/unison/codegen/generators/PaginationGenerator.java
@@ -62,10 +62,22 @@ public class PaginationGenerator {
private static final Logger LOGGER = Logger.getLogger(PaginationGenerator.class.getName());
+ private final String clientNamespace;
+
/**
- * Creates a new PaginationGenerator.
+ * Creates a new PaginationGenerator without namespace.
*/
public PaginationGenerator() {
+ this("");
+ }
+
+ /**
+ * Creates a new PaginationGenerator with namespace.
+ *
+ * @param clientNamespace The client namespace for prefixing types
+ */
+ public PaginationGenerator(String clientNamespace) {
+ this.clientNamespace = clientNamespace != null ? clientNamespace : "";
}
/**
@@ -127,21 +139,25 @@ public void generatePaginationHelper(OperationShape operation, Model model, Unis
}
PaginatedTrait pagination = paginatedTrait.get();
- String opName = UnisonSymbolProvider.toUnisonFunctionName(operation.getId().getName());
+ String opName = UnisonSymbolProvider.toNamespacedFunctionName(
+ operation.getId().getName(), clientNamespace);
// Get pagination configuration
String inputToken = pagination.getInputToken().orElse("continuationToken");
String outputToken = pagination.getOutputToken().orElse("nextContinuationToken");
String items = pagination.getItems().orElse("contents");
- // Get input/output types
+ // Get input/output types with namespace
String inputType = operation.getInput()
- .map(id -> UnisonSymbolProvider.toUnisonTypeName(id.getName()))
+ .map(id -> UnisonSymbolProvider.toNamespacedTypeName(id.getName(), clientNamespace))
.orElse("()");
String outputType = operation.getOutput()
- .map(id -> UnisonSymbolProvider.toUnisonTypeName(id.getName()))
+ .map(id -> UnisonSymbolProvider.toNamespacedTypeName(id.getName(), clientNamespace))
.orElse("()");
+ // Config type with namespace
+ String configType = UnisonSymbolProvider.toNamespacedTypeName("Config", clientNamespace);
+
// Get the item type from the output structure
String itemsField = UnisonSymbolProvider.toUnisonFunctionName(items);
String itemType = "a"; // default to polymorphic
@@ -163,7 +179,8 @@ public void generatePaginationHelper(OperationShape operation, Model model, Unis
if (itemsShape instanceof ListShape) {
ListShape listShape = (ListShape) itemsShape;
Shape memberShape = model.expectShape(listShape.getMember().getTarget());
- itemType = UnisonSymbolProvider.toUnisonTypeName(memberShape.getId().getName());
+ itemType = UnisonSymbolProvider.toNamespacedTypeName(
+ memberShape.getId().getName(), clientNamespace);
}
}
}
@@ -173,10 +190,10 @@ public void generatePaginationHelper(OperationShape operation, Model model, Unis
"Automatically fetches all pages and collects all items from the '" + items + "' field.\n" +
"Uses '" + inputToken + "' as input token and '" + outputToken + "' as output token.");
- // Function signature with concrete item type
+ // Function signature with concrete item type and namespaced types
// Note: HTTP operations use {IO, Exception, Threads} abilities for real HTTP via @unison/http
String helperName = opName + "All";
- writer.writeSignature(helperName, "Config -> " + inputType + " -> '{IO, Exception, Threads} [" + itemType + "]");
+ writer.writeSignature(helperName, configType + " -> " + inputType + " -> '{IO, Exception, Threads} [" + itemType + "]");
writer.write("$L config input =", helperName);
writer.indent();
diff --git a/src/main/java/io/smithy/unison/codegen/generators/StructureGenerator.java b/src/main/java/io/smithy/unison/codegen/generators/StructureGenerator.java
index b335c2f..9bb58fd 100644
--- a/src/main/java/io/smithy/unison/codegen/generators/StructureGenerator.java
+++ b/src/main/java/io/smithy/unison/codegen/generators/StructureGenerator.java
@@ -49,6 +49,7 @@ public final class StructureGenerator {
private final Model model;
private final StructureShape structure;
private final SymbolProvider symbolProvider;
+ private final String clientNamespace;
/**
* Creates a new structure generator.
@@ -61,6 +62,7 @@ public StructureGenerator(StructureShape structure, UnisonContext context) {
Objects.requireNonNull(context, "context is required");
this.model = context.model();
this.symbolProvider = context.symbolProvider();
+ this.clientNamespace = context.settings().getClientNamespace();
}
/**
@@ -71,9 +73,22 @@ public StructureGenerator(StructureShape structure, UnisonContext context) {
* @param symbolProvider The symbol provider
*/
public StructureGenerator(StructureShape structure, Model model, SymbolProvider symbolProvider) {
+ this(structure, model, symbolProvider, "");
+ }
+
+ /**
+ * Creates a new structure generator with explicit model, symbol provider, and namespace.
+ *
+ * @param structure The structure shape to generate
+ * @param model The Smithy model
+ * @param symbolProvider The symbol provider
+ * @param clientNamespace The client namespace for prefixing types
+ */
+ public StructureGenerator(StructureShape structure, Model model, SymbolProvider symbolProvider, String clientNamespace) {
this.structure = Objects.requireNonNull(structure, "structure is required");
this.model = Objects.requireNonNull(model, "model is required");
this.symbolProvider = Objects.requireNonNull(symbolProvider, "symbolProvider is required");
+ this.clientNamespace = clientNamespace != null ? clientNamespace : "";
}
/**
@@ -86,12 +101,12 @@ public StructureShape getStructure() {
}
/**
- * Gets the Unison type name for this structure.
+ * Gets the Unison type name for this structure (namespaced).
*
- * @return The type name (PascalCase)
+ * @return The namespaced type name (e.g., "Aws.S3.CreateBucketRequest")
*/
public String getTypeName() {
- return UnisonSymbolProvider.toUnisonTypeName(structure.getId().getName());
+ return UnisonSymbolProvider.toNamespacedTypeName(structure.getId().getName(), clientNamespace);
}
/**
@@ -210,12 +225,12 @@ private String wrapIfComplex(String type) {
}
/**
- * Gets the Unison type for a shape.
+ * Gets the Unison type for a shape (with namespace prefix for complex types).
*/
private String getUnisonType(Shape shape) {
if (shape instanceof StringShape) {
if (shape.hasTrait(EnumTrait.class)) {
- return UnisonSymbolProvider.toUnisonTypeName(shape.getId().getName());
+ return UnisonSymbolProvider.toNamespacedTypeName(shape.getId().getName(), clientNamespace);
}
return "Text";
} else if (shape instanceof IntegerShape || shape instanceof LongShape ||
@@ -249,13 +264,13 @@ private String getUnisonType(Shape shape) {
String valueType = getUnisonType(valueShape);
return "Map " + keyType + " " + valueType;
} else if (shape instanceof StructureShape) {
- return UnisonSymbolProvider.toUnisonTypeName(shape.getId().getName());
+ return UnisonSymbolProvider.toNamespacedTypeName(shape.getId().getName(), clientNamespace);
} else if (shape instanceof UnionShape) {
- return UnisonSymbolProvider.toUnisonTypeName(shape.getId().getName());
+ return UnisonSymbolProvider.toNamespacedTypeName(shape.getId().getName(), clientNamespace);
} else if (shape instanceof EnumShape) {
- return UnisonSymbolProvider.toUnisonTypeName(shape.getId().getName());
+ return UnisonSymbolProvider.toNamespacedTypeName(shape.getId().getName(), clientNamespace);
} else if (shape instanceof IntEnumShape) {
- return UnisonSymbolProvider.toUnisonTypeName(shape.getId().getName());
+ return UnisonSymbolProvider.toNamespacedTypeName(shape.getId().getName(), clientNamespace);
}
return "a"; // Generic type parameter as fallback
}
diff --git a/src/main/java/io/smithy/unison/codegen/generators/UnionGenerator.java b/src/main/java/io/smithy/unison/codegen/generators/UnionGenerator.java
index ca62ae6..59ec00e 100644
--- a/src/main/java/io/smithy/unison/codegen/generators/UnionGenerator.java
+++ b/src/main/java/io/smithy/unison/codegen/generators/UnionGenerator.java
@@ -55,6 +55,8 @@ public final class UnionGenerator {
private static final Logger LOGGER = Logger.getLogger(UnionGenerator.class.getName());
private final String typeName;
+ private final String namespacedTypeName;
+ private final String clientNamespace;
private final String documentation;
private final List variants;
@@ -84,10 +86,24 @@ public UnionVariant(String name, String payloadType) {
* @param model The Smithy model
*/
public UnionGenerator(UnionShape union, Model model) {
+ this(union, model, "");
+ }
+
+ /**
+ * Creates a union generator from a Smithy UnionShape with namespace.
+ *
+ * @param union The union shape to generate
+ * @param model The Smithy model
+ * @param clientNamespace The client namespace for prefixing
+ */
+ public UnionGenerator(UnionShape union, Model model, String clientNamespace) {
Objects.requireNonNull(union, "union is required");
Objects.requireNonNull(model, "model is required");
this.typeName = UnisonSymbolProvider.toUnisonTypeName(union.getId().getName());
+ this.clientNamespace = clientNamespace != null ? clientNamespace : "";
+ this.namespacedTypeName = UnisonSymbolProvider.toNamespacedTypeName(
+ union.getId().getName(), this.clientNamespace);
this.documentation = union.getTrait(DocumentationTrait.class)
.map(DocumentationTrait::getValue)
.orElse(null);
@@ -102,18 +118,32 @@ public UnionGenerator(UnionShape union, Model model) {
* @param documentation Optional documentation
*/
public UnionGenerator(String typeName, List variants, String documentation) {
+ this(typeName, variants, documentation, "");
+ }
+
+ /**
+ * Creates a union generator with explicit values and namespace.
+ *
+ * @param typeName The type name
+ * @param variants The union variants
+ * @param documentation Optional documentation
+ * @param clientNamespace The client namespace for prefixing
+ */
+ public UnionGenerator(String typeName, List variants, String documentation, String clientNamespace) {
this.typeName = Objects.requireNonNull(typeName, "typeName is required");
+ this.clientNamespace = clientNamespace != null ? clientNamespace : "";
+ this.namespacedTypeName = UnisonSymbolProvider.toNamespacedTypeName(typeName, this.clientNamespace);
this.variants = Objects.requireNonNull(variants, "variants is required");
this.documentation = documentation;
}
/**
- * Gets the Unison type name for this union.
+ * Gets the Unison type name for this union (namespaced).
*
- * @return The PascalCase type name
+ * @return The namespaced type name (e.g., "Aws.S3.StorageType")
*/
public String getTypeName() {
- return typeName;
+ return namespacedTypeName;
}
/**
@@ -126,13 +156,13 @@ public List getVariants() {
}
/**
- * Gets the full variant name for a member.
+ * Gets the full variant name for a member (using namespaced type).
*
* @param memberName The member name
- * @return The full variant name (TypeName'MemberName)
+ * @return The full variant name (Namespace.TypeName'MemberName)
*/
public String getVariantName(String memberName) {
- return UnisonSymbolProvider.toUnisonEnumVariant(typeName, memberName);
+ return UnisonSymbolProvider.toUnisonEnumVariant(namespacedTypeName, memberName);
}
/**
@@ -147,7 +177,7 @@ public void generate(UnisonWriter writer) {
}
/**
- * Generates the union type definition.
+ * Generates the union type definition with namespaced type name.
*
* @param writer The writer to output code to
*/
@@ -164,8 +194,8 @@ public void generateTypeDefinition(UnisonWriter writer) {
writerVariants.add(new UnisonWriter.Variant(variantName, variant.payloadType()));
}
- // Write union type
- writer.writeUnionType(typeName, writerVariants);
+ // Write union type with namespaced name
+ writer.writeUnionType(namespacedTypeName, writerVariants);
}
/**
@@ -206,11 +236,11 @@ private String toPascalCase(String name) {
}
/**
- * Gets the Unison type for a Smithy shape.
+ * Gets the Unison type for a Smithy shape (with namespace prefix for complex types).
*/
private String getUnisonType(Shape shape) {
if (shape.isStructureShape()) {
- return UnisonSymbolProvider.toUnisonTypeName(shape.getId().getName());
+ return UnisonSymbolProvider.toNamespacedTypeName(shape.getId().getName(), clientNamespace);
} else if (shape.isStringShape()) {
return "Text";
} else if (shape.isIntegerShape() || shape.isLongShape() ||
@@ -229,9 +259,9 @@ private String getUnisonType(Shape shape) {
} else if (shape.isMapShape()) {
return "Map Text a"; // Generic map for now
} else if (shape.isUnionShape()) {
- return UnisonSymbolProvider.toUnisonTypeName(shape.getId().getName());
+ return UnisonSymbolProvider.toNamespacedTypeName(shape.getId().getName(), clientNamespace);
}
- // Default to the shape's type name
- return UnisonSymbolProvider.toUnisonTypeName(shape.getId().getName());
+ // Default to the shape's namespaced type name
+ return UnisonSymbolProvider.toNamespacedTypeName(shape.getId().getName(), clientNamespace);
}
}
diff --git a/src/main/java/io/smithy/unison/codegen/protocols/AwsJsonProtocolGenerator.java b/src/main/java/io/smithy/unison/codegen/protocols/AwsJsonProtocolGenerator.java
index 9d06c40..a85a8f3 100644
--- a/src/main/java/io/smithy/unison/codegen/protocols/AwsJsonProtocolGenerator.java
+++ b/src/main/java/io/smithy/unison/codegen/protocols/AwsJsonProtocolGenerator.java
@@ -49,7 +49,9 @@ public String getContentType(ServiceShape service) {
@Override
public void generateOperation(OperationShape operation, UnisonWriter writer, UnisonContext context) {
// TODO: Implement AWS JSON operation generation
- String opName = UnisonSymbolProvider.toUnisonFunctionName(operation.getId().getName());
+ String clientNamespace = context.settings().getClientNamespace();
+ String opName = UnisonSymbolProvider.toNamespacedFunctionName(
+ operation.getId().getName(), clientNamespace);
writer.writeComment("AWS JSON " + version + " operation: " + opName + " (NOT IMPLEMENTED)");
}
diff --git a/src/main/java/io/smithy/unison/codegen/protocols/RestJsonProtocolGenerator.java b/src/main/java/io/smithy/unison/codegen/protocols/RestJsonProtocolGenerator.java
index 995b755..b520457 100644
--- a/src/main/java/io/smithy/unison/codegen/protocols/RestJsonProtocolGenerator.java
+++ b/src/main/java/io/smithy/unison/codegen/protocols/RestJsonProtocolGenerator.java
@@ -46,7 +46,9 @@ public String getContentType(ServiceShape service) {
@Override
public void generateOperation(OperationShape operation, UnisonWriter writer, UnisonContext context) {
// TODO: Implement REST-JSON operation generation
- String opName = UnisonSymbolProvider.toUnisonFunctionName(operation.getId().getName());
+ String clientNamespace = context.settings().getClientNamespace();
+ String opName = UnisonSymbolProvider.toNamespacedFunctionName(
+ operation.getId().getName(), clientNamespace);
writer.writeComment("REST-JSON operation: " + opName + " (NOT IMPLEMENTED)");
}
diff --git a/src/main/java/io/smithy/unison/codegen/protocols/RestXmlProtocolGenerator.java b/src/main/java/io/smithy/unison/codegen/protocols/RestXmlProtocolGenerator.java
index a7e8be8..42bd136 100644
--- a/src/main/java/io/smithy/unison/codegen/protocols/RestXmlProtocolGenerator.java
+++ b/src/main/java/io/smithy/unison/codegen/protocols/RestXmlProtocolGenerator.java
@@ -111,16 +111,19 @@ public String getContentType(ServiceShape service) {
public void generateOperation(OperationShape operation, UnisonWriter writer, UnisonContext context) {
Model model = context.model();
ServiceShape service = context.serviceShape();
+ String clientNamespace = context.settings().getClientNamespace();
- String opName = UnisonSymbolProvider.toUnisonFunctionName(operation.getId().getName());
+ String opName = UnisonSymbolProvider.toNamespacedFunctionName(
+ operation.getId().getName(), clientNamespace);
- // Determine input and output types
+ // Determine input and output types (namespaced)
String inputType = operation.getInput()
- .map(id -> UnisonSymbolProvider.toUnisonTypeName(id.getName()))
+ .map(id -> UnisonSymbolProvider.toNamespacedTypeName(id.getName(), clientNamespace))
.orElse("()");
String outputType = operation.getOutput()
- .map(id -> UnisonSymbolProvider.toUnisonTypeName(id.getName()))
+ .map(id -> UnisonSymbolProvider.toNamespacedTypeName(id.getName(), clientNamespace))
.orElse("()");
+ String configType = UnisonSymbolProvider.toNamespacedTypeName("Config", clientNamespace);
// Get HTTP method and URI from @http trait
String method = ProtocolUtils.getHttpMethod(operation, "GET");
@@ -153,7 +156,7 @@ public void generateOperation(OperationShape operation, UnisonWriter writer, Uni
// Write signature
// Note: HTTP operations use {IO, Exception} abilities - there is no separate Http ability in Unison
// Use '{IO, Exception, Threads} to support real HTTP via @unison/http bridge
- String signature = String.format("Config -> %s -> '{IO, Exception, Threads} %s", inputType, outputType);
+ String signature = String.format("%s -> %s -> '{IO, Exception, Threads} %s", configType, inputType, outputType);
writer.writeSignature(opName, signature);
// Write function definition with do block for delayed computation
@@ -169,13 +172,13 @@ public void generateOperation(OperationShape operation, UnisonWriter writer, Uni
generateUrlBuilding(uri, httpLabelMembers, useS3Url, inputType, writer);
// Build query string
- generateQueryString(httpQueryMembers, inputType, model, writer);
+ generateQueryString(httpQueryMembers, inputType, model, clientNamespace, writer);
// Build full URL
writer.write("fullUrl = url ++ queryString");
// Build headers
- generateRequestHeaders(httpHeaderInputMembers, inputType, model, writer);
+ generateRequestHeaders(httpHeaderInputMembers, inputType, model, clientNamespace, writer);
// Build request body
generateRequestBodyBinding(operation, model, bodyMembers, payloadMember, inputType, writer);
@@ -197,7 +200,7 @@ public void generateOperation(OperationShape operation, UnisonWriter writer, Uni
// Handle response - still in scope since we're in the do block
writer.write("-- Check for errors and parse response");
writer.write("_ = handleHttpResponse response");
- generateResponseParsing(operation, model, writer);
+ generateResponseParsing(operation, model, clientNamespace, writer);
writer.dedent(); // end function
writer.writeBlankLine();
@@ -273,7 +276,7 @@ private void generateUrlBuilding(String uri, List httpLabelMembers,
* Handles both required and optional fields, using type-specific toText functions.
*/
private void generateQueryString(List httpQueryMembers, String inputType,
- Model model, UnisonWriter writer) {
+ Model model, String clientNamespace, UnisonWriter writer) {
if (httpQueryMembers.isEmpty()) {
writer.write("queryString = \"\"");
return;
@@ -298,7 +301,7 @@ private void generateQueryString(List httpQueryMembers, String inpu
// Get the target shape to determine the correct toText function
Shape targetShape = model.expectShape(member.getTarget());
- String toTextFunc = getToTextFunction(targetShape);
+ String toTextFunc = getToTextFunction(targetShape, clientNamespace);
// Check if the member is required (not optional)
boolean isRequired = member.isRequired();
@@ -326,19 +329,22 @@ private void generateQueryString(List httpQueryMembers, String inpu
*
* Note: Timestamps are generated as Text in Unison (for HTTP serialization),
* so they don't need conversion.
+ *
+ * @param shape The shape to get toText function for
+ * @param clientNamespace The client namespace for namespacing enum functions
*/
- private String getToTextFunction(Shape shape) {
+ private String getToTextFunction(Shape shape, String clientNamespace) {
// Check for Smithy 2.0 enums first
if (shape instanceof EnumShape) {
- // Enums use a function named like: requestPayerToText (camelCase + ToText)
- String enumFuncName = UnisonSymbolProvider.toUnisonFunctionName(shape.getId().getName());
- return enumFuncName + "ToText";
+ // Enums use a namespaced function like: Aws.S3.requestPayerToText
+ return UnisonSymbolProvider.toNamespacedFunctionName(
+ shape.getId().getName() + "ToText", clientNamespace);
}
// Check for Smithy 1.0 style enums (strings with @enum trait)
if (shape.isStringShape() && shape.hasTrait(software.amazon.smithy.model.traits.EnumTrait.class)) {
- // Enums use a function named like: requestPayerToText (camelCase + ToText)
- String enumFuncName = UnisonSymbolProvider.toUnisonFunctionName(shape.getId().getName());
- return enumFuncName + "ToText";
+ // Enums use a namespaced function like: Aws.S3.requestPayerToText
+ return UnisonSymbolProvider.toNamespacedFunctionName(
+ shape.getId().getName() + "ToText", clientNamespace);
}
if (shape.isStringShape()) {
return ""; // No conversion needed for Text
@@ -365,7 +371,7 @@ private String getToTextFunction(Shape shape) {
*
Converts all header values to Optional Text for homogeneous list types.
*/
private void generateRequestHeaders(List httpHeaderMembers, String inputType,
- Model model, UnisonWriter writer) {
+ Model model, String clientNamespace, UnisonWriter writer) {
writer.write("-- Build headers from @httpHeader members");
if (httpHeaderMembers.isEmpty()) {
@@ -400,7 +406,7 @@ private void generateRequestHeaders(List httpHeaderMembers, String
if (targetShape.isListShape()) {
ListShape listShape = targetShape.asListShape().get();
Shape elementShape = model.expectShape(listShape.getMember().getTarget());
- String elementToText = getToTextFunction(elementShape);
+ String elementToText = getToTextFunction(elementShape, clientNamespace);
// Lists are serialized as comma-separated values
// Text.join "," (List.map ElementType.toText list)
@@ -425,7 +431,7 @@ private void generateRequestHeaders(List httpHeaderMembers, String
}
} else {
// Non-list types - original logic
- String toTextFunc = getToTextFunction(targetShape);
+ String toTextFunc = getToTextFunction(targetShape, clientNamespace);
if (isRequired) {
// Required field - wrap in Some and convert to Text
@@ -505,7 +511,8 @@ private void generateRequestBodyBinding(OperationShape operation, Model model,
* Body members - Decode from XML response body
*
*/
- private void generateResponseParsing(OperationShape operation, Model model, UnisonWriter writer) {
+ private void generateResponseParsing(OperationShape operation, Model model,
+ String clientNamespace, UnisonWriter writer) {
Optional outputShape = ProtocolUtils.getOutputShape(operation, model);
if (!outputShape.isPresent()) {
@@ -529,7 +536,7 @@ private void generateResponseParsing(OperationShape operation, Model model, Unis
// In do blocks, bindings are scoped to the rest of the block (no need for 'let')
// Extract response headers
- generateResponseHeaderExtraction(headerMembers, model, writer);
+ generateResponseHeaderExtraction(headerMembers, model, clientNamespace, writer);
// Extract response code if needed
if (responseCodeMember.isPresent()) {
@@ -550,46 +557,48 @@ private void generateResponseParsing(OperationShape operation, Model model, Unis
String varName = memberName + "Val";
String xmlElementName = getXmlElementName(member);
Shape targetShape = model.expectShape(member.getTarget());
- generateXmlFieldExtraction(varName, xmlElementName, targetShape, member, model, writer);
+ generateXmlFieldExtraction(varName, xmlElementName, targetShape, member, model, clientNamespace, writer);
}
}
// Build the result record (final expression of the do block)
generateResultRecordConstruction(output, payloadMember, headerMembers,
- responseCodeMember, bodyMembers, writer);
+ responseCodeMember, bodyMembers, clientNamespace, writer);
} else if (payloadMember.isPresent()) {
// Simple payload extraction - use positional arguments
MemberShape payload = payloadMember.get();
Shape targetShape = model.expectShape(payload.getTarget());
- String outputTypeName = UnisonSymbolProvider.toUnisonTypeName(output.getId().getName());
+ // Use base type name for constructor (Unison namespacing quirk)
+ String outputTypeName = UnisonSymbolProvider.toUnisonTypeName(
+ output.getId().getName());
boolean isOptional = !payload.isRequired();
if (targetShape.isBlobShape()) {
if (isOptional) {
- writer.write("$L.$L (Some (Response.body response))", outputTypeName, outputTypeName);
+ writer.write("$L (Some (Response.body response))", outputTypeName);
} else {
- writer.write("$L.$L (Response.body response)", outputTypeName, outputTypeName);
+ writer.write("$L (Response.body response)", outputTypeName);
}
} else if (targetShape.isStringShape()) {
if (isOptional) {
- writer.write("$L.$L (Some (Aws.Http.bytesToText (Response.body response)))", outputTypeName, outputTypeName);
+ writer.write("$L (Some (Aws.Http.bytesToText (Response.body response)))", outputTypeName);
} else {
- writer.write("$L.$L (Aws.Http.bytesToText (Response.body response))", outputTypeName, outputTypeName);
+ writer.write("$L (Aws.Http.bytesToText (Response.body response))", outputTypeName);
}
} else {
// Structure payload - generate XML parsing
- generateXmlResponseParsing(output, bodyMembers, model, writer);
+ generateXmlResponseParsing(output, bodyMembers, model, clientNamespace, writer);
}
} else if (bodyMembers.isEmpty()) {
// No body content expected - return empty record using constructor
- // For empty records like "type X = X", we construct with just "X"
+ // Use base type name for constructor (Unison namespacing quirk)
String outputTypeName = UnisonSymbolProvider.toUnisonTypeName(
operation.getOutput().get().getName());
writer.write("-- No body content expected");
- writer.write("$L.$L", outputTypeName, outputTypeName);
+ writer.write("$L", outputTypeName);
} else {
// Has body members - generate XML parsing
- generateXmlResponseParsing(output, bodyMembers, model, writer);
+ generateXmlResponseParsing(output, bodyMembers, model, clientNamespace, writer);
}
}
@@ -604,7 +613,8 @@ private void generateResponseParsing(OperationShape operation, Model model, Unis
* Enum types - convert Text to enum using fromText function
*
*/
- private void generateResponseHeaderExtraction(List headerMembers, Model model, UnisonWriter writer) {
+ private void generateResponseHeaderExtraction(List headerMembers, Model model,
+ String clientNamespace, UnisonWriter writer) {
if (headerMembers.isEmpty()) {
return;
}
@@ -623,7 +633,8 @@ private void generateResponseHeaderExtraction(List headerMembers, M
if (isEnumType) {
// Enum type - need to convert Optional Text to Optional EnumType
// Using pattern matching per UNISON_LANGUAGE_SPEC.md
- String enumFromText = UnisonSymbolProvider.toUnisonFunctionName(targetShape.getId().getName()) + "FromText";
+ String enumFromText = UnisonSymbolProvider.toNamespacedFunctionName(
+ targetShape.getId().getName() + "FromText", clientNamespace);
writer.write("$L = match Response.getHeader \"$L\" response with", memberName, headerName);
writer.indent();
@@ -689,8 +700,11 @@ private void generateResultRecordConstruction(StructureShape output,
List headerMembers,
Optional responseCodeMember,
List bodyMembers,
+ String clientNamespace,
UnisonWriter writer) {
- String outputTypeName = UnisonSymbolProvider.toUnisonTypeName(output.getId().getName());
+ // Use base type name for constructor (Unison namespacing quirk)
+ String outputTypeName = UnisonSymbolProvider.toUnisonTypeName(
+ output.getId().getName());
// Build a map of member name to value expression
// Use 'Val' suffix on local variables to avoid name clash with accessor functions
@@ -736,11 +750,12 @@ private void generateResultRecordConstruction(StructureShape output,
}
// Write the constructor call with positional arguments
+ // Record constructor is just the type name (e.g., Aws.S3.GetObjectOutput)
if (args.isEmpty()) {
- writer.write("$L.$L", outputTypeName, outputTypeName);
+ writer.write("$L", outputTypeName);
} else {
StringBuilder sb = new StringBuilder();
- sb.append(outputTypeName).append(".").append(outputTypeName);
+ sb.append(outputTypeName);
for (String arg : args) {
sb.append(" ").append(arg);
}
@@ -790,6 +805,7 @@ public void generateRequestSerializer(OperationShape operation, UnisonWriter wri
@Override
public void generateResponseDeserializer(OperationShape operation, UnisonWriter writer, UnisonContext context) {
Model model = context.model();
+ String clientNamespace = context.settings().getClientNamespace();
Optional outputShape = ProtocolUtils.getOutputShape(operation, model);
if (!outputShape.isPresent()) {
@@ -813,7 +829,7 @@ public void generateResponseDeserializer(OperationShape operation, UnisonWriter
} else {
// Structure payload - generate inline XML parsing
writer.writeComment("Structure payload - parse XML");
- generateXmlResponseParsing(outputShape.get(), ProtocolUtils.getBodyMembers(outputShape.get()), model, writer);
+ generateXmlResponseParsing(outputShape.get(), ProtocolUtils.getBodyMembers(outputShape.get()), model, clientNamespace, writer);
}
} else {
List bodyMembers = ProtocolUtils.getBodyMembers(outputShape.get());
@@ -823,7 +839,7 @@ public void generateResponseDeserializer(OperationShape operation, UnisonWriter
writer.write("{}");
} else {
writer.writeComment("Decode XML response body");
- generateXmlResponseParsing(outputShape.get(), bodyMembers, model, writer);
+ generateXmlResponseParsing(outputShape.get(), bodyMembers, model, clientNamespace, writer);
}
}
}
@@ -831,12 +847,14 @@ public void generateResponseDeserializer(OperationShape operation, UnisonWriter
@Override
public void generateErrorParser(OperationShape operation, UnisonWriter writer, UnisonContext context) {
ServiceShape service = context.serviceShape();
+ String clientNamespace = context.settings().getClientNamespace();
String serviceName = service.getId().getName();
// Remove "Service" suffix if present to avoid "S3ServiceServiceError"
if (serviceName.endsWith("Service")) {
serviceName = serviceName.substring(0, serviceName.length() - 7);
}
- String errorTypeName = UnisonSymbolProvider.toUnisonTypeName(serviceName) + "ServiceError";
+ String errorTypeName = UnisonSymbolProvider.toNamespacedTypeName(
+ serviceName + "ServiceError", clientNamespace);
writer.writeDocComment("Parse REST-XML error response");
writer.write("parseError : Response -> $L", errorTypeName);
@@ -864,8 +882,10 @@ public void generateErrorParser(OperationShape operation, UnisonWriter writer, U
*
*/
private void generateXmlResponseParsing(StructureShape output, List bodyMembers,
- Model model, UnisonWriter writer) {
- String outputTypeName = UnisonSymbolProvider.toUnisonTypeName(output.getId().getName());
+ Model model, String clientNamespace, UnisonWriter writer) {
+ // Use base type name for constructor (Unison namespacing quirk)
+ String outputTypeName = UnisonSymbolProvider.toUnisonTypeName(
+ output.getId().getName());
// Convert response body to text
writer.write("xmlText = fromUtf8 (Response.body response)");
@@ -882,11 +902,12 @@ private void generateXmlResponseParsing(StructureShape output, List
// Get the XML element name - use member name by default
String xmlElementName = getXmlElementName(member);
- generateXmlFieldExtraction(varName, xmlElementName, targetShape, member, model, writer);
+ generateXmlFieldExtraction(varName, xmlElementName, targetShape, member, model, clientNamespace, writer);
}
// Construct the output record with extracted values
- writer.write("$L.$L", outputTypeName, outputTypeName);
+ // Record constructor is just the type name (e.g., Aws.S3.ListObjectsV2Output)
+ writer.write("$L", outputTypeName);
writer.indent();
for (int i = 0; i < allMembers.size(); i++) {
MemberShape member = allMembers.get(i);
@@ -901,14 +922,15 @@ private void generateXmlResponseParsing(StructureShape output, List
* Generates code to extract a single field from XML.
*/
private void generateXmlFieldExtraction(String varName, String xmlElementName,
- Shape targetShape, MemberShape member, Model model, UnisonWriter writer) {
+ Shape targetShape, MemberShape member, Model model, String clientNamespace, UnisonWriter writer) {
boolean isOptional = !member.isRequired();
// Check enum types FIRST (before string check, since enums may inherit from string)
if (targetShape instanceof EnumShape ||
(targetShape.isStringShape() && targetShape.hasTrait(software.amazon.smithy.model.traits.EnumTrait.class))) {
// Enum type - extract text and convert using enumFromText
- String enumFromText = UnisonSymbolProvider.toUnisonFunctionName(targetShape.getId().getName()) + "FromText";
+ String enumFromText = UnisonSymbolProvider.toNamespacedFunctionName(
+ targetShape.getId().getName() + "FromText", clientNamespace);
String textVarName = varName + "Text";
writer.write("$L = Aws.Xml.extractElementOpt \"$L\" xmlText", textVarName, xmlElementName);
writer.write("$L = Optional.flatMap $L $L", varName, enumFromText, textVarName);
@@ -954,8 +976,9 @@ private void generateXmlFieldExtraction(String varName, String xmlElementName,
} else if (memberTarget instanceof StructureShape) {
// List of structures - use parseListFromXml with inline parser
StructureShape structShape = (StructureShape) memberTarget;
- String structTypeName = UnisonSymbolProvider.toUnisonTypeName(structShape.getId().getName());
- String parserName = "parse" + structTypeName + "FromXml";
+ String baseTypeName = UnisonSymbolProvider.toUnisonTypeName(structShape.getId().getName());
+ String parserName = UnisonSymbolProvider.toNamespacedFunctionName(
+ "parse" + baseTypeName + "FromXml", clientNamespace);
// Generate inline parsing that uses the structure parser
if (isOptional) {
@@ -968,8 +991,8 @@ private void generateXmlFieldExtraction(String varName, String xmlElementName,
} else if (memberTarget instanceof EnumShape ||
(memberTarget.isStringShape() && memberTarget.hasTrait(software.amazon.smithy.model.traits.EnumTrait.class))) {
// List of enums - extract text and map using fromText function
- String enumTypeName = UnisonSymbolProvider.toUnisonTypeName(memberTarget.getId().getName());
- String enumFromText = UnisonSymbolProvider.toUnisonFunctionName(memberTarget.getId().getName()) + "FromText";
+ String enumFromText = UnisonSymbolProvider.toNamespacedFunctionName(
+ memberTarget.getId().getName() + "FromText", clientNamespace);
writer.write("$L = Aws.Xml.extractAll \"$L\" xmlText", itemsVarName, itemElementName);
writer.write("$L = List.filterMap $L $L", varName + "Parsed", enumFromText, itemsVarName);
if (isOptional) {
@@ -1001,8 +1024,9 @@ private void generateXmlFieldExtraction(String varName, String xmlElementName,
} else if (targetShape instanceof StructureShape) {
// Nested structure - use parseNestedFromXml with structure parser
StructureShape structShape = (StructureShape) targetShape;
- String structTypeName = UnisonSymbolProvider.toUnisonTypeName(structShape.getId().getName());
- String parserName = "parse" + structTypeName + "FromXml";
+ String baseTypeName = UnisonSymbolProvider.toUnisonTypeName(structShape.getId().getName());
+ String parserName = UnisonSymbolProvider.toNamespacedFunctionName(
+ "parse" + baseTypeName + "FromXml", clientNamespace);
if (isOptional) {
writer.write("$L = Aws.Xml.parseNestedFromXml \"$L\" $L xmlText", varName, xmlElementName, parserName);
diff --git a/src/main/java/io/smithy/unison/codegen/symbol/UnisonSymbolProvider.java b/src/main/java/io/smithy/unison/codegen/symbol/UnisonSymbolProvider.java
index d98a480..70f1f60 100644
--- a/src/main/java/io/smithy/unison/codegen/symbol/UnisonSymbolProvider.java
+++ b/src/main/java/io/smithy/unison/codegen/symbol/UnisonSymbolProvider.java
@@ -148,6 +148,48 @@ public static String toUnisonEnumVariant(String typeName, String variantName) {
return typeName + "'" + variantName;
}
+ /**
+ * Converts a name to a namespaced Unison type name.
+ *
+ * Prepends the namespace prefix to the type name:
+ *
+ * - ("CreateBucketRequest", "Aws.S3") → "Aws.S3.CreateBucketRequest"
+ * - ("Config", "Aws.DynamoDB") → "Aws.DynamoDB.Config"
+ *
+ *
+ * @param name The base type name
+ * @param namespace The namespace prefix (e.g., "Aws.S3")
+ * @return The fully qualified type name
+ */
+ public static String toNamespacedTypeName(String name, String namespace) {
+ String typeName = toUnisonTypeName(name);
+ if (namespace == null || namespace.isEmpty()) {
+ return typeName;
+ }
+ return namespace + "." + typeName;
+ }
+
+ /**
+ * Converts a name to a namespaced Unison function name.
+ *
+ * Prepends the namespace prefix to the function name:
+ *
+ * - ("CreateBucket", "Aws.S3") → "Aws.S3.createBucket"
+ * - ("GetItem", "Aws.DynamoDB") → "Aws.DynamoDB.getItem"
+ *
+ *
+ * @param name The base function name (can be PascalCase or camelCase)
+ * @param namespace The namespace prefix (e.g., "Aws.S3")
+ * @return The fully qualified function name
+ */
+ public static String toNamespacedFunctionName(String name, String namespace) {
+ String funcName = toUnisonFunctionName(name);
+ if (namespace == null || namespace.isEmpty()) {
+ return funcName;
+ }
+ return namespace + "." + funcName;
+ }
+
/**
* Gets the namespace for symbols.
*/
diff --git a/src/test/java/io/smithy/unison/codegen/protocols/RestXmlProtocolGeneratorTest.java b/src/test/java/io/smithy/unison/codegen/protocols/RestXmlProtocolGeneratorTest.java
index 1cd104d..dfc76bb 100644
--- a/src/test/java/io/smithy/unison/codegen/protocols/RestXmlProtocolGeneratorTest.java
+++ b/src/test/java/io/smithy/unison/codegen/protocols/RestXmlProtocolGeneratorTest.java
@@ -157,8 +157,9 @@ void testGenerateOperationWithSimpleGetOperation() {
String output = writer.toString();
// Check that the function signature is generated (includes Threads for @unison/http)
- assertTrue(output.contains("getObject : Config -> GetObjectInput -> '{IO, Exception, Threads} GetObjectOutput"),
- "Should generate correct function signature. Got: " + output);
+ // With namespace "com.example", types are prefixed with "Com.Example."
+ assertTrue(output.contains("Com.Example.getObject : Com.Example.Config -> Com.Example.GetObjectInput -> '{IO, Exception, Threads} Com.Example.GetObjectOutput"),
+ "Should generate correct function signature with namespace. Got: " + output);
// Check that HTTP method is set
assertTrue(output.contains("method = \"GET\""),