Skip to content

Commit a46f985

Browse files
committed
Support a single protocol in the generator
This commit updates the generator to support a single protocol and removes the protocol name from the runtime code. We can add support for multiple protocols in the future if needed (for example, if a service actually splits their operations across protocols), but for now, it's safer to expose only a single protocol in both the generator and the generated code. The location and format of the generated protocol code all remains the same, however, the generator no longer supports multiple protocols in the "protocols" option, there's no longer a code generated "protocol" property available at runtime, and the method that used to dispatch to different protocols now just supports calling a single protocol.
1 parent 64d7735 commit a46f985

File tree

8 files changed

+124
-158
lines changed

8 files changed

+124
-158
lines changed

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

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,16 @@
1515

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

18-
import java.util.Collection;
19-
import java.util.List;
2018
import java.util.Objects;
21-
import java.util.logging.Logger;
22-
import java.util.stream.Collectors;
2319
import software.amazon.smithy.codegen.core.Symbol;
2420
import software.amazon.smithy.codegen.core.SymbolReference;
25-
import software.amazon.smithy.model.shapes.ServiceShape;
26-
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
27-
import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration;
2821

2922
/**
3023
* Represents the resolves {@link Symbol}s and references for an
3124
* application protocol (e.g., "http", "mqtt", etc).
3225
*/
3326
public final class ApplicationProtocol {
3427

35-
private static final Logger LOGGER = Logger.getLogger(ApplicationProtocol.class.getName());
36-
3728
private final String name;
3829
private final SymbolReference optionsType;
3930
private final SymbolReference requestType;
@@ -92,36 +83,6 @@ private static Symbol createHttpSymbol(TypeScriptDependency dependency, String s
9283
.build();
9384
}
9485

95-
static ApplicationProtocol resolve(
96-
TypeScriptSettings settings,
97-
ServiceShape service,
98-
Collection<TypeScriptIntegration> integrations
99-
) {
100-
List<String> resolvedProtocols = settings.resolveServiceProtocols(service);
101-
// Get the list of protocol generators that have implementations from the service.
102-
List<ProtocolGenerator> generators = integrations.stream()
103-
.flatMap(integration -> integration.getProtocolGenerators().stream())
104-
.filter(generator -> resolvedProtocols.contains(generator.getName()))
105-
.collect(Collectors.toList());
106-
107-
if (generators.isEmpty()) {
108-
// Default to "http" if no protocols are configured.
109-
LOGGER.warning(String.format(
110-
"No protocol generators could be found for the protocols supported by this service "
111-
+ "`%s`: %s. Assuming an HTTP-based protocol.", service.getId(), resolvedProtocols));
112-
return createDefaultHttpApplicationProtocol();
113-
}
114-
115-
// Ensure each protocol is compatible. If not, then an explicit list must be provided.
116-
ApplicationProtocol applicationProtocol = generators.get(0).getApplicationProtocol();
117-
for (int i = 1; i < generators.size(); i++) {
118-
ProtocolGenerator generator = generators.get(i);
119-
applicationProtocol = generator.resolveApplicationProtocol(service, generators, applicationProtocol);
120-
}
121-
122-
return applicationProtocol;
123-
}
124-
12586
/**
12687
* Gets the protocol name.
12788
*

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

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,15 @@
1616
package software.amazon.smithy.typescript.codegen;
1717

1818
import java.util.ArrayList;
19+
import java.util.Collection;
20+
import java.util.HashMap;
1921
import java.util.List;
2022
import java.util.Map;
2123
import java.util.Objects;
22-
import java.util.Optional;
2324
import java.util.ServiceLoader;
2425
import java.util.Set;
2526
import java.util.TreeSet;
26-
import java.util.function.Function;
2727
import java.util.logging.Logger;
28-
import java.util.stream.Collectors;
2928
import software.amazon.smithy.build.FileManifest;
3029
import software.amazon.smithy.build.PluginContext;
3130
import software.amazon.smithy.codegen.core.Symbol;
@@ -43,7 +42,6 @@
4342
import software.amazon.smithy.model.shapes.StructureShape;
4443
import software.amazon.smithy.model.shapes.UnionShape;
4544
import software.amazon.smithy.model.traits.EnumTrait;
46-
import software.amazon.smithy.model.traits.ProtocolsTrait;
4745
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
4846
import software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin;
4947
import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration;
@@ -70,6 +68,7 @@ class CodegenVisitor extends ShapeVisitor.Default<Void> {
7068
private final TypeScriptDelegator writers;
7169
private final List<TypeScriptIntegration> integrations = new ArrayList<>();
7270
private final List<RuntimeClientPlugin> runtimePlugins = new ArrayList<>();
71+
private final ProtocolGenerator protocolGenerator;
7372
private final ApplicationProtocol applicationProtocol;
7473

7574
CodegenVisitor(PluginContext context) {
@@ -100,8 +99,37 @@ class CodegenVisitor extends ShapeVisitor.Default<Void> {
10099
}
101100
symbolProvider = SymbolProvider.cache(resolvedProvider);
102101

102+
// Resolve the nullable protocol generator and application protocol.
103+
protocolGenerator = resolveProtocolGenerator(integrations, service, settings);
104+
applicationProtocol = protocolGenerator == null
105+
? ApplicationProtocol.createDefaultHttpApplicationProtocol()
106+
: protocolGenerator.getApplicationProtocol();
107+
103108
writers = new TypeScriptDelegator(settings, model, fileManifest, symbolProvider, integrations);
104-
applicationProtocol = ApplicationProtocol.resolve(settings, service, integrations);
109+
}
110+
111+
private static ProtocolGenerator resolveProtocolGenerator(
112+
Collection<TypeScriptIntegration> integrations,
113+
ServiceShape service,
114+
TypeScriptSettings settings
115+
) {
116+
// Collect all of the supported protocol generators.
117+
Map<String, ProtocolGenerator> generators = new HashMap<>();
118+
for (TypeScriptIntegration integration : integrations) {
119+
for (ProtocolGenerator generator : integration.getProtocolGenerators()) {
120+
generators.put(generator.getName(), generator);
121+
}
122+
}
123+
124+
String protocolName;
125+
try {
126+
protocolName = settings.resolveServiceProtocol(service, generators.keySet());
127+
} catch (UnresolvableProtocolException e) {
128+
LOGGER.warning("Unable to find a protocol generator for " + service.getId() + ": " + e.getMessage());
129+
protocolName = null;
130+
}
131+
132+
return protocolName != null ? generators.get(protocolName) : null;
105133
}
106134

107135
void execute() {
@@ -119,12 +147,8 @@ void execute() {
119147
// Generate the client Node and Browser configuration files. These
120148
// files are switched between in package.json based on the targeted
121149
// environment.
122-
String defaultProtocolName = getDefaultGenerator().map(ProtocolGenerator::getName).orElse(null);
123-
LOGGER.fine("Resolved the default protocol of the client to " + defaultProtocolName);
124-
125-
// Generate each runtime config file for targeted platforms.
126150
RuntimeConfigGenerator configGenerator = new RuntimeConfigGenerator(
127-
settings, model, symbolProvider, defaultProtocolName, writers, integrations);
151+
settings, model, symbolProvider, writers, integrations);
128152
for (LanguageTarget target : LanguageTarget.values()) {
129153
LOGGER.fine("Generating " + target + " runtime configuration");
130154
configGenerator.generate(target);
@@ -151,19 +175,6 @@ void execute() {
151175
settings, fileManifest, SymbolDependency.gatherDependencies(dependencies.stream()));
152176
}
153177

154-
// Finds the first listed protocol from the service that has a
155-
// discovered protocol generator that matches the name.
156-
private Optional<ProtocolGenerator> getDefaultGenerator() {
157-
List<String> protocols = settings.resolveServiceProtocols(service);
158-
Map<String, ProtocolGenerator> generators = integrations.stream()
159-
.flatMap(integration -> integration.getProtocolGenerators().stream())
160-
.collect(Collectors.toMap(ProtocolGenerator::getName, Function.identity()));
161-
return protocols.stream()
162-
.filter(generators::containsKey)
163-
.map(generators::get)
164-
.findFirst();
165-
}
166-
167178
@Override
168179
protected Void getDefault(Shape shape) {
169180
return null;
@@ -249,35 +260,27 @@ public Void serviceShape(ServiceShape shape) {
249260
for (OperationShape operation : topDownIndex.getContainedOperations(service)) {
250261
writers.useShapeWriter(operation, commandWriter -> new CommandGenerator(
251262
settings, model, operation, symbolProvider, commandWriter,
252-
runtimePlugins, applicationProtocol).run());
263+
runtimePlugins, protocolGenerator, applicationProtocol).run());
253264
}
254265

255-
// Generate each protocol.
256-
shape.getTrait(ProtocolsTrait.class).ifPresent(protocolsTrait -> {
257-
LOGGER.info("Looking for protocol generators for protocols: " + protocolsTrait.getProtocolNames());
258-
for (TypeScriptIntegration integration : integrations) {
259-
for (ProtocolGenerator generator : integration.getProtocolGenerators()) {
260-
if (protocolsTrait.hasProtocol(generator.getName())) {
261-
LOGGER.info("Generating serde for protocol " + generator.getName() + " on " + shape.getId());
262-
String fileRoot = "protocols/" + ProtocolGenerator.getSanitizedName(generator.getName());
263-
String namespace = "./" + fileRoot;
264-
TypeScriptWriter writer = new TypeScriptWriter(namespace);
265-
ProtocolGenerator.GenerationContext context = new ProtocolGenerator.GenerationContext();
266-
context.setProtocolName(generator.getName());
267-
context.setIntegrations(integrations);
268-
context.setModel(model);
269-
context.setService(shape);
270-
context.setSettings(settings);
271-
context.setSymbolProvider(symbolProvider);
272-
context.setWriter(writer);
273-
generator.generateRequestSerializers(context);
274-
generator.generateResponseDeserializers(context);
275-
generator.generateSharedComponents(context);
276-
fileManifest.writeFile(fileRoot + ".ts", writer.toString());
277-
}
278-
}
279-
}
280-
});
266+
if (protocolGenerator != null) {
267+
LOGGER.info("Generating serde for protocol " + protocolGenerator.getName() + " on " + shape.getId());
268+
String fileRoot = "protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName());
269+
String namespace = "./" + fileRoot;
270+
TypeScriptWriter writer = new TypeScriptWriter(namespace);
271+
ProtocolGenerator.GenerationContext context = new ProtocolGenerator.GenerationContext();
272+
context.setProtocolName(protocolGenerator.getName());
273+
context.setIntegrations(integrations);
274+
context.setModel(model);
275+
context.setService(shape);
276+
context.setSettings(settings);
277+
context.setSymbolProvider(symbolProvider);
278+
context.setWriter(writer);
279+
protocolGenerator.generateRequestSerializers(context);
280+
protocolGenerator.generateResponseDeserializers(context);
281+
protocolGenerator.generateSharedComponents(context);
282+
fileManifest.writeFile(fileRoot + ".ts", writer.toString());
283+
}
281284

282285
return null;
283286
}

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

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ final class CommandGenerator implements Runnable {
4747
private final OperationIndex operationIndex;
4848
private final Symbol inputType;
4949
private final Symbol outputType;
50+
private final ProtocolGenerator protocolGenerator;
5051
private final ApplicationProtocol applicationProtocol;
5152

5253
CommandGenerator(
@@ -56,6 +57,7 @@ final class CommandGenerator implements Runnable {
5657
SymbolProvider symbolProvider,
5758
TypeScriptWriter writer,
5859
List<RuntimeClientPlugin> runtimePlugins,
60+
ProtocolGenerator protocolGenerator,
5961
ApplicationProtocol applicationProtocol
6062
) {
6163
this.settings = settings;
@@ -67,6 +69,7 @@ final class CommandGenerator implements Runnable {
6769
this.runtimePlugins = runtimePlugins.stream()
6870
.filter(plugin -> plugin.matchesOperation(model, service, operation))
6971
.collect(Collectors.toList());
72+
this.protocolGenerator = protocolGenerator;
7073
this.applicationProtocol = applicationProtocol;
7174

7275
symbol = symbolProvider.toSymbol(operation);
@@ -190,7 +193,6 @@ private void writeSerde() {
190193
.write("private serialize(")
191194
.indent()
192195
.write("input: $T,", inputType)
193-
.write("protocol: string,")
194196
.write("context: SerdeContext")
195197
.dedent()
196198
.openBlock(
@@ -203,33 +205,24 @@ private void writeSerde() {
203205
.write("private deserialize(")
204206
.indent()
205207
.write("output: $T,", applicationProtocol.getResponseType())
206-
.write("protocol: string,")
207208
.write("context: SerdeContext")
208209
.dedent()
209210
.openBlock("): Promise<$T> {", "}", outputType, () -> writeSerdeDispatcher(false))
210211
.write("");
211212
}
212213

213214
private void writeSerdeDispatcher(boolean isInput) {
214-
writer.openBlock("switch (protocol) {", "}", () -> {
215-
// Generate case statements for each supported protocol.
216-
// For example:
217-
// case 'aws.rest-json-1.1':
218-
// return getFooCommandAws_RestJson1_1Serialize(input, utils);
219-
// TODO Validate this is the right set of protocols; settings.protocols was empty here.
220-
for (String protocol : settings.resolveServiceProtocols(service)) {
221-
String serdeFunctionName = isInput
222-
? ProtocolGenerator.getSerFunctionName(symbol, protocol)
223-
: ProtocolGenerator.getDeserFunctionName(symbol, protocol);
224-
writer.addImport(serdeFunctionName, serdeFunctionName,
225-
"./protocols/" + ProtocolGenerator.getSanitizedName(protocol));
226-
writer.write("case '$L':", protocol)
227-
.write(" return $L($L, context);", serdeFunctionName, isInput ? "input" : "output");
228-
}
229-
230-
writer.write("default:")
231-
.write(" throw new Error(\"Unknown protocol, \" + protocol + \". Expected one of: $L\");",
232-
settings.getProtocols());
233-
});
215+
// For example:
216+
// return getFooCommandAws_RestJson1_1Serialize(input, utils);
217+
if (protocolGenerator == null) {
218+
writer.write("throw new Error(\"No supported protocol was found\");");
219+
} else {
220+
String serdeFunctionName = isInput
221+
? ProtocolGenerator.getSerFunctionName(symbol, protocolGenerator.getName())
222+
: ProtocolGenerator.getDeserFunctionName(symbol, protocolGenerator.getName());
223+
writer.addImport(serdeFunctionName, serdeFunctionName,
224+
"./protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()));
225+
writer.write("return $L($L, context);", serdeFunctionName, isInput ? "input" : "output");
226+
}
234227
}
235228
}

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,20 @@ final class RuntimeConfigGenerator {
3232
private final Model model;
3333
private final ServiceShape service;
3434
private final SymbolProvider symbolProvider;
35-
private final String protocolName;
3635
private final TypeScriptDelegator delegator;
3736
private final List<TypeScriptIntegration> integrations;
3837

3938
RuntimeConfigGenerator(
4039
TypeScriptSettings settings,
4140
Model model,
4241
SymbolProvider symbolProvider,
43-
String protocolName,
4442
TypeScriptDelegator delegator,
4543
List<TypeScriptIntegration> integrations
4644
) {
4745
this.settings = settings;
4846
this.model = model;
4947
this.service = settings.getService(model);
5048
this.symbolProvider = symbolProvider;
51-
this.protocolName = protocolName;
5249
this.delegator = delegator;
5350
this.integrations = integrations;
5451
}
@@ -58,10 +55,6 @@ void generate(LanguageTarget target) {
5855
String contents = template
5956
.replace("${clientModuleName}", symbolProvider.toSymbol(service).getNamespace())
6057
.replace("${apiVersion}", service.getVersion())
61-
// Set the protocol to "undefined" if no default protocol can be resolved.
62-
// This should only be the case when testing out code generators. The runtime
63-
// code is expected to throw an exception when this value is encountered.
64-
.replace("${protocol}", protocolName == null ? "undefined" : protocolName)
6558
.replace("$", "$$") // sanitize template place holders.
6659
.replace("$${customizations}", "${L@customizations}");
6760

0 commit comments

Comments
 (0)