Skip to content

Commit 1735105

Browse files
Merge pull request #289 from JordonPhillips/ssdk-exception-handling
ssdk modeled exception handling
2 parents 6f3dda4 + 206de49 commit 1735105

File tree

6 files changed

+152
-29
lines changed

6 files changed

+152
-29
lines changed

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CodegenVisitor.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,9 @@ public Void serviceShape(ServiceShape shape) {
329329
context.withSymbolProvider(serverSymbolProvider);
330330
protocolGenerator.generateRequestDeserializers(serverContext);
331331
protocolGenerator.generateResponseSerializers(serverContext);
332-
protocolGenerator.generateHandlerFactory(serverContext);
332+
writers.useShapeWriter(shape, serverSymbolProvider, w -> {
333+
protocolGenerator.generateHandlerFactory(serverContext.withWriter(w));
334+
});
333335
}
334336
protocolGenerator.generateSharedComponents(context);
335337
});
@@ -408,7 +410,8 @@ private void generateCommands(ServiceShape shape) {
408410

409411
if (settings.generateServerSdk()) {
410412
writers.useShapeWriter(operation, serverSymbolProvider, commandWriter -> new ServerCommandGenerator(
411-
settings, model, operation, serverSymbolProvider, commandWriter).run());
413+
settings, model, operation, serverSymbolProvider, commandWriter,
414+
protocolGenerator, applicationProtocol).run());
412415
}
413416
}
414417
}

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServerCommandGenerator.java

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static software.amazon.smithy.typescript.codegen.CodegenUtils.getBlobStreamingMembers;
1919
import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeStreamingMemberType;
2020

21+
import java.util.Iterator;
2122
import java.util.List;
2223
import java.util.Optional;
2324
import software.amazon.smithy.codegen.core.Symbol;
@@ -26,7 +27,9 @@
2627
import software.amazon.smithy.model.knowledge.OperationIndex;
2728
import software.amazon.smithy.model.shapes.MemberShape;
2829
import software.amazon.smithy.model.shapes.OperationShape;
30+
import software.amazon.smithy.model.shapes.ShapeId;
2931
import software.amazon.smithy.model.shapes.StructureShape;
32+
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
3033

