Skip to content

Commit d3071d0

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 the `httpAuthSchemes` property on the client config interface, update it to `HttpAuthScheme[]` rather than `IdentityProviderConfiguration`.
1 parent 697310d commit d3071d0

File tree

8 files changed

+250
-149
lines changed

8 files changed

+250
-149
lines changed

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

Lines changed: 141 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@
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;
22-
import java.util.Map.Entry;
2323
import java.util.TreeMap;
2424
import java.util.function.Consumer;
25-
import java.util.stream.Collectors;
2625
import software.amazon.smithy.build.SmithyBuildException;
26+
import software.amazon.smithy.codegen.core.CodegenException;
2727
import software.amazon.smithy.codegen.core.SymbolProvider;
2828
import software.amazon.smithy.model.Model;
2929
import software.amazon.smithy.model.knowledge.ServiceIndex;
3030
import software.amazon.smithy.model.shapes.ServiceShape;
3131
import software.amazon.smithy.model.shapes.ShapeId;
32-
import software.amazon.smithy.model.traits.Trait;
3332
import software.amazon.smithy.typescript.codegen.auth.AuthUtils;
3433
import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthScheme;
3534
import software.amazon.smithy.typescript.codegen.auth.http.HttpAuthSchemeProviderGenerator;
@@ -208,59 +207,60 @@ private void generateHttpAuthSchemeConfig(
208207
TypeScriptWriter writer,
209208
LanguageTarget target
210209
) {
210+
// feat(experimentalIdentityAndAuth): write the default imported HttpAuthSchemeProvider
211+
if (target.equals(LanguageTarget.SHARED)) {
212+
configs.put("httpAuthSchemeProvider", w -> {
213+
String providerName = "default" + service.toShapeId().getName() + "HttpAuthSchemeProvider";
214+
w.addRelativeImport(providerName, null, Paths.get(".", CodegenUtils.SOURCE_FOLDER,
215+
HttpAuthSchemeProviderGenerator.HTTP_AUTH_FOLDER,
216+
HttpAuthSchemeProviderGenerator.HTTP_AUTH_SCHEME_RESOLVER_MODULE));
217+
w.write(providerName);
218+
});
219+
}
220+
211221
// feat(experimentalIdentityAndAuth): gather HttpAuthSchemes to generate
212-
SupportedHttpAuthSchemesIndex index = new SupportedHttpAuthSchemesIndex(integrations);
222+
SupportedHttpAuthSchemesIndex authIndex = new SupportedHttpAuthSchemesIndex(integrations);
213223
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) {
224+
Map<ShapeId, HttpAuthScheme> allEffectiveHttpAuthSchemes =
225+
AuthUtils.getAllEffectiveNoAuthAwareAuthSchemes(service, serviceIndex, authIndex);
226+
List<HttpAuthSchemeTarget> targetAuthSchemes = getHttpAuthSchemeTargets(target, allEffectiveHttpAuthSchemes);
227+
228+
// Generate only if the "inherited" target is different than the current target
229+
List<HttpAuthSchemeTarget> inheritedAuthSchemes = Collections.emptyList();
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 = getHttpAuthSchemeTargets(LanguageTarget.SHARED, allEffectiveHttpAuthSchemes);
236+
// REACT_NATIVE inherits from BROWSER
237+
} else if (target.equals(LanguageTarget.REACT_NATIVE)) {
238+
inheritedAuthSchemes = getHttpAuthSchemeTargets(LanguageTarget.BROWSER, allEffectiveHttpAuthSchemes);
239+
} else {
240+
throw new CodegenException("Unhandled Language Target: " + target);
241+
}
242+
243+
// If target and inherited auth schemes are equal, then don't generate target auth schemes.
244+
if (targetAuthSchemes.equals(inheritedAuthSchemes)) {
227245
return;
228246
}
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-
});
247+
237248
// feat(experimentalIdentityAndAuth): write the default IdentityProviderConfiguration with configured
238249
// HttpAuthSchemes
239250
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();
251+
w.openBlock("[", "]", () -> {
252+
Iterator<HttpAuthSchemeTarget> iter = targetAuthSchemes.iterator();
245253
while (iter.hasNext()) {
246-
Entry<ShapeId, HttpAuthScheme> entry = iter.next();
247-
// Default IdentityProvider or HttpSigner, otherwise write {@code never} to force a TypeScript
248-
// compilation failure.
249-
Consumer<TypeScriptWriter> defaultIdentityProvider = entry.getValue()
250-
.getDefaultIdentityProviders()
251-
.getOrDefault(target, AuthUtils.DEFAULT_NEVER_WRITER);
252-
Consumer<TypeScriptWriter> defaultSigner = entry.getValue()
253-
.getDefaultSigners()
254-
.getOrDefault(target, AuthUtils.DEFAULT_NEVER_WRITER);
254+
HttpAuthSchemeTarget entry = iter.next();
255255
w.write("""
256256
{
257257
schemeId: $S,
258258
identityProvider: $C,
259259
signer: $C,
260260
}""",
261-
entry.getKey(),
262-
defaultIdentityProvider,
263-
defaultSigner);
261+
entry.httpAuthScheme.getSchemeId(),
262+
entry.identityProvider,
263+
entry.signer);
264264
if (iter.hasNext()) {
265265
w.writeInline(", ");
266266
}
@@ -269,6 +269,105 @@ private void generateHttpAuthSchemeConfig(
269269
});
270270
}
271271

