Skip to content

Commit dec26ee

Browse files
adamthom-amznsrchase
authored andcommitted
Add generated error types for convenience (smithy-lang#298)
* Make the order for framework errors stable * Generate convenience types for operation errors It's distinctly unpleasant to throw a constructed object that has to include matching values of name, $fault, etc to an interface. This generates convenience classes for services that allow errors to be thrown in a more user-friendly fashion.
1 parent 71f3c4e commit dec26ee

File tree

4 files changed

+110
-1
lines changed

4 files changed

+110
-1
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ public Void serviceShape(ServiceShape shape) {
302302

303303
if (settings.generateServerSdk()) {
304304
generateServiceInterface(shape);
305+
generateServerErrors(shape);
305306
}
306307

307308
if (protocolGenerator != null) {
@@ -393,6 +394,19 @@ private void generateServiceInterface(ServiceShape shape) {
393394
});
394395
}
395396

397+
private void generateServerErrors(ServiceShape service) {
398+
TopDownIndex.of(model)
399+
.getContainedOperations(service)
400+
.stream()
401+
.flatMap(o -> o.getErrors().stream())
402+
.distinct()
403+
.map(id -> model.expectShape(id).asStructureShape().orElseThrow(IllegalArgumentException::new))
404+
.sorted()
405+
.forEachOrdered(error -> writers.useShapeWriter(service, serverSymbolProvider, writer -> {
406+
new ServerErrorGenerator(settings, model, error, serverSymbolProvider, writer).run();
407+
}));
408+
}
409+
396410
private void generateCommands(ServiceShape shape) {
397411
// Generate each operation for the service.
398412
TopDownIndex topDownIndex = TopDownIndex.of(model);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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.codegen.core.Symbol;
19+
import software.amazon.smithy.codegen.core.SymbolProvider;
20+
import software.amazon.smithy.model.Model;
21+
import software.amazon.smithy.model.shapes.StructureShape;
22+
import software.amazon.smithy.model.traits.ErrorTrait;
23+
24+
/**
25+
* Generates convenience error types for servers, since service developers will throw a modeled exception directly,
26+
* while clients typically care only about catching them.
27+
*/
28+
final class ServerErrorGenerator implements Runnable {
29+
30+
private final TypeScriptSettings settings;
31+
private final Model model;
32+
private final StructureShape errorShape;
33+
private final SymbolProvider symbolProvider;
34+
private final TypeScriptWriter writer;
35+
36+
ServerErrorGenerator(TypeScriptSettings settings,
37+
Model model,
38+
StructureShape errorShape,
39+
SymbolProvider symbolProvider,
40+
TypeScriptWriter writer) {
41+
this.settings = settings;
42+
this.model = model;
43+
this.errorShape = errorShape;
44+
this.symbolProvider = symbolProvider;
45+
this.writer = writer;
46+
}
47+
48+
49+
@Override
50+
public void run() {
51+
Symbol symbol = symbolProvider.toSymbol(errorShape);
52+
53+
// Did not add this as a symbol to the error shape since these should not be used by anyone but the user.
54+
String typeName = symbol.getName() + "Error";
55+
56+
writer.openBlock("export class $L implements $T {", "}",
57+
typeName,
58+
symbol,
59+
() -> {
60+
61+
writer.write("readonly name = $S;", errorShape.getId().getName());
62+
writer.write("readonly $$fault = $S;", errorShape.expectTrait(ErrorTrait.class).getValue());
63+
//TODO: remove me when we fix where $metadata goes
64+
writer.write("readonly $$metadata = {};");
65+
if (!errorShape.members().isEmpty()) {
66+
writer.write("");
67+
StructuredMemberWriter structuredMemberWriter = new StructuredMemberWriter(
68+
model, symbolProvider, errorShape.getAllMembers().values());
69+
structuredMemberWriter.writeMembers(writer, errorShape);
70+
writer.write("");
71+
structuredMemberWriter.writeConstructor(writer, errorShape);
72+
}
73+
});
74+
}
75+
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@ void writeMemberFilterSensitiveLog(TypeScriptWriter writer, MemberShape member,
107107
}
108108
}
109109

110+
/**
111+
* Writes a constructor function that takes in an object allowing modeled fields to be initialized.
112+
*/
113+
void writeConstructor(TypeScriptWriter writer, Shape shape) {
114+
writer.openBlock("constructor(opts: {", "}) {", () -> {
115+
writeMembers(writer, shape);
116+
});
117+
writer.indent();
118+
119+
for (MemberShape member : members) {
120+
if (skipMembers.contains(member.getMemberName())) {
121+
continue;
122+
}
123+
124+
writer.write("this.${1L} = opts.${1L};", getSanitizedMemberName(member));
125+
}
126+
127+
writer.closeBlock("}");
128+
}
129+
110130
/**
111131
* Writes SENSITIVE_STRING to hide the value of sensitive members.
112132
*/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public void generateFrameworkErrorSerializer(GenerationContext inputContext) {
237237
writeEmptyEndpoint(context);
238238

239239
writer.openBlock("switch (input.name) {", "}", () -> {
240-
for (final Shape shape : context.getModel().getShapesWithTrait(HttpErrorTrait.class)) {
240+
for (final Shape shape : new TreeSet<>(context.getModel().getShapesWithTrait(HttpErrorTrait.class))) {
241241
StructureShape errorShape = shape.asStructureShape().orElseThrow(IllegalArgumentException::new);
242242
writer.openBlock("case $S: {", "}", errorShape.getId().getName(), () -> {
243243
generateErrorSerializationImplementation(context, errorShape, responseType, bindingIndex);

0 commit comments

Comments
 (0)