diff --git a/codegen/kitchensink-test-codegen/model/main.smithy b/codegen/kitchensink-test-codegen/model/main.smithy index 4206a68c807..329c8c45751 100644 --- a/codegen/kitchensink-test-codegen/model/main.smithy +++ b/codegen/kitchensink-test-codegen/model/main.smithy @@ -4,6 +4,128 @@ namespace aws.kitchensinktest @aws.api#service(sdkId: "awsJson1 kitchen sink") @aws.protocols#awsJson1_0 @aws.auth#sigv4(name: "awsjson1kitchensink") +@smithy.rules#endpointRuleSet({ + version: "1.0", + parameters: { + Region: { + type: "string", + builtIn: "AWS::Region", + required: true, + documentation: "The AWS region" + }, + Id: { + type: "string", + documentation: "The item id" + } + }, + rules: [ + { + type: "endpoint", + documentation: "id-specific endpoint", + conditions: [ + { fn: "isSet", argv: [{ ref: "Id" }] } + ], + endpoint: { + url: "https://{Id}.example.{Region}.amazonaws.com" + } + }, + { + type: "endpoint", + documentation: "Default regional endpoint", + conditions: [], + endpoint: { + url: "https://example.{Region}.amazonaws.com" + } + } + ] +}) +@smithy.rules#endpointTests({ + version: "1.0", + testCases: [ + { + documentation: "id-specific endpoint", + params: { + Region: "us-east-1", + Id: "some-id" + }, + operationInputs: [{ + operationName: "GetItem", + "builtInParams": { + "AWS::Region": "us-east-1" + } + operationParams: { + id: "some-id", + } + }], + expect: { + endpoint: { + url: "https://some-id.example.us-east-1.amazonaws.com" + } + } + }, + { + documentation: "Default endpoint when id is not set", + params: { + Region: "us-west-2" + }, + expect: { + endpoint: { + url: "https://example.us-west-2.amazonaws.com" + } + } + } + ] +}) +@smithy.rules#endpointBdd( + version: "1.1" + parameters: { + Region: { + builtIn: "AWS::Region" + required: true + documentation: "The AWS region" + type: "string" + } + Id: { + required: false + documentation: "The item id" + type: "string" + } + } + conditions: [ + { + fn: "isSet" + argv: [ + { + ref: "Id" + } + ] + } + ] + results: [ + { + conditions: [] + endpoint: { + url: "https://{Id}.example.{Region}.amazonaws.com" + properties: {} + headers: {} + } + type: "endpoint" + } + { + documentation: "Default regional endpoint" + conditions: [] + endpoint: { + url: "https://example.{Region}.amazonaws.com" + properties: {} + headers: {} + } + type: "endpoint" + } + ] + root: 2 + nodeCount: 2 + nodes: "/////wAAAAH/////AAAAAAX14QEF9eEC" +) service AwsJson1KitchenSink { version: "2025-03-01", operations: [GetItem], @@ -17,6 +139,8 @@ operation GetItem { structure GetItemInput { item: Item, + @smithy.rules#contextParam(name: "Id") + id: String, } structure GetItemOutput {} diff --git a/internal/kitchensinktest/api_op_GetItem.go b/internal/kitchensinktest/api_op_GetItem.go index fb18b7da09f..0c2525ef808 100644 --- a/internal/kitchensinktest/api_op_GetItem.go +++ b/internal/kitchensinktest/api_op_GetItem.go @@ -27,11 +27,19 @@ func (c *Client) GetItem(ctx context.Context, params *GetItemInput, optFns ...fu } type GetItemInput struct { + Id *string + Item *types.Item noSmithyDocumentSerde } +func (in *GetItemInput) bindEndpointParams(p *EndpointParameters) { + + p.Id = in.Id + +} + type GetItemOutput struct { // Metadata pertaining to the operation's result. ResultMetadata middleware.Metadata diff --git a/internal/kitchensinktest/endpoints.go b/internal/kitchensinktest/endpoints.go index 0cc2fce9651..5f79139c1e8 100644 --- a/internal/kitchensinktest/endpoints.go +++ b/internal/kitchensinktest/endpoints.go @@ -17,6 +17,7 @@ import ( "github.com/aws/smithy-go/middleware" "github.com/aws/smithy-go/tracing" smithyhttp "github.com/aws/smithy-go/transport/http" + "net/http" "net/url" "os" "strings" @@ -229,6 +230,31 @@ func bindRegion(region string) (*string, error) { // EndpointParameters provides the parameters that influence how endpoints are // resolved. type EndpointParameters struct { + // The AWS region + // + // AWS::Region + Region *string + + // The item id + // + // Parameter is required. + Id *string +} + +// ValidateRequired validates required parameters are set. +func (p EndpointParameters) ValidateRequired() error { + if p.Region == nil { + return fmt.Errorf("parameter Region is required") + } + + return nil +} + +// WithDefaults returns a shallow copy of EndpointParameterswith default values +// applied to members where applicable. +func (p EndpointParameters) WithDefaults() EndpointParameters { + + return p } type stringSlice []string @@ -265,7 +291,55 @@ func (r *resolver) ResolveEndpoint( ) ( endpoint smithyendpoints.Endpoint, err error, ) { - return endpoint, fmt.Errorf("no endpoint rules defined") + params = params.WithDefaults() + if err = params.ValidateRequired(); err != nil { + return endpoint, fmt.Errorf("endpoint parameters are not valid, %w", err) + } + _Region := *params.Region + _ = _Region + + // id-specific endpoint + if exprVal := params.Id; exprVal != nil { + _Id := *exprVal + _ = _Id + uriString := func() string { + var out strings.Builder + out.WriteString("https://") + out.WriteString(_Id) + out.WriteString(".example.") + out.WriteString(_Region) + out.WriteString(".amazonaws.com") + return out.String() + }() + + uri, err := url.Parse(uriString) + if err != nil { + return endpoint, fmt.Errorf("Failed to parse uri: %s", uriString) + } + + return smithyendpoints.Endpoint{ + URI: *uri, + Headers: http.Header{}, + }, nil + } + // Default regional endpoint + uriString := func() string { + var out strings.Builder + out.WriteString("https://example.") + out.WriteString(_Region) + out.WriteString(".amazonaws.com") + return out.String() + }() + + uri, err := url.Parse(uriString) + if err != nil { + return endpoint, fmt.Errorf("Failed to parse uri: %s", uriString) + } + + return smithyendpoints.Endpoint{ + URI: *uri, + Headers: http.Header{}, + }, nil } type endpointParamsBinder interface { @@ -275,6 +349,12 @@ type endpointParamsBinder interface { func bindEndpointParams(ctx context.Context, input interface{}, options Options) (*EndpointParameters, error) { params := &EndpointParameters{} + region, err := bindRegion(options.Region) + if err != nil { + return nil, err + } + params.Region = region + if b, ok := input.(endpointParamsBinder); ok { b.bindEndpointParams(params) } diff --git a/internal/kitchensinktest/endpoints_test.go b/internal/kitchensinktest/endpoints_test.go index afa4c2ba11c..d16c9490e47 100644 --- a/internal/kitchensinktest/endpoints_test.go +++ b/internal/kitchensinktest/endpoints_test.go @@ -1,3 +1,85 @@ // Code generated by smithy-go-codegen DO NOT EDIT. package kitchensinktest + +import ( + "context" + smithy "github.com/aws/smithy-go" + smithyendpoints "github.com/aws/smithy-go/endpoints" + "github.com/aws/smithy-go/ptr" + "net/http" + "net/url" + "reflect" + "testing" +) + +// id-specific endpoint +func TestEndpointCase0(t *testing.T) { + var params = EndpointParameters{ + Region: ptr.String("us-east-1"), + Id: ptr.String("some-id"), + } + + resolver := NewDefaultEndpointResolverV2() + result, err := resolver.ResolveEndpoint(context.Background(), params) + _, _ = result, err + + if err != nil { + t.Fatalf("expect no error, got %v", err) + } + + uri, _ := url.Parse("https://some-id.example.us-east-1.amazonaws.com") + + expectEndpoint := smithyendpoints.Endpoint{ + URI: *uri, + Headers: http.Header{}, + Properties: smithy.Properties{}, + } + + if e, a := expectEndpoint.URI, result.URI; e != a { + t.Errorf("expect %v URI, got %v", e, a) + } + + if !reflect.DeepEqual(expectEndpoint.Headers, result.Headers) { + t.Errorf("expect headers to match\n%v != %v", expectEndpoint.Headers, result.Headers) + } + + if !reflect.DeepEqual(expectEndpoint.Properties, result.Properties) { + t.Errorf("expect properties to match\n%v != %v", expectEndpoint.Properties, result.Properties) + } +} + +// Default endpoint when id is not set +func TestEndpointCase1(t *testing.T) { + var params = EndpointParameters{ + Region: ptr.String("us-west-2"), + } + + resolver := NewDefaultEndpointResolverV2() + result, err := resolver.ResolveEndpoint(context.Background(), params) + _, _ = result, err + + if err != nil { + t.Fatalf("expect no error, got %v", err) + } + + uri, _ := url.Parse("https://example.us-west-2.amazonaws.com") + + expectEndpoint := smithyendpoints.Endpoint{ + URI: *uri, + Headers: http.Header{}, + Properties: smithy.Properties{}, + } + + if e, a := expectEndpoint.URI, result.URI; e != a { + t.Errorf("expect %v URI, got %v", e, a) + } + + if !reflect.DeepEqual(expectEndpoint.Headers, result.Headers) { + t.Errorf("expect headers to match\n%v != %v", expectEndpoint.Headers, result.Headers) + } + + if !reflect.DeepEqual(expectEndpoint.Properties, result.Properties) { + t.Errorf("expect properties to match\n%v != %v", expectEndpoint.Properties, result.Properties) + } +} diff --git a/internal/kitchensinktest/serializers.go b/internal/kitchensinktest/serializers.go index 146b8a6bba1..f4d0a6c639d 100644 --- a/internal/kitchensinktest/serializers.go +++ b/internal/kitchensinktest/serializers.go @@ -87,6 +87,11 @@ func awsAwsjson10_serializeOpDocumentGetItemInput(v *GetItemInput, value smithyj object := value.Object() defer object.Close() + if v.Id != nil { + ok := object.Key("id") + ok.String(*v.Id) + } + if v.Item != nil { ok := object.Key("item") if err := awsAwsjson10_serializeDocumentItem(v.Item, ok); err != nil {