Skip to content

Commit 0dd64aa

Browse files
committed
update runtime config customization to be fully overridable
Previously, some default runtime config values are hard-coded into templates. This change makes each one of the properties(e.g. requestHandler) to be overridable by customizations from SDK side. If no customization exists, the generator can still generate SDK with proper dependencies. This change is added to support the customization of using WebSocket requestHandler. This change also brings in the capability that customization applied later can override the runtime config values added in earlier customization. This is necessary sometimes. For example, if customization A adds runtime config: {x: X, y: Y}. x and y are deeply coupled that they need to be in the same customization. If there's a single client needs runtime config {x: X, y: Y-update}, we should be able to make a new customization overriding the original customization.
1 parent a78b6fd commit 0dd64aa

File tree

8 files changed

+260
-92
lines changed

8 files changed

+260
-92
lines changed

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

Lines changed: 200 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,18 @@
1515

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

18+
import java.util.Comparator;
19+
import java.util.HashMap;
1820
import java.util.List;
21+
import java.util.Map;
22+
import java.util.function.Consumer;
23+
24+
import software.amazon.smithy.build.SmithyBuildException;
1925
import software.amazon.smithy.codegen.core.SymbolProvider;
2026
import software.amazon.smithy.model.Model;
2127
import software.amazon.smithy.model.shapes.ServiceShape;
2228
import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration;
29+
import software.amazon.smithy.utils.MapUtils;
2330

