Skip to content

Commit cbcc084

Browse files
Merge pull request #284 from JordonPhillips/update-ssdk-request-tests
Use server symbol provider in protocol test gen
2 parents 1a6a906 + 976711c commit cbcc084

File tree

7 files changed

+161
-48
lines changed

7 files changed

+161
-48
lines changed

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

Lines changed: 36 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,34 @@ 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 `writeStreamingInputType`
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("export type $L = Omit<$T, $S> & {", "};", typeName, containerSymbol, memberName, () ->
127+
writer.write("$1L$2L: $3T[$1S]|string|Uint8Array|Buffer;",
128+
memberName, optionalSuffix, containerSymbol));
129+
}
94130
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ void execute() {
213213
// Generate protocol tests IFF found in the model.
214214
if (protocolGenerator != null) {
215215
ShapeId protocol = protocolGenerator.getProtocol();
216-
new HttpProtocolTestGenerator(settings, model, protocol, symbolProvider, writers, protocolGenerator).run();
216+
new HttpProtocolTestGenerator(
217+
settings, model, protocol, symbolProvider, serverSymbolProvider, writers, protocolGenerator).run();
217218
}
218219

219220
// Write each pending writer.
@@ -406,9 +407,8 @@ private void generateCommands(ServiceShape shape) {
406407
}
407408

408409
if (settings.generateServerSdk()) {
409-
writers.useShapeWriter(operation, serverSymbolProvider, commandWriter -> new CommandGenerator(
410-
settings, model, operation, serverSymbolProvider, commandWriter,
411-
runtimePlugins, protocolGenerator, applicationProtocol).run());
410+
writers.useShapeWriter(operation, serverSymbolProvider, commandWriter -> new ServerCommandGenerator(
411+
settings, model, operation, serverSymbolProvider, commandWriter).run());
412412
}
413413
}
414414
}

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

