Skip to content

Commit

Permalink
generate server-side exceptions as classes (smithy-lang#502)
Browse files Browse the repository at this point in the history
This change generates server-side exceptions as individual classes. So customers can handle exceptions with ease.
  • Loading branch information
AllanZhengYP authored and srchase committed Mar 17, 2023
1 parent 14ad7c2 commit 2191967
Show file tree
Hide file tree
Showing 22 changed files with 466 additions and 270 deletions.
1 change: 0 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ smithy-typescript-integ-tests/* @adamthom-amzn @gosar @JordonPhillips

# These are all specific to SSDK functionality.
smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServerCommandGenerator.java @adamthom-amzn @gosar @JordonPhillips
smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServerErrorGenerator.java @adamthom-amzn @gosar @JordonPhillips
smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServerGenerator.java @adamthom-amzn @gosar @JordonPhillips
smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServerSymbolVisitor.java @adamthom-amzn @gosar @JordonPhillips
2 changes: 1 addition & 1 deletion config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<!-- Files must contain a copyright header. -->
<module name="RegexpHeader">
<property name="header"
value="/\*\n \* Copyright 20(19|20|21) Amazon\.com, Inc\. or its affiliates\. All Rights Reserved\.\n"/>
value="/\*\n \* Copyright 20(19|20|21|22) Amazon\.com, Inc\. or its affiliates\. All Rights Reserved\.\n"/>
<property name="fileExtensions" value="java"/>
</module>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,6 @@ public Void serviceShape(ServiceShape shape) {

if (settings.generateServerSdk()) {
generateServiceInterface(shape);
generateServerErrors(shape);
}

if (protocolGenerator != null) {
Expand Down Expand Up @@ -470,20 +469,6 @@ private void generateServiceInterface(ServiceShape shape) {
});
}

private void generateServerErrors(ServiceShape service) {
final OperationIndex operationIndex = OperationIndex.of(model);

TopDownIndex.of(model)
.getContainedOperations(service)
.stream()
.flatMap(o -> operationIndex.getErrors(o, service).stream())
.distinct()
.sorted()
.forEachOrdered(error -> writers.useShapeWriter(service, symbolProvider, writer -> {
new ServerErrorGenerator(settings, model, error, symbolProvider, writer).run();
}));
}

private void generateCommands(ServiceShape shape) {
// Generate each operation for the service.
TopDownIndex topDownIndex = TopDownIndex.of(model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -741,9 +741,9 @@ private void writeServerResponseTest(OperationShape operation, HttpResponseTestC
writer.write("const request = new HttpRequest({method: 'POST', hostname: 'example.com'});");

// Create a new serializer factory that always returns our test serializer.
writer.addImport("SmithyException", "__SmithyException", "@aws-sdk/types");
writer.addImport("ServiceException", "__ServiceException", "@aws-smithy/server-common");
writer.addImport("OperationSerializer", "__OperationSerializer", "@aws-smithy/server-common");
writer.openBlock("const serFn: (op: $1T) => __OperationSerializer<$2T<{}>, $1T, __SmithyException> = (op) =>"
writer.openBlock("const serFn: (op: $1T) => __OperationSerializer<$2T<{}>, $1T, __ServiceException> = (op) =>"
+ " { return new TestSerializer(); };", serviceOperationsSymbol, serviceSymbol);

writer.addImport("serializeFrameworkException", null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ static void writeIndex(

// write export statement for models
writer.write("export * from \"./models\";");

// Write each custom export.
for (TypeScriptIntegration integration : integrations) {
integration.writeAdditionalExports(settings, model, symbolProvider, writer);
}

fileManifest.writeFile(Paths.get(CodegenUtils.SOURCE_FOLDER, "index.ts").toString(), writer.toString());
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ static void generateServiceHandler(SymbolProvider symbolProvider,
writer.write("private readonly service: $T<Context>;", serviceSymbol);
writer.write("private readonly mux: __Mux<$S, $T>;", serviceShape.getId().getName(), operationsType);
writer.write("private readonly serializerFactory: <T extends $T>(operation: T) => "
+ "__OperationSerializer<$T<Context>, T, __SmithyException>;",
+ "__OperationSerializer<$T<Context>, T, __ServiceException>;",
operationsType, serviceSymbol);
writer.write("private readonly serializeFrameworkException: (e: __SmithyFrameworkException, "
+ "ctx: __ServerSerdeContext) => Promise<__HttpResponse>;");
Expand All @@ -87,7 +87,7 @@ static void generateServiceHandler(SymbolProvider symbolProvider,
writer.write("service: $T<Context>,", serviceSymbol);
writer.write("mux: __Mux<$S, $T>,", serviceShape.getId().getName(), operationsType);
writer.write("serializerFactory:<T extends $T>(op: T) => "
+ "__OperationSerializer<$T<Context>, T, __SmithyException>,",
+ "__OperationSerializer<$T<Context>, T, __ServiceException>,",
operationsType, serviceSymbol);
writer.write("serializeFrameworkException: (e: __SmithyFrameworkException, ctx: __ServerSerdeContext) "
+ "=> Promise<__HttpResponse>,");
Expand Down Expand Up @@ -208,7 +208,7 @@ private static void addCommonHandlerImports(TypeScriptWriter writer) {
writer.addImport("SmithyFrameworkException", "__SmithyFrameworkException", "@aws-smithy/server-common");
writer.addImport("HttpRequest", "__HttpRequest", "@aws-sdk/protocol-http");
writer.addImport("HttpResponse", "__HttpResponse", "@aws-sdk/protocol-http");
writer.addImport("SmithyException", "__SmithyException", "@aws-sdk/types");
writer.addImport("ServiceException", "__ServiceException", "@aws-smithy/server-common");
writer.addImport("ValidationCustomizer", "__ValidationCustomizer", "@aws-smithy/server-common");
}

Expand All @@ -226,7 +226,7 @@ private static void writeHandleFunction(TypeScriptWriter writer) {
writer.write("request: __HttpRequest,");
writer.write("context: Context,");
writer.write("operationName: O,");
writer.write("serializer: __OperationSerializer<S, O, __SmithyException>,");
writer.write("serializer: __OperationSerializer<S, O, __ServiceException>,");
writer.write("operation: __Operation<__OperationInput<S[O]>, __OperationOutput<S[O]>, Context>,");
writer.write("serializeFrameworkException: (e: __SmithyFrameworkException, "
+ "ctx: __ServerSerdeContext) => Promise<__HttpResponse>,");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.codegen.core.SymbolReference;
Expand Down Expand Up @@ -140,61 +139,6 @@ private void renderNonErrorStructure() {
renderStructureNamespace(config, includeValidation);
}

/**
* Error structures generate interfaces that extend from SmithyException
* and add the appropriate fault property.
*
* <p>Given the following Smithy structure:
*
* <pre>{@code
* namespace smithy.example
*
* @error("client")
* structure NoSuchResource {
* @required
* resourceType: String
* }
* }</pre>
*
* <p>The following TypeScript is generated:
*
* <pre>{@code
* import {
* SmithyException as __SmithyException
* } from "@aws-sdk/types";
*
* export interface NoSuchResource extends __SmithyException, $MetadataBearer {
* name: "NoSuchResource";
* $fault: "client";
* resourceType: string | undefined;
* }
* }</pre>
*/
private void renderErrorStructure() {
ErrorTrait errorTrait = shape.getTrait(ErrorTrait.class).orElseThrow(IllegalStateException::new);
Symbol symbol = symbolProvider.toSymbol(shape);
writer.writeShapeDocs(shape);

// Find symbol references with the "extends" property, and add SmithyException.
writer.addImport("SmithyException", "__SmithyException", "@aws-sdk/types");
String extendsFrom = Stream.concat(
Stream.of("__SmithyException"),
symbol.getReferences().stream()
.filter(ref -> ref.getProperty(SymbolVisitor.IMPLEMENTS_INTERFACE_PROPERTY).isPresent())
.map(SymbolReference::getAlias)
).collect(Collectors.joining(", "));

writer.openBlock("export interface $L extends $L {", symbol.getName(), extendsFrom);
writer.write("name: $S;", shape.getId().getName());
writer.write("$$fault: $S;", errorTrait.getValue());
HttpProtocolGeneratorUtils.writeRetryableTrait(writer, shape, ";");
StructuredMemberWriter structuredMemberWriter = new StructuredMemberWriter(
model, symbolProvider, shape.getAllMembers().values());
structuredMemberWriter.writeMembers(writer, shape);
writer.closeBlock("}"); // interface
writer.write("");
}

private void renderStructureNamespace(StructuredMemberWriter structuredMemberWriter, boolean includeValidation) {
Symbol symbol = symbolProvider.toSymbol(shape);
writer.openBlock("export namespace $L {", "}", symbol.getName(), () -> {
Expand Down Expand Up @@ -228,4 +172,68 @@ private void renderStructureNamespace(StructuredMemberWriter structuredMemberWri
});
});
}

