Skip to content
  •  
  •  
  •  
419 changes: 419 additions & 0 deletions .changelog/70EEF6C5-262D-4DDE-9B4A-B6ADDE267267.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ type Config struct {

// Priority list of preferred auth scheme IDs.
AuthSchemePreference []string

// ServiceOptions provides service specific configuration options that will be applied
// when constructing clients for specific services. Each callback function receives the service ID
// and the service's Options struct, allowing for dynamic configuration based on the service.
ServiceOptions []func(string, any)
}

// NewConfig returns a new Config pointer that can be chained with builder
Expand Down
248 changes: 248 additions & 0 deletions aws/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package aws

import (
"testing"
)

// Mock service options struct for testing
type MockServiceOptions struct {
Field1 bool
Field2 string
Field3 int
}

func TestWithServiceOptions(t *testing.T) {
cfg := NewConfig()

// Add ServiceOptions directly to test the field
cfg.ServiceOptions = []func(string, any){
func(serviceID string, opts any) {
if serviceID == "TestService" {
if mockOpts, ok := opts.(*MockServiceOptions); ok {
mockOpts.Field1 = true
mockOpts.Field2 = "test"
}
}
},
}

if cfg.ServiceOptions == nil {
t.Fatal("ServiceOptions should not be nil")
}

if len(cfg.ServiceOptions) != 1 {
t.Fatalf("Expected 1 callback, got %d", len(cfg.ServiceOptions))
}

mockOpts := &MockServiceOptions{}
for _, callback := range cfg.ServiceOptions {
callback("TestService", mockOpts)
}

if !mockOpts.Field1 {
t.Error("Field1 should be true")
}

if mockOpts.Field2 != "test" {
t.Errorf("Field2 should be 'test', got '%s'", mockOpts.Field2)
}
}

func TestWithServiceOptionsMultiple(t *testing.T) {
cfg := NewConfig()

// Add ServiceOptions directly to test the field
cfg.ServiceOptions = []func(string, any){
func(serviceID string, opts any) {
if serviceID == "Service1" {
if mockOpts, ok := opts.(*MockServiceOptions); ok {
mockOpts.Field1 = true
}
}
},
func(serviceID string, opts any) {
if serviceID == "Service2" {
if mockOpts, ok := opts.(*MockServiceOptions); ok {
mockOpts.Field2 = "test"
}
}
},
}

if len(cfg.ServiceOptions) != 2 {
t.Fatalf("Expected 2 callbacks, got %d", len(cfg.ServiceOptions))
}

mockOpts1 := &MockServiceOptions{}
for _, callback := range cfg.ServiceOptions {
callback("Service1", mockOpts1)
}

if !mockOpts1.Field1 {
t.Error("Service1 Field1 should be true")
}

if mockOpts1.Field2 != "" {
t.Errorf("Service1 Field2 should be empty, got '%s'", mockOpts1.Field2)
}

mockOpts2 := &MockServiceOptions{}
for _, callback := range cfg.ServiceOptions {
callback("Service2", mockOpts2)
}

if mockOpts2.Field1 {
t.Error("Service2 Field1 should be false")
}

if mockOpts2.Field2 != "test" {
t.Errorf("Service2 Field2 should be 'test', got '%s'", mockOpts2.Field2)
}
}

func TestWithServiceOptionsMultipleServiceIDs(t *testing.T) {
cfg := NewConfig()

// Add ServiceOptions directly to test the field
cfg.ServiceOptions = []func(string, any){
func(serviceID string, opts any) {
if mockOpts, ok := opts.(*MockServiceOptions); ok {
switch serviceID {
case "Service1":
mockOpts.Field1 = true
mockOpts.Field2 = "service1"
case "Service2":
mockOpts.Field1 = false
mockOpts.Field2 = "service2"
case "Service3":
mockOpts.Field3 = 42
}
}
},
}

if len(cfg.ServiceOptions) != 1 {
t.Fatalf("Expected 1 callback, got %d", len(cfg.ServiceOptions))
}

mockOpts1 := &MockServiceOptions{}
for _, callback := range cfg.ServiceOptions {
callback("Service1", mockOpts1)
}

if !mockOpts1.Field1 {
t.Error("Service1 Field1 should be true")
}
if mockOpts1.Field2 != "service1" {
t.Errorf("Service1 Field2 should be 'service1', got '%s'", mockOpts1.Field2)
}
if mockOpts1.Field3 != 0 {
t.Errorf("Service1 Field3 should be 0, got %d", mockOpts1.Field3)
}

mockOpts2 := &MockServiceOptions{}
for _, callback := range cfg.ServiceOptions {
callback("Service2", mockOpts2)
}

if mockOpts2.Field1 {
t.Error("Service2 Field1 should be false")
}
if mockOpts2.Field2 != "service2" {
t.Errorf("Service2 Field2 should be 'service2', got '%s'", mockOpts2.Field2)
}
if mockOpts2.Field3 != 0 {
t.Errorf("Service2 Field3 should be 0, got %d", mockOpts2.Field3)
}

mockOpts3 := &MockServiceOptions{}
for _, callback := range cfg.ServiceOptions {
callback("Service3", mockOpts3)
}

if mockOpts3.Field1 {
t.Error("Service3 Field1 should be false")
}
if mockOpts3.Field2 != "" {
t.Errorf("Service3 Field2 should be empty, got '%s'", mockOpts3.Field2)
}
if mockOpts3.Field3 != 42 {
t.Errorf("Service3 Field3 should be 42, got %d", mockOpts3.Field3)
}
}

func TestApplyServiceOptionsNonExistent(t *testing.T) {
mockOpts := &MockServiceOptions{}

// No service options configured, so no callbacks to execute
if mockOpts.Field1 || mockOpts.Field2 != "" || mockOpts.Field3 != 0 {
t.Error("Options should not be modified for non-existent service")
}
}

