Skip to content

Commit 933c5c0

Browse files
authored
chore(client-s3): bucket contextParam customization for schema-serde mode (#7250)
* chore: bucket contextParam customization for schema-serde
1 parent a298ba0 commit 933c5c0

File tree

12 files changed

+157
-65
lines changed

12 files changed

+157
-65
lines changed

codegen/sdk-codegen/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ tasks.register("generate-smithy-build") {
110110
// e.g. "S3" - use this as exclusion list if needed.
111111
)
112112
val useSchemaSerde = setOf<String>(
113-
// "CloudWatch Logs"
113+
// "S3"
114114
)
115115
val projectionContents = Node.objectNodeBuilder()
116116
.withMember("imports", Node.fromStrings("${models.getAbsolutePath()}${File.separator}${file.name}"))

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public AddProtocolConfig() {
3838
SchemaGenerationAllowlist.allow("com.amazonaws.dynamodb#DynamoDB_20120810");
3939
SchemaGenerationAllowlist.allow("com.amazonaws.lambda#AWSGirApiService");
4040
SchemaGenerationAllowlist.allow("com.amazonaws.cloudwatchlogs#Logs_20140328");
41+
SchemaGenerationAllowlist.allow("com.amazonaws.sts#AWSSecurityTokenServiceV20110615");
4142

4243
// protocol tests
4344
SchemaGenerationAllowlist.allow("aws.protocoltests.json10#JsonRpc10");

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import software.amazon.smithy.model.knowledge.OperationIndex;
3535
import software.amazon.smithy.model.knowledge.TopDownIndex;
3636
import software.amazon.smithy.model.pattern.SmithyPattern;
37+
import software.amazon.smithy.model.pattern.UriPattern;
3738
import software.amazon.smithy.model.shapes.MemberShape;
3839
import software.amazon.smithy.model.shapes.OperationShape;
3940
import software.amazon.smithy.model.shapes.ServiceShape;
@@ -47,9 +48,11 @@
4748
import software.amazon.smithy.model.traits.EndpointTrait;
4849
import software.amazon.smithy.model.traits.HttpHeaderTrait;
4950
import software.amazon.smithy.model.traits.HttpPayloadTrait;
51+
import software.amazon.smithy.model.traits.HttpTrait;
5052
import software.amazon.smithy.model.traits.StreamingTrait;
5153
import software.amazon.smithy.model.traits.Trait;
5254
import software.amazon.smithy.model.transform.ModelTransformer;
55+
import software.amazon.smithy.rulesengine.traits.ContextParamTrait;
5356
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
5457
import software.amazon.smithy.typescript.codegen.LanguageTarget;
5558
import software.amazon.smithy.typescript.codegen.TypeScriptDependency;
@@ -58,6 +61,7 @@
5861
import software.amazon.smithy.typescript.codegen.auth.http.integration.AddHttpSigningPlugin;
5962
import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin;
6063
import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration;
64+
import software.amazon.smithy.typescript.codegen.schema.SchemaGenerationAllowlist;
6165
import software.amazon.smithy.utils.ListUtils;
6266
import software.amazon.smithy.utils.MapUtils;
6367
import software.amazon.smithy.utils.SetUtils;
@@ -113,6 +117,53 @@ public static Shape removeHostPrefixTrait(Shape shape) {
113117
.orElse(shape);
114118
}
115119

120+
/**
121+
* Remove `/{Bucket}` from the operation endpoint URI IFF
122+
* - it is in a prefix position.
123+
* - input has a member called "Bucket".
124+
* - "Bucket" input member is a contextParam.
125+
*/
126+
public static Shape removeUriBucketPrefix(Shape shape, Model model) {
127+
return shape.asOperationShape()
128+
.map(OperationShape::shapeToBuilder)
129+
.map((Object object) -> {
130+
OperationShape.Builder builder = (OperationShape.Builder) object;
131+
Trait trait = builder.getAllTraits().get(HttpTrait.ID);
132+
if (trait instanceof HttpTrait httpTrait) {
133+
String uri = httpTrait.getUri().toString();
134+
135+
StructureShape input = model.expectShape(
136+
shape.asOperationShape().get().getInputShape()
137+
).asStructureShape().orElseThrow(
138+
() -> new RuntimeException("operation must have input structure")
139+
);
140+
141+
boolean hasBucketPrefix = uri.startsWith("/{Bucket}");
142+
Optional<MemberShape> bucket = input.getMember("Bucket");
143+
boolean inputHasBucketMember = bucket.isPresent();
144+
boolean bucketIsContextParam = bucket
145+
.map(ms -> ms.getTrait(ContextParamTrait.class))
146+
.isPresent();
147+
148+
if (hasBucketPrefix && inputHasBucketMember && bucketIsContextParam) {
149+
String replaced = uri
150+
.replace("/{Bucket}/", "/")
151+
.replace("/{Bucket}", "/");
152+
builder.addTrait(
153+
httpTrait
154+
.toBuilder()
155+
.uri(UriPattern.parse(replaced))
156+
.build()
157+
);
158+
}
159+
}
160+
return builder;
161+
})
162+
.map(OperationShape.Builder::build)
163+
.map(s -> (Shape) s)
164+
.orElse(shape);
165+
}
166+
116167
@Override
117168
public List<String> runAfter() {
118169
return List.of(
@@ -243,7 +294,12 @@ public Model preprocessModel(Model model, TypeScriptSettings settings) {
243294
Model builtModel = modelBuilder.addShapes(inputShapes).build();
244295
if (hasRuleset) {
245296
return ModelTransformer.create().mapShapes(
246-
builtModel, AddS3Config::removeHostPrefixTrait
297+
builtModel, (shape) -> {
298+
if (SchemaGenerationAllowlist.allows(serviceShape.getId(), settings)) {
299+
return removeUriBucketPrefix(shape, model);
300+
}
301+
return shape;
302+
}
247303
);
248304
}
249305
return builtModel;

packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,26 @@ export abstract class AwsJsonRpcProtocol extends RpcProtocol {
8585
[namespace, errorName] = errorIdentifier.split("#");
8686
}
8787

88+
const errorMetadata = {
89+
$metadata: metadata,
90+
$response: response,
91+
$fault: response.statusCode <= 500 ? ("client" as const) : ("server" as const),
92+
};
93+
8894
const registry = TypeRegistry.for(namespace);
8995
let errorSchema: ErrorSchema;
9096
try {
9197
errorSchema = registry.getSchema(errorIdentifier) as ErrorSchema;
9298
} catch (e) {
99+
if (dataObject.Message) {
100+
dataObject.message = dataObject.Message;
101+
}
93102
const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException();
94103
if (baseExceptionSchema) {
95104
const ErrorCtor = baseExceptionSchema.ctor;
96-
throw Object.assign(new ErrorCtor(errorName), dataObject);
105+
throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject);
97106
}
98-
throw new Error(errorName);
107+
throw Object.assign(new Error(errorName), errorMetadata, dataObject);
99108
}
100109

101110
const ns = NormalizedSchema.of(errorSchema);
@@ -109,14 +118,14 @@ export abstract class AwsJsonRpcProtocol extends RpcProtocol {
109118
output[name] = this.codec.createDeserializer().readObject(member, dataObject[target]);
110119
}
111120

112-
Object.assign(exception, {
113-
$metadata: metadata,
114-
$response: response,
115-
$fault: ns.getMergedTraits().error,
116-
message,
117-
...output,
118-
});
119-
120-
throw exception;
121+
throw Object.assign(
122+
exception,
123+
errorMetadata,
124+
{
125+
$fault: ns.getMergedTraits().error,
126+
message,
127+
},
128+
output
129+
);
121130
}
122131
}

packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,26 @@ export class AwsRestJsonProtocol extends HttpBindingProtocol {
123123
[namespace, errorName] = errorIdentifier.split("#");
124124
}
125125

126+
const errorMetadata = {
127+
$metadata: metadata,
128+
$response: response,
129+
$fault: response.statusCode <= 500 ? ("client" as const) : ("server" as const),
130+
};
131+
126132
const registry = TypeRegistry.for(namespace);
127133
let errorSchema: ErrorSchema;
128134
try {
129135
errorSchema = registry.getSchema(errorIdentifier) as ErrorSchema;
130136
} catch (e) {
137+
if (dataObject.Message) {
138+
dataObject.message = dataObject.Message;
139+
}
131140
const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException();
132141
if (baseExceptionSchema) {
133142
const ErrorCtor = baseExceptionSchema.ctor;
134-
throw Object.assign(new ErrorCtor(errorName), dataObject);
143+
throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject);
135144
}
136-
throw new Error(errorName);
145+
throw Object.assign(new Error(errorName), errorMetadata, dataObject);
137146
}
138147

139148
const ns = NormalizedSchema.of(errorSchema);
@@ -147,15 +156,15 @@ export class AwsRestJsonProtocol extends HttpBindingProtocol {
147156
output[name] = this.codec.createDeserializer().readObject(member, dataObject[target]);
148157
}
149158

150-
Object.assign(exception, {
151-
$metadata: metadata,
152-
$response: response,
153-
$fault: ns.getMergedTraits().error,
154-
message,
155-
...output,
156-
});
157-
158-
throw exception;
159+
throw Object.assign(
160+
exception,
161+
errorMetadata,
162+
{
163+
$fault: ns.getMergedTraits().error,
164+
message,
165+
},
166+
output
167+
);
159168
}
160169

