Skip to content

Generate per-operation ServiceHandlers #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ public Void stringShape(StringShape shape) {
return null;
}

@Override
public Void operationShape(OperationShape operation) {
if (settings.generateServerSdk()) {
writers.useShapeWriter(operation, serverSymbolProvider, w -> {
ServerGenerator.generateOperationHandler(serverSymbolProvider, service, operation, w);
});
}
return null;
}

@Override
public Void serviceShape(ServiceShape shape) {
if (!Objects.equals(service, shape)) {
Expand Down Expand Up @@ -328,8 +338,13 @@ public Void serviceShape(ServiceShape shape) {
protocolGenerator.generateResponseSerializers(serverContext);
protocolGenerator.generateFrameworkErrorSerializer(serverContext);
writers.useShapeWriter(shape, serverSymbolProvider, w -> {
protocolGenerator.generateHandlerFactory(serverContext.withWriter(w));
protocolGenerator.generateServiceHandlerFactory(serverContext.withWriter(w));
});
for (OperationShape operation: TopDownIndex.of(model).getContainedOperations(service)) {
writers.useShapeWriter(operation, serverSymbolProvider, w -> {
protocolGenerator.generateOperationHandlerFactory(serverContext.withWriter(w), operation);
});
}
}
protocolGenerator.generateSharedComponents(context);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ private void writeOperationSerializer() {
String serializerName = operationSymbol.expectProperty("serializerType", Symbol.class).getName();
Symbol serverSymbol = symbolProvider.toSymbol(model.expectShape(settings.getService()));

writer.addImport("OperationSerializer", null, "@aws-smithy/server-common");
writer.openBlock("export class $L implements OperationSerializer<$T, $S, $T> {", "}",
writer.addImport("OperationSerializer", "__OperationSerializer", "@aws-smithy/server-common");
writer.openBlock("export class $L implements __OperationSerializer<$T, $S, $T> {", "}",
serializerName, serverSymbol, operation.getId().getName(), errorsType, () -> {
String serializerFunction = ProtocolGenerator.getGenericSerFunctionName(operationSymbol) + "Response";
String deserializerFunction = ProtocolGenerator.getGenericDeserFunctionName(operationSymbol) + "Request";
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public Symbol operationShape(OperationShape shape) {
builder.putProperty("outputType", intermediate.toBuilder().name(shapeName + "ServerOutput").build());
builder.putProperty("errorsType", intermediate.toBuilder().name(shapeName + "Errors").build());
builder.putProperty("serializerType", intermediate.toBuilder().name(shapeName + "Serializer").build());
builder.putProperty("handler",
intermediate.toBuilder().name(shapeName + "Handler").build());
return builder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ public void generateFrameworkErrorSerializer(GenerationContext inputContext) {
writer.write("");
}

private void generateMux(GenerationContext context) {
private void generateServiceMux(GenerationContext context) {
TopDownIndex topDownIndex = TopDownIndex.of(context.getModel());
TypeScriptWriter writer = context.getWriter();

Expand All @@ -273,6 +273,21 @@ private void generateMux(GenerationContext context) {
);
}

private void generateOperationMux(GenerationContext context, OperationShape operation) {
TypeScriptWriter writer = context.getWriter();

writer.addImport("httpbinding", null, "@aws-smithy/server-common");

writer.openBlock("const mux = new httpbinding.HttpBindingMux<$S, $S>([", "]);",
context.getService().getId().getName(),
operation.getId().getName(),
() -> {
HttpTrait httpTrait = operation.expectTrait(HttpTrait.class);
generateUriSpec(context, operation, httpTrait);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this is overkill, but could this be shared so it's only defined once?

Copy link
Contributor Author

@adamthom-amzn adamthom-amzn Apr 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the UriSpec is only defined in TS once? These end up in different files, so I'd have to have a uri specs file, and generate and export a unique const per spec. Or generate a UriSpec factory that can take in a service and operation and export that. Does not seem worth the upside to me at the moment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I meant define it once in the operation's file and then import it in the service. Doesn't really matter since it's pointless outside of the context of the mux anyway

}
);
}

private void generateUriSpec(GenerationContext context,
OperationShape operation,
HttpTrait httpTrait) {
Expand Down Expand Up @@ -321,7 +336,7 @@ private void generateUriSpec(GenerationContext context,
}

@Override
public void generateHandlerFactory(GenerationContext context) {
public void generateServiceHandlerFactory(GenerationContext context) {
TypeScriptWriter writer = context.getWriter();
TopDownIndex index = TopDownIndex.of(context.getModel());
Set<OperationShape> operations = index.getContainedOperations(context.getService());
Expand All @@ -334,11 +349,11 @@ public void generateHandlerFactory(GenerationContext context) {
Symbol handlerSymbol = serviceSymbol.expectProperty("handler", Symbol.class);
Symbol operationsSymbol = serviceSymbol.expectProperty("operations", Symbol.class);

writer.openBlock("export const get$L = (service: $T): ServiceHandler => {", "}",
writer.openBlock("export const get$L = (service: $T): __ServiceHandler => {", "}",
handlerSymbol.getName(), serviceSymbol, () -> {
generateMux(context);
generateServiceMux(context);
writer.addImport("SmithyException", "__SmithyException", "@aws-sdk/smithy-client");
writer.openBlock("const serFn: (op: $1T) => OperationSerializer<$2T, $1T, __SmithyException> = "
writer.openBlock("const serFn: (op: $1T) => __OperationSerializer<$2T, $1T, __SmithyException> = "
+ "(op) => {", "};", operationsSymbol, serviceSymbol, () -> {
writer.openBlock("switch (op) {", "}", () -> {
operations.stream()
Expand All @@ -350,6 +365,29 @@ public void generateHandlerFactory(GenerationContext context) {
});
}

@Override
public void generateOperationHandlerFactory(GenerationContext context, OperationShape operation) {
TypeScriptWriter writer = context.getWriter();
SymbolProvider symbolProvider = context.getSymbolProvider();

writer.addImport("serializeFrameworkException", null,
"./protocols/" + ProtocolGenerator.getSanitizedName(getName()));

final Symbol operationSymbol = symbolProvider.toSymbol(operation);
final Symbol inputType = operationSymbol.expectProperty("inputType", Symbol.class);
final Symbol outputType = operationSymbol.expectProperty("outputType", Symbol.class);
final Symbol serializerType = operationSymbol.expectProperty("serializerType", Symbol.class);
final Symbol operationHandlerSymbol = operationSymbol.expectProperty("handler", Symbol.class);

writer.openBlock("export const get$L = (operation: __Operation<$T, $T>): __ServiceHandler => {", "}",
operationHandlerSymbol.getName(), inputType, outputType,
() -> {
generateOperationMux(context, operation);
writer.write("return new $T(operation, mux, new $T(), serializeFrameworkException);",
operationHandlerSymbol, serializerType);
});
}

private Consumer<OperationShape> writeOperationCase(
TypeScriptWriter writer,
SymbolProvider symbolProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,12 @@ public void generateResponseSerializers(GenerationContext context) {
}

@Override
public void generateHandlerFactory(GenerationContext context) {
public void generateServiceHandlerFactory(GenerationContext context) {
LOGGER.warning("Handler factory generation is not currently supported for RPC protocols.");
}

@Override
public void generateOperationHandlerFactory(GenerationContext context, OperationShape operation) {
LOGGER.warning("Handler factory generation is not currently supported for RPC protocols.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
Expand Down Expand Up @@ -156,12 +157,21 @@ default void generateSharedComponents(GenerationContext context) {
void generateFrameworkErrorSerializer(GenerationContext serverContext);

/**
* Generates the code used to determine the service and operation
* targeted by a given request.
* Generates a factory for the ServiceHandler implementation for this service.
*
* @param context Generation context.
*/
void generateHandlerFactory(GenerationContext context);
void generateServiceHandlerFactory(GenerationContext context);

/**
* Generates the code used to handle a request for a specific operation in the given service. This allows the
* business logic for a service to be split among multiple deployment targets, for example, one Lambda function
* per operation.
*
* @param context Generation context.
* @param operation The operation to generate a handler factory for.
*/
void generateOperationHandlerFactory(GenerationContext context, OperationShape operation);

/**
* Generates the code used to deserialize the shapes of a service
Expand Down