3134
/**
3235
* Generates server operation types.
@@ -41,29 +44,40 @@ final class ServerCommandGenerator implements Runnable {
4144
private final OperationIndex operationIndex;
4245
private final Symbol inputType;
4346
private final Symbol outputType;
47+
private final Symbol errorsType;
48+
private final ProtocolGenerator protocolGenerator;
49+
private final ApplicationProtocol applicationProtocol;
4450

4551
ServerCommandGenerator(
4652
TypeScriptSettings settings,
4753
Model model,
4854
OperationShape operation,
4955
SymbolProvider symbolProvider,
50-
TypeScriptWriter writer
56+
TypeScriptWriter writer,
57+
ProtocolGenerator protocolGenerator,
58+
ApplicationProtocol applicationProtocol
5159
) {
5260
this.settings = settings;
5361
this.model = model;
5462
this.operation = operation;
5563
this.symbolProvider = symbolProvider;
5664
this.writer = writer;
65+
this.protocolGenerator = protocolGenerator;
66+
this.applicationProtocol = applicationProtocol;
5767

5868
Symbol operationSymbol = symbolProvider.toSymbol(operation);
5969
operationIndex = OperationIndex.of(model);
6070
inputType = operationSymbol.expectProperty("inputType", Symbol.class);
6171
outputType = operationSymbol.expectProperty("outputType", Symbol.class);
72+
errorsType = operationSymbol.expectProperty("errorsType", Symbol.class);
6273
}
6374

6475
@Override
6576
public void run() {
77+
writeOperationType();
6678
addInputAndOutputTypes();
79+
writeErrorType();
80+
writeOperationSerializer();
6781
}
6882

6983
private void addInputAndOutputTypes() {
@@ -99,4 +113,93 @@ private void writeOutputType(String typeName, Optional<StructureShape> outputSha
99113
writer.write("export type $L = __MetadataBearer", typeName);
100114
}
101115
}
116+
117+
private void writeErrorType() {
118+
if (operation.getErrors().isEmpty()) {
119+
writer.write("export type $L = never;", errorsType.getName());
120+
} else {
121+
writer.writeInline("export type $L = ", errorsType.getName());
122+
for (Iterator<ShapeId> iter = operation.getErrors().iterator(); iter.hasNext();) {
123+
writer.writeInline("$T", symbolProvider.toSymbol(model.expectShape(iter.next())));
124+
if (iter.hasNext()) {
125+
writer.writeInline(" | ");
126+
}
127+
}
128+
writer.write("");
129+
}
130+
writer.write("");
131+
}
132+
133+
private void writeOperationType() {
134+
Symbol operationSymbol = symbolProvider.toSymbol(operation);
135+
writer.addImport("Operation", "__Operation", "@aws-smithy/server-common");
136+
writer.write("export type $L = __Operation<$T, $T>", operationSymbol.getName(), inputType, outputType);
137+
writer.write("");
138+
}
139+
140+
private void writeOperationSerializer() {
141+
Symbol operationSymbol = symbolProvider.toSymbol(operation);
142+
String serializerName = operationSymbol.expectProperty("serializerType", Symbol.class).getName();
143+
Symbol serverSymbol = symbolProvider.toSymbol(model.expectShape(settings.getService()));
144+
145+
writer.addImport("OperationSerializer", null, "@aws-smithy/server-common");
146+
writer.openBlock("export class $L implements OperationSerializer<$T, $S, $T> {", "}",
147+
serializerName, serverSymbol, operation.getId().getName(), errorsType, () -> {
148+
String serializerFunction = ProtocolGenerator.getGenericSerFunctionName(operationSymbol) + "Response";
149+
String deserializerFunction = ProtocolGenerator.getGenericDeserFunctionName(operationSymbol) + "Request";
150+
writer.addImport(serializerFunction, null,
151+
"./protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()));
152+
writer.addImport(deserializerFunction, null,
153+
"./protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()));
154+
writer.write("serialize = $L;", serializerFunction);
155+
writer.write("deserialize = $L;", deserializerFunction);
156+
writer.write("");
157+
writeErrorChecker();
158+
writeErrorHandler();
159+
});
160+
writer.write("");
161+
}
162+
163+
private void writeErrorChecker() {
164+
writer.openBlock("isOperationError(error: any): error is $T {", "};", errorsType, () -> {
165+
if (operation.getErrors().isEmpty()) {
166+
writer.write("return false;");
167+
} else {
168+
writer.writeInline("const names: $T['name'][] = [", errorsType);
169+
for (Iterator<ShapeId> iter = operation.getErrors().iterator(); iter.hasNext();) {
170+
writer.writeInline("$S", iter.next().getName());
171+
if (iter.hasNext()) {
172+
writer.writeInline(", ");
173+
}
174+
}
175+
writer.write("];");
176+
writer.write("return names.includes(error.name);");
177+
}
178+
});
179+
writer.write("");
180+
}
181+
182+
private void writeErrorHandler() {
183+
writer.addImport("SerdeContext", null, "@aws-sdk/types");
184+
writer.openBlock("serializeError(error: $T, ctx: Omit<SerdeContext, 'endpoint'>): Promise<$T> {", "}",
185+
errorsType, applicationProtocol.getResponseType(), () -> {
186+
writer.openBlock("switch (error.name) {", "}", () -> {
187+
for (ShapeId errorId : operation.getErrors()) {
188+
writeErrorHandlerCase(errorId);
189+
}
190+
writer.openBlock("default: {", "}", () -> writer.write("throw error;"));
191+
});
192+
});
193+
writer.write("");
194+
}
195+
196+
private void writeErrorHandlerCase(ShapeId errorId) {
197+
Symbol errorSymbol = symbolProvider.toSymbol(model.expectShape(errorId));
198+
String serializerFunction = ProtocolGenerator.getGenericSerFunctionName(errorSymbol) + "Error";
199+
writer.addImport(serializerFunction, null,
200+
"./protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()));
201+
writer.openBlock("case $S: {", "}", errorId.getName(), () -> {
202+
writer.write("return $L(error, ctx);", serializerFunction);
203+
});
204+
}
102205
}

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServerGenerator.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ static void generateServiceHandler(SymbolProvider symbolProvider,
5757
writer.addImport("toUtf8", null, "@aws-sdk/util-utf8-node");
5858
writer.addImport("HttpRequest", null, "@aws-sdk/protocol-http");
5959
writer.addImport("HttpResponse", null, "@aws-sdk/protocol-http");
60+
writer.addImport("SmithyException", null, "@aws-sdk/smithy-client");
6061

6162
Symbol serviceSymbol = symbolProvider.toSymbol(serviceShape);
6263
Symbol handlerSymbol = serviceSymbol.expectProperty("handler", Symbol.class);
@@ -65,8 +66,8 @@ static void generateServiceHandler(SymbolProvider symbolProvider,
6566
writer.openBlock("export class $L implements ServiceHandler {", "}", handlerSymbol.getName(), () -> {
6667
writer.write("private service: $T;", serviceSymbol);
6768
writer.write("private mux: Mux<$S, $T>;", serviceShape.getId().getName(), operationsType);
68-
writer.write("private serializerFactory: <T extends $T>(operation: T) => OperationSerializer<$T, T>;",
69-
operationsType, serviceSymbol);
69+
writer.write("private serializerFactory: <T extends $T>(operation: T) => "
70+
+ "OperationSerializer<$T, T, SmithyException>;", operationsType, serviceSymbol);
7071
writer.openBlock("private serdeContextBase = {", "};", () -> {
7172
writer.write("base64Encoder: toBase64,");
7273
writer.write("base64Decoder: fromBase64,");
@@ -89,7 +90,8 @@ static void generateServiceHandler(SymbolProvider symbolProvider,
8990
});
9091
writer.openBlock("constructor(service: $1T, "
9192
+ "mux: Mux<$3S, $2T>, "
92-
+ "serializerFactory: <T extends $2T>(op: T) => OperationSerializer<$1T, T>) {", "}",
93+
+ "serializerFactory: <T extends $2T>(op: T) => "
94+
+ "OperationSerializer<$1T, T, SmithyException>) {", "}",
9395
serviceSymbol, operationsType, serviceShape.getId().getName(), () -> {
9496
writer.write("this.service = service;");
9597
writer.write("this.mux = mux;");
@@ -117,11 +119,19 @@ private static void generateHandlerCase(TypeScriptWriter writer,
117119
String opName = operationShape.getId().getName();
118120
writer.openBlock("case $S : {", "}", opName, () -> {
119121
writer.write("let serializer = this.serializerFactory($S);", opName);
120-
writer.openBlock("let input = await serializer.deserialize(request, {", "});", () -> {
121-
writer.write("endpoint: () => Promise.resolve(request), ...this.serdeContextBase");
122+
writer.openBlock("try {", "} catch(error: unknown) {", () -> {
123+
writer.openBlock("let input = await serializer.deserialize(request, {", "});", () -> {
124+
writer.write("endpoint: () => Promise.resolve(request), ...this.serdeContextBase");
125+
});
126+
writer.write("let output = this.service.$L(input, request);", operationSymbol.getName());
127+
writer.write("return serializer.serialize(output, this.serdeContextBase);");
128+
});
129+
writer.openBlock("", "}", () -> {
130+
writer.openBlock("if (serializer.isOperationError(error)) {", "}", () -> {
131+
writer.write("return serializer.serializeError(error, this.serdeContextBase);");
132+
});
133+
writer.write("throw error;");
122134
});
123-
writer.write("let output = this.service.$L(input, request);", operationSymbol.getName());
124-
writer.write("return serializer.serialize(output, this.serdeContextBase);");
125135
});
126136
}
127137

@@ -136,10 +146,7 @@ static void generateServerInterfaces(SymbolProvider symbolProvider,
136146
writer.openBlock("export interface $L {", "}", serviceInterfaceName, () -> {
137147
for (OperationShape operation : operations) {
138148
Symbol symbol = symbolProvider.toSymbol(operation);
139-
writer.write("$L: $L<$T, $T>", symbol.getName(),
140-
"__Operation",
141-
symbol.expectProperty("inputType", Symbol.class),
142-
symbol.expectProperty("outputType", Symbol.class));
149+
writer.write("$L: $T", symbol.getName(), symbol);
143150
}
144151
});
145152
}

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServerSymbolVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public Symbol operationShape(OperationShape shape) {
8181
//TODO: these names suck but otherwise they clash with the names in models
8282
builder.putProperty("inputType", intermediate.toBuilder().name(shapeName + "ServerInput").build());
8383
builder.putProperty("outputType", intermediate.toBuilder().name(shapeName + "ServerOutput").build());
84+
builder.putProperty("errorsType", intermediate.toBuilder().name(shapeName + "Errors").build());
85+
builder.putProperty("serializerType", intermediate.toBuilder().name(shapeName + "Serializer").build());
8486
return builder.build();
8587
}
8688

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/HttpBindingProtocolGenerator.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,12 @@ public void generateHandlerFactory(GenerationContext context) {
299299
Symbol handlerSymbol = serviceSymbol.expectProperty("handler", Symbol.class);
300300
Symbol operationsSymbol = serviceSymbol.expectProperty("operations", Symbol.class);
301301

302-
writer.addImport("ServiceHandler", null, "@aws-smithy/server-common");
303-
writer.addImport("OperationSerializer", null, "@aws-smithy/server-common");
304-
305302
writer.openBlock("export const get$L = (service: $T): ServiceHandler => {", "}",
306303
handlerSymbol.getName(), serviceSymbol, () -> {
307304
generateMux(context);
308-
writer.openBlock("const serFn: (op: $1T) => OperationSerializer<$2T, $1T> = (op) => {", "};",
309-
operationsSymbol, serviceSymbol, () -> {
305+
writer.addImport("SmithyException", "__SmithyException", "@aws-sdk/smithy-client");
306+
writer.openBlock("const serFn: (op: $1T) => OperationSerializer<$2T, $1T, __SmithyException> = "
307+
+ "(op) => {", "};", operationsSymbol, serviceSymbol, () -> {
310308
writer.openBlock("switch (op) {", "}", () -> {
311309
operations.stream()
312310
.filter(o -> o.getTrait(HttpTrait.class).isPresent())
@@ -317,15 +315,13 @@ public void generateHandlerFactory(GenerationContext context) {
317315
});
318316
}
319317

320-
private Consumer<OperationShape> writeOperationCase(TypeScriptWriter writer, SymbolProvider symbolProvider) {
318+
private Consumer<OperationShape> writeOperationCase(
319+
TypeScriptWriter writer,
320+
SymbolProvider symbolProvider
321+
) {
321322
return operation -> {
322-
Symbol symbol = symbolProvider.toSymbol(operation);
323-
writer.openBlock("case $S: {", "}", operation.getId().getName(), () -> {
324-
writer.openBlock("return {", "};", () -> {
325-
writer.write("serialize: $LResponse,", ProtocolGenerator.getGenericSerFunctionName(symbol));
326-
writer.write("deserialize: $LRequest,", ProtocolGenerator.getGenericDeserFunctionName(symbol));
327-
});
328-
});
323+
Symbol symbol = symbolProvider.toSymbol(operation).expectProperty("serializerType", Symbol.class);
324+
writer.write("case $S: return new $T();", operation.getId().getName(), symbol);
329325
};
330326
}
331327

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/ProtocolGenerator.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,16 +303,28 @@ public void setProtocolName(String protocolName) {
303303
this.protocolName = protocolName;
304304
}
305305

306-
public GenerationContext withSymbolProvider(SymbolProvider newProvider) {
306+
public GenerationContext copy() {
307307
GenerationContext copy = new GenerationContext();
308308
copy.setSettings(settings);
309309
copy.setModel(model);
310310
copy.setService(service);
311-
copy.setSymbolProvider(newProvider);
311+
copy.setSymbolProvider(symbolProvider);
312312
copy.setWriter(writer);
313313
copy.setIntegrations(integrations);
314314
copy.setProtocolName(protocolName);
315315
return copy;
316316
}
317+
318+
public GenerationContext withSymbolProvider(SymbolProvider newProvider) {
319+
GenerationContext copyContext = copy();
320+
copyContext.setSymbolProvider(newProvider);
321+
return copyContext;
322+
}
323+
324+
public GenerationContext withWriter(TypeScriptWriter newWriter) {
325+
GenerationContext copyContext = copy();
326+
copyContext.setWriter(newWriter);
327+
return copyContext;
328+
}
317329
}
318330
}

0 commit comments

Comments
 (0)