Lines changed: 6 additions & 32 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;
@@ -86,9 +87,7 @@ final class CommandGenerator implements Runnable {
8687
@Override
8788
public void run() {
8889
addInputAndOutputTypes();
89-
if (settings.generateClient()) {
90-
generateClientCommand();
91-
}
90+
generateClientCommand();
9291
}
9392

9493
private void generateClientCommand() {
@@ -195,11 +194,11 @@ private void addInputAndOutputTypes() {
195194
private void writeInputType(String typeName, Optional<StructureShape> inputShape) {
196195
if (inputShape.isPresent()) {
197196
StructureShape input = inputShape.get();
198-
List<MemberShape> blobStreamingMembers = getBlobStreamingMembers(input);
197+
List<MemberShape> blobStreamingMembers = getBlobStreamingMembers(model, input);
199198
if (blobStreamingMembers.isEmpty()) {
200199
writer.write("export type $L = $T;", typeName, symbolProvider.toSymbol(input));
201200
} else {
202-
writeStreamingInputType(typeName, input, blobStreamingMembers.get(0));
201+
writeStreamingMemberType(writer, symbolProvider.toSymbol(input), typeName, blobStreamingMembers.get(0));
203202
}
204203
} else {
205204
// If the input is non-existent, then use an empty object.
@@ -219,31 +218,6 @@ private void writeOutputType(String typeName, Optional<StructureShape> outputSha
219218
}
220219
}
221220

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

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
import software.amazon.smithy.utils.IoUtils;
6767
import software.amazon.smithy.utils.MapUtils;
6868
import software.amazon.smithy.utils.Pair;
69-
import software.amazon.smithy.utils.StringUtils;
7069

7170
/**
7271
* Generates HTTP protocol test cases to be run using Jest.
@@ -91,6 +90,7 @@ final class HttpProtocolTestGenerator implements Runnable {
9190
private final ServiceShape service;
9291
private final SymbolProvider symbolProvider;
9392
private final Symbol serviceSymbol;
93+
private final SymbolProvider serverSymbolProvider;
9494
private final Set<String> additionalStubs = new TreeSet<>();
9595
private final ProtocolGenerator protocolGenerator;
9696

@@ -105,6 +105,7 @@ final class HttpProtocolTestGenerator implements Runnable {
105105
Model model,
106106
ShapeId protocol,
107107
SymbolProvider symbolProvider,
108+
SymbolProvider serverSymbolProvider,
108109
TypeScriptDelegator delegator,
109110
ProtocolGenerator protocolGenerator
110111
) {
@@ -113,6 +114,7 @@ final class HttpProtocolTestGenerator implements Runnable {
113114
this.protocol = protocol;
114115
this.service = settings.getService(model);
115116
this.symbolProvider = symbolProvider;
117+
this.serverSymbolProvider = serverSymbolProvider;
116118
this.delegator = delegator;
117119
this.protocolGenerator = protocolGenerator;
118120
serviceSymbol = symbolProvider.toSymbol(service);
@@ -247,7 +249,7 @@ private void generateClientRequestTest(OperationShape operation, HttpRequestTest
247249
}
248250

249251
private void generateServerRequestTest(OperationShape operation, HttpRequestTestCase testCase) {
250-
Symbol operationSymbol = symbolProvider.toSymbol(operation);
252+
Symbol operationSymbol = serverSymbolProvider.toSymbol(operation);
251253

252254
// Lowercase all the headers we're expecting as this is what we'll get.
253255
Map<String, String> headers = testCase.getHeaders().entrySet().stream()
@@ -260,9 +262,8 @@ private void generateServerRequestTest(OperationShape operation, HttpRequestTest
260262
String testName = testCase.getId() + ":ServerRequest";
261263
testCase.getDocumentation().ifPresent(writer::writeDocs);
262264
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {
263-
// TODO: use the symbol provider when it's ready
264-
String serviceName = StringUtils.capitalize(service.getId().getName());
265-
String operationName = StringUtils.capitalize(operation.getId().getName());
265+
Symbol serviceSymbol = serverSymbolProvider.toSymbol(service);
266+
Symbol handlerSymbol = serviceSymbol.expectProperty("handler", Symbol.class);
266267
Symbol inputType = operationSymbol.expectProperty("inputType", Symbol.class);
267268
Symbol outputType = operationSymbol.expectProperty("outputType", Symbol.class);
268269

@@ -274,18 +275,18 @@ private void generateServerRequestTest(OperationShape operation, HttpRequestTest
274275
// We use a partial here so that we don't have to define the entire service, but still get the advantages
275276
// the type checker, including excess property checking. Later on we'll use `as` to cast this to the
276277
// full service so that we can actually use it.
277-
writer.addImport(serviceName + "Service", null, "./server");
278-
writer.openBlock("const testService: Partial<$LService> = {", "};", serviceName, () -> {
278+
writer.openBlock("const testService: Partial<$T> = {", "};", serviceSymbol, () -> {
279279
writer.addImport("Operation", "__Operation", "@aws-smithy/server-common");
280-
writer.write("$L: testFunction as __Operation<$T, $T>,", operationName, inputType, outputType);
280+
writer.write("$L: testFunction as __Operation<$T, $T>,",
281+
operationSymbol.getName(), inputType, outputType);
281282
});
282283

283-
String getHandlerName = String.format("get%sServiceHandler", serviceName);
284+
String getHandlerName = "get" + handlerSymbol.getName();
284285
writer.addImport(getHandlerName, getHandlerName,
285286
"./protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()));
286287

287288
// Cast the service as any so TS will ignore the fact that the type being passed in is incomplete.
288-
writer.write("const handler = $L(testService as $LService);", getHandlerName, serviceName);
289+
writer.write("const handler = $L(testService as $T);", getHandlerName, serviceSymbol);
289290

290291
// Construct a new http request according to the test case definition.
291292
writer.openBlock("const request = new HttpRequest({", "});", () -> {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ static void writeServerIndex(
7878
TopDownIndex topDownIndex = TopDownIndex.of(model);
7979
Set<OperationShape> containedOperations = new TreeSet<>(topDownIndex.getContainedOperations(service));
8080
for (OperationShape operation : containedOperations) {
81-
writer.write("export * from \"./types/$L\";", symbolProvider.toSymbol(operation).getName());
81+
writer.write("export * from \"./operations/$L\";", symbolProvider.toSymbol(operation).getName());
8282
}
8383
writer.write("export * from \"./$L\"", symbol.getName());
8484
fileManifest.writeFile("server/index.ts", writer.toString());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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 static software.amazon.smithy.typescript.codegen.CodegenUtils.getBlobStreamingMembers;
19+
import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeStreamingMemberType;
20+
21+
import java.util.List;
22+
import java.util.Optional;
23+
import software.amazon.smithy.codegen.core.Symbol;
24+
import software.amazon.smithy.codegen.core.SymbolProvider;
25+
import software.amazon.smithy.model.Model;
26+
import software.amazon.smithy.model.knowledge.OperationIndex;
27+
import software.amazon.smithy.model.shapes.MemberShape;
28+
import software.amazon.smithy.model.shapes.OperationShape;
29+
import software.amazon.smithy.model.shapes.StructureShape;
30+
31+
/**
32+
* Generates server operation types.
33+
*/
34+
final class ServerCommandGenerator implements Runnable {
35+
36+
private final TypeScriptSettings settings;
37+
private final Model model;
38+
private final OperationShape operation;
39+
private final SymbolProvider symbolProvider;
40+
private final TypeScriptWriter writer;
41+
private final OperationIndex operationIndex;
42+
private final Symbol inputType;
43+
private final Symbol outputType;
44+
45+
ServerCommandGenerator(
46+
TypeScriptSettings settings,
47+
Model model,
48+
OperationShape operation,
49+
SymbolProvider symbolProvider,
50+
TypeScriptWriter writer
51+
) {
52+
this.settings = settings;
53+
this.model = model;
54+
this.operation = operation;
55+
this.symbolProvider = symbolProvider;
56+
this.writer = writer;
57+
58+
Symbol operationSymbol = symbolProvider.toSymbol(operation);
59+
operationIndex = OperationIndex.of(model);
60+
inputType = operationSymbol.expectProperty("inputType", Symbol.class);
61+
outputType = operationSymbol.expectProperty("outputType", Symbol.class);
62+
}
63+
64+
@Override
65+
public void run() {
66+
addInputAndOutputTypes();
67+
}
68+
69+
private void addInputAndOutputTypes() {
70+
writeInputType(inputType.getName(), operationIndex.getInput(operation));
71+
writeOutputType(outputType.getName(), operationIndex.getOutput(operation));
72+
writer.write("");
73+
}
74+
75+
// TODO: Flip these so that metadata is attached to input and streaming customization is attached to output.
76+
private void writeInputType(String typeName, Optional<StructureShape> inputShape) {
77+
if (inputShape.isPresent()) {
78+
StructureShape input = inputShape.get();
79+
List<MemberShape> blobStreamingMembers = getBlobStreamingMembers(model, input);
80+
if (blobStreamingMembers.isEmpty()) {
81+
writer.write("export type $L = $T;", typeName, symbolProvider.toSymbol(input));
82+
} else {
83+
writeStreamingMemberType(writer, symbolProvider.toSymbol(input), typeName, blobStreamingMembers.get(0));
84+
}
85+
} else {
86+
// If the input is non-existent, then use an empty object.
87+
writer.write("export type $L = {}", typeName);
88+
}
89+
}
90+
91+
private void writeOutputType(String typeName, Optional<StructureShape> outputShape) {
92+
// Output types should always be MetadataBearers, possibly in addition
93+
// to a defined output shape.
94+
writer.addImport("MetadataBearer", "__MetadataBearer", TypeScriptDependency.AWS_SDK_TYPES.packageName);
95+
if (outputShape.isPresent()) {
96+
writer.write("export type $L = $T & __MetadataBearer;",
97+
typeName, symbolProvider.toSymbol(outputShape.get()));
98+
} else {
99+
writer.write("export type $L = __MetadataBearer", typeName);
100+
}
101+
}
102+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public String formatModuleName(Shape shape, String name) {
131131
if (shape.getType() == ShapeType.SERVICE) {
132132
return "./server/" + name;
133133
} else if (shape.getType() == ShapeType.OPERATION) {
134-
return "./server/types/" + name;
134+
return "./server/operations/" + name;
135135
}
136136

137137
throw new IllegalArgumentException("Unsupported shape type: " + shape.getType());

0 commit comments

Comments
 (0)