func TestTypeAssertionFailure(t *testing.T) {
cfg := NewConfig()

// Add ServiceOptions directly to test the field
cfg.ServiceOptions = []func(string, any){
func(serviceID string, opts any) {
if serviceID == "TestService" {
if mockOpts, ok := opts.(*MockServiceOptions); ok {
mockOpts.Field1 = true
}
}
},
}

differentOpts := &struct{ Field string }{Field: "test"}
for _, callback := range cfg.ServiceOptions {
callback("TestService", differentOpts)
}

if differentOpts.Field != "test" {
t.Error("Different options should not be modified")
}
}

func TestChaining(t *testing.T) {
cfg := NewConfig()

cfg.ServiceOptions = []func(string, any){
func(serviceID string, opts any) {
if serviceID == "Service1" {
if mockOpts, ok := opts.(*MockServiceOptions); ok {
mockOpts.Field1 = true
}
}
},
func(serviceID string, opts any) {
if serviceID == "Service2" {
if mockOpts, ok := opts.(*MockServiceOptions); ok {
mockOpts.Field2 = "chained"
}
}
},
}

if len(cfg.ServiceOptions) != 2 {
t.Fatalf("Expected 2 callbacks, got %d", len(cfg.ServiceOptions))
}

mockOpts1 := &MockServiceOptions{}
for _, callback := range cfg.ServiceOptions {
callback("Service1", mockOpts1)
}

if !mockOpts1.Field1 {
t.Error("Service1 Field1 should be true")
}

mockOpts2 := &MockServiceOptions{}
for _, callback := range cfg.ServiceOptions {
callback("Service2", mockOpts2)
}

if mockOpts2.Field2 != "chained" {
t.Errorf("Service2 Field2 should be 'chained', got '%s'", mockOpts2.Field2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,14 @@ private void writeAwsConfigConstructor(Model model, ServiceShape service, GoWrit
writer.write("$L(cfg, &opts)", awsResolverFunction.get());
}

writer.write("return New(opts, optFns...)");
writer.write("return New(opts, func(o *Options) {");
writer.write(" for _, opt := range cfg.ServiceOptions {");
writer.write(" opt(ServiceID, o)");
writer.write(" }");
writer.write(" for _, opt := range optFns {");
writer.write(" opt(o)");
writer.write(" }");
writer.write("})");
});
writer.write("");
}
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ var defaultAWSConfigResolvers = []awsConfigResolver{
resolveInterceptors,

resolveAuthSchemePreference,

// Sets the ServiceOptions if present in LoadOptions
resolveServiceOptions,
}

// A Config represents a generic configuration value or set of values. This type
Expand Down
18 changes: 18 additions & 0 deletions config/load_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ type LoadOptions struct {

// Priority list of preferred auth scheme names (e.g. sigv4a).
AuthSchemePreference []string

// ServiceOptions provides service specific configuration options that will be applied
// when constructing clients for specific services. Each callback function receives the service ID
// and the service's Options struct, allowing for dynamic configuration based on the service.
ServiceOptions []func(string, any)
}

func (o LoadOptions) getDefaultsMode(ctx context.Context) (aws.DefaultsMode, bool, error) {
Expand Down Expand Up @@ -314,6 +319,10 @@ func (o LoadOptions) getBaseEndpoint(context.Context) (string, bool, error) {
return o.BaseEndpoint, o.BaseEndpoint != "", nil
}

func (o LoadOptions) getServiceOptions(context.Context) ([]func(string, any), bool, error) {
return o.ServiceOptions, len(o.ServiceOptions) > 0, nil
}

// GetServiceBaseEndpoint satisfies (internal/configsources).ServiceBaseEndpointProvider.
//
// The sdkID value is unused because LoadOptions only supports setting a GLOBAL
Expand Down Expand Up @@ -1215,6 +1224,15 @@ func WithBaseEndpoint(v string) LoadOptionsFunc {
}
}

// WithServiceOptions is a helper function to construct functional options
// that sets ServiceOptions on config's LoadOptions.
func WithServiceOptions(callbacks ...func(string, any)) LoadOptionsFunc {
return func(o *LoadOptions) error {
o.ServiceOptions = append(o.ServiceOptions, callbacks...)
return nil
}
}

// WithBeforeExecution adds the BeforeExecutionInterceptor to config.
func WithBeforeExecution(i smithyhttp.BeforeExecutionInterceptor) LoadOptionsFunc {
return func(o *LoadOptions) error {
Expand Down
16 changes: 16 additions & 0 deletions config/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,3 +768,19 @@ func getAuthSchemePreference(ctx context.Context, configs configs) ([]string, bo
}
return nil, false
}

type serviceOptionsProvider interface {
getServiceOptions(ctx context.Context) ([]func(string, any), bool, error)
}

func getServiceOptions(ctx context.Context, configs configs) (v []func(string, any), found bool, err error) {
for _, c := range configs {
if p, ok := c.(serviceOptionsProvider); ok {
v, found, err = p.getServiceOptions(ctx)
if err != nil || found {
break
}
}
}
return v, found, err
}
13 changes: 13 additions & 0 deletions config/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,16 @@ func resolveAuthSchemePreference(ctx context.Context, cfg *aws.Config, configs c
}
return nil
}

func resolveServiceOptions(ctx context.Context, cfg *aws.Config, configs configs) error {
serviceOptions, found, err := getServiceOptions(ctx, configs)
if err != nil {
return err
}
if !found {
return nil
}

cfg.ServiceOptions = serviceOptions
return nil
}
9 changes: 8 additions & 1 deletion internal/kitchensinktest/api_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion internal/protocoltest/awsrestjson/api_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading