Skip to content

Commit a9fd0ef

Browse files
Merge pull request #315 from JordonPhillips/merge-ssdk
Merge ssdk into main
2 parents 896e6d1 + 066d60f commit a9fd0ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+10988
-239
lines changed

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.List;
20+
import java.util.stream.Collectors;
21+
import software.amazon.smithy.codegen.core.Symbol;
2022
import software.amazon.smithy.model.Model;
2123
import software.amazon.smithy.model.knowledge.EventStreamIndex;
24+
import software.amazon.smithy.model.shapes.MemberShape;
2225
import software.amazon.smithy.model.shapes.OperationShape;
26+
import software.amazon.smithy.model.shapes.Shape;
27+
import software.amazon.smithy.model.shapes.StructureShape;
28+
import software.amazon.smithy.model.traits.StreamingTrait;
2329

2430
/**
2531
* Utility methods needed across Java packages.
@@ -91,4 +97,39 @@ private static List<String> getDefaultOperationSerdeContextTypes(TypeScriptWrite
9197
contextInterfaceList.add("__SerdeContext");
9298
return contextInterfaceList;
9399
}
100+
101+
static List<MemberShape> getBlobStreamingMembers(Model model, StructureShape shape) {
102+
return shape.getAllMembers().values().stream()
103+
.filter(memberShape -> {
104+
// Streaming blobs need to have their types modified
105+
// See `writeStreamingMemberType`
106+
Shape target = model.expectShape(memberShape.getTarget());
107+
return target.isBlobShape() && target.hasTrait(StreamingTrait.class);
108+
})
109+
.collect(Collectors.toList());
110+
}
111+
112+
/**
113+
* Ease the input streaming member restriction so that users don't need to construct a stream every time.
114+
* This type decoration is allowed in Smithy because it makes input type more permissive than output type
115+
* for the same member.
116+
* Refer here for more rationales: https://github.com/aws/aws-sdk-js-v3/issues/843
117+
*/
118+
static void writeStreamingMemberType(
119+
TypeScriptWriter writer,
120+
Symbol containerSymbol,
121+
String typeName,
122+
MemberShape streamingMember
123+
) {
124+
String memberName = streamingMember.getMemberName();
125+
String optionalSuffix = streamingMember.isRequired() ? "" : "?";
126+
writer.openBlock("type $LType = Omit<$T, $S> & {", "};", typeName, containerSymbol, memberName, () -> {
127+
writer.writeDocs(String.format("For *`%1$s[\"%2$s\"]`*, see {@link %1$s.%2$s}.",
128+
containerSymbol.getName(), memberName));
129+
writer.write("$1L$2L: $3T[$1S]|string|Uint8Array|Buffer;", memberName, optionalSuffix, containerSymbol);
130+
});
131+
writer.writeDocs(String.format("This interface extends from `%1$s` interface. There are more parameters than"
132+
+ " `%2$s` defined in {@link %1$s}", containerSymbol.getName(), memberName));
133+
writer.write("export interface $1L extends $1LType {}", typeName);
134+
}
94135
}

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