161170
/**

packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,12 @@ export class AwsQueryProtocol extends RpcProtocol {
146146
[namespace, errorName] = errorIdentifier.split("#");
147147
}
148148

149-
const errorDataSource = this.loadQueryError(dataObject);
149+
const errorData = this.loadQueryError(dataObject);
150+
const errorMetadata = {
151+
$metadata: metadata,
152+
$response: response,
153+
$fault: response.statusCode <= 500 ? ("client" as const) : ("server" as const),
154+
};
150155

151156
const registry = TypeRegistry.for(namespace);
152157
let errorSchema: ErrorSchema;
@@ -159,12 +164,15 @@ export class AwsQueryProtocol extends RpcProtocol {
159164
errorSchema = registry.getSchema(errorIdentifier) as ErrorSchema;
160165
}
161166
} catch (e) {
167+
if (errorData.Message) {
168+
errorData.message = errorData.Message;
169+
}
162170
const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException();
163171
if (baseExceptionSchema) {
164172
const ErrorCtor = baseExceptionSchema.ctor;
165-
throw Object.assign(new ErrorCtor(errorName), errorDataSource);
173+
throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject);
166174
}
167-
throw new Error(errorName);
175+
throw Object.assign(new Error(errorName), errorMetadata, errorData);
168176
}
169177

170178
const ns = NormalizedSchema.of(errorSchema);
@@ -175,19 +183,19 @@ export class AwsQueryProtocol extends RpcProtocol {
175183

176184
for (const [name, member] of ns.structIterator()) {
177185
const target = member.getMergedTraits().xmlName ?? name;
178-
const value = errorDataSource[target] ?? dataObject[target];
186+
const value = errorData[target] ?? dataObject[target];
179187
output[name] = this.deserializer.readSchema(member, value);
180188
}
181189

182-
Object.assign(exception, {
183-
$metadata: metadata,
184-
$response: response,
185-
$fault: ns.getMergedTraits().error,
186-
message,
187-
...output,
188-
});
189-
190-
throw exception;
190+
throw Object.assign(
191+
exception,
192+
errorMetadata,
193+
{
194+
$fault: ns.getMergedTraits().error,
195+
message,
196+
},
197+
output
198+
);
191199
}
192200

193201
/**

packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ describe(AwsRestXmlProtocol.name, () => {
3030
},
3131
expected: {
3232
request: {
33-
path: "/",
33+
// S3 customization not active here since this is a mock.
34+
// customization does model preprocessing to remove /{Bucket} prefix
35+
// when it is a contextParam.
36+
path: "/{Bucket}",
3437
method: "POST",
3538
headers: {
3639
"content-type": "application/xml",

packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,6 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol {
6161
const ns = NormalizedSchema.of(operationSchema.input);
6262
const members = ns.getMemberSchemas();
6363

64-
request.path =
65-
String(request.path)
66-
.split("/")
67-
.filter((segment) => {
68-
// for legacy reasons,
69-
// Bucket is in the http trait but is handled by endpoints ruleset.
70-
return segment !== "{Bucket}";
71-
})
72-
.join("/") || "/";
73-
7464
if (!request.headers["content-type"]) {
7565
const httpPayloadMember = Object.values(members).find((m) => {
7666
return !!m.getMergedTraits().httpPayload;
@@ -136,17 +126,27 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol {
136126
[namespace, errorName] = errorIdentifier.split("#");
137127
}
138128

129+
const errorMetadata = {
130+
$metadata: metadata,
131+
$response: response,
132+
$fault: response.statusCode <= 500 ? ("client" as const) : ("server" as const),
133+
};
134+
139135
const registry = TypeRegistry.for(namespace);
136+
140137
let errorSchema: ErrorSchema;
141138
try {
142139
errorSchema = registry.getSchema(errorIdentifier) as ErrorSchema;
143140
} catch (e) {
141+
if (dataObject.Message) {
142+
dataObject.message = dataObject.Message;
143+
}
144144
const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException();
145145
if (baseExceptionSchema) {
146146
const ErrorCtor = baseExceptionSchema.ctor;
147-
throw Object.assign(new ErrorCtor(errorName), dataObject);
147+
throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject);
148148
}
149-
throw new Error(errorName);
149+
throw Object.assign(new Error(errorName), errorMetadata, dataObject);
150150
}
151151

152152
const ns = NormalizedSchema.of(errorSchema);
@@ -162,15 +162,15 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol {
162162
output[name] = this.codec.createDeserializer().readSchema(member, value);
163163
}
164164

165-
Object.assign(exception, {
166-
$metadata: metadata,
167-
$response: response,
168-
$fault: ns.getMergedTraits().error,
169-
message,
170-
...output,
171-
});
172-
173-
throw exception;
165+
throw Object.assign(
166+
exception,
167+
errorMetadata,
168+
{
169+
$fault: ns.getMergedTraits().error,
170+
message,
171+
},
172+
output
173+
);
174174
}
175175

176176
/**

packages/core/src/submodules/protocols/xml/XmlShapeSerializer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,6 @@ export class XmlShapeSerializer extends SerdeContextConfig implements ShapeSeria
7979

8080
const [xmlnsAttr, xmlns] = this.getXmlnsAttribute(ns, parentXmlns);
8181

82-
if (xmlns) {
83-
structXmlNode.addAttribute(xmlnsAttr as string, xmlns);
84-
}
85-
8682
for (const [memberName, memberSchema] of ns.structIterator()) {
8783
const val = (value as any)[memberName];
8884

@@ -108,6 +104,10 @@ export class XmlShapeSerializer extends SerdeContextConfig implements ShapeSeria
108104
}
109105
}
110106

107+
if (xmlns) {
108+
structXmlNode.addAttribute(xmlnsAttr as string, xmlns);
109+
}
110+
111111
return structXmlNode;
112112
}
113113

packages/middleware-logger/src/loggerMiddleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {
1+
import type {
22
AbsoluteLocation,
33
HandlerExecutionContext,
44
InitializeHandler,

0 commit comments

Comments
 (0)