Skip to content

Commit d76f0bc

Browse files
author
Steven Yuan
committed
feat(experimentalIdentityAndAuth): update HttpAuthOption and HttpAuthScheme codegen
After upgrading to Smithy 1.37.0 (#906), upgrade to `getEffectiveAuthSchemes` using `NO_AUTH_AWARE` and remove redundant code. For `HttpAuthOption`, allow any auth scheme to generate the corresponding `HttpAuthOption` function regardless of registering it in codegen. For `HttpAuthScheme`, reduce code duplication by looking at "inherited" `LanguageTarget` platforms (e.g. `REACT_NATIVE` inherits from `BROWSER`). `httpAuthSchemes` is only written in a `runtimeConfig` if the `IdentityProvider` and `Signer` are different between platforms, or if the registered `HttpAuthScheme`s are different. `httpAuthSchemes` is always written for `SHARED` as a default. For each of the generic auth (`@httpApiKeyAuth`, `@httpBearerAuth`, and `@aws.auth#sigv4`), make the identity providers and signers the same and available on all platforms. For the `httpAuthSchemes` property on the client config interface, update it to `HttpAuthScheme[]` rather than `IdentityProviderConfiguration`.
1 parent 697310d commit d76f0bc

File tree

8 files changed

+241
-140
lines changed

8 files changed

+241
-140
lines changed

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

Lines changed: 108 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.smithy.typescript.codegen;
1717

1818
import java.nio.file.Paths;
19+
import java.util.Collections;
1920
import java.util.Iterator;
2021
import java.util.List;
2122
import java.util.Map;
@@ -24,12 +25,12 @@
2425
import java.util.function.Consumer;
2526
import java.util.stream.Collectors;
2627
import software.amazon.smithy.build.SmithyBuildException;
28+
import software.amazon.smithy.codegen.core.CodegenException;
2729
import software.amazon.smithy.codegen.core.SymbolProvider;
2830
import software.amazon.smithy.model.Model;
2931
import software.amazon.smithy.model.knowledge.ServiceIndex;
3032
import software.amazon.smithy.model.shapes.ServiceShape;
3133
import software.amazon.smithy.model.shapes.ShapeId;
32-
import software.amazon.smithy.model.traits.Trait;
3334
import software.amazon.smithy.typescript.codegen.auth.AuthUtils;
3435
import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthScheme;
3536
import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthSchemeProviderGenerator;
@@ -45,7 +46,6 @@
4546
*/
4647
@SmithyInternalApi
4748
final class RuntimeConfigGenerator {
48-
4949
private final TypeScriptSettings settings;
5050
private final Model model;
5151
private final ServiceShape service;
@@ -208,50 +208,57 @@ private void generateHttpAuthSchemeConfig(
208208
TypeScriptWriter writer,
209209
LanguageTarget target
210210
) {
211+
// feat(experimentalIdentityAndAuth): write the default imported HttpAuthSchemeProvider
212+
if (target.equals(LanguageTarget.SHARED)) {
213+
configs.put("httpAuthSchemeProvider", w -> {
214+
String providerName = "default" + service.toShapeId().getName() + "HttpAuthSchemeProvider";
215+
w.addRelativeImport(providerName, null, Paths.get(".", CodegenUtils.SOURCE_FOLDER,
216+
HttpAuthSchemeProviderGenerator.HTTP_AUTH_FOLDER,
217+
HttpAuthSchemeProviderGenerator.HTTP_AUTH_SCHEME_RESOLVER_MODULE));
218+
w.write(providerName);
219+
});
220+
}
221+
211222
// feat(experimentalIdentityAndAuth): gather HttpAuthSchemes to generate
212-
SupportedHttpAuthSchemesIndex index = new SupportedHttpAuthSchemesIndex(integrations);
223+
SupportedHttpAuthSchemesIndex authIndex = new SupportedHttpAuthSchemesIndex(integrations);
213224
ServiceIndex serviceIndex = ServiceIndex.of(model);
214-
Map<ShapeId, Trait> authSchemeTraits = serviceIndex.getAuthSchemes(service);
215-
Map<ShapeId, HttpAuthScheme> authSchemes = authSchemeTraits.entrySet().stream()
216-
.filter(entry -> index.getHttpAuthScheme(entry.getKey()) != null)
217-
.collect(Collectors.toMap(
218-
entry -> entry.getKey(),
219-
entry -> index.getHttpAuthScheme(entry.getKey())));
220-
// feat(experimentalIdentityAndAuth): can be removed after changing to NO_AUTH_AWARE schemes
221-
// The default set of HttpAuthSchemes is @smithy.api#noAuth on the shared runtime config
222-
authSchemes.put(AuthUtils.NO_AUTH_ID, index.getHttpAuthScheme(AuthUtils.NO_AUTH_ID));
223-
boolean shouldGenerateHttpAuthSchemes = index.getSupportedHttpAuthSchemes().values().stream().anyMatch(value ->
224-
// If either an default identity or signer is configured for the target, generate auth schemes
225-
value.getDefaultIdentityProviders().containsKey(target) || value.getDefaultSigners().containsKey(target));
226-
if (!shouldGenerateHttpAuthSchemes) {
225+
Map<ShapeId, HttpAuthScheme> targetAuthSchemes = getTargetAuthSchemes(target, authIndex, serviceIndex);
226+
227+
// Generate only if the "inherited" target is different than the current target
228+
Map<ShapeId, HttpAuthScheme> inheritedAuthSchemes = Collections.emptyMap();
229+
LanguageTarget inherited = LanguageTarget.SHARED;
230+
// Always generated the SHARED target
231+
if (target.equals(LanguageTarget.SHARED)) {
232+
// no-op
233+
// NODE and BROWSER inherit from SHARED
234+
} else if (target.equals(LanguageTarget.NODE) || target.equals(LanguageTarget.BROWSER)) {
235+
inheritedAuthSchemes = getTargetAuthSchemes(LanguageTarget.SHARED, authIndex, serviceIndex);
236+
// REACT_NATIVE inherits from BROWSER
237+
} else if (target.equals(LanguageTarget.REACT_NATIVE)) {
238+
inheritedAuthSchemes = getTargetAuthSchemes(LanguageTarget.BROWSER, authIndex, serviceIndex);
239+
inherited = LanguageTarget.BROWSER;
240+
} else {
241+
throw new CodegenException("Unhandled Language Target: " + target);
242+
}
243+
244+
// If target and inherited auth schemes are equal, then don't generate target auth schemes.
245+
if (areAuthSchemesTargetEqual(targetAuthSchemes, target, inheritedAuthSchemes, inherited)) {
227246
return;
228247
}
229-
// feat(experimentalIdentityAndAuth): write the default imported HttpAuthSchemeProvider
230-
configs.put("httpAuthSchemeProvider", w -> {
231-
String providerName = "default" + service.toShapeId().getName() + "HttpAuthSchemeProvider";
232-
w.addRelativeImport(providerName, null, Paths.get(".", CodegenUtils.SOURCE_FOLDER,
233-
HttpAuthSchemeProviderGenerator.HTTP_AUTH_FOLDER,
234-
HttpAuthSchemeProviderGenerator.HTTP_AUTH_SCHEME_RESOLVER_MODULE));
235-
w.write(providerName);
236-
});
248+
237249
// feat(experimentalIdentityAndAuth): write the default IdentityProviderConfiguration with configured
238250
// HttpAuthSchemes
239251
configs.put("httpAuthSchemes", w -> {
240-
w.addDependency(TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH);
241-
w.addImport("DefaultIdentityProviderConfiguration", null,
242-
TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH);
243-
w.openBlock("new DefaultIdentityProviderConfiguration([", "])", () -> {
244-
Iterator<Entry<ShapeId, HttpAuthScheme>> iter = authSchemes.entrySet().iterator();
252+
w.openBlock("[", "]", () -> {
253+
Iterator<Entry<ShapeId, HttpAuthScheme>> iter = targetAuthSchemes.entrySet().iterator();
245254
while (iter.hasNext()) {
246255
Entry<ShapeId, HttpAuthScheme> entry = iter.next();
247-
// Default IdentityProvider or HttpSigner, otherwise write {@code never} to force a TypeScript
248-
// compilation failure.
249256
Consumer<TypeScriptWriter> defaultIdentityProvider = entry.getValue()
250257
.getDefaultIdentityProviders()
251-
.getOrDefault(target, AuthUtils.DEFAULT_NEVER_WRITER);
258+
.get(target);
252259
Consumer<TypeScriptWriter> defaultSigner = entry.getValue()
253260
.getDefaultSigners()
254-
.getOrDefault(target, AuthUtils.DEFAULT_NEVER_WRITER);
261+
.get(target);
255262
w.write("""
256263
{
257264
schemeId: $S,
@@ -269,6 +276,74 @@ private void generateHttpAuthSchemeConfig(
269276
});
270277
}
271278

279+
/**
280+
* Get auth schemes for a {@link LanguageTarget}.
281+
*
282+
* Criteria for an auth scheme is:
283+
* - Auth scheme must be registered
284+
* - Identity Provider must be provided for target
285+
* - Signer must be provided for target
286+
*
287+
* @param target target platform
288+
* @param authIndex supported auth scheme index
289+
* @param serviceIndex service index
290+
* @return returns effective auth schemes for a particular target.
291+
*/
292+
private Map<ShapeId, HttpAuthScheme> getTargetAuthSchemes(
293+
LanguageTarget target,
294+
SupportedHttpAuthSchemesIndex authIndex,
295+
ServiceIndex serviceIndex
296+
) {
297+
return AuthUtils.getAllEffectiveNoAuthAwareAuthSchemes(this.service, serviceIndex, authIndex)
298+
.entrySet()
299+
.stream()
300+
.filter(e -> e.getValue() != null
301+
&& e.getValue().getDefaultIdentityProviders().get(target) != null
302+
&& e.getValue().getDefaultSigners().get(target) != null)
303+
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
304+
}
305+
306+
/**
307+
* Checks if auth schemes are equal based on {@link LanguageTarget}.
308+
*
309+
* Criteria for equivalent auth schemes are:
310+
*
311+
* - Must have the same auth schemes registered
312+
* - Registered auth scheme targets must have equivalent identity providers
313+
* - Registered auth scheme targets must have equivalent signers
314+
*
315+
* @param targetAuthSchemes target auth schemes
316+
* @param target target platform
317+
* @param inheritedAuthSchemes inherited auth schemes
318+
* @param inherited inherited platform
319+
* @return if auth schemes are equal.
320+
*/
321+
private boolean areAuthSchemesTargetEqual(
322+
Map<ShapeId, HttpAuthScheme> targetAuthSchemes,
323+
LanguageTarget target,
324+
Map<ShapeId, HttpAuthScheme> inheritedAuthSchemes,
325+
LanguageTarget inherited
326+
) {
327+
if (targetAuthSchemes.size() != inheritedAuthSchemes.size()) {
328+
return false;
329+
}
330+
for (var targetEntry : targetAuthSchemes.entrySet()) {
331+
var inheritedAuthScheme = inheritedAuthSchemes.get(targetEntry.getKey());
332+
if (inheritedAuthScheme == null) {
333+
return false;
334+
}
335+
if (targetEntry.getValue().getDefaultIdentityProviders().get(target)
336+
!= inheritedAuthScheme.getDefaultIdentityProviders().get(inherited)) {
337+
return false;
338+
}
339+
if (targetEntry.getValue().getDefaultSigners().get(target)
340+
!= inheritedAuthScheme.getDefaultSigners().get(inherited)) {
341+
return false;
342+
}
343+
}
344+
return true;
345+
}
346+
272347
private Map<String, Consumer<TypeScriptWriter>> getDefaultRuntimeConfigs(LanguageTarget target) {
273348
switch (target) {
274349
case NODE:

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,12 @@ private void generateClientDefaults() {
305305
// feat(experimentalIdentityAndAuth): write httpAuthSchemes and httpAuthSchemeProvider into ClientDefaults
306306
if (settings.getExperimentalIdentityAndAuth()) {
307307
writer.addDependency(TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH);
308-
writer.addImport("IdentityProviderConfiguration", null,
309-
TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH);
308+
writer.addImport("HttpAuthScheme", null, TypeScriptDependency.EXPERIMENTAL_IDENTITY_AND_AUTH);
310309
writer.writeDocs("""
311310
experimentalIdentityAndAuth: Configuration of HttpAuthSchemes for a client which provides \
312311
default identity providers and signers per auth scheme.
313312
@internal""");
314-
writer.write("httpAuthSchemes?: IdentityProviderConfiguration;\n");
313+
writer.write("httpAuthSchemes?: HttpAuthScheme[];\n");
315314

316315
String httpAuthSchemeProviderName = service.toShapeId().getName() + "HttpAuthSchemeProvider";
317316
writer.addRelativeImport(httpAuthSchemeProviderName, null, Paths.get(".",

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/auth/AuthUtils.java

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,41 @@
55

66
package software.amazon.smithy.typescript.codegen.auth;
77

8-
import java.util.function.Consumer;
8+
import java.util.Map;
9+
import java.util.TreeMap;
10+
import software.amazon.smithy.model.knowledge.ServiceIndex;
11+
import software.amazon.smithy.model.knowledge.ServiceIndex.AuthSchemeMode;
12+
import software.amazon.smithy.model.shapes.ServiceShape;
913
import software.amazon.smithy.model.shapes.ShapeId;
10-
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
14+
import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthScheme;
15+
import software.amazon.smithy.typescript.codegen.auth.http.SupportedHttpAuthSchemesIndex;
1116
import software.amazon.smithy.utils.SmithyInternalApi;
1217

1318
/**
1419
* Auth utility methods needed across Java packages.
1520
*/
1621
@SmithyInternalApi
1722
public final class AuthUtils {
18-
/**
19-
* Writes out `never`, which will make TypeScript compilation fail if used as a value.
20-
*/
21-
public static final Consumer<TypeScriptWriter> DEFAULT_NEVER_WRITER = w -> w.write("never");
22-
23-
/**
24-
* Should be replaced when the synthetic NoAuthTrait is released in Smithy.
25-
*/
26-
public static final ShapeId NO_AUTH_ID = ShapeId.from("smithy.api#noAuth");
27-
2823
private AuthUtils() {}
24+
25+
public static Map<ShapeId, HttpAuthScheme> getAllEffectiveNoAuthAwareAuthSchemes(
26+
ServiceShape serviceShape,
27+
ServiceIndex serviceIndex,
28+
SupportedHttpAuthSchemesIndex authIndex
29+
) {
30+
Map<ShapeId, HttpAuthScheme> effectiveAuthSchemes = new TreeMap<>();
31+
var serviceEffectiveAuthSchemes =
32+
serviceIndex.getEffectiveAuthSchemes(serviceShape, AuthSchemeMode.NO_AUTH_AWARE);
33+
for (ShapeId shapeId : serviceEffectiveAuthSchemes.keySet()) {
34+
effectiveAuthSchemes.put(shapeId, authIndex.getHttpAuthScheme(shapeId));
35+
}
36+
for (var operation : serviceShape.getAllOperations()) {
37+
var operationEffectiveAuthSchemes =
38+
serviceIndex.getEffectiveAuthSchemes(serviceShape, operation, AuthSchemeMode.NO_AUTH_AWARE);
39+
for (ShapeId shapeId : operationEffectiveAuthSchemes.keySet()) {
40+
effectiveAuthSchemes.put(shapeId, authIndex.getHttpAuthScheme(shapeId));
41+
}
42+
}
43+
return effectiveAuthSchemes;
44+
}
2945
}

0 commit comments

Comments
 (0)