Lines changed: 127 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class CodegenVisitor extends ShapeVisitor.Default<Void> {
7070
private final ServiceShape service;
7171
private final FileManifest fileManifest;
7272
private final SymbolProvider symbolProvider;
73+
private final SymbolProvider serverSymbolProvider;
7374
private final Model nonTraits;
7475
private final TypeScriptDelegator writers;
7576
private final List<TypeScriptIntegration> integrations = new ArrayList<>();
@@ -107,14 +108,20 @@ class CodegenVisitor extends ShapeVisitor.Default<Void> {
107108
nonTraits = context.getModelWithoutTraitShapes();
108109
service = settings.getService(model);
109110
fileManifest = context.getFileManifest();
110-
LOGGER.info(() -> "Generating TypeScript client for service " + service.getId());
111+
LOGGER.info(() -> String.format("Generating TypeScript %s for service %s",
112+
settings.generateClient() ? "client" : "server", service.getId()));
111113

112114
// Decorate the symbol provider using integrations.
113115
SymbolProvider resolvedProvider = TypeScriptCodegenPlugin.createSymbolProvider(model, settings);
114116
for (TypeScriptIntegration integration : integrations) {
115117
resolvedProvider = integration.decorateSymbolProvider(settings, model, resolvedProvider);
116118
}
117119
symbolProvider = SymbolProvider.cache(resolvedProvider);
120+
if (settings.generateServerSdk()) {
121+
serverSymbolProvider = SymbolProvider.cache(new ServerSymbolVisitor(model, symbolProvider));
122+
} else {
123+
serverSymbolProvider = symbolProvider;
124+
}
118125

119126
// Resolve the nullable protocol generator and application protocol.
120127
protocolGenerator = resolveProtocolGenerator(integrations, service, settings);
@@ -175,11 +182,15 @@ void execute() {
175182
// Generate the client Node and Browser configuration files. These
176183
// files are switched between in package.json based on the targeted
177184
// environment.
178-
RuntimeConfigGenerator configGenerator = new RuntimeConfigGenerator(
179-
settings, model, symbolProvider, writers, integrations);
180-
for (LanguageTarget target : LanguageTarget.values()) {
181-
LOGGER.fine("Generating " + target + " runtime configuration");
182-
configGenerator.generate(target);
185+
if (settings.generateClient()) {
186+
// For now these are only generated for clients.
187+
// TODO: generate ssdk config
188+
RuntimeConfigGenerator configGenerator = new RuntimeConfigGenerator(
189+
settings, model, symbolProvider, writers, integrations);
190+
for (LanguageTarget target : LanguageTarget.values()) {
191+
LOGGER.fine("Generating " + target + " runtime configuration");
192+
configGenerator.generate(target);
193+
}
183194
}
184195

185196
// Write each custom file.
@@ -189,12 +200,18 @@ void execute() {
189200
}
190201

191202
// Generate index for client.
192-
IndexGenerator.writeIndex(settings, model, symbolProvider, fileManifest, integrations);
203+
IndexGenerator.writeIndex(settings, model, symbolProvider, fileManifest, integrations, protocolGenerator);
204+
205+
if (settings.generateServerSdk()) {
206+
// Generate index for server
207+
IndexGenerator.writeServerIndex(settings, model, serverSymbolProvider, fileManifest);
208+
}
193209

194210
// Generate protocol tests IFF found in the model.
195211
if (protocolGenerator != null) {
196212
ShapeId protocol = protocolGenerator.getProtocol();
197-
new HttpProtocolTestGenerator(settings, model, protocol, symbolProvider, writers).run();
213+
new HttpProtocolTestGenerator(
214+
settings, model, protocol, symbolProvider, serverSymbolProvider, writers, protocolGenerator).run();
198215
}
199216

200217
// Write each pending writer.
@@ -270,13 +287,74 @@ public Void stringShape(StringShape shape) {
270287
return null;
271288
}
272289

290+
@Override
291+
public Void operationShape(OperationShape operation) {
292+
if (settings.generateServerSdk()) {
293+
writers.useShapeWriter(operation, serverSymbolProvider, w -> {
294+
ServerGenerator.generateOperationHandler(serverSymbolProvider, service, operation, w);
295+
});
296+
}
297+
return null;
298+
}
299+
273300
@Override
274301
public Void serviceShape(ServiceShape shape) {
275302
if (!Objects.equals(service, shape)) {
276303
LOGGER.fine(() -> "Skipping `" + shape.getId() + "` because it is not `" + service.getId() + "`");
277304
return null;
278305
}
279306

307+
if (settings.generateClient()) {
308+
generateClient(shape);
309+
}
310+
if (settings.generateClient() || settings.generateServerSdk()) {
311+
generateCommands(shape);
312+
}
313+
314+
if (settings.generateServerSdk()) {
315+
generateServiceInterface(shape);
316+
generateServerErrors(shape);
317+
}
318+
319+
if (protocolGenerator != null) {
320+
LOGGER.info("Generating serde for protocol " + protocolGenerator.getName() + " on " + shape.getId());
321+
String fileName = "protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()) + ".ts";
322+
writers.useFileWriter(fileName, writer -> {
323+
ProtocolGenerator.GenerationContext context = new ProtocolGenerator.GenerationContext();
324+
context.setProtocolName(protocolGenerator.getName());
325+
context.setIntegrations(integrations);
326+
context.setModel(model);
327+
context.setService(shape);
328+
context.setSettings(settings);
329+
context.setSymbolProvider(symbolProvider);
330+
context.setWriter(writer);
331+
if (context.getSettings().generateClient()) {
332+
protocolGenerator.generateRequestSerializers(context);
333+
protocolGenerator.generateResponseDeserializers(context);
334+
}
335+
if (context.getSettings().generateServerSdk()) {
336+
ProtocolGenerator.GenerationContext serverContext =
337+
context.withSymbolProvider(serverSymbolProvider);
338+
protocolGenerator.generateRequestDeserializers(serverContext);
339+
protocolGenerator.generateResponseSerializers(serverContext);
340+
protocolGenerator.generateFrameworkErrorSerializer(serverContext);
341+
writers.useShapeWriter(shape, serverSymbolProvider, w -> {
342+
protocolGenerator.generateServiceHandlerFactory(serverContext.withWriter(w));
343+
});
344+
for (OperationShape operation: TopDownIndex.of(model).getContainedOperations(service)) {
345+
writers.useShapeWriter(operation, serverSymbolProvider, w -> {
346+
protocolGenerator.generateOperationHandlerFactory(serverContext.withWriter(w), operation);
347+
});
348+
}
349+
}
350+
protocolGenerator.generateSharedComponents(context);
351+
});
352+
}
353+
354+
return null;
355+
}
356+
357+
private void generateClient(ServiceShape shape) {
280358
// Generate the modular service client.
281359
writers.useShapeWriter(shape, writer -> new ServiceGenerator(
282360
settings, model, symbolProvider, writer, integrations, runtimePlugins, applicationProtocol).run());
@@ -294,9 +372,6 @@ public Void serviceShape(ServiceShape shape) {
294372
boolean hasPaginatedOperation = false;
295373

296374
for (OperationShape operation : containedOperations) {
297-
writers.useShapeWriter(operation, commandWriter -> new CommandGenerator(
298-
settings, model, operation, symbolProvider, commandWriter,
299-
runtimePlugins, protocolGenerator, applicationProtocol).run());
300375
if (operation.hasTrait(PaginatedTrait.ID)) {
301376
hasPaginatedOperation = true;
302377
String outputFilename = PaginationGenerator.getOutputFilelocation(operation);
@@ -323,25 +398,48 @@ public Void serviceShape(ServiceShape shape) {
323398
serviceSymbol,
324399
paginationWriter));
325400
}
401+
}
326402

327-
if (protocolGenerator != null) {
328-
LOGGER.info("Generating serde for protocol " + protocolGenerator.getName() + " on " + shape.getId());
329-
String fileName = "protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()) + ".ts";
330-
writers.useFileWriter(fileName, writer -> {
331-
ProtocolGenerator.GenerationContext context = new ProtocolGenerator.GenerationContext();
332-
context.setProtocolName(protocolGenerator.getName());
333-
context.setIntegrations(integrations);
334-
context.setModel(model);
335-
context.setService(shape);
336-
context.setSettings(settings);
337-
context.setSymbolProvider(symbolProvider);
338-
context.setWriter(writer);
339-
protocolGenerator.generateRequestSerializers(context);
340-
protocolGenerator.generateResponseDeserializers(context);
341-
protocolGenerator.generateSharedComponents(context);
342-
});
343-
}
403+
private void generateServiceInterface(ServiceShape shape) {
404+
TopDownIndex topDownIndex = TopDownIndex.of(model);
405+
Set<OperationShape> operations = new TreeSet<>(topDownIndex.getContainedOperations(shape));
406+
writers.useShapeWriter(shape, serverSymbolProvider, writer -> {
407+
ServerGenerator.generateOperationsType(serverSymbolProvider, shape, operations, writer);
408+
ServerGenerator.generateServerInterfaces(serverSymbolProvider, shape, operations, writer);
409+
ServerGenerator.generateServiceHandler(serverSymbolProvider, shape, operations, writer);
410+
});
411+
}
344412

345-
return null;
413+
private void generateServerErrors(ServiceShape service) {
414+
TopDownIndex.of(model)
415+
.getContainedOperations(service)
416+
.stream()
417+
.flatMap(o -> o.getErrors().stream())
418+
.distinct()
419+
.map(id -> model.expectShape(id).asStructureShape().orElseThrow(IllegalArgumentException::new))
420+
.sorted()
421+
.forEachOrdered(error -> writers.useShapeWriter(service, serverSymbolProvider, writer -> {
422+
new ServerErrorGenerator(settings, model, error, serverSymbolProvider, writer).run();
423+
}));
424+
}
425+
426+
private void generateCommands(ServiceShape shape) {
427+
// Generate each operation for the service.
428+
TopDownIndex topDownIndex = TopDownIndex.of(model);
429+
Set<OperationShape> containedOperations = new TreeSet<>(topDownIndex.getContainedOperations(shape));
430+
for (OperationShape operation : containedOperations) {
431+
// Right now this only generates stubs
432+
if (settings.generateClient()) {
433+
writers.useShapeWriter(operation, commandWriter -> new CommandGenerator(
434+
settings, model, operation, symbolProvider, commandWriter,
435+
runtimePlugins, protocolGenerator, applicationProtocol).run());
436+
}
437+
438+
if (settings.generateServerSdk()) {
439+
writers.useShapeWriter(operation, serverSymbolProvider, commandWriter -> new ServerCommandGenerator(
440+
settings, model, operation, serverSymbolProvider, commandWriter,
441+
protocolGenerator, applicationProtocol).run());
442+
}
443+
}
346444
}
347445
}

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

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
package software.amazon.smithy.typescript.codegen;
1717

18+
import static software.amazon.smithy.typescript.codegen.CodegenUtils.getBlobStreamingMembers;
19+
import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeStreamingMemberType;
20+
1821
import java.util.List;
1922
import java.util.Optional;
2023
import java.util.stream.Collectors;
@@ -25,9 +28,7 @@
2528
import software.amazon.smithy.model.shapes.MemberShape;
2629
import software.amazon.smithy.model.shapes.OperationShape;
2730
import software.amazon.smithy.model.shapes.ServiceShape;
28-
import software.amazon.smithy.model.shapes.Shape;
2931
import software.amazon.smithy.model.shapes.StructureShape;
30-
import software.amazon.smithy.model.traits.StreamingTrait;
3132
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
3233
import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin;
3334
import software.amazon.smithy.utils.OptionalUtils;
@@ -85,6 +86,11 @@ final class CommandGenerator implements Runnable {
8586

8687
@Override
8788
public void run() {
89+
addInputAndOutputTypes();
90+
generateClientCommand();
91+
}
92+
93+
private void generateClientCommand() {
8894
Symbol serviceSymbol = symbolProvider.toSymbol(service);
8995
String configType = ServiceGenerator.getResolvedConfigTypeName(serviceSymbol);
9096

@@ -98,8 +104,6 @@ public void run() {
98104
writer.addImport("HandlerExecutionContext", "HandlerExecutionContext", "@aws-sdk/types");
99105
writer.addImport("MiddlewareStack", "MiddlewareStack", "@aws-sdk/types");
100106

101-
addInputAndOutputTypes();
102-
103107
String name = symbol.getName();
104108
writer.writeShapeDocs(operation);
105109
writer.openBlock("export class $L extends $$Command<$T, $T, $L> {", "}", name, inputType, outputType,
@@ -190,11 +194,11 @@ private void addInputAndOutputTypes() {
190194
private void writeInputType(String typeName, Optional<StructureShape> inputShape) {
191195
if (inputShape.isPresent()) {
192196
StructureShape input = inputShape.get();
193-
List<MemberShape> blobStreamingMembers = getBlobStreamingMembers(input);
197+
List<MemberShape> blobStreamingMembers = getBlobStreamingMembers(model, input);
194198
if (blobStreamingMembers.isEmpty()) {
195199
writer.write("export interface $L extends $T {}", typeName, symbolProvider.toSymbol(input));
196200
} else {
197-
writeStreamingInputType(typeName, input, blobStreamingMembers.get(0));
201+
writeStreamingMemberType(writer, symbolProvider.toSymbol(input), typeName, blobStreamingMembers.get(0));
198202
}
199203
} else {
200204
// If the input is non-existent, then use an empty object.
@@ -214,37 +218,6 @@ private void writeOutputType(String typeName, Optional<StructureShape> outputSha
214218
}
215219
}
216220

217-
private List<MemberShape> getBlobStreamingMembers(StructureShape shape) {
218-
return shape.getAllMembers().values().stream()
219-
.filter(memberShape -> {
220-
// Streaming blobs need to have their types modified
221-
// See `writeStreamingInputType`
222-
Shape target = model.expectShape(memberShape.getTarget());
223-
return target.isBlobShape() && target.hasTrait(StreamingTrait.class);
224-
})
225-
.collect(Collectors.toList());
226-
}
227-
228-
/**
229-
* Ease the input streaming member restriction so that users don't need to construct a stream every time.
230-
* This type decoration is allowed in Smithy because it makes input type more permissive than output type
231-
* for the same member.
232-
* Refer here for more rationales: https://github.com/aws/aws-sdk-js-v3/issues/843
233-
*/
234-
private void writeStreamingInputType(String typeName, StructureShape inputShape, MemberShape streamingMember) {
235-
Symbol inputSymbol = symbolProvider.toSymbol(inputShape);
236-
String memberName = streamingMember.getMemberName();
237-
String optionalSuffix = streamingMember.isRequired() ? "" : "?";
238-
writer.openBlock("type $LType = Omit<$T, $S> & {", "};", typeName, inputSymbol, memberName, () -> {
239-
writer.writeDocs(String.format("For *`%1$s[\"%2$s\"]`*, see {@link %1$s.%2$s}.",
240-
inputSymbol.getName(), memberName));
241-
writer.write("$1L$2L: $3T[$1S]|string|Uint8Array|Buffer;", memberName, optionalSuffix, inputSymbol);
242-
});
243-
writer.writeDocs(String.format("This interface extends from `%1$s` interface. There are more parameters than"
244-
+ " `%2$s` defined in {@link %1$s}", inputSymbol.getName(), memberName));
245-
writer.write("export interface $1L extends $1LType {}", typeName);
246-
}
247-
248221
private void addCommandSpecificPlugins() {
249222
// Some plugins might only apply to specific commands. They are added to the
250223
// command's middleware stack here. Plugins that apply to all commands are

0 commit comments

Comments
 (0)