/**
* Error structures generate classes that extend from service base exception
* (ServiceException in case of server SDK), and add the appropriate fault
* property.
*
* <p>Given the following Smithy structure:
*
* <pre>{@code
* namespace smithy.example
*
* @error("client")
* structure NoSuchResource {
* @required
* resourceType: String
* }
* }</pre>
*
* <p>The following TypeScript is generated:
*
* <pre>{@code
* import { ExceptionOptionType as __ExceptionOptionType } from "@aws-sdk/smithy-client";
* import { FooServiceException as __BaseException } from "./FooServiceException";
* // In server SDK:
* // import { ServiceException as __BaseException } from "@aws-smithy/server-common";
*
* export class NoSuchResource extends __BaseException {
* name: "NoSuchResource";
* $fault: "client";
* resourceType: string | undefined;
* // @internal
* constructor(opts: __ExceptionOptionType<NoSuchResource, __BaseException>) {
* super({
* name: "NoSuchResource",
* $fault: "client",
* ...opts
* });
* Object.setPrototypeOf(this, NoSuchResource.prototype);
* this.resourceType = opts.resourceType;
* }
* }
* }</pre>
*/
private void renderErrorStructure() {
ErrorTrait errorTrait = shape.getTrait(ErrorTrait.class).orElseThrow(IllegalStateException::new);
Symbol symbol = symbolProvider.toSymbol(shape);
writer.writeShapeDocs(shape);
boolean isServerSdk = this.includeValidation;
writer.openBlock("export class $T extends $L {", symbol, "__BaseException");
writer.write("readonly name: $1S = $1S;", shape.getId().getName());
writer.write("readonly $$fault: $1S = $1S;", errorTrait.getValue());
if (!isServerSdk) {
HttpProtocolGeneratorUtils.writeRetryableTrait(writer, shape, ";");
}
StructuredMemberWriter structuredMemberWriter = new StructuredMemberWriter(model, symbolProvider,
shape.getAllMembers().values());
// since any error interface must extend from JavaScript Error interface, message member is already
// required in the JavaScript Error interface
structuredMemberWriter.skipMembers.add("message");
structuredMemberWriter.writeMembers(writer, shape);
structuredMemberWriter.writeErrorConstructor(writer, shape, isServerSdk);
writer.closeBlock("}");
writer.write("");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,29 @@ void writeMemberFilterSensitiveLog(TypeScriptWriter writer, MemberShape member,
}

/**
* Writes a constructor function that takes in an object allowing modeled fields to be initialized.
* Writes constructor of SDK exception classes.
*/
void writeConstructor(TypeScriptWriter writer, Shape shape) {
writer.openBlock("constructor(opts: {", "}) {", () -> {
writeMembers(writer, shape);
void writeErrorConstructor(TypeScriptWriter writer, Shape shape, boolean isServerSdk) {
ErrorTrait errorTrait = shape.getTrait(ErrorTrait.class).orElseThrow(IllegalStateException::new);
Symbol symbol = symbolProvider.toSymbol(shape);
if (!isServerSdk) {
writer.writeDocs("@internal");
}
writer.addImport("ExceptionOptionType", "__ExceptionOptionType",
TypeScriptDependency.AWS_SMITHY_CLIENT.packageName);
writer.openBlock("constructor(opts: __ExceptionOptionType<$L, __BaseException>) {", symbol.getName());
writer.openBlock("super({", "});", () -> {
writer.write("name: $S,", shape.getId().getName());
writer.write("$$fault: $S,", errorTrait.getValue());
writer.write("...opts");
});
writer.indent();

writer.write("Object.setPrototypeOf(this, $L.prototype);", symbol.getName());
for (MemberShape member : members) {
if (skipMembers.contains(member.getMemberName())) {
continue;
}

writer.write("this.${1L} = opts.${1L};", getSanitizedMemberName(member));
}

writer.closeBlock("}");
}

Expand Down
Loading

0 comments on commit 2191967

Please sign in to comment.