diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 000000000..5bdb70c9a --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,3 @@ +// Package auth defines protocol-agnostic authentication types for smithy +// clients. +package auth diff --git a/auth/identity.go b/auth/identity.go new file mode 100644 index 000000000..ba8cf70d4 --- /dev/null +++ b/auth/identity.go @@ -0,0 +1,47 @@ +package auth + +import ( + "context" + "time" + + "github.com/aws/smithy-go" +) + +// Identity contains information that identifies who the user making the +// request is. +type Identity interface { + Expiration() time.Time +} + +// IdentityResolver defines the interface through which an Identity is +// retrieved. +type IdentityResolver interface { + GetIdentity(context.Context, smithy.Properties) (Identity, error) +} + +// IdentityResolverOptions defines the interface through which an entity can be +// queried to retrieve an IdentityResolver for a given auth scheme. +type IdentityResolverOptions interface { + GetIdentityResolver(schemeID string) IdentityResolver +} + +// AnonymousIdentity is a sentinel to indicate no identity. +type AnonymousIdentity struct{} + +var _ Identity = (*AnonymousIdentity)(nil) + +// Expiration returns the zero value for time, as anonymous identity never +// expires. +func (*AnonymousIdentity) Expiration() time.Time { + return time.Time{} +} + +// AnonymousIdentityResolver returns AnonymousIdentity. +type AnonymousIdentityResolver struct{} + +var _ IdentityResolver = (*AnonymousIdentityResolver)(nil) + +// GetIdentity returns AnonymousIdentity. +func (*AnonymousIdentityResolver) GetIdentity(_ context.Context, _ smithy.Properties) (Identity, error) { + return &AnonymousIdentity{}, nil +} diff --git a/auth/option.go b/auth/option.go new file mode 100644 index 000000000..d5dabff04 --- /dev/null +++ b/auth/option.go @@ -0,0 +1,25 @@ +package auth + +import "github.com/aws/smithy-go" + +type ( + authOptionsKey struct{} +) + +// Option represents a possible authentication method for an operation. +type Option struct { + SchemeID string + IdentityProperties smithy.Properties + SignerProperties smithy.Properties +} + +// GetAuthOptions gets auth Options from Properties. +func GetAuthOptions(p *smithy.Properties) ([]*Option, bool) { + v, ok := p.Get(authOptionsKey{}).([]*Option) + return v, ok +} + +// SetAuthOptions sets auth Options on Properties. +func SetAuthOptions(p *smithy.Properties, options []*Option) { + p.Set(authOptionsKey{}, options) +} diff --git a/auth/scheme_id.go b/auth/scheme_id.go new file mode 100644 index 000000000..fb6a57c64 --- /dev/null +++ b/auth/scheme_id.go @@ -0,0 +1,20 @@ +package auth + +// Anonymous +const ( + SchemeIDAnonymous = "smithy.api#noAuth" +) + +// HTTP auth schemes +const ( + SchemeIDHTTPBasic = "smithy.api#httpBasicAuth" + SchemeIDHTTPDigest = "smithy.api#httpDigestAuth" + SchemeIDHTTPBearer = "smithy.api#httpBearerAuth" + SchemeIDHTTPAPIKey = "smithy.api#httpApiKeyAuth" +) + +// AWS auth schemes +const ( + SchemeIDSigV4 = "aws.auth#sigv4" + SchemeIDSigV4A = "aws.auth#sigv4a" +) diff --git a/codegen/smithy-go-codegen/build.gradle.kts b/codegen/smithy-go-codegen/build.gradle.kts index 8ff377a42..c8e49f11b 100644 --- a/codegen/smithy-go-codegen/build.gradle.kts +++ b/codegen/smithy-go-codegen/build.gradle.kts @@ -21,6 +21,7 @@ extra["moduleName"] = "software.amazon.smithy.go.codegen" dependencies { api("software.amazon.smithy:smithy-codegen-core:$smithyVersion") + api("software.amazon.smithy:smithy-aws-traits:$smithyVersion") implementation("software.amazon.smithy:smithy-waiters:$smithyVersion") api("com.atlassian.commonmark:commonmark:0.15.2") api("org.jsoup:jsoup:1.14.1") diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ClientOptions.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ClientOptions.java new file mode 100644 index 000000000..ba4b59482 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ClientOptions.java @@ -0,0 +1,238 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen; + +import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import software.amazon.smithy.go.codegen.auth.AuthSchemeResolverGenerator; +import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; +import software.amazon.smithy.go.codegen.integration.ConfigField; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.go.codegen.integration.auth.AnonymousDefinition; +import software.amazon.smithy.model.knowledge.ServiceIndex; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.synthetic.NoAuthTrait; +import software.amazon.smithy.utils.MapUtils; + +/** + * Implements codegen for service client config. + */ +public class ClientOptions implements GoWriter.Writable { + public static final String NAME = "Options"; + + private final ProtocolGenerator.GenerationContext context; + private final ApplicationProtocol protocol; + + private final List fields; + private final Map authSchemes; + + public ClientOptions(ProtocolGenerator.GenerationContext context, ApplicationProtocol protocol) { + this.context = context; + this.protocol = protocol; + + this.fields = context.getIntegrations().stream() + .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) + .flatMap(it -> it.getConfigFields().stream()) + .distinct() + .sorted(Comparator.comparing(ConfigField::getName)) + .toList(); + this.authSchemes = context.getIntegrations().stream() + .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) + .flatMap(it -> it.getAuthSchemeDefinitions().entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + public void accept(GoWriter writer) { + writer.write(generate()); + } + + private GoWriter.Writable generate() { + var apiOptionsDocs = goDocTemplate( + "Set of options to modify how an operation is invoked. These apply to all operations " + + "invoked for this client. Use functional options on operation call to modify this " + + "list for per operation behavior." + ); + return goTemplate(""" + $protocolTypes:W + + type $options:L struct { + $apiOptionsDocs:W + APIOptions []func($stack:P) error + + $fields:W + + $protocolFields:W + } + + $copy:W + + $getIdentityResolver:W + + $helpers:W + """, + MapUtils.of( + "protocolTypes", generateProtocolTypes(), + "apiOptionsDocs", apiOptionsDocs, + "options", NAME, + "stack", SmithyGoTypes.Middleware.Stack, + "fields", GoWriter.ChainWritable.of(fields.stream().map(this::writeField).toList()).compose(), + "protocolFields", generateProtocolFields(), + "copy", generateCopy(), + "getIdentityResolver", generateGetIdentityResolver(), + "helpers", generateHelpers() + )); + } + + private GoWriter.Writable generateProtocolTypes() { + ensureSupportedProtocol(); + return goTemplate(""" + type HTTPClient interface { + Do($P) ($P, error) + } + """, GoStdlibTypes.Net.Http.Request, GoStdlibTypes.Net.Http.Response); + } + + private GoWriter.Writable writeField(ConfigField field) { + GoWriter.Writable docs = writer -> { + field.getDocumentation().ifPresent(writer::writeDocs); + field.getDeprecated().ifPresent(s -> { + if (field.getDocumentation().isPresent()) { + writer.writeDocs(""); + } + writer.writeDocs(String.format("Deprecated: %s", s)); + }); + }; + return goTemplate(""" + $W + $L $P + """, docs, field.getName(), field.getType()); + } + + private GoWriter.Writable generateProtocolFields() { + ensureSupportedProtocol(); + return goTemplate(""" + $1W + HTTPClient HTTPClient + + $2W + AuthSchemeResolver $4L + + $3W + AuthSchemes []$5T + """, + goDocTemplate("The HTTP client to invoke API calls with. " + + "Defaults to client's default HTTP implementation if nil."), + goDocTemplate("The auth scheme resolver which determines how to authenticate for each operation."), + goDocTemplate("The list of auth schemes supported by the client."), + AuthSchemeResolverGenerator.INTERFACE_NAME, + SmithyGoTypes.Transport.Http.AuthScheme); + } + + private GoWriter.Writable generateCopy() { + return goTemplate(""" + // Copy creates a clone where the APIOptions list is deep copied. + func (o $1L) Copy() $1L { + to := o + to.APIOptions = make([]func($2P) error, len(o.APIOptions)) + copy(to.APIOptions, o.APIOptions) + + return to + } + """, NAME, SmithyGoTypes.Middleware.Stack); + } + + private GoWriter.Writable generateGetIdentityResolver() { + return goTemplate(""" + func (o $L) GetIdentityResolver(schemeID string) $T { + $W + $W + return nil + } + """, + NAME, + SmithyGoTypes.Auth.IdentityResolver, + GoWriter.ChainWritable.of( + ServiceIndex.of(context.getModel()) + .getEffectiveAuthSchemes(context.getService()).keySet().stream() + .filter(authSchemes::containsKey) + .map(trait -> generateGetIdentityResolverMapping(trait, authSchemes.get(trait))) + .toList() + ).compose(false), + generateGetIdentityResolverMapping(NoAuthTrait.ID, new AnonymousDefinition())); + } + + private GoWriter.Writable generateGetIdentityResolverMapping(ShapeId schemeId, AuthSchemeDefinition scheme) { + return goTemplate(""" + if schemeID == $S { + return $W + }""", schemeId.toString(), scheme.generateOptionsIdentityResolver()); + } + + private GoWriter.Writable generateHelpers() { + return writer -> { + writer.write(""" + $W + func WithAPIOptions(optFns ...func($P) error) func(*Options) { + return func (o *Options) { + o.APIOptions = append(o.APIOptions, optFns...) + } + } + """, + goDocTemplate( + "WithAPIOptions returns a functional option for setting the Client's APIOptions option." + ), + SmithyGoTypes.Middleware.Stack); + + fields.stream().filter(ConfigField::getWithHelper).filter(ConfigField::isDeprecated) + .forEach(configField -> { + writer.writeDocs(configField.getDeprecated().get()); + writeHelper(writer, configField); + }); + + fields.stream().filter(ConfigField::getWithHelper).filter(Predicate.not(ConfigField::isDeprecated)) + .forEach(configField -> { + writer.writeDocs( + String.format( + "With%s returns a functional option for setting the Client's %s option.", + configField.getName(), configField.getName())); + writeHelper(writer, configField); + }); + }; + } + + private void writeHelper(GoWriter writer, ConfigField configField) { + writer.write(""" + func With$1L(v $2P) func(*Options) { + return func(o *Options) { + o.$1L = v + } + } + """, configField.getName(), configField.getType()); + } + + private void ensureSupportedProtocol() { + if (!protocol.isHttpProtocol()) { + throw new UnsupportedOperationException("Protocols other than HTTP are not yet implemented: " + protocol); + } + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java index 91e68b14e..ee26839c4 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java @@ -247,6 +247,11 @@ void execute() { protocolGenerator.generateEndpointResolution(context); }); + writers.useFileWriter("auth.go", settings.getModuleName(), writer -> { + ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); + protocolGenerator.generateAuth(context); + }); + writers.useFileWriter("endpoints_test.go", settings.getModuleName(), writer -> { ProtocolGenerator.GenerationContext context = contextBuilder.writer(writer).build(); protocolGenerator.generateEndpointResolutionTests(context); @@ -315,6 +320,19 @@ public Void serviceShape(ServiceShape shape) { return null; } + var protocol = protocolGenerator != null + ? protocolGenerator.getApplicationProtocol() + : ApplicationProtocol.createDefaultHttpApplicationProtocol(); + var context = ProtocolGenerator.GenerationContext.builder() + .protocolName(protocol.getName()) + .integrations(integrations) + .model(model) + .service(service) + .settings(settings) + .symbolProvider(symbolProvider) + .delegator(writers) + .build(); + // Write API client's package doc for the service. writers.useFileWriter("doc.go", settings.getModuleName(), (writer) -> { writer.writePackageDocs(String.format( @@ -344,6 +362,9 @@ public Void serviceShape(ServiceShape shape) { protocolGenerator, runtimePlugins).run()); } }); + + var clientOptions = new ClientOptions(context, protocol); + writers.useFileWriter("options.go", settings.getModuleName(), clientOptions); return null; } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoDependency.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoDependency.java index 882c1986d..8de9745ce 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoDependency.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoDependency.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.TreeSet; import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.codegen.core.SymbolDependencyContainer; import software.amazon.smithy.utils.SetUtils; @@ -146,6 +147,24 @@ public String getVersion() { return version; } + /** + * Creates a Symbol for a name exported by this package. + * @param name The name. + * @return The symbol. + */ + public Symbol valueSymbol(String name) { + return SymbolUtils.createValueSymbolBuilder(name, this).build(); + } + + /** + * Creates a pointable Symbol for a name exported by this package. + * @param name The name. + * @return The symbol. + */ + public Symbol pointableSymbol(String name) { + return SymbolUtils.createPointableSymbolBuilder(name, this).build(); + } + @Override public List getDependencies() { Set symbolDependencySet = new TreeSet<>(SetUtils.of(getSymbolDependency())); diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoStackStepMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoStackStepMiddlewareGenerator.java index a53816e99..87a087cdb 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoStackStepMiddlewareGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoStackStepMiddlewareGenerator.java @@ -15,6 +15,8 @@ package software.amazon.smithy.go.codegen; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + import java.util.function.BiConsumer; import software.amazon.smithy.codegen.core.Symbol; @@ -65,6 +67,28 @@ public static GoStackStepMiddlewareGenerator createInitializeStepMiddleware(Stri .build()); } + /** + * Create an inline Initialize func. + * + * @param body is the function body. + * @return the generated middleware func. + */ + public static GoWriter.Writable generateInitializeMiddlewareFunc(GoWriter.Writable body) { + return goTemplate(""" + func(ctx $T, in $T, next $T) ( + out $T, metadata $T, err error, + ) { + $W + } + """, + GoStdlibTypes.Context.Context, + SmithyGoTypes.Middleware.InitializeInput, + SmithyGoTypes.Middleware.InitializeHandler, + SmithyGoTypes.Middleware.InitializeOutput, + SmithyGoTypes.Middleware.Metadata, + body); + } + /** * Create a new BuildStep middleware generator with the provided type name. * @@ -97,6 +121,44 @@ public static GoStackStepMiddlewareGenerator createSerializeStepMiddleware(Strin SymbolUtils.createValueSymbolBuilder("SerializeHandler", SmithyGoDependency.SMITHY_MIDDLEWARE).build()); } + /** + * Create a new FinalizeStep middleware generator with the provided type name. + * + * @param name is the type name to identify the middleware. + * @param id the unique ID for the middleware. + * @return the middleware generator. + */ + public static GoStackStepMiddlewareGenerator createFinalizeStepMiddleware(String name, MiddlewareIdentifier id) { + return createMiddleware(name, + id, + "HandleFinalize", + SmithyGoTypes.Middleware.FinalizeInput, + SmithyGoTypes.Middleware.FinalizeOutput, + SmithyGoTypes.Middleware.FinalizeHandler); + } + + /** + * Create an inline Finalize func. + * + * @param body is the function body. + * @return the generated middleware func. + */ + public static GoWriter.Writable generateFinalizeMiddlewareFunc(GoWriter.Writable body) { + return goTemplate(""" + func(ctx $T, in $T, next $T) ( + out $T, metadata $T, err error, + ) { + $W + } + """, + GoStdlibTypes.Context.Context, + SmithyGoTypes.Middleware.FinalizeInput, + SmithyGoTypes.Middleware.FinalizeHandler, + SmithyGoTypes.Middleware.FinalizeOutput, + SmithyGoTypes.Middleware.Metadata, + body); + } + /** * Create a new DeserializeStep middleware generator with the provided type name. * @@ -216,6 +278,20 @@ public void writeMiddleware( }); } + /** + * Creates a Writable which renders the middleware. + * @param body A Writable that renders the middleware body. + * @param fields A Writable that renders the middleware struct's fields. + * @return the writable. + */ + public GoWriter.Writable asWritable(GoWriter.Writable body, GoWriter.Writable fields) { + return writer -> writeMiddleware( + writer, + (generator, bodyWriter) -> bodyWriter.write("$W", body), + (generator, fieldWriter) -> fieldWriter.write("$W", fields) + ); + } + /** * Returns a new middleware generator builder. * diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoStdlibTypes.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoStdlibTypes.java new file mode 100644 index 000000000..f818ce7d1 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoStdlibTypes.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen; + +import software.amazon.smithy.codegen.core.Symbol; + +/** + * Collection of Symbol constants for types in the go standard library. + */ +@SuppressWarnings({"checkstyle:ConstantName", "checkstyle:LineLength"}) +public final class GoStdlibTypes { + private GoStdlibTypes() { } + + public static final class Context { + public static final Symbol Context = SmithyGoDependency.CONTEXT.valueSymbol("Context"); + } + + public static final class Fmt { + public static final Symbol Errorf = SmithyGoDependency.FMT.valueSymbol("Errorf"); + } + + public static final class Net { + public static final class Http { + public static final Symbol Request = SmithyGoDependency.NET_HTTP.pointableSymbol("Request"); + public static final Symbol Response = SmithyGoDependency.NET_HTTP.pointableSymbol("Response"); + } + } + + public static final class Path { + public static final Symbol Join = SmithyGoDependency.PATH.valueSymbol("Join"); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoUniverseTypes.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoUniverseTypes.java index 6300dc0dc..0482f8b71 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoUniverseTypes.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoUniverseTypes.java @@ -21,32 +21,33 @@ * Collection of Symbol constants for golang universe types. * See predeclared identifiers. */ +@SuppressWarnings("checkstyle:ConstantName") public final class GoUniverseTypes { - public static Symbol tAny = universe("any"); - public static Symbol tBool = universe("bool"); - public static Symbol tByte = universe("byte"); - public static Symbol tComparable = universe("comparable"); - - public static Symbol tComplex64 = universe("complex64"); - public static Symbol tComplex128 = universe("complex128"); - public static Symbol tError = universe("error"); - public static Symbol tFloat32 = universe("float32"); - public static Symbol tFloat64 = universe("float64"); - - public static Symbol tInt = universe("int"); - public static Symbol tInt8 = universe("int8"); - public static Symbol tInt16 = universe("int16"); - public static Symbol tInt32 = universe("int32"); - public static Symbol tInt64 = universe("int64"); - public static Symbol tRune = universe("rune"); - public static Symbol tString = universe("string"); - - public static Symbol tUint = universe("uint"); - public static Symbol tUint8 = universe("uint8"); - public static Symbol tUint16 = universe("uint16"); - public static Symbol tUint32 = universe("uint32"); - public static Symbol tUint64 = universe("uint64"); - public static Symbol tUintptr = universe("uintptr"); + public static final Symbol Any = universe("any"); + public static final Symbol Bool = universe("bool"); + public static final Symbol Byte = universe("byte"); + public static final Symbol Comparable = universe("comparable"); + + public static final Symbol Complex64 = universe("complex64"); + public static final Symbol Complex128 = universe("complex128"); + public static final Symbol Error = universe("error"); + public static final Symbol Float32 = universe("float32"); + public static final Symbol Float64 = universe("float64"); + + public static final Symbol Int = universe("int"); + public static final Symbol Int8 = universe("int8"); + public static final Symbol Int16 = universe("int16"); + public static final Symbol Int32 = universe("int32"); + public static final Symbol Int64 = universe("int64"); + public static final Symbol Rune = universe("rune"); + public static final Symbol String = universe("string"); + + public static final Symbol Uint = universe("uint"); + public static final Symbol Uint8 = universe("uint8"); + public static final Symbol Uint16 = universe("uint16"); + public static final Symbol Uint32 = universe("uint32"); + public static final Symbol Uint64 = universe("uint64"); + public static final Symbol Uintptr = universe("uintptr"); private GoUniverseTypes() {} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java index 852ab3d58..195113c2b 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoWriter.java @@ -46,6 +46,7 @@ import software.amazon.smithy.model.traits.RequiredTrait; import software.amazon.smithy.model.traits.StringTrait; import software.amazon.smithy.utils.AbstractCodeWriter; +import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.StringUtils; /** @@ -136,6 +137,17 @@ public static Writable goTemplate(String contents, Map... args) }; } + /** + * Returns a Writable for the string and args to be composed inline to another writer's contents. + * + * @param content string to write. + * @param args Arguments to use when evaluating the contents string. + * @return Writable to be evaluated. + */ + public static Writable goTemplate(Object content, Object... args) { + return writer -> writer.write(content, args); + } + public static Writable goDocTemplate(String contents) { return goDocTemplate(contents, new HashMap<>()); } @@ -842,6 +854,10 @@ private boolean isTargetDeprecated(Model model, MemberShape member) { && !Prelude.isPreludeShape(member.getTarget()); } + public void write(Writable w) { + write("$W", w); + } + @Override public String toString() { String contents = super.toString(); @@ -987,6 +1003,22 @@ public ChainWritable() { writables = new ArrayList<>(); } + public static ChainWritable of(GoWriter.Writable... writables) { + var chain = new ChainWritable(); + chain.writables.addAll(ListUtils.of(writables)); + return chain; + } + + public static ChainWritable of(List writables) { + var chain = new ChainWritable(); + chain.writables.addAll(writables); + return chain; + } + + public boolean isEmpty() { + return writables.isEmpty(); + } + public ChainWritable add(GoWriter.Writable writable) { writables.add(writable); return this; @@ -1004,11 +1036,11 @@ public ChainWritable add(boolean include, GoWriter.Writable writable) { return this; } - public GoWriter.Writable compose() { + public GoWriter.Writable compose(boolean writeNewlines) { return (GoWriter writer) -> { var hasPrevious = false; for (GoWriter.Writable writable : writables) { - if (hasPrevious) { + if (hasPrevious && writeNewlines) { writer.write(""); } hasPrevious = true; @@ -1016,5 +1048,9 @@ public GoWriter.Writable compose() { } }; } + + public GoWriter.Writable compose() { + return compose(true); + } } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java index 6cd5b704f..74443210c 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java @@ -22,6 +22,7 @@ import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; +import software.amazon.smithy.go.codegen.endpoints.EndpointParameterOperationBindingsGenerator; import software.amazon.smithy.go.codegen.integration.MiddlewareRegistrar; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; @@ -130,6 +131,12 @@ public void run() { .renderStructure(() -> { }, true); + writer.write(""" + $W + """, + new EndpointParameterOperationBindingsGenerator(operation, inputShape, inputSymbol).generate() + ); + // The output structure gets a metadata member added. Symbol metadataSymbol = SymbolUtils.createValueSymbolBuilder("Metadata", SmithyGoDependency.SMITHY_MIDDLEWARE) .build(); @@ -194,8 +201,6 @@ private void generateAddOperationMiddleware() { MiddlewareRegistrar middlewareRegistrar = runtimeClientPlugin.registerMiddleware().get(); Collection functionArguments = middlewareRegistrar.getFunctionArguments(); - // TODO these functions do not all return err like they should. This should be fixed. - // TODO Must be fixed for all public functions. if (middlewareRegistrar.getInlineRegisterMiddlewareStatement() != null) { String registerStatement = String.format("if err = stack.%s", middlewareRegistrar.getInlineRegisterMiddlewareStatement()); @@ -235,6 +240,12 @@ private void generateOperationProtocolMiddlewareAdders() { } writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE); + // persist operation input to context for internal build/finalize middleware access + writer.write(""" + if err := stack.Serialize.Add(&setOperationInputMiddleware{}, middleware.After); err != nil { + return err + }"""); + // Add request serializer middleware String serializerMiddlewareName = ProtocolGenerator.getSerializeMiddlewareName( operation.getId(), service, protocolGenerator.getProtocolName()); @@ -246,6 +257,15 @@ private void generateOperationProtocolMiddlewareAdders() { operation.getId(), service, protocolGenerator.getProtocolName()); writer.write("err = stack.Deserialize.Add(&$L{}, middleware.After)", deserializerMiddlewareName); writer.write("if err != nil { return err }"); + + // FUTURE: retry middleware should be at the front of finalize, right now it's added by the SDK + writer.write(""" + if err := addProtocolFinalizerMiddlewares(stack, options, $S); err != nil { + return $T("add protocol finalizers: %v", err) + }""", + operationSymbol.getName(), + GoStdlibTypes.Fmt.Errorf); + writer.write(""); } /** diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java index 6a0454e08..055329971 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java @@ -15,24 +15,36 @@ package software.amazon.smithy.go.codegen; +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.function.Predicate; +import java.util.Map; import java.util.stream.Collectors; -import software.amazon.smithy.codegen.core.Symbol; +import java.util.stream.Stream; import software.amazon.smithy.codegen.core.SymbolProvider; +import software.amazon.smithy.go.codegen.auth.AuthSchemeResolverGenerator; +import software.amazon.smithy.go.codegen.auth.GetIdentityMiddlewareGenerator; +import software.amazon.smithy.go.codegen.auth.ResolveAuthSchemeMiddlewareGenerator; +import software.amazon.smithy.go.codegen.auth.SignRequestMiddlewareGenerator; +import software.amazon.smithy.go.codegen.endpoints.EndpointMiddlewareGenerator; +import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; import software.amazon.smithy.go.codegen.integration.ClientMember; import software.amazon.smithy.go.codegen.integration.ClientMemberResolver; -import software.amazon.smithy.go.codegen.integration.ConfigField; import software.amazon.smithy.go.codegen.integration.ConfigFieldResolver; import software.amazon.smithy.go.codegen.integration.GoIntegration; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.ServiceIndex; import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.MapUtils; /** - * Generates a service client and configuration. + * Generates a service client, its constructors, and core supporting logic. */ final class ServiceGenerator implements Runnable { @@ -46,6 +58,7 @@ final class ServiceGenerator implements Runnable { private final List integrations; private final List runtimePlugins; private final ApplicationProtocol applicationProtocol; + private final Map authSchemes; ServiceGenerator( GoSettings settings, @@ -65,203 +78,170 @@ final class ServiceGenerator implements Runnable { this.integrations = integrations; this.runtimePlugins = runtimePlugins; this.applicationProtocol = applicationProtocol; + this.authSchemes = integrations.stream() + .flatMap(it -> it.getClientPlugins(model, service).stream()) + .flatMap(it -> it.getAuthSchemeDefinitions().entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override public void run() { - String serviceId = settings.getService().toString(); - for (GoIntegration integration : integrations) { + writer.write(generate()); + writeProtocolResolverImpls(); + } + + private GoWriter.Writable generate() { + return GoWriter.ChainWritable.of( + generateMetadata(), + generateClient(), + generateNew(), + generateInvokeOperation(), + generateInputContextFuncs(), + generateAddProtocolFinalizerMiddleware() + ).compose(); + } + + private GoWriter.Writable generateMetadata() { + var serviceId = settings.getService().toString(); + for (var integration : integrations) { serviceId = integration.processServiceId(settings, model, serviceId); } - writer.write("const ServiceID = $S", serviceId); - writer.write("const ServiceAPIVersion = $S", service.getVersion()); - writer.write(""); - - Symbol serviceSymbol = symbolProvider.toSymbol(service); - writer.writeDocs(String.format("%s provides the API client to make operations call for %s.", - serviceSymbol.getName(), - CodegenUtils.getServiceTitle(service, "the API"))); - writer.openBlock("type $T struct {", "}", serviceSymbol, () -> { - writer.write("options $L", CONFIG_NAME); - - // Add client members resolved from runtime plugins to the client struct. - for (ClientMember clientMember : getAllClientMembers()) { - writer.write(""); - clientMember.getDocumentation().ifPresent(writer::writeDocs); - writer.write("$L $P", clientMember.getName(), clientMember.getType()); - } - }); + return goTemplate(""" + const ServiceID = $S + const ServiceAPIVersion = $S + """, serviceId, service.getVersion()); + } - generateConstructor(serviceSymbol); - generateConfig(); - generateClientInvokeOperation(); + private GoWriter.Writable generateClient() { + return goTemplate(""" + $W + type $T struct { + options $L + + $W + } + """, + generateClientDocs(), + symbolProvider.toSymbol(service), + CONFIG_NAME, + GoWriter.ChainWritable.of( + getAllClientMembers().stream() + .map(this::generateClientMember) + .toList() + ).compose()); } - private void writeClientMemberResolvers( - GoWriter writer, - RuntimeClientPlugin plugin, - Predicate predicate - ) { - plugin.getClientMemberResolvers().stream().filter(predicate) - .forEach(resolver -> { - writer.write("$T(client)", resolver.getResolver()); - writer.write(""); - }); + private GoWriter.Writable generateClientDocs() { + return writer -> + writer.writeDocs(String.format( + "%s provides the API client to make operations call for %s.", + symbolProvider.toSymbol(service).getName(), + CodegenUtils.getServiceTitle(service, "the API") + )); } - private void writeConfigFieldResolvers( - GoWriter writer, - RuntimeClientPlugin plugin, - Predicate predicate - ) { - plugin.getConfigFieldResolvers().stream().filter(predicate) - .forEach(resolver -> { - writer.writeInline("$T(&options", resolver.getResolver()); - if (resolver.isWithOperationName()) { - writer.writeInline(", opID"); - } - if (resolver.isWithClientInput()) { - if (resolver.getLocation() == ConfigFieldResolver.Location.CLIENT) { - writer.writeInline(", client"); - } else { - writer.writeInline(", *c"); - } - } - writer.write(")"); - writer.write(""); - }); + private GoWriter.Writable generateClientMember(ClientMember member) { + return goTemplate(""" + $W + $L $P + """, + member.getDocumentation().isPresent() + ? goDocTemplate(member.getDocumentation().get()) + : emptyGoTemplate(), + member.getName(), + member.getType()); } - private void generateConstructor(Symbol serviceSymbol) { - writer.writeDocs(String.format("New returns an initialized %s based on the functional options. " - + "Provide additional functional options to further configure the behavior " - + "of the client, such as changing the client's endpoint or adding custom " - + "middleware behavior.", serviceSymbol.getName())); - Symbol optionsSymbol = SymbolUtils.createPointableSymbolBuilder(CONFIG_NAME).build(); - writer.openBlock("func New(options $T, optFns ...func($P)) $P {", "}", optionsSymbol, optionsSymbol, - serviceSymbol, () -> { - writer.write("options = options.Copy()").write(""); - - List plugins = runtimePlugins.stream().filter(plugin -> - plugin.matchesService(model, service)) - .collect(Collectors.toList()); - - // Run any config initialization functions registered by runtime plugins. - for (RuntimeClientPlugin plugin : plugins) { - writeConfigFieldResolvers(writer, plugin, resolver -> - resolver.getLocation() == ConfigFieldResolver.Location.CLIENT - && resolver.getTarget() == ConfigFieldResolver.Target.INITIALIZATION); + private GoWriter.Writable generateNew() { + var plugins = runtimePlugins.stream() + .filter(it -> it.matchesService(model, service)) + .toList(); + var serviceSymbol = symbolProvider.toSymbol(service); + var docs = goDocTemplate( + "New returns an initialized $name:L based on the functional options. Provide " + + "additional functional options to further configure the behavior of the client, such as changing the " + + "client's endpoint or adding custom middleware behavior.", + MapUtils.of("name", serviceSymbol.getName())); + return goTemplate(""" + $docs:W + func New(options $options:L, optFns ...func(*$options:L)) *$client:L { + options = options.Copy() + + $resolvers:W + + $protocolResolvers:W + + for _, fn := range optFns { + fn(&options) } - writer.openBlock("for _, fn := range optFns {", "}", () -> writer.write("fn(&options)")); - writer.write(""); + $finalizers:W - writer.openBlock("client := &$T{", "}", serviceSymbol, () -> { - writer.write("options: options,"); - }).write(""); + $protocolFinalizers:W - // Run any config finalization functions registered by runtime plugins. - for (RuntimeClientPlugin plugin : plugins) { - writeConfigFieldResolvers(writer, plugin, resolver -> - resolver.getLocation() == ConfigFieldResolver.Location.CLIENT - && resolver.getTarget() == ConfigFieldResolver.Target.FINALIZATION); + client := &$client:L{ + options: options, } - // Run any client member resolver functions registered by runtime plugins. - for (RuntimeClientPlugin plugin : plugins) { - writeClientMemberResolvers(writer, plugin, resolver -> true); - } - - writer.write("return client"); - }); + $withClientFinalizers:W + + $clientMemberResolvers:W + + return client + } + """, MapUtils.of( + "docs", docs, + "options", CONFIG_NAME, + "client", serviceSymbol.getName(), + "protocolResolvers", generateProtocolResolvers(), + "protocolFinalizers", generateProtocolFinalizers(), + "resolvers", GoWriter.ChainWritable.of( + getConfigResolvers( + ConfigFieldResolver.Location.CLIENT, + ConfigFieldResolver.Target.INITIALIZATION + ).map(this::generateConfigFieldResolver).toList() + ).compose(), + "finalizers", GoWriter.ChainWritable.of( + getConfigResolvers( + ConfigFieldResolver.Location.CLIENT, + ConfigFieldResolver.Target.FINALIZATION + ).map(this::generateConfigFieldResolver).toList() + ).compose(), + "withClientFinalizers", GoWriter.ChainWritable.of( + getConfigResolvers( + ConfigFieldResolver.Location.CLIENT, + ConfigFieldResolver.Target.FINALIZATION_WITH_CLIENT + ).map(this::generateConfigFieldResolver).toList() + ).compose(), + "clientMemberResolvers", GoWriter.ChainWritable.of( + plugins.stream() + .flatMap(it -> it.getClientMemberResolvers().stream()) + .map(this::generateClientMemberResolver) + .toList() + ).compose() + )); } - private void generateConfig() { - writer.openBlock("type $L struct {", "}", CONFIG_NAME, () -> { - writer.writeDocs("Set of options to modify how an operation is invoked. These apply to all operations " - + "invoked for this client. Use functional options on operation call to modify this " - + "list for per operation behavior." - ); - Symbol stackSymbol = SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE) - .build(); - writer.write("APIOptions []func($P) error", stackSymbol).write(""); - - // Add config fields to the options struct. - for (ConfigField configField : getAllConfigFields()) { - configField.getDocumentation().ifPresent(writer::writeDocs); - configField.getDeprecated().ifPresent(s -> { - if (configField.getDocumentation().isPresent()) { - writer.writeDocs(""); + private GoWriter.Writable generateConfigFieldResolver(ConfigFieldResolver resolver) { + return writer -> { + writer.writeInline("$T(&options", resolver.getResolver()); + if (resolver.isWithOperationName()) { + writer.writeInline(", opID"); + } + if (resolver.isWithClientInput()) { + if (resolver.getLocation() == ConfigFieldResolver.Location.CLIENT) { + writer.writeInline(", client"); + } else { + writer.writeInline(", *c"); } - writer.writeDocs(String.format("Deprecated: %s", s)); - }); - writer.write("$L $P", configField.getName(), configField.getType()); - writer.write(""); - } - - generateApplicationProtocolConfig(); - }).write(""); - - writer.writeDocs("WithAPIOptions returns a functional option for setting the Client's APIOptions option."); - writer.openBlock("func WithAPIOptions(optFns ...func(*middleware.Stack) error) func(*Options) {", "}", () -> { - writer.openBlock("return func(o *Options) {", "}", () -> { - writer.write("o.APIOptions = append(o.APIOptions, optFns...)"); - }); - }); - - getAllConfigFields().stream().filter(ConfigField::getWithHelper).filter(ConfigField::isDeprecated) - .forEach(configField -> { - writer.writeDocs(configField.getDeprecated().get()); - writeWithHelperFunction(writer, configField); - }); - - getAllConfigFields().stream().filter(ConfigField::getWithHelper).filter( - Predicate.not(ConfigField::isDeprecated)) - .forEach(configField -> { - writer.writeDocs( - String.format( - "With%s returns a functional option for setting the Client's %s option.", - configField.getName(), configField.getName())); - writeWithHelperFunction(writer, configField); - - }); - - generateApplicationProtocolTypes(); - - writer.writeDocs("Copy creates a clone where the APIOptions list is deep copied."); - writer.openBlock("func (o $L) Copy() $L {", "}", CONFIG_NAME, CONFIG_NAME, () -> { - writer.write("to := o"); - Symbol stackSymbol = SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE) - .build(); - writer.write("to.APIOptions = make([]func($P) error, len(o.APIOptions))", stackSymbol); - writer.write("copy(to.APIOptions, o.APIOptions)").write(""); - writer.write("return to"); - }); + } + writer.write(")"); + }; } - private void writeWithHelperFunction(GoWriter writer, ConfigField configField) { - writer.openBlock("func With$L(v $P) func(*Options) {", "}", configField.getName(), - configField.getType(), - () -> { - writer.openBlock("return func(o *Options) {", "}", () -> { - writer.write("o.$L = v", configField.getName()); - }); - }).write(""); - } - - private List getAllConfigFields() { - List configFields = new ArrayList<>(); - for (RuntimeClientPlugin runtimeClientPlugin : runtimePlugins) { - if (!runtimeClientPlugin.matchesService(model, service)) { - continue; - } - configFields.addAll(runtimeClientPlugin.getConfigFields()); - } - return configFields.stream() - .distinct() - .sorted(Comparator.comparing(ConfigField::getName)) - .collect(Collectors.toList()); + private GoWriter.Writable generateClientMemberResolver(ClientMemberResolver resolver) { + return goTemplate("$T(client)", resolver.getResolver()); } private List getAllClientMembers() { @@ -279,104 +259,124 @@ private List getAllClientMembers() { .collect(Collectors.toList()); } - private void generateApplicationProtocolConfig() { + private GoWriter.Writable generateProtocolResolvers() { ensureSupportedProtocol(); - writer.writeDocs( - "The HTTP client to invoke API calls with. Defaults to client's default HTTP implementation if nil."); - writer.write("HTTPClient HTTPClient").write(""); + return goTemplate(""" + resolveAuthSchemeResolver(&options) + """); } - private void generateApplicationProtocolTypes() { + private GoWriter.Writable generateProtocolFinalizers() { ensureSupportedProtocol(); - writer.addUseImports(SmithyGoDependency.NET_HTTP); - writer.openBlock("type HTTPClient interface {", "}", () -> { - writer.write("Do(*http.Request) (*http.Response, error)"); - }).write(""); + return goTemplate(""" + resolveAuthSchemes(&options) + """); } - private void generateClientInvokeOperation() { - writer.addUseImports(SmithyGoDependency.CONTEXT); - writer.addUseImports(SmithyGoDependency.SMITHY); - - writer.openBlock("func (c *Client) invokeOperation(" - + "ctx context.Context, " - + "opID string, " - + "params interface{}, " - + "optFns []func(*Options), " - + "stackFns ...func(*middleware.Stack, Options) error" - + ") " - + "(result interface{}, metadata middleware.Metadata, err error) {", "}", () -> { - writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE); - writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_TRANSPORT); - - // Ensure operation stack invocations start with clean set of stack values. - writer.write("ctx = middleware.ClearStackValues(ctx)"); - - generateConstructStack(); - writer.write("options := c.options.Copy()"); - - List plugins = runtimePlugins.stream().filter(plugin -> - plugin.matchesService(model, service)) - .collect(Collectors.toList()); - - for (RuntimeClientPlugin plugin : plugins) { - writeConfigFieldResolvers(writer, plugin, resolver -> - resolver.getLocation() == ConfigFieldResolver.Location.OPERATION - && resolver.getTarget() == ConfigFieldResolver.Target.INITIALIZATION); - } - - writer.write("for _, fn := range optFns { fn(&options) }"); - writer.write(""); + private void writeProtocolResolverImpls() { + ensureSupportedProtocol(); - for (RuntimeClientPlugin plugin : plugins) { - writeConfigFieldResolvers(writer, plugin, resolver -> - resolver.getLocation() == ConfigFieldResolver.Location.OPERATION - && resolver.getTarget() == ConfigFieldResolver.Target.FINALIZATION); - } + var schemeMappings = GoWriter.ChainWritable.of( + ServiceIndex.of(model) + .getEffectiveAuthSchemes(service).keySet().stream() + .filter(authSchemes::containsKey) + .map(authSchemes::get) + .map(it -> goTemplate("$W, ", it.generateDefaultAuthScheme())) + .toList() + ).compose(false); + + writer.write(""" + func resolveAuthSchemeResolver(options *Options) { + if options.AuthSchemeResolver == nil { + options.AuthSchemeResolver = &$L{} + } + } - writer.openBlock("for _, fn := range stackFns {", "}", () -> { - writer.write("if err := fn(stack, options); err != nil { return nil, metadata, err }"); - }); - writer.write(""); - - writer.openBlock("for _, fn := range options.APIOptions {", "}", () -> { - writer.write("if err := fn(stack); err != nil { return nil, metadata, err }"); - }); - writer.write(""); - - generateConstructStackHandler(); - writer.write("result, metadata, err = handler.Handle(ctx, params)"); - writer.openBlock("if err != nil {", "}", () -> { - writer.openBlock("err = &smithy.OperationError{", "}", () -> { - writer.write("ServiceID: ServiceID,"); - writer.write("OperationName: opID,"); - writer.write("Err: err,"); - }); - }); - writer.write("return result, metadata, err"); - }); + func resolveAuthSchemes(options *Options) { + if options.AuthSchemes == nil { + options.AuthSchemes = []$T{ + $W + } + } + } + """, + AuthSchemeResolverGenerator.DEFAULT_NAME, + SmithyGoTypes.Transport.Http.AuthScheme, + schemeMappings); } - private void generateConstructStack() { - ensureSupportedProtocol(); + @SuppressWarnings("checkstyle:LineLength") + private GoWriter.Writable generateInvokeOperation() { + return goTemplate(""" + func (c *Client) invokeOperation(ctx $context:T, opID string, params interface{}, optFns []func(*Options), stackFns ...func($stack:P, Options) error) (result interface{}, metadata $metadata:T, err error) { + ctx = $clearStackValues:T(ctx) + $newStack:W + options := c.options.Copy() + $resolvers:W + + for _, fn := range optFns { + fn(&options) + } - Symbol newStack = SymbolUtils.createValueSymbolBuilder( - "NewStack", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); - Symbol newStackRequest = SymbolUtils.createValueSymbolBuilder( - "NewStackRequest", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build(); + $finalizers:W - writer.write("stack := $T(opID, $T)", newStack, newStackRequest); + for _, fn := range stackFns { + if err := fn(stack, options); err != nil { + return nil, metadata, err + } + } + + for _, fn := range options.APIOptions { + if err := fn(stack); err != nil { + return nil, metadata, err + } + } + + $newStackHandler:W + result, metadata, err = handler.Handle(ctx, params) + if err != nil { + err = &$operationError:T{ + ServiceID: ServiceID, + OperationName: opID, + Err: err, + } + } + return result, metadata, err + } + """, + MapUtils.of( + "context", GoStdlibTypes.Context.Context, + "stack", SmithyGoTypes.Middleware.Stack, + "metadata", SmithyGoTypes.Middleware.Metadata, + "clearStackValues", SmithyGoTypes.Middleware.ClearStackValues, + "newStack", generateNewStack(), + "newStackHandler", generateNewStackHandler(), + "operationError", SmithyGoTypes.Smithy.OperationError, + "resolvers", GoWriter.ChainWritable.of( + getConfigResolvers( + ConfigFieldResolver.Location.OPERATION, + ConfigFieldResolver.Target.INITIALIZATION + ).map(this::generateConfigFieldResolver).toList() + ).compose(), + "finalizers", GoWriter.ChainWritable.of( + getConfigResolvers( + ConfigFieldResolver.Location.OPERATION, + ConfigFieldResolver.Target.FINALIZATION + ).map(this::generateConfigFieldResolver).toList() + ).compose() + )); } - private void generateConstructStackHandler() { + private GoWriter.Writable generateNewStack() { ensureSupportedProtocol(); + return goTemplate("stack := $T(opID, $T)", + SmithyGoTypes.Middleware.NewStack, SmithyGoTypes.Transport.Http.NewStackRequest); + } - Symbol decorateHandler = SymbolUtils.createValueSymbolBuilder( - "DecorateHandler", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); - Symbol newClientHandler = SymbolUtils.createValueSymbolBuilder( - "NewClientHandler", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build(); - - writer.write("handler := $T($T(options.HTTPClient), stack)", decorateHandler, newClientHandler); + private GoWriter.Writable generateNewStackHandler() { + ensureSupportedProtocol(); + return goTemplate("handler := $T($T(options.HTTPClient), stack)", + SmithyGoTypes.Middleware.DecorateHandler, SmithyGoTypes.Transport.Http.NewClientHandler); } private void ensureSupportedProtocol() { @@ -385,4 +385,51 @@ private void ensureSupportedProtocol() { "Protocols other than HTTP are not yet implemented: " + applicationProtocol); } } + + private Stream getConfigResolvers( + ConfigFieldResolver.Location location, ConfigFieldResolver.Target target + ) { + return runtimePlugins.stream() + .filter(it -> it.matchesService(model, service)) + .flatMap(it -> it.getConfigFieldResolvers().stream()) + .filter(it -> it.getLocation() == location && it.getTarget() == target); + } + + private GoWriter.Writable generateInputContextFuncs() { + return goTemplate(""" + type operationInputKey struct{} + + func setOperationInput(ctx $1T, input interface{}) $1T { + return $2T(ctx, operationInputKey{}, input) + } + + func getOperationInput(ctx $1T) interface{} { + return $3T(ctx, operationInputKey{}) + } + + $4W + """, + GoStdlibTypes.Context.Context, + SmithyGoTypes.Middleware.WithStackValue, + SmithyGoTypes.Middleware.GetStackValue, + new SetOperationInputContextMiddleware().generate()); + } + + private GoWriter.Writable generateAddProtocolFinalizerMiddleware() { + ensureSupportedProtocol(); + return goTemplate(""" + func addProtocolFinalizerMiddlewares(stack $P, options $L, operation string) error { + $W + return nil + } + """, + SmithyGoTypes.Middleware.Stack, + CONFIG_NAME, + GoWriter.ChainWritable.of( + ResolveAuthSchemeMiddlewareGenerator.generateAddToProtocolFinalizers(), + GetIdentityMiddlewareGenerator.generateAddToProtocolFinalizers(), + EndpointMiddlewareGenerator.generateAddToProtocolFinalizers(), + SignRequestMiddlewareGenerator.generateAddToProtocolFinalizers() + ).compose(false)); + } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SetOperationInputContextMiddleware.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SetOperationInputContextMiddleware.java new file mode 100644 index 000000000..7854b2eba --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SetOperationInputContextMiddleware.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen; + +import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createSerializeStepMiddleware; +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +/** + * Middleware to set the final operation input on the context at the start of the serialize step such that protocol + * middlewares in later phases can use it. + */ +public class SetOperationInputContextMiddleware { + public static final String MIDDLEWARE_NAME = "setOperationInputMiddleware"; + public static final String MIDDLEWARE_ID = "setOperationInput"; + + public GoWriter.Writable generate() { + return createSerializeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) + .asWritable(generateBody(), emptyGoTemplate()); + } + + private GoWriter.Writable generateBody() { + return goTemplate(""" + ctx = setOperationInput(ctx, in.Parameters) + return next.HandleSerialize(ctx, in) + """); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java index 3df52d363..ec310fd45 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java @@ -66,6 +66,7 @@ public final class SmithyGoDependency { public static final GoDependency SMITHY_DOCUMENT = smithy("document", "smithydocument"); public static final GoDependency SMITHY_DOCUMENT_JSON = smithy("document/json", "smithydocumentjson"); public static final GoDependency SMITHY_SYNC = smithy("sync", "smithysync"); + public static final GoDependency SMITHY_AUTH = smithy("auth", "smithyauth"); public static final GoDependency SMITHY_AUTH_BEARER = smithy("auth/bearer"); public static final GoDependency SMITHY_ENDPOINTS = smithy("endpoints", "smithyendpoints"); public static final GoDependency SMITHY_ENDPOINT_RULESFN = smithy("endpoints/private/rulesfn"); diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoTypes.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoTypes.java new file mode 100644 index 000000000..c4e2b75c6 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoTypes.java @@ -0,0 +1,108 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen; + +import software.amazon.smithy.codegen.core.Symbol; + +/** + * Collection of Symbol constants for types in the smithy-go runtime. + */ +@SuppressWarnings({"checkstyle:ConstantName", "checkstyle:LineLength"}) +public final class SmithyGoTypes { + private SmithyGoTypes() { } + + public static final class Smithy { + public static final Symbol Properties = SmithyGoDependency.SMITHY.pointableSymbol("Properties"); + public static final Symbol OperationError = SmithyGoDependency.SMITHY.pointableSymbol("OperationError"); + } + + public static final class Ptr { + public static final Symbol String = SmithyGoDependency.SMITHY_PTR.valueSymbol("String"); + public static final Symbol Bool = SmithyGoDependency.SMITHY_PTR.valueSymbol("Bool"); + } + + public static final class Middleware { + public static final Symbol Stack = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("Stack"); + public static final Symbol NewStack = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("NewStack"); + public static final Symbol Metadata = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("Metadata"); + public static final Symbol ClearStackValues = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("ClearStackValues"); + public static final Symbol WithStackValue = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("WithStackValue"); + public static final Symbol GetStackValue = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("GetStackValue"); + public static final Symbol After = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("After"); + public static final Symbol Before = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("Before"); + public static final Symbol DecorateHandler = SmithyGoDependency.SMITHY_MIDDLEWARE.valueSymbol("DecorateHandler"); + + public static final Symbol InitializeInput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("InitializeInput"); + public static final Symbol InitializeOutput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("InitializeOutput"); + public static final Symbol InitializeHandler = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("InitializeHandler"); + public static final Symbol SerializeInput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("SerializeInput"); + public static final Symbol SerializeOutput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("SerializeOutput"); + public static final Symbol SerializeHandler = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("SerializeHandler"); + public static final Symbol FinalizeInput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("FinalizeInput"); + public static final Symbol FinalizeOutput = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("FinalizeOutput"); + public static final Symbol FinalizeHandler = SmithyGoDependency.SMITHY_MIDDLEWARE.pointableSymbol("FinalizeHandler"); + } + + public static final class Transport { + public static final class Http { + public static final Symbol Request = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.pointableSymbol("Request"); + public static final Symbol NewStackRequest = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewStackRequest"); + public static final Symbol NewClientHandler = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewClientHandler"); + + public static final Symbol JoinPath = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("JoinPath"); + + public static final Symbol GetHostnameImmutable = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetHostnameImmutable"); + + public static final Symbol AuthScheme = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("AuthScheme"); + public static final Symbol NewAnonymousScheme = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("NewAnonymousScheme"); + + public static final Symbol GetSigV4SigningName = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetSigV4SigningName"); + public static final Symbol GetSigV4SigningRegion = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetSigV4SigningRegion"); + public static final Symbol GetSigV4ASigningName = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetSigV4ASigningName"); + public static final Symbol GetSigV4ASigningRegions = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("GetSigV4ASigningRegions"); + + public static final Symbol SetSigV4SigningName = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetSigV4SigningName"); + public static final Symbol SetSigV4SigningRegion = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetSigV4SigningRegion"); + public static final Symbol SetSigV4ASigningName = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetSigV4ASigningName"); + public static final Symbol SetSigV4ASigningRegions = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetSigV4ASigningRegions"); + public static final Symbol SetIsUnsignedPayload = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetIsUnsignedPayload"); + public static final Symbol SetDisableDoubleEncoding = SmithyGoDependency.SMITHY_HTTP_TRANSPORT.valueSymbol("SetDisableDoubleEncoding"); + } + } + + public static final class Auth { + public static final Symbol Option = SmithyGoDependency.SMITHY_AUTH.pointableSymbol("Option"); + public static final Symbol IdentityResolver = SmithyGoDependency.SMITHY_AUTH.valueSymbol("IdentityResolver"); + public static final Symbol Identity = SmithyGoDependency.SMITHY_AUTH.valueSymbol("Identity"); + public static final Symbol AnonymousIdentityResolver = SmithyGoDependency.SMITHY_AUTH.pointableSymbol("AnonymousIdentityResolver"); + public static final Symbol GetAuthOptions = SmithyGoDependency.SMITHY_AUTH.valueSymbol("GetAuthOptions"); + public static final Symbol SetAuthOptions = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SetAuthOptions"); + + public static final Symbol SchemeIDAnonymous = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDAnonymous"); + public static final Symbol SchemeIDHTTPBasic = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDHTTPBasic"); + public static final Symbol SchemeIDHTTPDigest = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDHTTPDigest"); + public static final Symbol SchemeIDHTTPBearer = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDHTTPBearer"); + public static final Symbol SchemeIDHTTPAPIKey = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDHTTPAPIKey"); + public static final Symbol SchemeIDSigV4 = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDSigV4"); + public static final Symbol SchemeIDSigV4A = SmithyGoDependency.SMITHY_AUTH.valueSymbol("SchemeIDSigV4A"); + + public static final class Bearer { + public static final Symbol TokenProvider = SmithyGoDependency.SMITHY_AUTH_BEARER.valueSymbol("TokenProvider"); + public static final Symbol Signer = SmithyGoDependency.SMITHY_AUTH_BEARER.valueSymbol("Signer"); + public static final Symbol NewSignHTTPSMessage = SmithyGoDependency.SMITHY_AUTH_BEARER.valueSymbol("NewSignHTTPSMessage"); + } + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SymbolUtils.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SymbolUtils.java index 3530931c7..e3059dcaf 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SymbolUtils.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SymbolUtils.java @@ -193,17 +193,13 @@ public static boolean isUniverseType(Symbol symbol) { .orElse(false); } - public static Symbol.Builder getPackageSymbol( - String importPath, String symbolName, String namespaceAlias, boolean pointable - ) { - Symbol.Builder builder; - if (pointable) { - builder = SymbolUtils.createPointableSymbolBuilder(symbolName); - } else { - builder = SymbolUtils.createValueSymbolBuilder(symbolName); - } - - // TODO this doesn't seem right - return builder.namespace(importPath, "/").putProperty(SymbolUtils.NAMESPACE_ALIAS, namespaceAlias); + /** + * Builds a symbol within the context of the package in which codegen is taking place. + * + * @param name Symbol name. + * @return The built symbol. + */ + public static Symbol buildPackageSymbol(String name) { + return Symbol.builder().name(name).build(); } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthGenerator.java new file mode 100644 index 000000000..e20c5bc0b --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthGenerator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; + +/** + * Entry point into smithy client auth generation. + */ +public class AuthGenerator { + private final ProtocolGenerator.GenerationContext context; + + public AuthGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; + } + + public void generate() { + if (context.getWriter().isEmpty()) { + throw new CodegenException("writer is required"); + } + + context.getWriter().get() + .write("$W\n", new AuthParametersGenerator(context).generate()) + .write("$W\n", new AuthParametersResolverGenerator(context).generate()) + .write("$W\n", getResolverGenerator().generate()) + .write("$W\n", new ResolveAuthSchemeMiddlewareGenerator(context).generate()) + .write("$W\n", new GetIdentityMiddlewareGenerator(context).generate()) + .write("$W\n", new SignRequestMiddlewareGenerator(context).generate()); + } + + // TODO(i&a): allow consuming generators to overwrite + private AuthSchemeResolverGenerator getResolverGenerator() { + return new AuthSchemeResolverGenerator(context); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParameter.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParameter.java new file mode 100644 index 000000000..007a3b96d --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParameter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.GoUniverseTypes; + +public record AuthParameter(String name, String docs, Symbol type) { + public static final AuthParameter OPERATION = new AuthParameter( + "Operation", + "The name of the operation being invoked.", + GoUniverseTypes.String + ); + + public static final AuthParameter REGION = new AuthParameter( + "Region", + "The region in which the operation is being invoked.", + GoUniverseTypes.String + ); +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersGenerator.java new file mode 100644 index 000000000..70171b2fc --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersGenerator.java @@ -0,0 +1,99 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.ArrayList; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SymbolUtils; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.utils.ListUtils; +import software.amazon.smithy.utils.MapUtils; + +/** + * Generates auth scheme resolver parameters. + * By default, the only field that exists universally is the name of the operation being invoked. Services that use + * SigV4[A] will also have a field for the region. + * Additional parameters can be loaded via GoIntegration. + */ +public class AuthParametersGenerator { + public static final String STRUCT_NAME = "AuthResolverParameters"; + + public static final Symbol STRUCT_SYMBOL = SymbolUtils.createPointableSymbolBuilder(STRUCT_NAME).build(); + + private final ProtocolGenerator.GenerationContext context; + + private final ArrayList fields = new ArrayList<>( + ListUtils.of(AuthParameter.OPERATION) + ); + + public AuthParametersGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; + } + + public GoWriter.Writable generate() { + loadFields(); + + return goTemplate( + """ + $doc:W + type $name:L struct { + $fields:W + } + """, + MapUtils.of( + "doc", generateDocs(), + "name", STRUCT_NAME, + "fields", generateFields() + ) + ); + } + + private GoWriter.Writable generateDocs() { + return goDocTemplate( + "$name:L contains the set of inputs necessary for auth scheme resolution.", + MapUtils.of("name", STRUCT_NAME) + ); + } + + private GoWriter.Writable generateFields() { + return (writer) -> { + for (var field: fields) { + writer.write(""" + $W + $L $P + """, + goDocTemplate(field.docs()), + field.name(), + field.type() + ); + } + }; + } + + private void loadFields() { + for (var integration: context.getIntegrations()) { + var plugins = integration.getClientPlugins().stream().filter(it -> + it.matchesService(context.getModel(), context.getService())).toList(); + for (var plugin: plugins) { + fields.addAll(plugin.getAuthParameters()); + } + } + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolver.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolver.java new file mode 100644 index 000000000..b5e037dff --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolver.java @@ -0,0 +1,20 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import software.amazon.smithy.codegen.core.Symbol; + +public record AuthParametersResolver(Symbol resolver) { } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolverGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolverGenerator.java new file mode 100644 index 000000000..8dda1b970 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthParametersResolverGenerator.java @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.ArrayList; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.utils.MapUtils; + +/** + * Generates a single method which binds auth scheme resolver parameters from operation input and client options. + * The only value bound by default is the operation name. Generators must load additional bindings through + * GoIntegration. + */ +public class AuthParametersResolverGenerator { + public static final String FUNC_NAME = "bindAuthResolverParams"; + + private final ProtocolGenerator.GenerationContext context; + + private final ArrayList resolvers = new ArrayList<>(); + + public AuthParametersResolverGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; + } + + public GoWriter.Writable generate() { + loadResolvers(); + + return goTemplate(""" + func $name:L(operation string, input interface{}, options Options) $params:P { + params := &$params:T{ + Operation: operation, + } + + $bindings:W + + return params + } + """, + MapUtils.of( + "name", FUNC_NAME, + "params", AuthParametersGenerator.STRUCT_SYMBOL, + "bindings", generateResolvers() + )); + } + + private GoWriter.Writable generateResolvers() { + return (writer) -> { + for (var resolver: resolvers) { + writer.write("$T(params, input, options)", resolver.resolver()); + } + }; + } + + private void loadResolvers() { + for (var integration: context.getIntegrations()) { + var plugins = integration.getClientPlugins().stream().filter(it -> + it.matchesService(context.getModel(), context.getService())).toList(); + for (var plugin: plugins) { + resolvers.addAll(plugin.getAuthParameterResolvers()); + } + } + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthSchemeResolverGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthSchemeResolverGenerator.java new file mode 100644 index 000000000..46befd07b --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/AuthSchemeResolverGenerator.java @@ -0,0 +1,202 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait; +import software.amazon.smithy.go.codegen.GoStdlibTypes; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.model.knowledge.ServiceIndex; +import software.amazon.smithy.model.knowledge.TopDownIndex; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.MapUtils; + +/** + * Implements modeled auth scheme resolver generation. + */ +public class AuthSchemeResolverGenerator { + public static final String INTERFACE_NAME = "AuthSchemeResolver"; + public static final String DEFAULT_NAME = "defaultAuthSchemeResolver"; + + private final ProtocolGenerator.GenerationContext context; + private final ServiceIndex serviceIndex; + private final Map schemeDefinitions; + + public AuthSchemeResolverGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; + this.serviceIndex = ServiceIndex.of(context.getModel()); + this.schemeDefinitions = context.getIntegrations().stream() + .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) + .flatMap(it -> it.getAuthSchemeDefinitions().entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + // an operation has auth overrides if any of the following are true: + // 1. its list of supported schemes differs from that of the service + // 2. its auth optionality differs from that of the service (covered by checking [1] w/ NO_AUTH_AWARE) + // 3. it has an unsigned payload + private boolean hasAuthOverrides(OperationShape operation) { + var serviceSchemes = serviceIndex + .getEffectiveAuthSchemes(context.getService(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) + .keySet(); + var operationSchemes = serviceIndex + .getEffectiveAuthSchemes(context.getService(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) + .keySet(); + return !serviceSchemes.equals(operationSchemes) || operation.hasTrait(UnsignedPayloadTrait.class); + } + + public GoWriter.Writable generate() { + return goTemplate(""" + $W + + $W + """, generateInterface(), generateDefault()); + } + + private GoWriter.Writable generateInterface() { + return goTemplate(""" + $W + type $L interface { + ResolveAuthSchemes($T, *$L) ([]$P, error) + } + """, + generateDocs(), + INTERFACE_NAME, + GoStdlibTypes.Context.Context, + AuthParametersGenerator.STRUCT_NAME, + SmithyGoTypes.Auth.Option); + } + + private GoWriter.Writable generateDocs() { + return goDocTemplate("AuthSchemeResolver returns a set of possible authentication options for an " + + "operation."); + } + + private GoWriter.Writable generateDefault() { + return goTemplate(""" + $W + + $W + """, + generateDefaultStruct(), + generateDefaultResolve()); + } + + private GoWriter.Writable generateDefaultStruct() { + return goTemplate(""" + type $1L struct{} + + var _ $2L = (*$1L)(nil) + """, DEFAULT_NAME, INTERFACE_NAME); + } + + private GoWriter.Writable generateDefaultResolve() { + return goTemplate(""" + func (*$receiver:L) ResolveAuthSchemes(ctx $ctx:L, params *$params:L) ([]$options:P, error) { + if overrides, ok := operationAuthOptions[params.Operation]; ok { + return overrides(params), nil + } + return serviceAuthOptions(params), nil + } + + $opAuthOptions:W + + $svcAuthOptions:W + """, MapUtils.of( + "receiver", DEFAULT_NAME, + "ctx", GoStdlibTypes.Context.Context, + "params", AuthParametersGenerator.STRUCT_NAME, + "options", SmithyGoTypes.Auth.Option, + "opAuthOptions", generateOperationAuthOptions(), + "svcAuthOptions", generateServiceAuthOptions())); + } + + private GoWriter.Writable generateOperationAuthOptions() { + var options = new GoWriter.ChainWritable(); + TopDownIndex.of(context.getModel()) + .getContainedOperations(context.getService()).stream() + .filter(this::hasAuthOverrides) + .forEach(it -> { + options.add(generateOperationAuthOptionsEntry(it)); + }); + + return goTemplate(""" + var operationAuthOptions = map[string]func(*$L) []$P{ + $W + } + """, + AuthParametersGenerator.STRUCT_NAME, + SmithyGoTypes.Auth.Option, + options.compose()); + } + + private GoWriter.Writable generateOperationAuthOptionsEntry(OperationShape operation) { + var options = new GoWriter.ChainWritable(); + serviceIndex + .getEffectiveAuthSchemes(context.getService(), operation, ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) + .entrySet().stream() + .filter(it -> schemeDefinitions.containsKey(it.getKey())) + .forEach(it -> { + var definition = schemeDefinitions.get(it.getKey()); + options.add(definition.generateOperationOption(context, operation)); + }); + + return options.isEmpty() + ? emptyGoTemplate() + : goTemplate(""" + $1S: func(params *$2L) []$3P { + return []$3P{ + $4W + } + },""", + operation.getId().getName(), + AuthParametersGenerator.STRUCT_NAME, + SmithyGoTypes.Auth.Option, + options.compose()); + } + + private GoWriter.Writable generateServiceAuthOptions() { + var options = new GoWriter.ChainWritable(); + serviceIndex + .getEffectiveAuthSchemes(context.getService(), ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE) + .entrySet().stream() + .filter(it -> schemeDefinitions.containsKey(it.getKey())) + .forEach(it -> { + var definition = schemeDefinitions.get(it.getKey()); + options.add(definition.generateServiceOption(context, context.getService())); + }); + + return goTemplate(""" + func serviceAuthOptions(params *$1L) []$2P { + return []$2P{ + $3W + } + } + """, + AuthParametersGenerator.STRUCT_NAME, + SmithyGoTypes.Auth.Option, + options.compose()); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/GetIdentityMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/GetIdentityMiddlewareGenerator.java new file mode 100644 index 000000000..643743b42 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/GetIdentityMiddlewareGenerator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.go.codegen.GoStdlibTypes; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.MiddlewareIdentifier; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.utils.MapUtils; + +public class GetIdentityMiddlewareGenerator { + public static final String MIDDLEWARE_NAME = "getIdentityMiddleware"; + public static final String MIDDLEWARE_ID = "GetIdentity"; + + private final ProtocolGenerator.GenerationContext context; + + public GetIdentityMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; + } + + public static GoWriter.Writable generateAddToProtocolFinalizers() { + return goTemplate(""" + if err := stack.Finalize.Insert(&$L{options: options}, $S, $T); err != nil { + return $T("add $L: %v", err) + } + """, + MIDDLEWARE_NAME, + ResolveAuthSchemeMiddlewareGenerator.MIDDLEWARE_ID, + SmithyGoTypes.Middleware.After, + GoStdlibTypes.Fmt.Errorf, + MIDDLEWARE_ID); + } + + public GoWriter.Writable generate() { + return GoWriter.ChainWritable.of( + createFinalizeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) + .asWritable(generateBody(), generateFields()), + generateContextFuncs() + ).compose(); + } + + private GoWriter.Writable generateFields() { + return goTemplate(""" + options Options + """); + } + + private GoWriter.Writable generateBody() { + return goTemplate(""" + rscheme := getResolvedAuthScheme(ctx) + if rscheme == nil { + return out, metadata, $errorf:T("no resolved auth scheme") + } + + resolver := rscheme.Scheme.IdentityResolver(m.options) + if resolver == nil { + return out, metadata, $errorf:T("no identity resolver") + } + + identity, err := resolver.GetIdentity(ctx, rscheme.IdentityProperties) + if err != nil { + return out, metadata, $errorf:T("get identity: %v", err) + } + + ctx = setIdentity(ctx, identity) + return next.HandleFinalize(ctx, in) + """, + MapUtils.of( + "errorf", GoStdlibTypes.Fmt.Errorf + )); + } + + private GoWriter.Writable generateContextFuncs() { + return goTemplate(""" + type identityKey struct{} + + func setIdentity(ctx $context:T, identity $identity:T) $context:T { + return $withStackValue:T(ctx, identityKey{}, identity) + } + + func getIdentity(ctx $context:T) $identity:T { + v, _ := $getStackValue:T(ctx, identityKey{}).($identity:T) + return v + } + """, + MapUtils.of( + "context", GoStdlibTypes.Context.Context, + "withStackValue", SmithyGoTypes.Middleware.WithStackValue, + "getStackValue", SmithyGoTypes.Middleware.GetStackValue, + "identity", SmithyGoTypes.Auth.Identity + )); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/ResolveAuthSchemeMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/ResolveAuthSchemeMiddlewareGenerator.java new file mode 100644 index 000000000..bf45ae315 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/ResolveAuthSchemeMiddlewareGenerator.java @@ -0,0 +1,161 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.GoStdlibTypes; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.MiddlewareIdentifier; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.utils.MapUtils; + +public class ResolveAuthSchemeMiddlewareGenerator { + public static final String MIDDLEWARE_NAME = "resolveAuthSchemeMiddleware"; + public static final String MIDDLEWARE_ID = "ResolveAuthScheme"; + + private final ProtocolGenerator.GenerationContext context; + + public ResolveAuthSchemeMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; + } + + public static GoWriter.Writable generateAddToProtocolFinalizers() { + return goTemplate(""" + if err := stack.Finalize.Add(&$L{operation: operation, options: options}, $T); err != nil { + return $T("add $L: %v", err) + } + """, + MIDDLEWARE_NAME, + SmithyGoTypes.Middleware.Before, + GoStdlibTypes.Fmt.Errorf, + MIDDLEWARE_ID); + } + + public GoWriter.Writable generate() { + return GoWriter.ChainWritable.of( + createFinalizeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) + .asWritable(generateBody(), generateFields()), + generateSelectScheme(), + generateContextFuncs() + ).compose(); + } + + private GoWriter.Writable generateFields() { + return goTemplate(""" + operation string + options Options + """); + } + + private GoWriter.Writable generateBody() { + return goTemplate(""" + params := $1L(m.operation, getOperationInput(ctx), m.options) + options, err := m.options.AuthSchemeResolver.ResolveAuthSchemes(ctx, params) + if err != nil { + return out, metadata, $2T("resolve auth scheme: %v", err) + } + + scheme, ok := m.selectScheme(options) + if !ok { + return out, metadata, $2T("could not select an auth scheme") + } + + ctx = setResolvedAuthScheme(ctx, scheme) + return next.HandleFinalize(ctx, in) + """, + AuthParametersResolverGenerator.FUNC_NAME, + GoStdlibTypes.Fmt.Errorf + ); + } + + private GoWriter.Writable generateSelectScheme() { + return goTemplate(""" + func (m *$middlewareName:L) selectScheme(options []$option:P) (*resolvedAuthScheme, bool) { + for _, option := range options { + if option.SchemeID == $schemeIDAnonymous:T { + return newResolvedAuthScheme($newAnonymousScheme:T(), option), true + } + + for _, scheme := range m.options.AuthSchemes { + if scheme.SchemeID() != option.SchemeID { + continue + } + + if scheme.IdentityResolver(m.options) != nil { + return newResolvedAuthScheme(scheme, option), true + } + } + } + + return nil, false + } + """, + MapUtils.of( + "middlewareName", MIDDLEWARE_NAME, + "option", SmithyGoTypes.Auth.Option, + "schemeIDAnonymous", SmithyGoTypes.Auth.SchemeIDAnonymous, + "newAnonymousScheme", SmithyGoTypes.Transport.Http.NewAnonymousScheme + ) + ); + } + + private GoWriter.Writable generateContextFuncs() { + return goTemplate(""" + type resolvedAuthSchemeKey struct{} + + type resolvedAuthScheme struct { + Scheme $authScheme:T + IdentityProperties $properties:T + SignerProperties $properties:T + } + + func newResolvedAuthScheme(scheme $authScheme:T, option $option:P) *resolvedAuthScheme { + return &resolvedAuthScheme{ + Scheme: scheme, + IdentityProperties: option.IdentityProperties, + SignerProperties: option.SignerProperties, + } + } + + func setResolvedAuthScheme(ctx $context:T, scheme *resolvedAuthScheme) $context:T { + return $withStackValue:T(ctx, resolvedAuthSchemeKey{}, scheme) + } + + func getResolvedAuthScheme(ctx $context:T) *resolvedAuthScheme { + v, _ := $getStackValue:T(ctx, resolvedAuthSchemeKey{}).(*resolvedAuthScheme) + return v + } + """, + MapUtils.of( + "authScheme", getAuthSchemeSymbol(), + "option", SmithyGoTypes.Auth.Option, + "properties", SmithyGoTypes.Smithy.Properties, + "context", GoStdlibTypes.Context.Context, + "withStackValue", SmithyGoTypes.Middleware.WithStackValue, + "getStackValue", SmithyGoTypes.Middleware.GetStackValue + )); + } + + // FUTURE(#458): when protocols are defined here, they should supply the auth scheme symbol, for + // now it's pinned to the HTTP variant + private Symbol getAuthSchemeSymbol() { + return SmithyGoTypes.Transport.Http.AuthScheme; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SigV4aTrait.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SigV4aTrait.java new file mode 100644 index 000000000..d36ecb5e5 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SigV4aTrait.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AbstractTrait; + +/** + * Fake trait for aws.auth#sigv4a until smithy adds it. + */ +public final class SigV4aTrait extends AbstractTrait { + public static final ShapeId ID = ShapeId.from("aws.auth#sigv4a"); + + public SigV4aTrait() { + super(ID, Node.objectNode()); + } + + @Override + protected Node createNode() { + return Node.objectNode(); + } + + @Override + public boolean isSynthetic() { + return true; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SignRequestMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SignRequestMiddlewareGenerator.java new file mode 100644 index 000000000..0fac9fa7a --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/auth/SignRequestMiddlewareGenerator.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.auth; + +import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware; +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.go.codegen.GoStdlibTypes; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.MiddlewareIdentifier; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.endpoints.EndpointMiddlewareGenerator; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.utils.MapUtils; + +public class SignRequestMiddlewareGenerator { + public static final String MIDDLEWARE_NAME = "signRequestMiddleware"; + public static final String MIDDLEWARE_ID = "Signing"; + + private final ProtocolGenerator.GenerationContext context; + + public SignRequestMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; + } + + public static GoWriter.Writable generateAddToProtocolFinalizers() { + return goTemplate(""" + if err := stack.Finalize.Insert(&$L{}, $S, $T); err != nil { + return $T("add $L: %v", err) + } + """, + MIDDLEWARE_NAME, + EndpointMiddlewareGenerator.MIDDLEWARE_ID, + SmithyGoTypes.Middleware.After, + GoStdlibTypes.Fmt.Errorf, + MIDDLEWARE_ID); + } + + public GoWriter.Writable generate() { + return createFinalizeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) + .asWritable(generateBody(), generateFields()); + } + + private GoWriter.Writable generateFields() { + return emptyGoTemplate(); + } + + private GoWriter.Writable generateBody() { + return goTemplate(""" + req, ok := in.Request.($request:P) + if !ok { + return out, metadata, $errorf:T("unexpected transport type %T", in.Request) + } + + rscheme := getResolvedAuthScheme(ctx) + if rscheme == nil { + return out, metadata, $errorf:T("no resolved auth scheme") + } + + identity := getIdentity(ctx) + if identity == nil { + return out, metadata, $errorf:T("no identity") + } + + signer := rscheme.Scheme.Signer() + if signer == nil { + return out, metadata, $errorf:T("no signer") + } + + if err := signer.SignRequest(ctx, req, identity, rscheme.SignerProperties); err != nil { + return out, metadata, $errorf:T("sign request: %v", err) + } + + return next.HandleFinalize(ctx, in) + """, + MapUtils.of( + // FUTURE(#458) protocol generator should specify the transport type + "request", SmithyGoTypes.Transport.Http.Request, + "errorf", GoStdlibTypes.Fmt.Errorf + )); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/AuthSchemePropertyGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/AuthSchemePropertyGenerator.java new file mode 100644 index 000000000..8e1f9e46b --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/AuthSchemePropertyGenerator.java @@ -0,0 +1,109 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.endpoints; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.rulesengine.language.syntax.Identifier; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; +import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.RecordLiteral; +import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.StringLiteral; +import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.TupleLiteral; + +/** + * Handles codegen for the `authSchemes` endpoint property. This property is represented in codegen as `[]*auth.Option`. + */ +public class AuthSchemePropertyGenerator { + private final ExpressionGenerator generator; + + public AuthSchemePropertyGenerator(ExpressionGenerator generator) { + this.generator = generator; + } + + public static String mapEndpointPropertyAuthSchemeName(String name) { + return switch (name) { + case "sigv4" -> "aws.auth#sigv4"; + case "sigv4a" -> "aws.auth#sigv4a"; + default -> throw new IllegalStateException("Unexpected value: " + name); + }; + } + + public GoWriter.Writable generate(Expression expr) { + return goTemplate(""" + $T(&out, []$P{ + $W + }) + """, + SmithyGoTypes.Auth.SetAuthOptions, + SmithyGoTypes.Auth.Option, + GoWriter.ChainWritable.of( + ((TupleLiteral) expr).members().stream() + .map(it -> generateOption(generator, (RecordLiteral) it)) + .toList() + ).compose(false)); + } + + private GoWriter.Writable generateOption(ExpressionGenerator generator, RecordLiteral scheme) { + var members = scheme.members(); + var schemeName = ((StringLiteral) members.get(Identifier.of("name"))).value().expectLiteral(); + return goTemplate(""" + { + SchemeID: $1S, + SignerProperties: func() $2T { + var sp $2T + $3W + return sp + }(), + },""", + mapEndpointPropertyAuthSchemeName(schemeName), + SmithyGoTypes.Smithy.Properties, + generateOptionSignerProps(generator, scheme)); + } + + private GoWriter.Writable generateOptionSignerProps(ExpressionGenerator generator, RecordLiteral scheme) { + var props = new GoWriter.ChainWritable(); + scheme.members().forEach((ident, expr) -> { + var name = ident.getName().expectStringNode().getValue(); + switch (name) { // properties that don't apply to the scheme would just be ignored by the signer impl. + case "signingName" -> props.add(goTemplate(""" + $1T(&sp, $3W) + $2T(&sp, $3W)""", + SmithyGoTypes.Transport.Http.SetSigV4SigningName, + SmithyGoTypes.Transport.Http.SetSigV4ASigningName, + generator.generate(expr))); + case "signingRegion" -> props.add(goTemplate("$T(&sp, $W)", + SmithyGoTypes.Transport.Http.SetSigV4SigningRegion, generator.generate(expr))); + case "signingRegionSet" -> { + var regions = GoWriter.ChainWritable.of( + ((TupleLiteral) expr).members().stream() + .map(generator::generate) + .toList() + ).compose(); + props.add(goTemplate("$T(&sp, []string{$W})", + SmithyGoTypes.Transport.Http.SetSigV4ASigningRegions, regions)); + } + case "disableDoubleEncoding" -> props.add(goTemplate("$T(&sp, $W)", + SmithyGoTypes.Transport.Http.SetDisableDoubleEncoding, generator.generate(expr))); + default -> { + return; + } + } + }); + return props.compose(); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointBuiltInHandler.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointBuiltInHandler.java deleted file mode 100644 index 35d832e8d..000000000 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointBuiltInHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.smithy.go.codegen.endpoints; - -import software.amazon.smithy.go.codegen.GoWriter; -import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters; - -/** - * Used by integrations to provide BuiltIn value resolution - * functionality during endpoint resolution. - * - */ -public interface EndpointBuiltInHandler { - - /** - * Used by integrations to set a member on the endpoints resolution - * middleware object. - * - * @param writer Settings used to generate. - */ - default void renderEndpointBuiltInField(GoWriter writer) { - // pass - } - - /** - * Used by integrations to set invoke BuiltIn resolution during Endpoint - * resolution. - * - * @param writer Settings used to generate. - */ - default void renderEndpointBuiltInInvocation(GoWriter writer) { - // pass - } - - - /** - * Used by integrations to set initialize BuiltIn values on the Endpoint - * resolution object. - * - * @param writer Settings used to generate. - */ - default void renderEndpointBuiltInInitialization(GoWriter writer, Parameters parameters) { - // pass - } -} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointClientPluginsGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointClientPluginsGenerator.java index ae51cd219..5efc73b1d 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointClientPluginsGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointClientPluginsGenerator.java @@ -17,21 +17,13 @@ import java.util.ArrayList; import java.util.List; -import java.util.Optional; import software.amazon.smithy.codegen.core.Symbol; -import software.amazon.smithy.codegen.core.SymbolProvider; -import software.amazon.smithy.go.codegen.GoCodegenPlugin; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.go.codegen.integration.ConfigField; import software.amazon.smithy.go.codegen.integration.GoIntegration; -import software.amazon.smithy.go.codegen.integration.MiddlewareRegistrar; import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.knowledge.TopDownIndex; -import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.ServiceShape; -import software.amazon.smithy.model.shapes.ToShapeId; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait; @@ -48,11 +40,6 @@ public class EndpointClientPluginsGenerator implements GoIntegration { private final List runtimeClientPlugins = new ArrayList<>(); - - private static String getAddEndpointMiddlewareFuncName(String operationName) { - return String.format("add%sResolveEndpointMiddleware", operationName); - } - private static String getExportedParameterName(Parameter parameter) { return StringUtils.capitalize(parameter.getName().getName().getValue()); } @@ -90,65 +77,32 @@ public List getClientPlugins() { @Override public void processFinalizedModel(GoSettings settings, Model model) { - ServiceShape service = settings.getService(model); - var rulesetTrait = service.getTrait(EndpointRuleSetTrait.class); - Optional rulesetOpt = (rulesetTrait.isPresent()) - ? Optional.of(EndpointRuleSet.fromNode(rulesetTrait.get().getRuleSet())) - : Optional.empty(); - var clientContextParamsTrait = service.getTrait(ClientContextParamsTrait.class); - - if (!rulesetOpt.isPresent()) { + var service = settings.getService(model); + if (!service.hasTrait(EndpointRuleSetTrait.class) || !service.hasTrait(ClientContextParamsTrait.class)) { return; } - var topDownIndex = TopDownIndex.of(model); - - for (ToShapeId operationId : topDownIndex.getContainedOperations(service)) { - OperationShape operationShape = model.expectShape(operationId.toShapeId(), OperationShape.class); - - SymbolProvider symbolProvider = GoCodegenPlugin.createSymbolProvider(model, settings); + var ruleset = EndpointRuleSet.fromNode(service.expectTrait(EndpointRuleSetTrait.class).getRuleSet()); + var ccParams = service.expectTrait(ClientContextParamsTrait.class).getParameters(); + var parameters = ruleset.getParameters(); + parameters.forEach(param -> { + var ccParam = ccParams.get(param.getName().getName().getValue()); + if (ccParam == null || param.getBuiltIn().isPresent()) { + return; + } - String inputHelperFuncName = getAddEndpointMiddlewareFuncName( - symbolProvider.toSymbol(operationShape).getName() + runtimeClientPlugins.add( + RuntimeClientPlugin.builder() + .addConfigField( + ConfigField.builder() + .name(getExportedParameterName(param)) + .type(parameterAsSymbol(param)) + .documentation(ccParam.getDocumentation().orElse("")) + .build() + ) + .build() ); - runtimeClientPlugins.add(RuntimeClientPlugin.builder() - .operationPredicate((m, s, o) -> { - return o.equals(operationShape); - }) - .registerMiddleware(MiddlewareRegistrar.builder() - .resolvedFunction(SymbolUtils.createValueSymbolBuilder(inputHelperFuncName) - .build()) - .useClientOptions() - .build()) - .build()); - - if (clientContextParamsTrait.isPresent()) { - if (rulesetOpt.isPresent()) { - var clientContextParams = clientContextParamsTrait.get(); - var parameters = rulesetOpt.get().getParameters(); - parameters.forEach(param -> { - if ( - clientContextParams.getParameters().containsKey(param.getName().getName().getValue()) - && !param.getBuiltIn().isPresent() - ) { - var documentation = param.getDocumentation().isPresent() - ? param.getDocumentation().get() - : ""; - - runtimeClientPlugins.add(RuntimeClientPlugin.builder() - .configFields(ListUtils.of( - ConfigField.builder() - .name(getExportedParameterName(param)) - .type(parameterAsSymbol(param)) - .documentation(documentation) - .build() - )) - .build()); - } - }); - } - } - } + }); } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointMiddlewareGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointMiddlewareGenerator.java index 7cd43cc9d..683056d8a 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointMiddlewareGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointMiddlewareGenerator.java @@ -15,520 +15,157 @@ package software.amazon.smithy.go.codegen.endpoints; +import static software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import software.amazon.smithy.codegen.core.CodegenException; -import software.amazon.smithy.codegen.core.Symbol; -import software.amazon.smithy.codegen.core.SymbolProvider; -import software.amazon.smithy.go.codegen.GoDelegator; -import software.amazon.smithy.go.codegen.GoSettings; +import software.amazon.smithy.go.codegen.GoStdlibTypes; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.MiddlewareIdentifier; -import software.amazon.smithy.go.codegen.SmithyGoDependency; -import software.amazon.smithy.go.codegen.SymbolUtils; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.auth.GetIdentityMiddlewareGenerator; import software.amazon.smithy.go.codegen.integration.GoIntegration; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.knowledge.OperationIndex; -import software.amazon.smithy.model.knowledge.TopDownIndex; -import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.ToShapeId; -import software.amazon.smithy.rulesengine.language.EndpointRuleSet; -import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; -import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType; -import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters; -import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait; -import software.amazon.smithy.rulesengine.traits.ContextParamTrait; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; -import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait; import software.amazon.smithy.utils.MapUtils; -import software.amazon.smithy.utils.SmithyBuilder; -import software.amazon.smithy.utils.StringUtils; - /** * Class responsible for generating middleware * that will be used during endpoint resolution. */ public final class EndpointMiddlewareGenerator { - // TODO(ep20): remove existing v1 "ResolveEndpoint" and rename + public static final String MIDDLEWARE_NAME = "resolveEndpointV2Middleware"; public static final String MIDDLEWARE_ID = "ResolveEndpointV2"; - List integrations; + private final ProtocolGenerator.GenerationContext context; - private EndpointMiddlewareGenerator(Builder builder) { - this.integrations = SmithyBuilder.requiredState("integrations", builder.integrations); + public EndpointMiddlewareGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; } - - public void generate(GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator) { - var serviceShape = settings.getService(model); - - var rulesetTrait = serviceShape.getTrait(EndpointRuleSetTrait.class); - var clientContextParamsTrait = serviceShape.getTrait(ClientContextParamsTrait.class); - - Optional rulesetOpt = (rulesetTrait.isPresent()) - ? Optional.of(EndpointRuleSet.fromNode(rulesetTrait.get().getRuleSet())) - : Optional.empty(); - - TopDownIndex topDownIndex = TopDownIndex.of(model); - - for (ToShapeId operation : topDownIndex.getContainedOperations(serviceShape)) { - OperationShape operationShape = model.expectShape(operation.toShapeId(), OperationShape.class); - goDelegator.useShapeWriter(operationShape, writer -> { - if (rulesetOpt.isPresent()) { - var parameters = rulesetOpt.get().getParameters(); - Symbol operationSymbol = symbolProvider.toSymbol(operationShape); - String operationName = operationSymbol.getName(); - writer.write( - """ - $W - - $W - - $W - """, - generateMiddlewareType(parameters, clientContextParamsTrait, operationName), - generateMiddlewareMethods( - parameters, settings, clientContextParamsTrait, symbolProvider, operationShape, model), - generateMiddlewareAdder(parameters, operationName, clientContextParamsTrait) - ); + public static GoWriter.Writable generateAddToProtocolFinalizers() { + return goTemplate(""" + if err := stack.Finalize.Insert(&$L{options: options}, $S, $T); err != nil { + return $T("add $L: %v", err) } - }); - } + """, + MIDDLEWARE_NAME, + GetIdentityMiddlewareGenerator.MIDDLEWARE_ID, + SmithyGoTypes.Middleware.After, + GoStdlibTypes.Fmt.Errorf, + MIDDLEWARE_ID); } - private GoWriter.Writable generateMiddlewareType( - Parameters parameters, Optional clientContextParamsTrait, String operationName) { - return (GoWriter w) -> { - w.openBlock("type $L struct {", "}", getMiddlewareObjectName(operationName), () -> { - w.write("EndpointResolver $T", SymbolUtils.createValueSymbolBuilder("EndpointResolverV2").build()); - for (Iterator iter = parameters.iterator(); iter.hasNext();) { - if (iter.next().getBuiltIn().isPresent()) { - for (GoIntegration integration : this.integrations) { - var builtInHandlerOpt = integration.getEndpointBuiltinHandler(); - if (builtInHandlerOpt.isPresent()) { - builtInHandlerOpt.get().renderEndpointBuiltInField(w); - } - } - break; - } - } - - if (clientContextParamsTrait.isPresent()) { - var clientContextParams = clientContextParamsTrait.get(); - parameters.forEach(param -> { - if (clientContextParams.getParameters().containsKey(param.getName().getName().getValue()) - && !param.getBuiltIn().isPresent()) { - w.write("$L $P", getExportedParameterName(param), parameterAsSymbol(param)); - } - }); - } - - }); - }; + public GoWriter.Writable generate() { + return createFinalizeStepMiddleware(MIDDLEWARE_NAME, MiddlewareIdentifier.string(MIDDLEWARE_ID)) + .asWritable(generateBody(), generateFields()); } - private GoWriter.Writable generateMiddlewareMethods( - Parameters parameters, GoSettings settings, - Optional clientContextParamsTrait, - SymbolProvider symbolProvider, OperationShape operationShape, Model model) { - - Symbol operationSymbol = symbolProvider.toSymbol(operationShape); - String operationName = operationSymbol.getName(); - String middlewareName = getMiddlewareObjectName(operationName); - Symbol middlewareSymbol = SymbolUtils.createPointableSymbolBuilder(middlewareName).build(); - return (GoWriter writer) -> { - writer.openBlock("func ($P) ID() string {", "}", middlewareSymbol, () -> { - writer.writeInline("return "); - MiddlewareIdentifier.string(MIDDLEWARE_ID).writeInline(writer); - writer.write(""); - }); - - writer.write(""); - - String handleMethodName = "HandleSerialize"; - Symbol contextType = - SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build(); - Symbol metadataType = - SymbolUtils.createValueSymbolBuilder("Metadata", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); - var inputType = - SymbolUtils.createValueSymbolBuilder("SerializeInput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); - var outputType = - SymbolUtils.createValueSymbolBuilder("SerializeOutput", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); - var handlerType = - SymbolUtils.createValueSymbolBuilder("SerializeHandler", SmithyGoDependency.SMITHY_MIDDLEWARE).build(); - - - writer.openBlock("func (m $P) $L(ctx $T, in $T, next $T) (\n" - + "\tout $T, metadata $T, err error,\n" - + ") {", "}", - new Object[]{ - middlewareSymbol, handleMethodName, contextType, inputType, handlerType, outputType, - metadataType, - }, - () -> { - writer.write("$W", - generateMiddlewareResolverBody( - operationShape, model, parameters, clientContextParamsTrait, settings) - ); - }); - }; + private GoWriter.Writable generateFields() { + return goTemplate(""" + options Options + """); } - private GoWriter.Writable generateMiddlewareResolverBody( - OperationShape operationShape, Model model, Parameters parameters, - Optional clientContextParamsTrait, - GoSettings settings) { - return goTemplate( - """ - $preEndpointResolutionHook:W - - $requestValidator:W - - $inputValidator:W - - $legacyResolverValidator:W - - params := $endpointParametersType:L{} - - $builtInResolverInvocation:W + private GoWriter.Writable generateBody() { + if (!context.getService().hasTrait(EndpointRuleSetTrait.class)) { + return goTemplate("return next.HandleFinalize(ctx, in)"); + } - $clientContextBinding:W + return goTemplate(""" + $pre:W - $contextBinding:W + $assertRequest:W - $staticContextBinding:W + $assertResolver:W - $endpointResolution:W + $resolveEndpoint:W - $postEndpointResolution:W + $mergeAuthProperties:W - return next.HandleSerialize(ctx, in) + $post:W - """, - MapUtils.of( - "preEndpointResolutionHook", generatePreEndpointResolutionHook(settings, model) - ), - MapUtils.of( - "requestValidator", generateRequestValidator(), - "inputValidator", generateInputValidator(model, operationShape), - "legacyResolverValidator", generateLegacyResolverValidator(), - "endpointParametersType", EndpointResolutionGenerator.PARAMETERS_TYPE_NAME, - "builtInResolverInvocation", generateBuiltInResolverInvocation(parameters), - "clientContextBinding", generateClientContextParamBinding(parameters, clientContextParamsTrait), - "contextBinding", generateContextParamBinding(operationShape, model), - "staticContextBinding", generateStaticContextParamBinding(parameters, operationShape), - "endpointResolution", generateEndpointResolution(), - "postEndpointResolution", generatePostEndpointResolutionHook(settings, model, operationShape) - ) - ); + return next.HandleFinalize(ctx, in) + """, + MapUtils.of( + "pre", generatePreResolutionHooks(), + "assertRequest", generateAssertRequest(), + "assertResolver", generateAssertResolver(), + "resolveEndpoint", generateResolveEndpoint(), + "mergeAuthProperties", generateMergeAuthProperties(), + "post", generatePostResolutionHooks() + )); } - private GoWriter.Writable generatePreEndpointResolutionHook(GoSettings settings, Model model) { + private GoWriter.Writable generatePreResolutionHooks() { return (GoWriter writer) -> { - for (GoIntegration integration : this.integrations) { - integration.renderPreEndpointResolutionHook(settings, writer, model); + for (GoIntegration integration : context.getIntegrations()) { + integration.renderPreEndpointResolutionHook(context.getSettings(), writer, context.getModel()); } }; } - private GoWriter.Writable generateRequestValidator() { - return (GoWriter writer) -> { - writer.write( - """ - req, ok := in.Request.($P) - if !ok { - return out, metadata, $T(\"unknown transport type %T\", in.Request) - } - """, - SymbolUtils.createPointableSymbolBuilder("Request", SmithyGoDependency.SMITHY_HTTP_TRANSPORT).build(), - SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build() - ); - }; - } - - private GoWriter.Writable generateInputValidator(Model model, OperationShape operationShape) { - var opIndex = OperationIndex.of(model); - var inputOpt = opIndex.getInput(operationShape); - GoWriter.Writable inputValidator = (GoWriter writer) -> { - writer.write(""); - }; - - if (inputOpt.isPresent()) { - var input = inputOpt.get(); - for (var inputMember : input.getAllMembers().values()) { - var contextParamTraitOpt = inputMember.getTrait(ContextParamTrait.class); - if (contextParamTraitOpt.isPresent()) { - inputValidator = (GoWriter writer) -> { - writer.write( - """ - input, ok := in.Parameters.($P) - if !ok { - return out, metadata, $T(\"unknown transport type %T\", in.Request) - } - """, - SymbolUtils.createPointableSymbolBuilder(operationShape.getInput().get().getName()).build(), - SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build() - ); - }; + private GoWriter.Writable generateAssertRequest() { + return goTemplate(""" + req, ok := in.Request.($P) + if !ok { + return out, metadata, $T("unknown transport type %T", in.Request) } - } - } - return inputValidator; - } - - private GoWriter.Writable generateLegacyResolverValidator() { - return (GoWriter writer) -> { - writer.write( - """ - if m.EndpointResolver == nil { - return out, metadata, $T(\"expected endpoint resolver to not be nil\") - } """, - SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build() - ); - }; - } - - private GoWriter.Writable generateBuiltInResolverInvocation(Parameters parameters) { - return (GoWriter writer) -> { - for (Iterator iter = parameters.iterator(); iter.hasNext();) { - if (iter.next().getBuiltIn().isPresent()) { - for (GoIntegration integration : this.integrations) { - var builtInHandlerOpt = integration.getEndpointBuiltinHandler(); - if (builtInHandlerOpt.isPresent()) { - builtInHandlerOpt.get().renderEndpointBuiltInInvocation(writer); - } - } - break; - } - } - }; - } - - private GoWriter.Writable generateClientContextParamBinding( - Parameters parameters, Optional clientContextParamsTrait) { - return (GoWriter writer) -> { - if (clientContextParamsTrait.isPresent()) { - var clientContextParams = clientContextParamsTrait.get(); - parameters.forEach(param -> { - if (clientContextParams.getParameters().containsKey(param.getName().getName().getValue()) - && !param.getBuiltIn().isPresent() - ) { - var name = getExportedParameterName(param); - writer.write("params.$L = m.$L", name, name); - } - }); - } - }; + SmithyGoTypes.Transport.Http.Request, + GoStdlibTypes.Fmt.Errorf); } - private GoWriter.Writable generateContextParamBinding(OperationShape operationShape, Model model) { - return (GoWriter writer) -> { - var opIndex = OperationIndex.of(model); - var inputOpt = opIndex.getInput(operationShape); - if (inputOpt.isPresent()) { - var input = inputOpt.get(); - input.getAllMembers().values().forEach(inputMember -> { - var contextParamTraitOpt = inputMember.getTrait(ContextParamTrait.class); - if (contextParamTraitOpt.isPresent()) { - var contextParamTrait = contextParamTraitOpt.get(); - writer.write( - """ - params.$L = input.$L - """, - contextParamTrait.getName(), - inputMember.getMemberName() - ); - writer.write(""); - } - }); - } - writer.write(""); - }; - } - - private GoWriter.Writable generateStaticContextParamBinding(Parameters parameters, OperationShape operationShape) { - var staticContextParamTraitOpt = operationShape.getTrait(StaticContextParamsTrait.class); - return (GoWriter writer) -> { - parameters.forEach(param -> { - if (staticContextParamTraitOpt.isPresent()) { - var paramName = param.getName().getName().getValue(); - - var staticParam = staticContextParamTraitOpt - .get() - .getParameters() - .get(paramName); - if (staticParam != null) { - Symbol valueWrapper; - if (param.getType() == ParameterType.BOOLEAN) { - valueWrapper = SymbolUtils.createValueSymbolBuilder( - "Bool", SmithyGoDependency.SMITHY_PTR).build(); - writer.write( - "params.$L = $T($L)", paramName, valueWrapper, staticParam.getValue()); - } else if (param.getType() == ParameterType.STRING) { - valueWrapper = SymbolUtils.createValueSymbolBuilder( - "String", SmithyGoDependency.SMITHY_PTR).build(); - writer.write( - "params.$L = $T($L)", paramName, valueWrapper, String.format( - "\"%s\"", staticParam.getValue() - )); - - } else { - throw new CodegenException( - String.format("unexpected static context param type: %s", param.getType())); - } - } + private GoWriter.Writable generateAssertResolver() { + return goTemplate(""" + if m.options.EndpointResolverV2 == nil { + return out, metadata, $T("expected endpoint resolver to not be nil") } - }); - writer.write(""); - }; + """, + GoStdlibTypes.Fmt.Errorf); } - private GoWriter.Writable generateEndpointResolution() { - return goTemplate( - """ - var resolvedEndpoint $endpointType:T - resolvedEndpoint, err = m.EndpointResolver.ResolveEndpoint(ctx, params) + private GoWriter.Writable generateResolveEndpoint() { + return goTemplate(""" + params := bindEndpointParams(getOperationInput(ctx), m.options) + endpt, err := m.options.EndpointResolverV2.ResolveEndpoint(ctx, *params) if err != nil { - return out, metadata, $errorType:T(\"failed to resolve service endpoint, %w\", err) + return out, metadata, $1T("failed to resolve service endpoint, %w", err) } - req.URL = &resolvedEndpoint.URI - - for k := range resolvedEndpoint.Headers { - req.Header.Set( - k, - resolvedEndpoint.Headers.Get(k), - ) + if endpt.URI.RawPath == "" && req.URL.RawPath != "" { + endpt.URI.RawPath = endpt.URI.Path } - - """, - MapUtils.of( - "endpointType", SymbolUtils.createValueSymbolBuilder( - "Endpoint", SmithyGoDependency.SMITHY_ENDPOINTS - ).build(), - "errorType", SymbolUtils.createValueSymbolBuilder("Errorf", SmithyGoDependency.FMT).build() - ) - ); - } - - private GoWriter.Writable generatePostEndpointResolutionHook( - GoSettings settings, Model model, OperationShape operation - ) { - return (GoWriter writer) -> { - for (GoIntegration integration : this.integrations) { - integration.renderPostEndpointResolutionHook(settings, writer, model, Optional.of(operation)); - } - }; - } - - - private GoWriter.Writable generateMiddlewareAdder( - Parameters parameters, String operationName, Optional clientContextParamsTrait) { - - return (GoWriter writer) -> { - writer.write( - """ - func $L(stack $P, options Options) error { - return stack.Serialize.Insert(&$L{ - EndpointResolver: options.EndpointResolverV2, - $W - $W - }, \"ResolveEndpoint\", middleware.After) + req.URL.Scheme = endpt.URI.Scheme + req.URL.Host = endpt.URI.Host + req.URL.Path = $2T(endpt.URI.Path, req.URL.Path) + req.URL.RawPath = $2T(endpt.URI.RawPath, req.URL.RawPath) + for k := range endpt.Headers { + req.Header.Set(k, endpt.Headers.Get(k)) } """, - SymbolUtils.createValueSymbolBuilder(getAddEndpointMiddlewareFuncName(operationName)).build(), - SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE).build(), - SymbolUtils.createValueSymbolBuilder(getMiddlewareObjectName(operationName)).build(), - generateBuiltInInitialization(parameters), - generateClientContextParamInitialization(parameters, clientContextParamsTrait) - ); - }; + GoStdlibTypes.Fmt.Errorf, + SmithyGoTypes.Transport.Http.JoinPath); } - private GoWriter.Writable generateBuiltInInitialization(Parameters parameters) { - return (GoWriter writer) -> { - for (Iterator iter = parameters.iterator(); iter.hasNext();) { - if (iter.next().getBuiltIn().isPresent()) { - for (GoIntegration integration : this.integrations) { - var builtInHandlerOpt = integration.getEndpointBuiltinHandler(); - if (builtInHandlerOpt.isPresent()) { - builtInHandlerOpt.get().renderEndpointBuiltInInitialization(writer, parameters); - } - } - break; + private GoWriter.Writable generateMergeAuthProperties() { + return goTemplate(""" + rscheme := getResolvedAuthScheme(ctx) + if rscheme == nil { + return out, metadata, $T("no resolved auth scheme") } - } - }; - } - private GoWriter.Writable generateClientContextParamInitialization( - Parameters parameters, Optional clientContextParamsTrait) { + opts, _ := $T(&endpt.Properties) + for _, o := range opts { + rscheme.SignerProperties.SetAll(&o.SignerProperties) + } + """, GoStdlibTypes.Fmt.Errorf, SmithyGoTypes.Auth.GetAuthOptions); + } + private GoWriter.Writable generatePostResolutionHooks() { return (GoWriter writer) -> { - if (clientContextParamsTrait.isPresent()) { - var clientContextParams = clientContextParamsTrait.get(); - parameters.forEach(param -> { - if ( - clientContextParams.getParameters().containsKey(param.getName().getName().getValue()) - && !param.getBuiltIn().isPresent() - ) { - var name = getExportedParameterName(param); - writer.write("$L: options.$L,", name, name); - } - }); + for (GoIntegration integration : context.getIntegrations()) { + integration.renderPostEndpointResolutionHook(context.getSettings(), writer, context.getModel()); } }; } - - public static String getAddEndpointMiddlewareFuncName(String operationName) { - return String.format("add%sResolveEndpointMiddleware", operationName); - } - - - public static String getMiddlewareObjectName(String operationName) { - return String.format("op%sResolveEndpointMiddleware", operationName); - } - - public static String getExportedParameterName(Parameter parameter) { - return StringUtils.capitalize(parameter.getName().getName().getValue()); - } - - public static Symbol parameterAsSymbol(Parameter parameter) { - return switch (parameter.getType()) { - case STRING -> SymbolUtils.createPointableSymbolBuilder("string") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true).build(); - - case BOOLEAN -> SymbolUtils.createPointableSymbolBuilder("bool") - .putProperty(SymbolUtils.GO_UNIVERSE_TYPE, true).build(); - }; - } - - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder implements SmithyBuilder { - - List integrations; - - private Builder() { - } - - public Builder integrations(List integrations) { - this.integrations = integrations; - return this; - } - - - @Override - public EndpointMiddlewareGenerator build() { - return new EndpointMiddlewareGenerator(this); - } - } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterBindingsGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterBindingsGenerator.java new file mode 100644 index 000000000..e7884dff2 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterBindingsGenerator.java @@ -0,0 +1,121 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.endpoints; + +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter; +import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait; +import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; +import software.amazon.smithy.utils.MapUtils; + +/** + * Generates the main endpoint parameter binding function. Operation-specific bindings are generated elsewhere + * conditionally through EndpointParameterOperationBindingsGenerator. + */ +public class EndpointParameterBindingsGenerator { + private final ProtocolGenerator.GenerationContext context; + + private final Map builtinBindings; + + public EndpointParameterBindingsGenerator(ProtocolGenerator.GenerationContext context) { + this.context = context; + this.builtinBindings = context.getIntegrations().stream() + .flatMap(it -> it.getClientPlugins(context.getModel(), context.getService()).stream()) + .flatMap(it -> it.getEndpointBuiltinBindings().entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + public GoWriter.Writable generate() { + if (!context.getService().hasTrait(EndpointRuleSetTrait.class)) { + return emptyGoTemplate(); + } + + return goTemplate(""" + type endpointParamsBinder interface { + bindEndpointParams(*EndpointParameters) + } + + func bindEndpointParams(input interface{}, options Options) *EndpointParameters { + params := &EndpointParameters{} + + $builtinBindings:W + + $clientContextBindings:W + + if b, ok := input.(endpointParamsBinder); ok { + b.bindEndpointParams(params) + } + + return params + } + """, + MapUtils.of( + "builtinBindings", generateBuiltinBindings(), + "clientContextBindings", generateClientContextBindings() + )); + } + + private GoWriter.Writable generateBuiltinBindings() { + var bindings = new HashMap(); + for (var integration: context.getIntegrations()) { + var plugins = integration.getClientPlugins(context.getModel(), context.getService()); + for (var plugin: plugins) { + bindings.putAll(plugin.getEndpointBuiltinBindings()); + } + } + + var params = new ArrayList(); + context.getEndpointRules().getParameters().forEach(params::add); + var boundBuiltins = params.stream() + .filter(it -> it.isBuiltIn() && bindings.containsKey(it.getBuiltIn().get())) + .toList(); + return writer -> { + for (var param: boundBuiltins) { + writer.write( + "params.$L = $W", + EndpointParametersGenerator.getExportedParameterName(param), + builtinBindings.get(param.getBuiltIn().get())); + } + }; + } + + private GoWriter.Writable generateClientContextBindings() { + if (!context.getService().hasTrait(ClientContextParamsTrait.class)) { + return goTemplate(""); + } + + var allParams = new ArrayList(); + context.getEndpointRules().getParameters().forEach(allParams::add); + var contextParams = context.getService().expectTrait(ClientContextParamsTrait.class).getParameters(); + var params = allParams.stream() + .filter(it -> contextParams.containsKey(it.getName().getName().getValue()) && !it.isBuiltIn()) + .toList(); + return writer -> { + params.forEach(it -> { + writer.write("params.$1L = options.$1L", + EndpointParametersGenerator.getExportedParameterName(it)); + }); + }; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterOperationBindingsGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterOperationBindingsGenerator.java new file mode 100644 index 000000000..4060747ac --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterOperationBindingsGenerator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.endpoints; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.rulesengine.traits.ContextParamTrait; +import software.amazon.smithy.rulesengine.traits.StaticContextParamDefinition; +import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait; + +/** + * Generates operation-specific bindings (@contextParam + @staticContextParam) as a receiver method on the operation's + * input structure. + */ +public class EndpointParameterOperationBindingsGenerator { + private final OperationShape operation; + private final StructureShape input; + private final Symbol inputSymbol; + + public EndpointParameterOperationBindingsGenerator( + OperationShape operation, + StructureShape input, + Symbol inputSymbol + ) { + this.operation = operation; + this.input = input; + this.inputSymbol = inputSymbol; + } + + private boolean hasBindings() { + var hasContextBindings = input.getAllMembers().values().stream().anyMatch(it -> + it.hasTrait(ContextParamTrait.class)); + return hasContextBindings || operation.hasTrait(StaticContextParamsTrait.class); + } + + public GoWriter.Writable generate() { + if (!hasBindings()) { + return goTemplate(""); + } + + return goTemplate(""" + func (in $P) bindEndpointParams(p *EndpointParameters) { + $W + $W + } + """, + inputSymbol, + generateContextParamBindings(), + generateStaticContextParamBindings()); + } + + private GoWriter.Writable generateContextParamBindings() { + return writer -> { + input.getAllMembers().values().forEach(it -> { + if (!it.hasTrait(ContextParamTrait.class)) { + return; + } + + var contextParam = it.expectTrait(ContextParamTrait.class); + writer.write("p.$L = in.$L", contextParam.getName(), it.getMemberName()); + }); + }; + } + + private GoWriter.Writable generateStaticContextParamBindings() { + if (!operation.hasTrait(StaticContextParamsTrait.class)) { + return goTemplate(""); + } + + StaticContextParamsTrait params = operation.expectTrait(StaticContextParamsTrait.class); + return writer -> { + params.getParameters().forEach((k, v) -> { + writer.write("p.$L = $W", k, generateStaticLiteral(v)); + }); + }; + } + + private GoWriter.Writable generateStaticLiteral(StaticContextParamDefinition literal) { + return writer -> { + Node value = literal.getValue(); + if (value.isStringNode()) { + writer.writeInline("$T($S)", SmithyGoTypes.Ptr.String, value.expectStringNode().getValue()); + } else if (value.isBooleanNode()) { + writer.writeInline("$T($L)", SmithyGoTypes.Ptr.Bool, value.expectBooleanNode().getValue()); + } else { + throw new CodegenException("unrecognized static context param value type"); + } + }; + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParametersGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParametersGenerator.java index cbc210108..3a35f8217 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParametersGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParametersGenerator.java @@ -43,8 +43,6 @@ public final class EndpointParametersGenerator { public static final String VALIDATE_REQUIRED_FUNC_NAME = "ValidateRequired"; public static final String DEFAULT_VALUE_FUNC_NAME = "WithDefaults"; - public static final String LOGGER_MEMBER_NAME = "SDKClientLogger"; - public static final String ENABLE_LOGGING_MEMBER_NAME = "LogRuleFuncErrors"; private final Map commonCodegenArgs; private EndpointParametersGenerator(Builder builder) { diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java index ba488adc7..b46bcdeac 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; @@ -28,9 +29,6 @@ import software.amazon.smithy.rulesengine.traits.EndpointTestCase; import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait; - - - /** * Generates all components required for Smithy Ruleset Endpoint Resolution. * These components include a Provider, Parameters, and Tests. @@ -64,6 +62,11 @@ public EndpointResolutionGenerator(FnProvider fnProvider) { } public void generate(ProtocolGenerator.GenerationContext context) { + if (context.getWriter().isEmpty()) { + throw new CodegenException("writer is required"); + } + + var writer = context.getWriter().get(); var serviceShape = context.getService(); @@ -81,45 +84,28 @@ public void generate(ProtocolGenerator.GenerationContext context) { .fnProvider(this.fnProvider) .build(); - var middlewareGenerator = EndpointMiddlewareGenerator.builder() - .integrations(context.getIntegrations()) - .build(); + var bindingGenerator = new EndpointParameterBindingsGenerator(context); + var middlewareGenerator = new EndpointMiddlewareGenerator(context); Optional ruleset = serviceShape.getTrait(EndpointRuleSetTrait.class) - .map( - (trait) - -> EndpointRuleSet.fromNode(trait.getRuleSet()) - ); - - - context.getWriter() - .map( - (writer) - -> writer.write("$W", parametersGenerator.generate(ruleset)) - ); - - context.getWriter() - .map( - (writer) - -> writer.write("$W", resolverGenerator.generate(ruleset)) - ); - - middlewareGenerator.generate( - context.getSettings(), - context.getModel(), - context.getSymbolProvider(), - context.getDelegator()); + .map((trait) -> EndpointRuleSet.fromNode(trait.getRuleSet())); + + writer + .write("$W\n", parametersGenerator.generate(ruleset)) + .write("$W\n", resolverGenerator.generate(ruleset)) + .write("$W\n", bindingGenerator.generate()) + .write("$W", middlewareGenerator.generate()); } public void generateTests(ProtocolGenerator.GenerationContext context) { + if (context.getWriter().isEmpty()) { + throw new CodegenException("writer is required"); + } + var writer = context.getWriter().get(); var serviceShape = context.getService(); Optional ruleset = serviceShape.getTrait(EndpointRuleSetTrait.class) - .map( - (trait) - -> EndpointRuleSet.fromNode(trait.getRuleSet()) - ); - + .map((trait) -> EndpointRuleSet.fromNode(trait.getRuleSet())); var testsGenerator = EndpointTestsGenerator.builder() .parametersType(parametersType) @@ -132,15 +118,6 @@ public void generateTests(ProtocolGenerator.GenerationContext context) { var endpointTestTrait = serviceShape.getTrait(EndpointTestsTrait.class); endpointTestTrait.ifPresent(trait -> testCases.addAll(trait.getTestCases())); - context.getWriter() - .map( - (writer) -> { - return writer.write("$W", testsGenerator.generate(ruleset, testCases)); - } - ); - + writer.write("$W", testsGenerator.generate(ruleset, testCases)); } - - - } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java index 37bee724b..5d7190c2f 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java @@ -35,6 +35,7 @@ import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.SmithyGoTypes; import software.amazon.smithy.go.codegen.SymbolUtils; import software.amazon.smithy.model.SourceException; import software.amazon.smithy.model.SourceLocation; @@ -422,37 +423,31 @@ private GoWriter.Writable generateNewHeaderValue(String headerName, List properties, Scope scope) { - Map propertyTypeArg = MapUtils.of( - "memberName", "Properties", - "propertyType", SymbolUtils.createValueSymbolBuilder("Properties", - SmithyGoDependency.SMITHY).build()); - if (properties.isEmpty()) { return emptyGoTemplate(); } - var writableProperties = new TreeMap(); var generator = new ExpressionGenerator(scope, this.fnProvider); - properties.forEach((k, v) -> { - writableProperties.put(k.toString(), generator.generate(v)); - }); + return goTemplate(""" + Properties: func() $1T { + var out $1T + $2W + return out + }(), + """, + SmithyGoTypes.Smithy.Properties, + GoWriter.ChainWritable.of( + properties.entrySet().stream() + .map(it -> generateSetProperty(generator, it.getKey(), it.getValue())) + .toList() + ).compose(false)); + } - return goBlockTemplate( - """ - $memberName:L: func() $propertyType:T{ - var out $propertyType:T - """, - """ - return out - }(), - """, propertyTypeArg, - (w) -> { - writableProperties.forEach((k, v) -> { - // TODO these properties should be typed, and ignore properties that are - // unknown. - w.write("out.Set($S, $W)", k, v); - }); - }); + private GoWriter.Writable generateSetProperty(ExpressionGenerator generator, Identifier ident, Expression expr) { + // FUTURE: add these via GoIntegration? + return ident.toString().equals("authSchemes") + ? new AuthSchemePropertyGenerator(generator).generate(expr) + : goTemplate("out.Set($S, $W)", ident.toString(), generator.generate(expr)); } class RuleVisitor implements RuleValueVisitor { diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointTestsGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointTestsGenerator.java index a57d34898..751fc3297 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointTestsGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointTestsGenerator.java @@ -42,6 +42,7 @@ import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.rulesengine.language.EndpointRuleSet; +import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression; import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters; import software.amazon.smithy.rulesengine.traits.EndpointTestCase; import software.amazon.smithy.rulesengine.traits.ExpectedEndpoint; @@ -274,19 +275,26 @@ GoWriter.Writable generateProperties(Map properties) { return goTemplate("Properties: $propertiesType:T{},", commonArgs); } + var expressionGenerator = new ExpressionGenerator(Scope.empty(), name -> null); + return goBlockTemplate(""" Properties: func() $propertiesType:T { - var properties $propertiesType:T + var out $propertiesType:T """, """ - return properties + return out }(), """, commonArgs, (w) -> { properties.forEach((key, value) -> { - w.writeGoTemplate("properties.Set($key:S, $value:W)", - commonArgs, MapUtils.of( - "key", key, - "value", generateNodeValue(value))); + if (key.equals("authSchemes")) { + w.write("$W", new AuthSchemePropertyGenerator(expressionGenerator) + .generate(Expression.fromNode(value))); + } else { + w.writeGoTemplate("out.Set($key:S, $value:W)", + commonArgs, MapUtils.of( + "key", key, + "value", generateNodeValue(value))); + } }); }); } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/ExpressionGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/ExpressionGenerator.java index 7c9d33d9e..29e27d1d8 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/ExpressionGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/ExpressionGenerator.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.function.Function; -import java.util.stream.Stream; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; @@ -132,8 +131,9 @@ public GoWriter.Writable visitBoolean(boolean b) { @Override public GoWriter.Writable visitString(Template value) { - Stream parts = value.accept( - new TemplateGeneratorVisitor((expr) -> new ExpressionGenerator(scope, fnProvider).generate(expr))); + var parts = value.accept( + new TemplateGeneratorVisitor((expr) -> new ExpressionGenerator(scope, fnProvider).generate(expr)) + ).toList(); return (GoWriter w) -> { parts.forEach((p) -> w.write("$W", p)); diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/AuthSchemeDefinition.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/AuthSchemeDefinition.java new file mode 100644 index 000000000..015e1c69b --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/AuthSchemeDefinition.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + * + */ + +package software.amazon.smithy.go.codegen.integration; + +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; + +/** + * Defines code generation for a modeled auth scheme. + */ +public interface AuthSchemeDefinition { + /** + * Generates the service default option for this scheme. + */ + GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext context, ServiceShape service); + + /** + * Generates an operation-specific option for this scheme. This will only be called when the generator encounters + * an operation with auth overrides. + */ + GoWriter.Writable generateOperationOption(ProtocolGenerator.GenerationContext context, OperationShape operation); + + /** + * Generates a default auth scheme. Called within a context where client Options are available. + */ + default GoWriter.Writable generateDefaultAuthScheme() { + return emptyGoTemplate(); + } + + /** + * Generates the value to return from Options.GetIdentityResolver(schemeID). Called within a context where client + * Options are available. + */ + default GoWriter.Writable generateOptionsIdentityResolver() { + return goTemplate("nil"); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ConfigFieldResolver.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ConfigFieldResolver.java index 67874cce3..93e229d87 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ConfigFieldResolver.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ConfigFieldResolver.java @@ -120,7 +120,14 @@ public enum Target { /** * Indicates that the resolver targets config fields after customer mutation. */ - FINALIZATION + FINALIZATION, + + /** + * Indicates that the resolver targets config fields after the client has been instantiated. Resolvers with this + * target can then take a reference to the instantiated client in existing Options, but CANNOT modify fields on + * Options since it is passed to the already-existing client by value. + */ + FINALIZATION_WITH_CLIENT } public static class Builder implements SmithyBuilder { diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EndpointHostPrefixMiddleware.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EndpointHostPrefixMiddleware.java index 93484e73f..8b7853eb8 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EndpointHostPrefixMiddleware.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EndpointHostPrefixMiddleware.java @@ -27,6 +27,7 @@ import software.amazon.smithy.go.codegen.MiddlewareIdentifier; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.SymbolUtils; +import software.amazon.smithy.go.codegen.endpoints.EndpointMiddlewareGenerator; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.TopDownIndex; import software.amazon.smithy.model.pattern.SmithyPattern; @@ -89,8 +90,8 @@ public void writeAdditionalFiles( middlewareHelperName, () -> { writer.write( - "return stack.Serialize.Insert(&$L{}, `OperationSerializer`, middleware.After)", - middlewareName); + "return stack.Finalize.Insert(&$L{}, $S, middleware.After)", + middlewareName, EndpointMiddlewareGenerator.MIDDLEWARE_ID); }); }); }); @@ -104,7 +105,7 @@ private static void writeMiddleware( SmithyPattern pattern ) { GoStackStepMiddlewareGenerator middlewareGenerator = - GoStackStepMiddlewareGenerator.createSerializeStepMiddleware( + GoStackStepMiddlewareGenerator.createFinalizeStepMiddleware( getMiddlewareName(operation), MIDDLEWARE_ID ); @@ -130,9 +131,10 @@ private static void writeMiddleware( writer.addUseImports(SmithyGoDependency.STRINGS); writer.addUseImports(SmithyGoDependency.SMITHY); StructureShape input = ProtocolUtils.expectInput(model, operation); - writer.write("input, ok := in.Parameters.($P)", symbolProvider.toSymbol(input)); + writer.write("opaqueInput := getOperationInput(ctx)"); + writer.write("input, ok := opaqueInput.($P)", symbolProvider.toSymbol(input)); w.openBlock("if !ok {", "}", () -> { - writer.write("return out, metadata, fmt.Errorf(\"unknown input type %T\", in.Parameters)"); + writer.write("return out, metadata, fmt.Errorf(\"unknown input type %T\", opaqueInput)"); }).write(""); w.write("var prefix strings.Builder"); diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/GoIntegration.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/GoIntegration.java index 28569dcaf..077c43ff9 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/GoIntegration.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/GoIntegration.java @@ -17,16 +17,14 @@ import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.function.Consumer; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoDelegator; import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.TriConsumer; -import software.amazon.smithy.go.codegen.endpoints.EndpointBuiltInHandler; import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; /** @@ -177,6 +175,15 @@ default List getClientPlugins() { return Collections.emptyList(); } + /** + * Gets a list of plugins to apply to the generated client. + * + * @return Returns the list of RuntimePlugins to apply to the client. + */ + default List getClientPlugins(Model model, ServiceShape service) { + return getClientPlugins().stream().filter(plugin -> plugin.matchesService(model, service)).toList(); + } + /** * Processes the given serviceId and may return a unmodified, modified, or replacement value. * @@ -189,22 +196,11 @@ default String processServiceId(GoSettings settings, Model model, String service return serviceId; } - /** - * Used by integrations to provide an EndpointBuiltInHandler - * that allows endpoint resolution middleware generation - * to resolve BuiltIn values. - */ - default Optional getEndpointBuiltinHandler() { - return Optional.empty(); - } - default void renderPreEndpointResolutionHook(GoSettings settings, GoWriter writer, Model model) { // pass } - default void renderPostEndpointResolutionHook( - GoSettings settings, GoWriter writer, Model model, Optional operation) { + default void renderPostEndpointResolutionHook(GoSettings settings, GoWriter writer, Model model) { // pass } - } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java index 9fed920f3..a8840ce06 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java @@ -29,6 +29,7 @@ import software.amazon.smithy.go.codegen.GoSettings; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.Synthetic; +import software.amazon.smithy.go.codegen.auth.AuthGenerator; import software.amazon.smithy.go.codegen.endpoints.EndpointResolutionGenerator; import software.amazon.smithy.go.codegen.endpoints.FnGenerator; import software.amazon.smithy.model.Model; @@ -40,6 +41,8 @@ import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.rulesengine.language.EndpointRuleSet; +import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; import software.amazon.smithy.utils.CaseUtils; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.StringUtils; @@ -481,6 +484,15 @@ default void generateEndpointResolutionTests(GenerationContext context) { generator.generateTests(context); } + /** + * Generates smithy client auth components. + * + * @param context The generation context. + */ + default void generateAuth(GenerationContext context) { + new AuthGenerator(context).generate(); + } + /** * Context object used for service serialization and deserialization. */ @@ -533,6 +545,10 @@ public ServiceShape getService() { return service; } + public EndpointRuleSet getEndpointRules() { + return EndpointRuleSet.fromNode(service.expectTrait(EndpointRuleSetTrait.class).getRuleSet()); + } + public SymbolProvider getSymbolProvider() { return symbolProvider; } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/RuntimeClientPlugin.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/RuntimeClientPlugin.java index 8b3b7075e..94f499649 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/RuntimeClientPlugin.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/RuntimeClientPlugin.java @@ -16,14 +16,20 @@ package software.amazon.smithy.go.codegen.integration; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BiPredicate; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.auth.AuthParameter; +import software.amazon.smithy.go.codegen.auth.AuthParametersResolver; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; @@ -43,7 +49,11 @@ public final class RuntimeClientPlugin implements ToSmithyBuilder configFieldResolvers; private final Set clientMembers; private final Set clientMemberResolvers; + private final Set authParameters; + private final Set authParameterResolvers; private final MiddlewareRegistrar registerMiddleware; + private final Map endpointBuiltinBindings; + private final Map authSchemeDefinitions; private RuntimeClientPlugin(Builder builder) { operationPredicate = builder.operationPredicate; @@ -52,10 +62,13 @@ private RuntimeClientPlugin(Builder builder) { registerMiddleware = builder.registerMiddleware; clientMembers = builder.clientMembers; clientMemberResolvers = builder.clientMemberResolvers; + authParameters = builder.authParameters; + authParameterResolvers = builder.authParameterResolvers; configFieldResolvers = builder.configFieldResolvers; + endpointBuiltinBindings = builder.endpointBuiltinBindings; + authSchemeDefinitions = builder.authSchemeDefinitions; } - @FunctionalInterface public interface OperationPredicate { /** @@ -85,6 +98,38 @@ public Set getClientMemberResolvers() { return clientMemberResolvers; } + /** + * Gets the auth parameters that will be added by this plugin. + * @return the auth parameters. + */ + public Set getAuthParameters() { + return authParameters; + } + + /** + * Gets the auth parameter resolvers that will be added by this plugin. + * @return the auth parameter resolvers. + */ + public Set getAuthParameterResolvers() { + return authParameterResolvers; + } + + /** + * Gets the endpoint builtin bindings that will be rendered by this plugin. + * @return the bindings. + */ + public Map getEndpointBuiltinBindings() { + return endpointBuiltinBindings; + } + + /** + * Gets the registered auth scheme codegen definitions. + * @return the definitions. + */ + public Map getAuthSchemeDefinitions() { + return authSchemeDefinitions; + } + /** * Gets the optionally present middleware registrar object that resolves to middleware registering function. * @@ -192,7 +237,11 @@ public static final class Builder implements SmithyBuilder private Set configFieldResolvers = new HashSet<>(); private Set clientMembers = new HashSet<>(); private Set clientMemberResolvers = new HashSet<>(); + private Set authParameters = new HashSet<>(); + private Set authParameterResolvers = new HashSet<>(); + private Map endpointBuiltinBindings = new HashMap<>(); private MiddlewareRegistrar registerMiddleware; + private Map authSchemeDefinitions = new HashMap<>(); @Override public RuntimeClientPlugin build() { @@ -403,5 +452,49 @@ public Builder addClientMemberResolver(ClientMemberResolver clientMemberResolver this.clientMemberResolvers.add(clientMemberResolver); return this; } + + /** + * Adds a field to the auth parameters. + * + * @param param The field. + * @return Returns the builder. + */ + public Builder addAuthParameter(AuthParameter param) { + this.authParameters.add(param); + return this; + } + + /** + * Adds a resolver for fields on auth parameters. + * + * @param resolver The auth field resolver. + * @return Returns the builder. + */ + public Builder addAuthParameterResolver(AuthParametersResolver resolver) { + this.authParameterResolvers.add(resolver); + return this; + } + + /** + * Adds a binding for an endpoint parameter builtin. + * @param name The name of the builtin. + * @param binding The writable binding. + * @return Returns the builder. + */ + public Builder addEndpointBuiltinBinding(String name, GoWriter.Writable binding) { + this.endpointBuiltinBindings.put(name, binding); + return this; + } + + /** + * Registers a codegen definition for a modeled auth scheme. + * @param schemeId The scheme id. + * @param definition The codegen definition. + * @return Returns the builder. + */ + public Builder addAuthSchemeDefinition(ShapeId schemeId, AuthSchemeDefinition definition) { + this.authSchemeDefinitions.put(schemeId, definition); + return this; + } } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/AnonymousAuthScheme.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/AnonymousAuthScheme.java new file mode 100644 index 000000000..c97bc81d9 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/AnonymousAuthScheme.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.integration.auth; + +import java.util.List; +import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; +import software.amazon.smithy.go.codegen.integration.GoIntegration; +import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; +import software.amazon.smithy.model.traits.synthetic.NoAuthTrait; +import software.amazon.smithy.utils.ListUtils; + +public class AnonymousAuthScheme implements GoIntegration { + private static final AuthSchemeDefinition ANONYMOUS_DEFINITION = new AnonymousDefinition(); + + @Override + public List getClientPlugins() { + return ListUtils.of( + RuntimeClientPlugin.builder() + .addAuthSchemeDefinition(NoAuthTrait.ID, ANONYMOUS_DEFINITION) + .build() + ); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/AnonymousDefinition.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/AnonymousDefinition.java new file mode 100644 index 000000000..0be8d1ec0 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/AnonymousDefinition.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.integration.auth; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; + +/** + * Implements codegen for smithy.api#noAuth. + */ +public class AnonymousDefinition implements AuthSchemeDefinition { + @Override + public GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext c, ServiceShape s) { + return goTemplate("&$T{SchemeID: $T},", + SmithyGoTypes.Auth.Option, + SmithyGoTypes.Auth.SchemeIDAnonymous); + } + + @Override + public GoWriter.Writable generateOperationOption(ProtocolGenerator.GenerationContext c, OperationShape o) { + return goTemplate("&$T{SchemeID: $T},", + SmithyGoTypes.Auth.Option, + SmithyGoTypes.Auth.SchemeIDAnonymous); + } + + @Override + public GoWriter.Writable generateOptionsIdentityResolver() { + return goTemplate("&$T{}", SmithyGoTypes.Auth.AnonymousIdentityResolver); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/HttpBearerAuth.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/HttpBearerAuth.java deleted file mode 100644 index 5ec697581..000000000 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/HttpBearerAuth.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.smithy.go.codegen.integration.auth; - -import java.util.List; -import java.util.Map; -import software.amazon.smithy.codegen.core.SymbolProvider; -import software.amazon.smithy.go.codegen.GoDelegator; -import software.amazon.smithy.go.codegen.GoSettings; -import software.amazon.smithy.go.codegen.GoWriter; -import software.amazon.smithy.go.codegen.SmithyGoDependency; -import software.amazon.smithy.go.codegen.SymbolUtils; -import software.amazon.smithy.go.codegen.integration.ConfigField; -import software.amazon.smithy.go.codegen.integration.ConfigFieldResolver; -import software.amazon.smithy.go.codegen.integration.GoIntegration; -import software.amazon.smithy.go.codegen.integration.MiddlewareRegistrar; -import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.knowledge.ServiceIndex; -import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.ServiceShape; -import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.traits.HttpBearerAuthTrait; -import software.amazon.smithy.model.traits.OptionalAuthTrait; -import software.amazon.smithy.model.traits.Trait; -import software.amazon.smithy.utils.ListUtils; - -/** - * Integration to add support for httpBearerAuth authentication scheme to an API client. - */ -public class HttpBearerAuth implements GoIntegration { - - public static final String TOKEN_PROVIDER_OPTION_NAME = "BearerAuthTokenProvider"; - private static final String SIGNER_OPTION_NAME = "BearerAuthSigner"; - private static final String NEW_DEFAULT_SIGNER_NAME = "newDefault" + SIGNER_OPTION_NAME; - private static final String SIGNER_RESOLVER_NAME = "resolve" + SIGNER_OPTION_NAME; - private static final String REGISTER_MIDDLEWARE_NAME = "add" + SIGNER_OPTION_NAME + "Middleware"; - - @Override - public void writeAdditionalFiles( - GoSettings settings, - Model model, - SymbolProvider symbolProvider, - GoDelegator goDelegator - ) { - var service = settings.getService(model); - if (!isSupportedAuthentication(model, service)) { - return; - } - - goDelegator.useShapeWriter(service, (writer) -> { - writeMiddlewareRegister(writer); - writeSignerConfigFieldResolver(writer); - writeNewSignerFunc(writer); - }); - } - - private void writeMiddlewareRegister(GoWriter writer) { - writer.pushState(); - - writer.putContext("funcName", REGISTER_MIDDLEWARE_NAME); - writer.putContext("stack", SymbolUtils.createValueSymbolBuilder("Stack", - SmithyGoDependency.SMITHY_MIDDLEWARE).build()); - writer.putContext("addMiddleware", SymbolUtils.createValueSymbolBuilder("AddAuthenticationMiddleware", - SmithyGoDependency.SMITHY_AUTH_BEARER).build()); - writer.putContext("signerOption", SIGNER_OPTION_NAME); - writer.putContext("providerOption", TOKEN_PROVIDER_OPTION_NAME); - - writer.write(""" - func $funcName:L(stack *$stack:T, o Options) error { - return $addMiddleware:T(stack, o.$signerOption:L, o.$providerOption:L) - } - """); - - writer.popState(); - } - - private void writeSignerConfigFieldResolver(GoWriter writer) { - writer.pushState(); - - writer.putContext("funcName", SIGNER_RESOLVER_NAME); - writer.putContext("signerOption", SIGNER_OPTION_NAME); - writer.putContext("newDefaultSigner", NEW_DEFAULT_SIGNER_NAME); - - writer.write(""" - func $funcName:L(o *Options) { - if o.$signerOption:L != nil { - return - } - o.$signerOption:L = $newDefaultSigner:L(*o) - } - """); - - writer.popState(); - } - - private void writeNewSignerFunc(GoWriter writer) { - writer.pushState(); - - writer.putContext("funcName", NEW_DEFAULT_SIGNER_NAME); - writer.putContext("signerInterface", SymbolUtils.createValueSymbolBuilder("Signer", - SmithyGoDependency.SMITHY_AUTH_BEARER).build()); - - // TODO this is HTTP specific, should be based on protocol/transport of API. - writer.putContext("newDefaultSigner", SymbolUtils.createValueSymbolBuilder("NewSignHTTPSMessage", - SmithyGoDependency.SMITHY_AUTH_BEARER).build()); - - writer.write(""" - func $funcName:L(o Options) $signerInterface:T { - return $newDefaultSigner:T() - } - """); - - writer.popState(); - } - - @Override - public List getClientPlugins() { - return ListUtils.of( - RuntimeClientPlugin.builder() - .servicePredicate(HttpBearerAuth::isSupportedAuthentication) - .addConfigField(ConfigField.builder() - .name(TOKEN_PROVIDER_OPTION_NAME) - .type(SymbolUtils.createValueSymbolBuilder("TokenProvider", - SmithyGoDependency.SMITHY_AUTH_BEARER).build()) - .documentation("Bearer token value provider") - .build()) - .build(), - RuntimeClientPlugin.builder() - .servicePredicate(HttpBearerAuth::isSupportedAuthentication) - .addConfigField(ConfigField.builder() - .name(SIGNER_OPTION_NAME) - .type(SymbolUtils.createValueSymbolBuilder("Signer", - SmithyGoDependency.SMITHY_AUTH_BEARER).build()) - .documentation("Signer for authenticating requests with bearer auth") - .build()) - .addConfigFieldResolver(ConfigFieldResolver.builder() - .location(ConfigFieldResolver.Location.CLIENT) - .target(ConfigFieldResolver.Target.INITIALIZATION) - .resolver(SymbolUtils.createValueSymbolBuilder(SIGNER_RESOLVER_NAME).build()) - .build()) - .build(), - - // TODO this is incorrect for an API client/operation that supports multiple auth schemes. - RuntimeClientPlugin.builder() - .operationPredicate(HttpBearerAuth::hasBearerAuthScheme) - .registerMiddleware(MiddlewareRegistrar.builder() - .resolvedFunction(SymbolUtils.createValueSymbolBuilder( - REGISTER_MIDDLEWARE_NAME).build()) - .useClientOptions() - .build()) - .build() - ); - } - - /** - * Returns if the service has the httpBearerAuth trait. - * - * @param model model definition - * @param service service shape for the API - * @return if the httpBearerAuth trait is used by the service - */ - public static boolean isSupportedAuthentication(Model model, ServiceShape service) { - return ServiceIndex.of(model).getAuthSchemes(service).values().stream().anyMatch(trait -> trait.getClass() - .equals(HttpBearerAuthTrait.class)); - - } - - /** - * Returns if the service and operation support the httpBearerAuthTrait. - * - * @param model model definition - * @param service service shape for the API - * @param operation operation shape - * @return if the service and operation support the httpBearerAuthTrait - */ - public static boolean hasBearerAuthScheme(Model model, ServiceShape service, OperationShape operation) { - Map auth = ServiceIndex.of(model).getEffectiveAuthSchemes(service.getId(), operation.getId()); - return auth.containsKey(HttpBearerAuthTrait.ID) && !operation.hasTrait(OptionalAuthTrait.class); - } -} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/HttpBearerDefinition.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/HttpBearerDefinition.java new file mode 100644 index 000000000..35dab1109 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/HttpBearerDefinition.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.integration.auth; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; + +/** + * Implements codegen for smithy.api#httpBearerAuth. + */ +public class HttpBearerDefinition implements AuthSchemeDefinition { + @Override + public GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext context, ServiceShape service) { + return goTemplate("&$T{SchemeID: $T},", + SmithyGoTypes.Auth.Option, + SmithyGoTypes.Auth.SchemeIDHTTPBearer); + } + + @Override + public GoWriter.Writable generateOperationOption(ProtocolGenerator.GenerationContext c, OperationShape o) { + return goTemplate("&$T{SchemeID: $T},", + SmithyGoTypes.Auth.Option, + SmithyGoTypes.Auth.SchemeIDHTTPBearer); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4AuthScheme.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4AuthScheme.java new file mode 100644 index 000000000..087be72ab --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4AuthScheme.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.integration.auth; + +import java.util.List; +import software.amazon.smithy.aws.traits.auth.SigV4Trait; +import software.amazon.smithy.go.codegen.auth.AuthParameter; +import software.amazon.smithy.go.codegen.integration.GoIntegration; +import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.utils.ListUtils; + +/** + * Code generation for SigV4. + */ +public class SigV4AuthScheme implements GoIntegration { + private boolean isSigV4Service(Model model, ServiceShape service) { + return service.hasTrait(SigV4Trait.class); + } + + @Override + public List getClientPlugins() { + // FUTURE: add default Region client option, scheme definition, and resolver - we need a more structured way of + // suppressing elements of a GoIntegration before we do so, for now those live on the SDK side + return ListUtils.of( + RuntimeClientPlugin.builder() + .servicePredicate(this::isSigV4Service) + .addAuthParameter(AuthParameter.REGION) + .build() + ); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4Definition.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4Definition.java new file mode 100644 index 000000000..bef4c50ff --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4Definition.java @@ -0,0 +1,92 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.integration.auth; + +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import java.util.Map; +import software.amazon.smithy.aws.traits.auth.SigV4Trait; +import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.utils.MapUtils; + +/** + * Implements codegen for aws.auth#sigv4. + */ +public class SigV4Definition implements AuthSchemeDefinition { + private static final Map COMMON_ENV = MapUtils.of( + "properties", SmithyGoTypes.Smithy.Properties, + "option", SmithyGoTypes.Auth.Option, + "schemeId", SmithyGoTypes.Auth.SchemeIDSigV4, + "setSigningName", SmithyGoTypes.Transport.Http.SetSigV4SigningName, + "setSigningRegion", SmithyGoTypes.Transport.Http.SetSigV4SigningRegion + ); + + @Override + public GoWriter.Writable generateServiceOption(ProtocolGenerator.GenerationContext context, ServiceShape service) { + var trait = service.expectTrait(SigV4Trait.class); + return goTemplate(""" + &$option:T{ + SchemeID: $schemeId:T, + SignerProperties: func() $properties:T { + var props $properties:T + $setSigningName:T(&props, $name:S) + $setSigningRegion:T(&props, params.Region) + return props + }(), + },""", + COMMON_ENV, + MapUtils.of( + "name", trait.getName() + )); + } + + @Override + public GoWriter.Writable generateOperationOption( + ProtocolGenerator.GenerationContext context, + OperationShape operation + ) { + var trait = context.getService().expectTrait(SigV4Trait.class); + return goTemplate(""" + &$option:T{ + SchemeID: $schemeId:T, + SignerProperties: func() $properties:T { + var props $properties:T + $setSigningName:T(&props, $name:S) + $setSigningRegion:T(&props, params.Region) + $unsignedPayload:W + return props + }(), + },""", + COMMON_ENV, + MapUtils.of( + "name", trait.getName(), + "unsignedPayload", generateIsUnsignedPayload(operation) + )); + } + + private GoWriter.Writable generateIsUnsignedPayload(OperationShape operation) { + return operation.hasTrait(UnsignedPayloadTrait.class) + ? goTemplate("$T(&props, true)", SmithyGoTypes.Transport.Http.SetIsUnsignedPayload) + : emptyGoTemplate(); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4aDefinition.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4aDefinition.java new file mode 100644 index 000000000..af4f3b88b --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/auth/SigV4aDefinition.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.go.codegen.integration.auth; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoTypes; +import software.amazon.smithy.go.codegen.integration.AuthSchemeDefinition; +import software.amazon.smithy.go.codegen.integration.ProtocolGenerator; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; + +/** + * Implements codegen for aws.auth#sigv4a. + */ +public class SigV4aDefinition implements AuthSchemeDefinition { + // FUTURE: reference modeled sigv4a trait + + @Override + public GoWriter.Writable generateServiceOption( + ProtocolGenerator.GenerationContext context, ServiceShape service + ) { + return goTemplate("&$T{SchemeID: $T},", + SmithyGoTypes.Auth.Option, + SmithyGoTypes.Auth.SchemeIDSigV4A); + } + + @Override + public GoWriter.Writable generateOperationOption( + ProtocolGenerator.GenerationContext context, OperationShape operation + ) { + return goTemplate("&$T{SchemeID: $T},", + SmithyGoTypes.Auth.Option, + SmithyGoTypes.Auth.SchemeIDSigV4A); + } +} diff --git a/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration b/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration index 908c87260..af72bfc17 100644 --- a/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration +++ b/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration @@ -1,4 +1,3 @@ -software.amazon.smithy.go.codegen.integration.auth.HttpBearerAuth software.amazon.smithy.go.codegen.integration.ValidationGenerator software.amazon.smithy.go.codegen.integration.IdempotencyTokenMiddlewareGenerator software.amazon.smithy.go.codegen.integration.AddChecksumRequiredMiddleware @@ -9,3 +8,7 @@ software.amazon.smithy.go.codegen.integration.Paginators software.amazon.smithy.go.codegen.integration.Waiters software.amazon.smithy.go.codegen.integration.ClientLogger software.amazon.smithy.go.codegen.endpoints.EndpointClientPluginsGenerator + +# modeled auth schemes +software.amazon.smithy.go.codegen.integration.auth.SigV4AuthScheme +software.amazon.smithy.go.codegen.integration.auth.AnonymousAuthScheme diff --git a/properties.go b/properties.go index 17d659c53..c9af66c0e 100644 --- a/properties.go +++ b/properties.go @@ -7,12 +7,10 @@ type PropertiesReader interface { } // Properties provides storing and reading metadata values. Keys may be any -// comparable value type. Get and set will panic if key is not a comparable -// value type. +// comparable value type. Get and Set will panic if a key is not comparable. // -// Properties uses lazy initialization, and Set method must be called as an -// addressable value, or pointer. Not doing so may cause key/value pair to not -// be set. +// The zero value for a Properties instance is ready for reads/writes without +// any additional initialization. type Properties struct { values map[interface{}]interface{} } @@ -22,21 +20,16 @@ type Properties struct { // // Panics if key type is not comparable. func (m *Properties) Get(key interface{}) interface{} { + m.lazyInit() return m.values[key] } // Set stores the value pointed to by the key. If a value already exists at // that key it will be replaced with the new value. // -// Set method must be called as an addressable value, or pointer. If Set is not -// called as an addressable value or pointer, the key value pair being set may -// be lost. -// // Panics if the key type is not comparable. func (m *Properties) Set(key, value interface{}) { - if m.values == nil { - m.values = map[interface{}]interface{}{} - } + m.lazyInit() m.values[key] = value } @@ -44,9 +37,26 @@ func (m *Properties) Set(key, value interface{}) { // // Panics if the key type is not comparable. func (m *Properties) Has(key interface{}) bool { - if m.values == nil { - return false - } + m.lazyInit() _, ok := m.values[key] return ok } + +// SetAll accepts all of the given Properties into the receiver, overwriting +// any existing keys in the case of conflicts. +func (m *Properties) SetAll(other *Properties) { + if other.values == nil { + return + } + + m.lazyInit() + for k, v := range other.values { + m.values[k] = v + } +} + +func (m *Properties) lazyInit() { + if m.values == nil { + m.values = map[interface{}]interface{}{} + } +} diff --git a/transport/http/auth.go b/transport/http/auth.go new file mode 100644 index 000000000..58e1ab5ef --- /dev/null +++ b/transport/http/auth.go @@ -0,0 +1,21 @@ +package http + +import ( + "context" + + smithy "github.com/aws/smithy-go" + "github.com/aws/smithy-go/auth" +) + +// AuthScheme defines an HTTP authentication scheme. +type AuthScheme interface { + SchemeID() string + IdentityResolver(auth.IdentityResolverOptions) auth.IdentityResolver + Signer() Signer +} + +// Signer defines the interface through which HTTP requests are supplemented +// with an Identity. +type Signer interface { + SignRequest(context.Context, *Request, auth.Identity, smithy.Properties) error +} diff --git a/transport/http/auth_schemes.go b/transport/http/auth_schemes.go new file mode 100644 index 000000000..d60cf2a60 --- /dev/null +++ b/transport/http/auth_schemes.go @@ -0,0 +1,45 @@ +package http + +import ( + "context" + + smithy "github.com/aws/smithy-go" + "github.com/aws/smithy-go/auth" +) + +// NewAnonymousScheme returns the anonymous HTTP auth scheme. +func NewAnonymousScheme() AuthScheme { + return &authScheme{ + schemeID: auth.SchemeIDAnonymous, + signer: &nopSigner{}, + } +} + +// authScheme is parameterized to generically implement the exported AuthScheme +// interface +type authScheme struct { + schemeID string + signer Signer +} + +var _ AuthScheme = (*authScheme)(nil) + +func (s *authScheme) SchemeID() string { + return s.schemeID +} + +func (s *authScheme) IdentityResolver(o auth.IdentityResolverOptions) auth.IdentityResolver { + return o.GetIdentityResolver(s.schemeID) +} + +func (s *authScheme) Signer() Signer { + return s.signer +} + +type nopSigner struct{} + +var _ Signer = (*nopSigner)(nil) + +func (*nopSigner) SignRequest(context.Context, *Request, auth.Identity, smithy.Properties) error { + return nil +} diff --git a/transport/http/properties.go b/transport/http/properties.go new file mode 100644 index 000000000..c65aa3932 --- /dev/null +++ b/transport/http/properties.go @@ -0,0 +1,80 @@ +package http + +import smithy "github.com/aws/smithy-go" + +type ( + sigV4SigningNameKey struct{} + sigV4SigningRegionKey struct{} + + sigV4ASigningNameKey struct{} + sigV4ASigningRegionsKey struct{} + + isUnsignedPayloadKey struct{} + disableDoubleEncodingKey struct{} +) + +// GetSigV4SigningName gets the signing name from Properties. +func GetSigV4SigningName(p *smithy.Properties) (string, bool) { + v, ok := p.Get(sigV4SigningNameKey{}).(string) + return v, ok +} + +// SetSigV4SigningName sets the signing name on Properties. +func SetSigV4SigningName(p *smithy.Properties, name string) { + p.Set(sigV4SigningNameKey{}, name) +} + +// GetSigV4SigningRegion gets the signing region from Properties. +func GetSigV4SigningRegion(p *smithy.Properties) (string, bool) { + v, ok := p.Get(sigV4SigningRegionKey{}).(string) + return v, ok +} + +// SetSigV4SigningRegion sets the signing region on Properties. +func SetSigV4SigningRegion(p *smithy.Properties, region string) { + p.Set(sigV4SigningRegionKey{}, region) +} + +// GetSigV4ASigningName gets the v4a signing name from Properties. +func GetSigV4ASigningName(p *smithy.Properties) (string, bool) { + v, ok := p.Get(sigV4ASigningNameKey{}).(string) + return v, ok +} + +// SetSigV4ASigningName sets the signing name on Properties. +func SetSigV4ASigningName(p *smithy.Properties, name string) { + p.Set(sigV4ASigningNameKey{}, name) +} + +// GetSigV4ASigningRegion gets the v4a signing region set from Properties. +func GetSigV4ASigningRegions(p *smithy.Properties) ([]string, bool) { + v, ok := p.Get(sigV4ASigningRegionsKey{}).([]string) + return v, ok +} + +// SetSigV4ASigningRegions sets the v4a signing region set on Properties. +func SetSigV4ASigningRegions(p *smithy.Properties, regions []string) { + p.Set(sigV4ASigningRegionsKey{}, regions) +} + +// GetIsUnsignedPayload gets whether the payload is unsigned from Properties. +func GetIsUnsignedPayload(p *smithy.Properties) (bool, bool) { + v, ok := p.Get(isUnsignedPayloadKey{}).(bool) + return v, ok +} + +// SetIsUnsignedPayload sets whether the payload is unsigned on Properties. +func SetIsUnsignedPayload(p *smithy.Properties, isUnsignedPayload bool) { + p.Set(isUnsignedPayloadKey{}, isUnsignedPayload) +} + +// GetDisableDoubleEncoding gets whether the payload is unsigned from Properties. +func GetDisableDoubleEncoding(p *smithy.Properties) (bool, bool) { + v, ok := p.Get(disableDoubleEncodingKey{}).(bool) + return v, ok +} + +// SetDisableDoubleEncoding sets whether the payload is unsigned on Properties. +func SetDisableDoubleEncoding(p *smithy.Properties, disableDoubleEncoding bool) { + p.Set(disableDoubleEncodingKey{}, disableDoubleEncoding) +}