Skip to content

Commit 257d395

Browse files
Merge pull request #295 from adamthom-amzn/ssdk
Support for framework exceptions
2 parents 2e2664f + 3eb717b commit 257d395

File tree

7 files changed

+160
-37
lines changed

7 files changed

+160
-37
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ public Void serviceShape(ServiceShape shape) {
329329
context.withSymbolProvider(serverSymbolProvider);
330330
protocolGenerator.generateRequestDeserializers(serverContext);
331331
protocolGenerator.generateResponseSerializers(serverContext);
332+
protocolGenerator.generateFrameworkErrorSerializer(serverContext);
332333
writers.useShapeWriter(shape, serverSymbolProvider, w -> {
333334
protocolGenerator.generateHandlerFactory(serverContext.withWriter(w));
334335
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.smithy.typescript.codegen;
17+
18+
import software.amazon.smithy.model.Model;
19+
20+
public enum FrameworkErrorModel {
21+
22+
INSTANCE;
23+
24+
private final Model model = Model.assembler()
25+
.addImport(FrameworkErrorModel.class.getResource("framework-errors.smithy"))
26+
.assemble()
27+
.unwrap();
28+
29+
public Model getModel() {
30+
return model;
31+
}
32+
}

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

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ static void generateServiceHandler(SymbolProvider symbolProvider,
4949
writer.addImport("ServiceHandler", null, "@aws-smithy/server-common");
5050
writer.addImport("Mux", null, "@aws-smithy/server-common");
5151
writer.addImport("OperationSerializer", null, "@aws-smithy/server-common");
52+
writer.addImport("UnknownOperationException", null, "@aws-smithy/server-common");
53+
writer.addImport("InternalFailureException", null, "@aws-smithy/server-common");
54+
writer.addImport("SerializationException", null, "@aws-smithy/server-common");
55+
writer.addImport("SmithyFrameworkException", null, "@aws-smithy/server-common");
56+
writer.addImport("SerdeContext", null, "@aws-sdk/types");
5257
writer.addImport("NodeHttpHandler", null, "@aws-sdk/node-http-handler");
5358
writer.addImport("streamCollector", null, "@aws-sdk/node-http-handler");
5459
writer.addImport("fromBase64", null, "@aws-sdk/util-base64-node");
@@ -68,6 +73,8 @@ static void generateServiceHandler(SymbolProvider symbolProvider,
6873
writer.write("private mux: Mux<$S, $T>;", serviceShape.getId().getName(), operationsType);
6974
writer.write("private serializerFactory: <T extends $T>(operation: T) => "
7075
+ "OperationSerializer<$T, T, SmithyException>;", operationsType, serviceSymbol);
76+
writer.write("private serializeFrameworkException: (e: SmithyFrameworkException, "
77+
+ "ctx: Omit<SerdeContext, 'endpoint'>) => Promise<HttpResponse>;");
7178
writer.openBlock("private serdeContextBase = {", "};", () -> {
7279
writer.write("base64Encoder: toBase64,");
7380
writer.write("base64Decoder: fromBase64,");
@@ -87,51 +94,66 @@ static void generateServiceHandler(SymbolProvider symbolProvider,
8794
writer.write("operation in $T that ", serviceSymbol);
8895
writer.writeInline(" ")
8996
.write("handles deserialization of requests and serialization of responses");
97+
writer.write("@param serializeFrameworkException A function that can serialize "
98+
+ "{@link SmithyFrameworkException}s");
9099
});
91-
writer.openBlock("constructor(service: $1T, "
92-
+ "mux: Mux<$3S, $2T>, "
93-
+ "serializerFactory: <T extends $2T>(op: T) => "
94-
+ "OperationSerializer<$1T, T, SmithyException>) {", "}",
95-
serviceSymbol, operationsType, serviceShape.getId().getName(), () -> {
96-
writer.write("this.service = service;");
97-
writer.write("this.mux = mux;");
98-
writer.write("this.serializerFactory = serializerFactory;");
100+
writer.openBlock("constructor(", ") {", () -> {
101+
writer.write("service: $T,", serviceSymbol);
102+
writer.write("mux: Mux<$S, $T>,", serviceShape.getId().getName(), operationsType);
103+
writer.write("serializerFactory:<T extends $T>(op: T) => OperationSerializer<$T, T, SmithyException>,",
104+
operationsType, serviceSymbol);
105+
writer.write("serializeFrameworkException: (e: SmithyFrameworkException, ctx: Omit<SerdeContext, "
106+
+ "'endpoint'>) => Promise<HttpResponse>");
99107
});
108+
writer.indent();
109+
writer.write("this.service = service;");
110+
writer.write("this.mux = mux;");
111+
writer.write("this.serializerFactory = serializerFactory;");
112+
writer.write("this.serializeFrameworkException = serializeFrameworkException;");
113+
writer.closeBlock("}");
100114
writer.openBlock("async handle(request: HttpRequest): Promise<HttpResponse> {", "}", () -> {
101115
writer.write("const target = this.mux.match(request);");
102116
writer.openBlock("if (target === undefined) {", "}", () -> {
103-
writer.write("throw new Error(`Could not match any operation to $${request.method} "
104-
+ "$${request.path} $${JSON.stringify(request.query)}`);");
117+
writer.write("return serializeFrameworkException(new UnknownOperationException(), "
118+
+ "this.serdeContextBase);");
105119
});
106120
writer.openBlock("switch (target.operation) {", "}", () -> {
107121
for (OperationShape operation : operations) {
108-
generateHandlerCase(writer, serviceSymbol, operation, symbolProvider.toSymbol(operation));
122+
generateHandlerCase(writer, operation, symbolProvider.toSymbol(operation));
109123
}
110124
});
111125
});
112126
});
113127
}
114128

115129
private static void generateHandlerCase(TypeScriptWriter writer,
116-
Symbol serviceSymbol,
117130
Shape operationShape,
118131
Symbol operationSymbol) {
119132
String opName = operationShape.getId().getName();
120133
writer.openBlock("case $S : {", "}", opName, () -> {
121134
writer.write("let serializer = this.serializerFactory($S);", opName);
122-
writer.openBlock("try {", "} catch(error: unknown) {", () -> {
123-
writer.openBlock("let input = await serializer.deserialize(request, {", "});", () -> {
135+
writer.write("let input;");
136+
writer.openBlock("try {", "} catch (error: unknown) {", () -> {
137+
writer.openBlock("input = await serializer.deserialize(request, {", "});", () -> {
124138
writer.write("endpoint: () => Promise.resolve(request), ...this.serdeContextBase");
125139
});
126-
writer.write("let output = this.service.$L(input, request);", operationSymbol.getName());
140+
});
141+
writer.indent();
142+
writer.write("return this.serializeFrameworkException(new SerializationException(), "
143+
+ "this.serdeContextBase);");
144+
writer.closeBlock("}");
145+
writer.openBlock("try {", "} catch(error: unknown) {", () -> {
146+
writer.write("let output = await this.service.$L(input, request);", operationSymbol.getName());
127147
writer.write("return serializer.serialize(output, this.serdeContextBase);");
128148
});
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;");
149+
writer.indent();
150+
writer.openBlock("if (serializer.isOperationError(error)) {", "}", () -> {
151+
writer.write("return serializer.serializeError(error, this.serdeContextBase);");
134152
});
153+
writer.write("console.log('Received an unexpected error', error);");
154+
writer.write("return this.serializeFrameworkException(new InternalFailureException(), "
155+
+ "this.serdeContextBase);");
156+
writer.closeBlock("}");
135157
});
136158
}
137159

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

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
6969
import software.amazon.smithy.typescript.codegen.ApplicationProtocol;
7070
import software.amazon.smithy.typescript.codegen.CodegenUtils;
71+
import software.amazon.smithy.typescript.codegen.FrameworkErrorModel;
7172
import software.amazon.smithy.typescript.codegen.TypeScriptDependency;
7273
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
7374
import software.amazon.smithy.utils.ListUtils;
@@ -216,6 +217,37 @@ public void generateResponseSerializers(GenerationContext context) {
216217
}
217218
}
218219

220+
@Override
221+
public void generateFrameworkErrorSerializer(GenerationContext inputContext) {
222+
final GenerationContext context = inputContext.copy();
223+
context.setModel(FrameworkErrorModel.INSTANCE.getModel());
224+
225+
SymbolReference responseType = getApplicationProtocol().getResponseType();
226+
HttpBindingIndex bindingIndex = HttpBindingIndex.of(context.getModel());
227+
TypeScriptWriter writer = context.getWriter();
228+
229+
writer.addImport("SmithyFrameworkException", "__SmithyFrameworkException", "@aws-smithy/server-common");
230+
writer.addUseImports(responseType);
231+
232+
writer.openBlock("export const serializeFrameworkException = async(\n"
233+
+ " input: __SmithyFrameworkException,\n"
234+
+ " ctx: Omit<__SerdeContext, 'endpoint'>\n"
235+
+ "): Promise<$T> => {", "}", responseType, () -> {
236+
237+
writeEmptyEndpoint(context);
238+
239+
writer.openBlock("switch (input.name) {", "}", () -> {
240+
for (final Shape shape : context.getModel().getShapesWithTrait(HttpErrorTrait.class)) {
241+
StructureShape errorShape = shape.asStructureShape().orElseThrow(IllegalArgumentException::new);
242+
writer.openBlock("case $S: {", "}", errorShape.getId().getName(), () -> {
243+
generateErrorSerializationImplementation(context, errorShape, responseType, bindingIndex);
244+
});
245+
}
246+
});
247+
});
248+
writer.write("");
249+
}
250+
219251
private void generateMux(GenerationContext context) {
220252
TopDownIndex topDownIndex = TopDownIndex.of(context.getModel());
221253
TypeScriptWriter writer = context.getWriter();
@@ -295,6 +327,9 @@ public void generateHandlerFactory(GenerationContext context) {
295327
Set<OperationShape> operations = index.getContainedOperations(context.getService());
296328
SymbolProvider symbolProvider = context.getSymbolProvider();
297329

330+
writer.addImport("serializeFrameworkException", null,
331+
"./protocols/" + ProtocolGenerator.getSanitizedName(getName()));
332+
298333
Symbol serviceSymbol = symbolProvider.toSymbol(context.getService());
299334
Symbol handlerSymbol = serviceSymbol.expectProperty("handler", Symbol.class);
300335
Symbol operationsSymbol = serviceSymbol.expectProperty("operations", Symbol.class);
@@ -311,7 +346,7 @@ public void generateHandlerFactory(GenerationContext context) {
311346
.forEach(writeOperationCase(writer, symbolProvider));
312347
});
313348
});
314-
writer.write("return new $T(service, mux, serFn);", handlerSymbol);
349+
writer.write("return new $T(service, mux, serFn, serializeFrameworkException);", handlerSymbol);
315350
});
316351
}
317352

@@ -402,25 +437,33 @@ private void generateErrorSerializer(GenerationContext context, StructureShape e
402437
+ " ctx: Omit<__SerdeContext, 'endpoint'>\n"
403438
+ "): Promise<$T> => {", "}", methodName, symbol, responseType, () -> {
404439
writeEmptyEndpoint(context);
405-
writeErrorStatusCode(context, error);
406-
writeResponseHeaders(context, error, bindingIndex, () -> writeDefaultErrorHeaders(context, error));
440+
generateErrorSerializationImplementation(context, error, responseType, bindingIndex);
441+
});
442+
writer.write("");
443+
}
407444

408-
List<HttpBinding> bodyBindings = writeResponseBody(context, error, bindingIndex);
409-
if (!bodyBindings.isEmpty()) {
410-
// Track all shapes bound to the body so their serializers may be generated.
411-
bodyBindings.stream()
412-
.map(HttpBinding::getMember)
413-
.map(member -> context.getModel().expectShape(member.getTarget()))
414-
.forEach(serializingDocumentShapes::add);
415-
}
445+
private void generateErrorSerializationImplementation(GenerationContext context,
446+
StructureShape error,
447+
SymbolReference responseType,
448+
HttpBindingIndex bindingIndex) {
449+
TypeScriptWriter writer = context.getWriter();
450+
writeErrorStatusCode(context, error);
451+
writeResponseHeaders(context, error, bindingIndex, () -> writeDefaultErrorHeaders(context, error));
452+
453+
List<HttpBinding> bodyBindings = writeResponseBody(context, error, bindingIndex);
454+
if (!bodyBindings.isEmpty()) {
455+
// Track all shapes bound to the body so their serializers may be generated.
456+
bodyBindings.stream()
457+
.map(HttpBinding::getMember)
458+
.map(member -> context.getModel().expectShape(member.getTarget()))
459+
.forEach(serializingDocumentShapes::add);
460+
}
416461

417-
writer.openBlock("return new $T({", "});", responseType, () -> {
418-
writer.write("headers,");
419-
writer.write("body,");
420-
writer.write("statusCode,");
421-
});
462+
writer.openBlock("return new $T({", "});", responseType, () -> {
463+
writer.write("headers,");
464+
writer.write("body,");
465+
writer.write("statusCode,");
422466
});
423-
writer.write("");
424467
}
425468

426469
private void writeEmptyEndpoint(GenerationContext context) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ public void generateRequestSerializers(GenerationContext context) {
143143
}
144144
}
145145

146+
@Override
147+
public void generateFrameworkErrorSerializer(GenerationContext serverContext) {
148+
LOGGER.warning("Framework error serialization is not currently supported for RPC protocols.");
149+
}
150+
146151
@Override
147152
public void generateRequestDeserializers(GenerationContext context) {
148153
LOGGER.warning("Request deserialization is not currently supported for RPC protocols.");

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ default void generateSharedComponents(GenerationContext context) {
148148
*/
149149
void generateResponseSerializers(GenerationContext context);
150150

151+
/**
152+
* Generates the code used to serialize unmodeled errors for servers.
153+
*
154+
* @param serverContext Serialization context.
155+
*/
156+
void generateFrameworkErrorSerializer(GenerationContext serverContext);
157+
151158
/**
152159
* Generates the code used to determine the service and operation
153160
* targeted by a given request.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace smithy.framework
2+
3+
@error("server")
4+
@httpError(500)
5+
structure InternalFailure {}
6+
7+
@error("client")
8+
@httpError(404)
9+
structure UnknownOperationException {}
10+
11+
@error("client")
12+
@httpError(400)
13+
structure SerializationException {}

0 commit comments

Comments
 (0)