272+
private static class HttpAuthSchemeTarget {
273+
public HttpAuthScheme httpAuthScheme;
274+
public Consumer<TypeScriptWriter> identityProvider;
275+
public Consumer<TypeScriptWriter> signer;
276+
277+
HttpAuthSchemeTarget(
278+
HttpAuthScheme httpAuthScheme,
279+
Consumer<TypeScriptWriter> identityProvider,
280+
Consumer<TypeScriptWriter> signer
281+
) {
282+
this.httpAuthScheme = httpAuthScheme;
283+
this.identityProvider = identityProvider;
284+
this.signer = signer;
285+
}
286+
287+
@Override
288+
public boolean equals(Object other) {
289+
if (!(other instanceof HttpAuthSchemeTarget)) {
290+
return false;
291+
}
292+
HttpAuthSchemeTarget o = (HttpAuthSchemeTarget) other;
293+
return httpAuthScheme.equals(o.httpAuthScheme)
294+
&& identityProvider.equals(o.identityProvider)
295+
&& signer.equals(o.signer);
296+
}
297+
298+
@Override
299+
public int hashCode() {
300+
return super.hashCode();
301+
}
302+
}
303+
304+
private List<HttpAuthSchemeTarget> getHttpAuthSchemeTargets(
305+
LanguageTarget target,
306+
Map<ShapeId, HttpAuthScheme> httpAuthSchemes
307+
) {
308+
return getPartialHttpAuthSchemeTargets(target, httpAuthSchemes)
309+
.values()
310+
.stream()
311+
.filter(httpAuthSchemeTarget ->
312+
httpAuthSchemeTarget.identityProvider != null && httpAuthSchemeTarget.signer != null)
313+
.toList();
314+
}
315+
316+
private Map<ShapeId, HttpAuthSchemeTarget> getPartialHttpAuthSchemeTargets(
317+
LanguageTarget target,
318+
Map<ShapeId, HttpAuthScheme> httpAuthSchemes
319+
) {
320+
LanguageTarget inherited;
321+
if (target.equals(LanguageTarget.SHARED)) {
322+
// SHARED doesn't inherit any target, so inherited is null
323+
inherited = null;
324+
} else if (target.equals(LanguageTarget.NODE) || target.equals(LanguageTarget.BROWSER)) {
325+
inherited = LanguageTarget.SHARED;
326+
} else if (target.equals(LanguageTarget.REACT_NATIVE)) {
327+
inherited = LanguageTarget.BROWSER;
328+
} else {
329+
throw new CodegenException("Unsupported Language Target: " + target);
330+
}
331+
332+
Map<ShapeId, HttpAuthSchemeTarget> httpAuthSchemeTargets = inherited == null
333+
// SHARED inherits no HttpAuthSchemeTargets
334+
? new TreeMap<>()
335+
// Otherwise, get inherited HttpAuthSchemeTargets
336+
: getPartialHttpAuthSchemeTargets(inherited, httpAuthSchemes);
337+
338+
for (HttpAuthScheme httpAuthScheme : httpAuthSchemes.values()) {
339+
// If HttpAuthScheme is not registered, skip code generation
340+
if (httpAuthScheme == null) {
341+
continue;
342+
}
343+
344+
// Get identity provider and signer for the current target
345+
Consumer<TypeScriptWriter> identityProvider =
346+
httpAuthScheme.getDefaultIdentityProviders().get(target);
347+
Consumer<TypeScriptWriter> signer =
348+
httpAuthScheme.getDefaultSigners().get(target);
349+
350+
HttpAuthSchemeTarget existingEntry =
351+
httpAuthSchemeTargets.get(httpAuthScheme.getSchemeId());
352+
353+
// If HttpAuthScheme is not added yet, add the entry
354+
if (existingEntry == null) {
355+
httpAuthSchemeTargets.put(httpAuthScheme.getSchemeId(),
356+
new HttpAuthSchemeTarget(httpAuthScheme, identityProvider, signer));
357+
continue;
358+
}
359+
360+
// Mutate existing entry for identity provider and signer if available
361+
if (identityProvider != null) {
362+
existingEntry.identityProvider = identityProvider;
363+
}
364+
if (signer != null) {
365+
existingEntry.signer = signer;
366+
}
367+
}
368+
return httpAuthSchemeTargets;
369+
}
370+
272371
private Map<String, Consumer<TypeScriptWriter>> getDefaultRuntimeConfigs(LanguageTarget target) {
273372
switch (target) {
274373
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)