github.com/aavshr/aws-sdk-go@v1.41.3/private/model/api/customization_passes.go (about)

     1  //go:build codegen
     2  // +build codegen
     3  
     4  package api
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  )
    13  
    14  type service struct {
    15  	srcName string
    16  	dstName string
    17  
    18  	serviceVersion string
    19  }
    20  
    21  var mergeServices = map[string]service{
    22  	"dynamodbstreams": {
    23  		dstName: "dynamodb",
    24  		srcName: "streams.dynamodb",
    25  	},
    26  	"wafregional": {
    27  		dstName:        "waf",
    28  		srcName:        "waf-regional",
    29  		serviceVersion: "2015-08-24",
    30  	},
    31  }
    32  
    33  var serviceAliaseNames = map[string]string{
    34  	"costandusagereportservice": "CostandUsageReportService",
    35  	"elasticloadbalancing":      "ELB",
    36  	"elasticloadbalancingv2":    "ELBV2",
    37  	"config":                    "ConfigService",
    38  }
    39  
    40  func (a *API) setServiceAliaseName() {
    41  	if newName, ok := serviceAliaseNames[a.PackageName()]; ok {
    42  		a.name = newName
    43  	}
    44  }
    45  
    46  // customizationPasses Executes customization logic for the API by package name.
    47  func (a *API) customizationPasses() error {
    48  	var svcCustomizations = map[string]func(*API) error{
    49  		"s3":         s3Customizations,
    50  		"s3control":  s3ControlCustomizations,
    51  		"cloudfront": cloudfrontCustomizations,
    52  		"rds":        rdsCustomizations,
    53  		"neptune":    neptuneCustomizations,
    54  		"docdb":      docdbCustomizations,
    55  
    56  		// Disable endpoint resolving for services that require customer
    57  		// to provide endpoint them selves.
    58  		"cloudsearchdomain": disableEndpointResolving,
    59  		"iotdataplane":      disableEndpointResolving,
    60  
    61  		// MTurk smoke test is invalid. The service requires AWS account to be
    62  		// linked to Amazon Mechanical Turk Account.
    63  		"mturk": supressSmokeTest,
    64  
    65  		// Backfill the authentication type for cognito identity and sts.
    66  		// Removes the need for the customizations in these services.
    67  		"cognitoidentity": backfillAuthType(NoneAuthType,
    68  			"GetId",
    69  			"GetOpenIdToken",
    70  			"UnlinkIdentity",
    71  			"GetCredentialsForIdentity",
    72  		),
    73  		"sts": backfillAuthType(NoneAuthType,
    74  			"AssumeRoleWithSAML",
    75  			"AssumeRoleWithWebIdentity",
    76  		),
    77  	}
    78  
    79  	for k := range mergeServices {
    80  		svcCustomizations[k] = mergeServicesCustomizations
    81  	}
    82  
    83  	if fn := svcCustomizations[a.PackageName()]; fn != nil {
    84  		err := fn(a)
    85  		if err != nil {
    86  			return fmt.Errorf("service customization pass failure for %s: %v", a.PackageName(), err)
    87  		}
    88  	}
    89  
    90  	return nil
    91  }
    92  
    93  func supressSmokeTest(a *API) error {
    94  	a.SmokeTests.TestCases = []SmokeTestCase{}
    95  	return nil
    96  }
    97  
    98  // Customizes the API generation to replace values specific to S3.
    99  func s3Customizations(a *API) error {
   100  
   101  	// back-fill signing name as 's3'
   102  	a.Metadata.SigningName = "s3"
   103  
   104  	var strExpires *Shape
   105  
   106  	var keepContentMD5Ref = map[string]struct{}{
   107  		"PutObjectInput":  {},
   108  		"UploadPartInput": {},
   109  	}
   110  
   111  	for name, s := range a.Shapes {
   112  		// Remove ContentMD5 members unless specified otherwise.
   113  		if _, keep := keepContentMD5Ref[name]; !keep {
   114  			if _, have := s.MemberRefs["ContentMD5"]; have {
   115  				delete(s.MemberRefs, "ContentMD5")
   116  			}
   117  		}
   118  
   119  		// Generate getter methods for API operation fields used by customizations.
   120  		for _, refName := range []string{"Bucket", "SSECustomerKey", "CopySourceSSECustomerKey"} {
   121  			if ref, ok := s.MemberRefs[refName]; ok {
   122  				ref.GenerateGetter = true
   123  			}
   124  		}
   125  
   126  		// Generate a endpointARN method for the BucketName shape if this is used as an operation input
   127  		if s.UsedAsInput {
   128  			if s.ShapeName == "CreateBucketInput" {
   129  				// For all operations but CreateBucket the BucketName shape
   130  				// needs to be decorated.
   131  				continue
   132  			}
   133  			var endpointARNShape *ShapeRef
   134  			for _, ref := range s.MemberRefs {
   135  				if ref.OrigShapeName != "BucketName" || ref.Shape.Type != "string" {
   136  					continue
   137  				}
   138  				if endpointARNShape != nil {
   139  					return fmt.Errorf("more then one BucketName shape present on shape")
   140  				}
   141  				ref.EndpointARN = true
   142  				endpointARNShape = ref
   143  			}
   144  			if endpointARNShape != nil {
   145  				s.HasEndpointARNMember = true
   146  				a.HasEndpointARN = true
   147  			}
   148  		}
   149  
   150  		// Decorate member references that are modeled with the wrong type.
   151  		// Specifically the case where a member was modeled as a string, but is
   152  		// expected to sent across the wire as a base64 value.
   153  		//
   154  		// e.g. S3's SSECustomerKey and CopySourceSSECustomerKey
   155  		for _, refName := range []string{
   156  			"SSECustomerKey",
   157  			"CopySourceSSECustomerKey",
   158  		} {
   159  			if ref, ok := s.MemberRefs[refName]; ok {
   160  				ref.CustomTags = append(ref.CustomTags, ShapeTag{
   161  					"marshal-as", "blob",
   162  				})
   163  			}
   164  		}
   165  
   166  		// Expires should be a string not time.Time since the format is not
   167  		// enforced by S3, and any value can be set to this field outside of the SDK.
   168  		if strings.HasSuffix(name, "Output") {
   169  			if ref, ok := s.MemberRefs["Expires"]; ok {
   170  				if strExpires == nil {
   171  					newShape := *ref.Shape
   172  					strExpires = &newShape
   173  					strExpires.Type = "string"
   174  					strExpires.refs = []*ShapeRef{}
   175  				}
   176  				ref.Shape.removeRef(ref)
   177  				ref.Shape = strExpires
   178  				ref.Shape.refs = append(ref.Shape.refs, &s.MemberRef)
   179  			}
   180  		}
   181  	}
   182  	s3CustRemoveHeadObjectModeledErrors(a)
   183  
   184  	return nil
   185  }
   186  
   187  // S3 HeadObject API call incorrect models NoSuchKey as valid
   188  // error code that can be returned. This operation does not
   189  // return error codes, all error codes are derived from HTTP
   190  // status codes.
   191  //
   192  // aws/aws-sdk-go#1208
   193  func s3CustRemoveHeadObjectModeledErrors(a *API) {
   194  	op, ok := a.Operations["HeadObject"]
   195  	if !ok {
   196  		return
   197  	}
   198  	op.Documentation += `
   199  //
   200  // See http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#RESTErrorResponses
   201  // for more information on returned errors.`
   202  	op.ErrorRefs = []ShapeRef{}
   203  }
   204  
   205  // S3 service operations with an AccountId need accessors to be generated for
   206  // them so the fields can be dynamically accessed without reflection.
   207  func s3ControlCustomizations(a *API) error {
   208  	for _, s := range a.Shapes {
   209  		// Generate a endpointARN method for the BucketName shape if this is used as an operation input
   210  		if s.UsedAsInput {
   211  			if s.ShapeName == "CreateBucketInput" || s.ShapeName == "ListRegionalBucketsInput" {
   212  				// For operations CreateBucketInput and ListRegionalBuckets the OutpostID shape
   213  				// needs to be decorated
   214  				var outpostIDMemberShape *ShapeRef
   215  				for memberName, ref := range s.MemberRefs {
   216  					if memberName != "OutpostId" || ref.Shape.Type != "string" {
   217  						continue
   218  					}
   219  					if outpostIDMemberShape != nil {
   220  						return fmt.Errorf("more then one OutpostID shape present on shape")
   221  					}
   222  					ref.OutpostIDMember = true
   223  					outpostIDMemberShape = ref
   224  				}
   225  				if outpostIDMemberShape != nil {
   226  					s.HasOutpostIDMember = true
   227  					a.HasOutpostID = true
   228  				}
   229  				continue
   230  			}
   231  
   232  			// List of input shapes that use accesspoint names as arnable fields
   233  			accessPointNameArnables := map[string]struct{}{
   234  				"GetAccessPointInput":          {},
   235  				"DeleteAccessPointInput":       {},
   236  				"PutAccessPointPolicyInput":    {},
   237  				"GetAccessPointPolicyInput":    {},
   238  				"DeleteAccessPointPolicyInput": {},
   239  			}
   240  
   241  			var endpointARNShape *ShapeRef
   242  			for _, ref := range s.MemberRefs {
   243  				// Operations that have AccessPointName field that takes in an ARN as input
   244  				if _, ok := accessPointNameArnables[s.ShapeName]; ok {
   245  					if ref.OrigShapeName != "AccessPointName" || ref.Shape.Type != "string" {
   246  						continue
   247  					}
   248  				} else if ref.OrigShapeName != "BucketName" || ref.Shape.Type != "string" {
   249  					// All other operations currently allow BucketName field to take in ARN.
   250  					// Exceptions for these are CreateBucket and ListRegionalBucket which use
   251  					// Outpost id and are handled above separately.
   252  					continue
   253  				}
   254  
   255  				if endpointARNShape != nil {
   256  					return fmt.Errorf("more then one member present on shape takes arn as input")
   257  				}
   258  				ref.EndpointARN = true
   259  				endpointARNShape = ref
   260  			}
   261  			if endpointARNShape != nil {
   262  				s.HasEndpointARNMember = true
   263  				a.HasEndpointARN = true
   264  
   265  				for _, ref := range s.MemberRefs {
   266  					// check for account id customization
   267  					if ref.OrigShapeName == "AccountId" && ref.Shape.Type == "string" {
   268  						ref.AccountIDMemberWithARN = true
   269  						s.HasAccountIdMemberWithARN = true
   270  						a.HasAccountIdWithARN = true
   271  					}
   272  				}
   273  			}
   274  		}
   275  	}
   276  
   277  	return nil
   278  }
   279  
   280  // cloudfrontCustomizations customized the API generation to replace values
   281  // specific to CloudFront.
   282  func cloudfrontCustomizations(a *API) error {
   283  	// MaxItems members should always be integers
   284  	for _, s := range a.Shapes {
   285  		if ref, ok := s.MemberRefs["MaxItems"]; ok {
   286  			ref.ShapeName = "Integer"
   287  			ref.Shape = a.Shapes["Integer"]
   288  		}
   289  	}
   290  	return nil
   291  }
   292  
   293  // mergeServicesCustomizations references any duplicate shapes from DynamoDB
   294  func mergeServicesCustomizations(a *API) error {
   295  	info := mergeServices[a.PackageName()]
   296  
   297  	p := strings.Replace(a.path, info.srcName, info.dstName, -1)
   298  
   299  	if info.serviceVersion != "" {
   300  		index := strings.LastIndex(p, string(filepath.Separator))
   301  		files, _ := ioutil.ReadDir(p[:index])
   302  		if len(files) > 1 {
   303  			panic("New version was introduced")
   304  		}
   305  		p = p[:index] + "/" + info.serviceVersion
   306  	}
   307  
   308  	file := filepath.Join(p, "api-2.json")
   309  
   310  	serviceAPI := API{}
   311  	serviceAPI.Attach(file)
   312  	serviceAPI.Setup()
   313  
   314  	for n := range a.Shapes {
   315  		if _, ok := serviceAPI.Shapes[n]; ok {
   316  			a.Shapes[n].resolvePkg = SDKImportRoot + "/service/" + info.dstName
   317  		}
   318  	}
   319  
   320  	return nil
   321  }
   322  
   323  // rdsCustomizations are customization for the service/rds. This adds
   324  // non-modeled fields used for presigning.
   325  func rdsCustomizations(a *API) error {
   326  	inputs := []string{
   327  		"CopyDBSnapshotInput",
   328  		"CreateDBInstanceReadReplicaInput",
   329  		"CopyDBClusterSnapshotInput",
   330  		"CreateDBClusterInput",
   331  		"StartDBInstanceAutomatedBackupsReplicationInput",
   332  	}
   333  	generatePresignedURL(a, inputs)
   334  	return nil
   335  }
   336  
   337  // neptuneCustomizations are customization for the service/neptune. This adds
   338  // non-modeled fields used for presigning.
   339  func neptuneCustomizations(a *API) error {
   340  	inputs := []string{
   341  		"CopyDBClusterSnapshotInput",
   342  		"CreateDBClusterInput",
   343  	}
   344  	generatePresignedURL(a, inputs)
   345  	return nil
   346  }
   347  
   348  // neptuneCustomizations are customization for the service/neptune. This adds
   349  // non-modeled fields used for presigning.
   350  func docdbCustomizations(a *API) error {
   351  	inputs := []string{
   352  		"CopyDBClusterSnapshotInput",
   353  		"CreateDBClusterInput",
   354  	}
   355  	generatePresignedURL(a, inputs)
   356  	return nil
   357  }
   358  
   359  func generatePresignedURL(a *API, inputShapes []string) {
   360  	for _, input := range inputShapes {
   361  		if ref, ok := a.Shapes[input]; ok {
   362  			ref.MemberRefs["SourceRegion"] = &ShapeRef{
   363  				Documentation: docstring(`SourceRegion is the source region where the resource exists. This is not sent over the wire and is only used for presigning. This value should always have the same region as the source ARN.`),
   364  				ShapeName:     "String",
   365  				Shape:         a.Shapes["String"],
   366  				Ignore:        true,
   367  			}
   368  			ref.MemberRefs["DestinationRegion"] = &ShapeRef{
   369  				Documentation: docstring(`DestinationRegion is used for presigning the request to a given region.`),
   370  				ShapeName:     "String",
   371  				Shape:         a.Shapes["String"],
   372  			}
   373  		}
   374  	}
   375  }
   376  
   377  func disableEndpointResolving(a *API) error {
   378  	a.Metadata.NoResolveEndpoint = true
   379  	return nil
   380  }
   381  
   382  func backfillAuthType(typ AuthType, opNames ...string) func(*API) error {
   383  	return func(a *API) error {
   384  		for _, opName := range opNames {
   385  			op, ok := a.Operations[opName]
   386  			if !ok {
   387  				panic("unable to backfill auth-type for unknown operation " + opName)
   388  			}
   389  			if v := op.AuthType; len(v) != 0 {
   390  				fmt.Fprintf(os.Stderr, "unable to backfill auth-type for %s, already set, %s\n", opName, v)
   391  				continue
   392  			}
   393  
   394  			op.AuthType = typ
   395  		}
   396  
   397  		return nil
   398  	}
   399  }