2431
/**
2532
* Generates runtime configuration files, files that are used to
@@ -34,6 +41,167 @@ final class RuntimeConfigGenerator {
3441
private final SymbolProvider symbolProvider;
3542
private final TypeScriptDelegator delegator;
3643
private final List<TypeScriptIntegration> integrations;
44+
private final Map<String, Consumer<TypeScriptWriter>> nodeRuntimeConfigDefaults = MapUtils.of(
45+
"requestHandler", writer -> {
46+
writer.addDependency(TypeScriptDependency.AWS_SDK_NODE_HTTP_HANDLER);
47+
writer.addImport("NodeHttpHandler", "NodeHttpHandler",
48+
TypeScriptDependency.AWS_SDK_NODE_HTTP_HANDLER.packageName);
49+
writer.write("new NodeHttpHandler()");
50+
},
51+
"sha256", writer -> {
52+
writer.addDependency(TypeScriptDependency.AWS_SDK_HASH_NODE);
53+
writer.addImport("Hash", "Hash",
54+
TypeScriptDependency.AWS_SDK_HASH_NODE.packageName);
55+
writer.write("Hash.bind(null, \"sha256\")");
56+
},
57+
"urlParser", writer -> {
58+
writer.addDependency(TypeScriptDependency.AWS_SDK_URL_PARSER_NODE);
59+
writer.addImport("parseUrl", "parseUrl",
60+
TypeScriptDependency.AWS_SDK_URL_PARSER_NODE.packageName);
61+
writer.write("parseUrl");
62+
},
63+
"bodyLengthChecker", writer -> {
64+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_BODY_LENGTH_NODE);
65+
writer.addImport("calculateBodyLength", "calculateBodyLength",
66+
TypeScriptDependency.AWS_SDK_UTIL_BODY_LENGTH_NODE.packageName);
67+
writer.write("calculateBodyLength");
68+
},
69+
"streamCollector", writer -> {
70+
writer.addDependency(TypeScriptDependency.AWS_SDK_STREAM_COLLECTOR_NODE);
71+
writer.addImport("streamCollector", "streamCollector",
72+
TypeScriptDependency.AWS_SDK_STREAM_COLLECTOR_NODE.packageName);
73+
},
74+
"base64Decoder", writer -> {
75+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_BASE64_NODE);
76+
writer.addImport("fromBase64", "fromBase64",
77+
TypeScriptDependency.AWS_SDK_UTIL_BASE64_NODE.packageName);
78+
writer.write("fromBase64");
79+
},
80+
"base64Encoder", writer -> {
81+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_BASE64_NODE);
82+
writer.addImport("toBase64", "toBase64",
83+
TypeScriptDependency.AWS_SDK_UTIL_BASE64_NODE.packageName);
84+
writer.write("toBase64");
85+
},
86+
"utf8Decoder", writer -> {
87+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_UTF8_NODE);
88+
writer.addImport("fromUtf8", "fromUtf8",
89+
TypeScriptDependency.AWS_SDK_UTIL_UTF8_NODE.packageName);
90+
writer.write("fromUtf8");
91+
},
92+
"utf8Encoder", writer -> {
93+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_UTF8_NODE);
94+
writer.addImport("toUtf8", "toUtf8",
95+
TypeScriptDependency.AWS_SDK_UTIL_UTF8_NODE.packageName);
96+
writer.write("toUtf8");
97+
},
98+
"defaultUserAgent", writer -> {
99+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_USER_AGENT_NODE);
100+
writer.addImport("defaultUserAgent", "defaultUserAgent",
101+
TypeScriptDependency.AWS_SDK_UTIL_USER_AGENT_NODE.packageName);
102+
writer.addImport("name", "name", "./package.json");
103+
writer.addImport("version", "version", "./package.json");
104+
writer.write("defaultUserAgent(name, version)");
105+
}
106+
);
107+
private final Map<String, Consumer<TypeScriptWriter>> browserRuntimeConfigDefaults = MapUtils.of(
108+
"requestHandler", writer -> {
109+
writer.addDependency(TypeScriptDependency.AWS_SDK_FETCH_HTTP_HANDLER);
110+
writer.addImport("FetchHttpHandler", "FetchHttpHandler",
111+
TypeScriptDependency.AWS_SDK_FETCH_HTTP_HANDLER.packageName);
112+
writer.write("new FetchHttpHandler()");
113+
},
114+
"sha256", writer -> {
115+
writer.addDependency(TypeScriptDependency.AWS_CRYPTO_SHA256_BROWSER);
116+
writer.addImport("Sha256", "Sha256",
117+
TypeScriptDependency.AWS_CRYPTO_SHA256_BROWSER.packageName);
118+
writer.write("Sha256");
119+
},
120+
"urlParser", writer -> {
121+
writer.addDependency(TypeScriptDependency.AWS_SDK_URL_PARSER_BROWSER);
122+
writer.addImport("parseUrl", "parseUrl",
123+
TypeScriptDependency.AWS_SDK_URL_PARSER_BROWSER.packageName);
124+
writer.write("parseUrl");
125+
},
126+
"bodyLengthChecker", writer -> {
127+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_BODY_LENGTH_BROWSER);
128+
writer.addImport("calculateBodyLength", "calculateBodyLength",
129+
TypeScriptDependency.AWS_SDK_UTIL_BODY_LENGTH_BROWSER.packageName);
130+
writer.write("calculateBodyLength");
131+
},
132+
"streamCollector", writer -> {
133+
writer.addDependency(TypeScriptDependency.AWS_SDK_STREAM_COLLECTOR_BROWSER);
134+
writer.addImport("streamCollector", "streamCollector",
135+
TypeScriptDependency.AWS_SDK_STREAM_COLLECTOR_BROWSER.packageName);
136+
},
137+
"base64Decoder", writer -> {
138+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_BASE64_BROWSER);
139+
writer.addImport("fromBase64", "fromBase64",
140+
TypeScriptDependency.AWS_SDK_UTIL_BASE64_BROWSER.packageName);
141+
writer.write("fromBase64");
142+
},
143+
"base64Encoder", writer -> {
144+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_BASE64_BROWSER);
145+
writer.addImport("toBase64", "toBase64",
146+
TypeScriptDependency.AWS_SDK_UTIL_BASE64_BROWSER.packageName);
147+
writer.write("toBase64");
148+
},
149+
"utf8Decoder", writer -> {
150+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_UTF8_BROWSER);
151+
writer.addImport("fromUtf8", "fromUtf8",
152+
TypeScriptDependency.AWS_SDK_UTIL_UTF8_BROWSER.packageName);
153+
writer.write("fromUtf8");
154+
},
155+
"utf8Encoder", writer -> {
156+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_UTF8_BROWSER);
157+
writer.addImport("toUtf8", "toUtf8",
158+
TypeScriptDependency.AWS_SDK_UTIL_UTF8_BROWSER.packageName);
159+
writer.write("toUtf8");
160+
},
161+
"defaultUserAgent", writer -> {
162+
writer.addDependency(TypeScriptDependency.AWS_SDK_UTIL_USER_AGENT_BROWSER);
163+
writer.addImport("defaultUserAgent", "defaultUserAgent",
164+
TypeScriptDependency.AWS_SDK_UTIL_USER_AGENT_BROWSER.packageName);
165+
writer.addImport("name", "name", "./package.json");
166+
writer.addImport("version", "version", "./package.json");
167+
writer.write("defaultUserAgent(name, version)");
168+
}
169+
);
170+
private final Map<String, Consumer<TypeScriptWriter>> reactNativeRuntimeConfigDefaults = MapUtils.of(
171+
"requestHandler", writer -> {
172+
writer.addDependency(TypeScriptDependency.AWS_SDK_FETCH_HTTP_HANDLER);
173+
writer.addImport("FetchHttpHandler", "FetchHttpHandler",
174+
TypeScriptDependency.AWS_SDK_FETCH_HTTP_HANDLER.packageName);
175+
writer.write("new FetchHttpHandler({ bufferBody: true })");
176+
},
177+
"sha256", writer -> {
178+
writer.addDependency(TypeScriptDependency.AWS_CRYPTO_SHA256_JS);
179+
writer.addImport("Sha256", "Sha256",
180+
TypeScriptDependency.AWS_CRYPTO_SHA256_JS.packageName);
181+
writer.write("Sha256");
182+
},
183+
"urlParser", writer -> {
184+
writer.addDependency(TypeScriptDependency.AWS_SDK_URL_PARSER_BROWSER);
185+
writer.addImport("parseUrl", "parseUrl",
186+
TypeScriptDependency.AWS_SDK_URL_PARSER_BROWSER.packageName);
187+
writer.write("parseUrl");
188+
},
189+
"streamCollector", writer -> {
190+
writer.addDependency(TypeScriptDependency.AWS_SDK_STREAM_COLLECTOR_RN);
191+
writer.addImport("streamCollector", "streamCollector",
192+
TypeScriptDependency.AWS_SDK_STREAM_COLLECTOR_RN.packageName);
193+
},
194+
"defaultUserAgent", writer -> {
195+
writer.addImport("name", "name", "./package.json");
196+
writer.addImport("version", "version", "./package.json");
197+
writer.write("`aws-sdk-js-v3-react-native-$${name}/$${version}`");
198+
}
199+
);
200+
private final Map<String, Consumer<TypeScriptWriter>> sharedRuntimeConfigDefaults = MapUtils.of(
201+
"disableHostPrefix", writer -> {
202+
writer.write("false");
203+
}
204+
);
37205

38206
RuntimeConfigGenerator(
39207
TypeScriptSettings settings,
@@ -62,12 +230,41 @@ void generate(LanguageTarget target) {
62230
// Inject customizations into the ~template.
63231
writer.onSection("customizations", original -> {
64232
writer.indent();
65-
for (TypeScriptIntegration integration : integrations) {
66-
integration.addRuntimeConfigValues(settings, model, symbolProvider, writer, target);
67-
}
233+
Map<String, Consumer<TypeScriptWriter>> defaultConfigs =
234+
new HashMap(getDefaultRuntimeConfigs(target));
235+
//TODO: ensure integrations order is always desired.
236+
Map<String, Consumer<TypeScriptWriter>> aggregatedConfigs = integrations.stream()
237+
.map(integration -> integration.addRuntimeConfigValues(settings, model, symbolProvider, target))
238+
.reduce(defaultConfigs, (aggregated, configMap) -> {
239+
aggregated.putAll(configMap);
240+
return aggregated;
241+
});
242+
aggregatedConfigs.entrySet().stream()
243+
.sorted(Comparator.comparing(Map.Entry::getKey))
244+
.forEach(entry -> {
245+
writer.onSection(entry.getKey(), text -> {
246+
entry.getValue().accept(writer);
247+
});
248+
writer.write(String.format("%s: ${L@%s},", entry.getKey(), entry.getKey()), "");
249+
});
68250
writer.dedent();
69251
});
70252
writer.write(contents, "");
71253
});
72254
}
255+
256+
private Map<String, Consumer<TypeScriptWriter>> getDefaultRuntimeConfigs(LanguageTarget target) {
257+
switch (target) {
258+
case NODE:
259+
return nodeRuntimeConfigDefaults;
260+
case BROWSER:
261+
return browserRuntimeConfigDefaults;
262+
case REACT_NATIVE:
263+
return reactNativeRuntimeConfigDefaults;
264+
case SHARED:
265+
return sharedRuntimeConfigDefaults;
266+
default:
267+
throw new SmithyBuildException("Unknown target: " + target);
268+
}
269+
}
73270
}

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/AddEventStreamDependency.java

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515

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

18+
import java.util.Collections;
1819
import java.util.List;
20+
import java.util.Map;
1921
import java.util.Set;
22+
import java.util.function.Consumer;
2023

2124
import software.amazon.smithy.codegen.core.SymbolProvider;
2225
import software.amazon.smithy.model.Model;
@@ -29,6 +32,7 @@
2932
import software.amazon.smithy.typescript.codegen.TypeScriptSettings;
3033
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
3134
import software.amazon.smithy.utils.ListUtils;
35+
import software.amazon.smithy.utils.MapUtils;
3236

3337
/**
3438
* Adds event streams if needed.
@@ -65,42 +69,43 @@ public void addConfigInterfaceFields(
6569
}
6670

6771
@Override
68-
public void addRuntimeConfigValues(
72+
public Map<String, Consumer<TypeScriptWriter>> addRuntimeConfigValues(
6973
TypeScriptSettings settings,
7074
Model model,
7175
SymbolProvider symbolProvider,
72-
TypeScriptWriter writer,
7376
LanguageTarget target
7477
) {
7578
if (!hasEventStream(model, settings.getService(model))) {
76-
return;
79+
return Collections.emptyMap();
7780
}
78-
7981
switch (target) {
8082
case NODE:
81-
writer.addDependency(TypeScriptDependency.AWS_SDK_EVENTSTREAM_SERDE_NODE);
82-
writer.addImport("eventStreamSerdeProvider", "eventStreamSerdeProvider",
83-
TypeScriptDependency.AWS_SDK_EVENTSTREAM_SERDE_NODE.packageName);
84-
writer.write("eventStreamSerdeProvider");
85-
break;
83+
return MapUtils.of("eventStreamSerdeProvider", writer -> {
84+
writer.addDependency(TypeScriptDependency.AWS_SDK_EVENTSTREAM_SERDE_NODE);
85+
writer.addImport("eventStreamSerdeProvider", "eventStreamSerdeProvider",
86+
TypeScriptDependency.AWS_SDK_EVENTSTREAM_SERDE_NODE.packageName);
87+
writer.write("eventStreamSerdeProvider");
88+
});
8689
case BROWSER:
87-
writer.addDependency(TypeScriptDependency.AWS_SDK_EVENTSTREAM_SERDE_BROWSER);
88-
writer.addImport("eventStreamSerdeProvider", "eventStreamSerdeProvider",
89-
TypeScriptDependency.AWS_SDK_EVENTSTREAM_SERDE_BROWSER.packageName);
90-
writer.write("eventStreamSerdeProvider");
91-
break;
90+
return MapUtils.of("eventStreamSerdeProvider", writer -> {
91+
writer.addDependency(TypeScriptDependency.AWS_SDK_EVENTSTREAM_SERDE_BROWSER);
92+
writer.addImport("eventStreamSerdeProvider", "eventStreamSerdeProvider",
93+
TypeScriptDependency.AWS_SDK_EVENTSTREAM_SERDE_BROWSER.packageName);
94+
writer.write("eventStreamSerdeProvider");
95+
});
9296
case REACT_NATIVE:
9397
// TODO: add ReactNative eventstream support
94-
writer.addDependency(TypeScriptDependency.INVALID_DEPENDENCY);
95-
writer.addImport("invalidFunction", "invalidFunction",
96-
TypeScriptDependency.INVALID_DEPENDENCY.packageName);
97-
writer.openBlock("eventStreamSerdeProvider: () => ({", "})", () -> {
98-
writer.write("serialize: invalidFunction(\"event stream is not supported in ReactNative.\"),");
99-
writer.write("deserialize: invalidFunction(\"event stream is not supported in ReactNative.\")");
98+
return MapUtils.of("eventStreamSerdeProvider", writer -> {
99+
writer.addDependency(TypeScriptDependency.INVALID_DEPENDENCY);
100+
writer.addImport("invalidFunction", "invalidFunction",
101+
TypeScriptDependency.INVALID_DEPENDENCY.packageName);
102+
writer.openBlock("eventStreamSerdeProvider: () => ({", "})", () -> {
103+
writer.write("serialize: invalidFunction(\"event stream is not supported in ReactNative.\"),");
104+
writer.write("deserialize: invalidFunction(\"event stream is not supported in ReactNative.\")");
105+
});
100106
});
101-
break;
102107
default:
103-
// do nothing
108+
return Collections.emptyMap();
104109
}
105110
}
106111

0 commit comments

Comments
 (0)