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

     1  //go:build codegen
     2  // +build codegen
     3  
     4  package api
     5  
     6  import (
     7  	"fmt"
     8  	"regexp"
     9  	"strings"
    10  	"unicode"
    11  )
    12  
    13  func (a *API) enableGeneratedTypedErrors() {
    14  	switch a.Metadata.Protocol {
    15  	case "json":
    16  	case "rest-json":
    17  	default:
    18  		return
    19  	}
    20  
    21  	a.WithGeneratedTypedErrors = true
    22  }
    23  
    24  // updateTopLevelShapeReferences moves resultWrapper, locationName, and
    25  // xmlNamespace traits from toplevel shape references to the toplevel
    26  // shapes for easier code generation
    27  func (a *API) updateTopLevelShapeReferences() {
    28  	for _, o := range a.Operations {
    29  		// these are for REST-XML services
    30  		if o.InputRef.LocationName != "" {
    31  			o.InputRef.Shape.LocationName = o.InputRef.LocationName
    32  		}
    33  		if o.InputRef.Location != "" {
    34  			o.InputRef.Shape.Location = o.InputRef.Location
    35  		}
    36  		if o.InputRef.Payload != "" {
    37  			o.InputRef.Shape.Payload = o.InputRef.Payload
    38  		}
    39  		if o.InputRef.XMLNamespace.Prefix != "" {
    40  			o.InputRef.Shape.XMLNamespace.Prefix = o.InputRef.XMLNamespace.Prefix
    41  		}
    42  		if o.InputRef.XMLNamespace.URI != "" {
    43  			o.InputRef.Shape.XMLNamespace.URI = o.InputRef.XMLNamespace.URI
    44  		}
    45  	}
    46  
    47  }
    48  
    49  // writeShapeNames sets each shape's API and shape name values. Binding the
    50  // shape to its parent API. This will set OrigShapeName on each Shape and ShapeRef
    51  // to allow access to the original shape name for code generation.
    52  func (a *API) writeShapeNames() {
    53  	writeOrigShapeName := func(s *ShapeRef) {
    54  		if len(s.ShapeName) > 0 {
    55  			s.OrigShapeName = s.ShapeName
    56  		}
    57  	}
    58  
    59  	for n, s := range a.Shapes {
    60  		s.API = a
    61  		s.ShapeName, s.OrigShapeName = n, n
    62  		for _, ref := range s.MemberRefs {
    63  			writeOrigShapeName(ref)
    64  		}
    65  		writeOrigShapeName(&s.MemberRef)
    66  		writeOrigShapeName(&s.KeyRef)
    67  		writeOrigShapeName(&s.ValueRef)
    68  	}
    69  }
    70  
    71  func (a *API) resolveReferences() {
    72  	resolver := referenceResolver{API: a, visited: map[*ShapeRef]bool{}}
    73  
    74  	for _, s := range a.Shapes {
    75  		resolver.resolveShape(s)
    76  	}
    77  
    78  	for _, o := range a.Operations {
    79  		o.API = a // resolve parent reference
    80  
    81  		resolver.resolveReference(&o.InputRef)
    82  		resolver.resolveReference(&o.OutputRef)
    83  
    84  		// Resolve references for errors also
    85  		for i := range o.ErrorRefs {
    86  			resolver.resolveReference(&o.ErrorRefs[i])
    87  			o.ErrorRefs[i].Shape.Exception = true
    88  			o.ErrorRefs[i].Shape.ErrorInfo.Type = o.ErrorRefs[i].Shape.ShapeName
    89  		}
    90  	}
    91  }
    92  
    93  func (a *API) backfillErrorMembers() {
    94  	stubShape := &Shape{
    95  		ShapeName: "string",
    96  		Type:      "string",
    97  	}
    98  	var locName string
    99  	switch a.Metadata.Protocol {
   100  	case "ec2", "query", "rest-xml":
   101  		locName = "Message"
   102  	case "json", "rest-json":
   103  		locName = "message"
   104  	}
   105  
   106  	for _, s := range a.Shapes {
   107  		if !s.Exception {
   108  			continue
   109  		}
   110  
   111  		var haveMessage bool
   112  		for name := range s.MemberRefs {
   113  			if strings.EqualFold(name, "Message") {
   114  				haveMessage = true
   115  				break
   116  			}
   117  		}
   118  		if !haveMessage {
   119  			ref := &ShapeRef{
   120  				ShapeName:    stubShape.ShapeName,
   121  				Shape:        stubShape,
   122  				LocationName: locName,
   123  			}
   124  			s.MemberRefs["Message"] = ref
   125  			stubShape.refs = append(stubShape.refs, ref)
   126  		}
   127  	}
   128  
   129  	if len(stubShape.refs) != 0 {
   130  		a.Shapes["SDKStubErrorMessageShape"] = stubShape
   131  	}
   132  }
   133  
   134  // A referenceResolver provides a way to resolve shape references to
   135  // shape definitions.
   136  type referenceResolver struct {
   137  	*API
   138  	visited map[*ShapeRef]bool
   139  }
   140  
   141  // resolveReference updates a shape reference to reference the API and
   142  // its shape definition. All other nested references are also resolved.
   143  func (r *referenceResolver) resolveReference(ref *ShapeRef) {
   144  	if ref.ShapeName == "" {
   145  		return
   146  	}
   147  
   148  	shape, ok := r.API.Shapes[ref.ShapeName]
   149  	if !ok {
   150  		panic(fmt.Sprintf("unable resolve reference, %s", ref.ShapeName))
   151  	}
   152  
   153  	if ref.JSONValue {
   154  		ref.ShapeName = "JSONValue"
   155  		if _, ok := r.API.Shapes[ref.ShapeName]; !ok {
   156  			r.API.Shapes[ref.ShapeName] = &Shape{
   157  				API:       r.API,
   158  				ShapeName: "JSONValue",
   159  				Type:      "jsonvalue",
   160  				ValueRef: ShapeRef{
   161  					JSONValue: true,
   162  				},
   163  			}
   164  		}
   165  	}
   166  
   167  	ref.API = r.API   // resolve reference back to API
   168  	ref.Shape = shape // resolve shape reference
   169  
   170  	if r.visited[ref] {
   171  		return
   172  	}
   173  	r.visited[ref] = true
   174  
   175  	shape.refs = append(shape.refs, ref) // register the ref
   176  
   177  	// resolve shape's references, if it has any
   178  	r.resolveShape(shape)
   179  }
   180  
   181  // resolveShape resolves a shape's Member Key Value, and nested member
   182  // shape references.
   183  func (r *referenceResolver) resolveShape(shape *Shape) {
   184  	r.resolveReference(&shape.MemberRef)
   185  	r.resolveReference(&shape.KeyRef)
   186  	r.resolveReference(&shape.ValueRef)
   187  	for _, m := range shape.MemberRefs {
   188  		r.resolveReference(m)
   189  	}
   190  }
   191  
   192  // fixStutterNames fixes all name stuttering based on Go naming conventions.
   193  // "Stuttering" is when the prefix of a structure or function matches the
   194  // package name (case insensitive).
   195  func (a *API) fixStutterNames() {
   196  	names, ok := legacyStutterNames[ServiceID(a)]
   197  	if !ok {
   198  		return
   199  	}
   200  
   201  	shapeNames := names.ShapeOrder
   202  	if len(shapeNames) == 0 {
   203  		shapeNames = make([]string, 0, len(names.Shapes))
   204  		for k := range names.Shapes {
   205  			shapeNames = append(shapeNames, k)
   206  		}
   207  	}
   208  
   209  	for _, shapeName := range shapeNames {
   210  		s := a.Shapes[shapeName]
   211  		newName := names.Shapes[shapeName]
   212  		if other, ok := a.Shapes[newName]; ok && (other.Type == "structure" || other.Type == "enum") {
   213  			panic(fmt.Sprintf(
   214  				"shape name already exists, renaming %v to %v\n",
   215  				s.ShapeName, newName))
   216  		}
   217  		s.Rename(newName)
   218  	}
   219  
   220  	for opName, newName := range names.Operations {
   221  		if _, ok := a.Operations[newName]; ok {
   222  			panic(fmt.Sprintf(
   223  				"operation name already exists, renaming %v to %v\n",
   224  				opName, newName))
   225  		}
   226  		op := a.Operations[opName]
   227  		delete(a.Operations, opName)
   228  		a.Operations[newName] = op
   229  		op.ExportedName = newName
   230  	}
   231  }
   232  
   233  // regexpForValidatingShapeName is used by validateShapeName to filter acceptable shape names
   234  // that may be renamed to a new valid shape name, if not already.
   235  // The regex allows underscores(_) at the beginning of the shape name
   236  // There may be 0 or more underscores(_). The next character would be the leading character
   237  // in the renamed shape name and thus, must be an alphabetic character.
   238  // The regex allows alphanumeric characters along with underscores(_) in rest of the string.
   239  var regexForValidatingShapeName = regexp.MustCompile("^[_]*[a-zA-Z][a-zA-Z0-9_]*$")
   240  
   241  // legacyShapeNames is a map of shape names that are supported and bypass the validateShapeNames util
   242  var legacyShapeNames = map[string][]string{
   243  	"mediapackage": {
   244  		"__AdTriggersElement",
   245  		"__PeriodTriggersElement",
   246  	},
   247  }
   248  
   249  // validateShapeNames is valid only for shapes of type structure or enums
   250  // We validate a shape name to check if its a valid shape name
   251  // A valid shape name would only contain alphanumeric characters and have an alphabet as leading character.
   252  //
   253  // If we encounter a shape name with underscores(_), we remove the underscores, and
   254  // follow a canonical upper camel case naming scheme to create a new shape name.
   255  // If the shape name collides with an existing shape name we return an error.
   256  // The resulting shape name must be a valid shape name or throw an error.
   257  func (a *API) validateShapeNames() error {
   258  loop:
   259  	for _, s := range a.Shapes {
   260  		if s.Type == "structure" || s.IsEnum() {
   261  			for _, legacyname := range legacyShapeNames[a.PackageName()] {
   262  				if s.ShapeName == legacyname {
   263  					continue loop
   264  				}
   265  			}
   266  			name := s.ShapeName
   267  			if b := regexForValidatingShapeName.MatchString(name); !b {
   268  				return fmt.Errorf("invalid shape name found: %v", s.ShapeName)
   269  			}
   270  
   271  			// Slice of strings returned after we split a string
   272  			// with a non alphanumeric character as delimiter.
   273  			slice := strings.FieldsFunc(name, func(r rune) bool {
   274  				return !unicode.IsLetter(r) && !unicode.IsNumber(r)
   275  			})
   276  
   277  			// Build a string that follows canonical upper camel casing
   278  			var b strings.Builder
   279  			for _, word := range slice {
   280  				b.WriteString(strings.Title(word))
   281  			}
   282  
   283  			name = b.String()
   284  			if s.ShapeName != name {
   285  				if a.Shapes[name] != nil {
   286  					// throw an error if shape with a new shape name already exists
   287  					return fmt.Errorf("attempt to rename shape %v to %v for package %v failed, as this rename would result in shape name collision",
   288  						s.ShapeName, name, a.PackageName())
   289  				}
   290  				debugLogger.Logf("Renaming shape %v to %v for package %v \n", s.ShapeName, name, a.PackageName())
   291  				s.Rename(name)
   292  			}
   293  		}
   294  	}
   295  	return nil
   296  }
   297  
   298  // renameExportable renames all operation names to be exportable names.
   299  // All nested Shape names are also updated to the exportable variant.
   300  func (a *API) renameExportable() {
   301  	for name, op := range a.Operations {
   302  		newName := a.ExportableName(name)
   303  		if newName != name {
   304  			delete(a.Operations, name)
   305  			a.Operations[newName] = op
   306  		}
   307  		op.ExportedName = newName
   308  	}
   309  
   310  	for k, s := range a.Shapes {
   311  		// FIXME SNS has lower and uppercased shape names with the same name,
   312  		// except the lowercased variant is used exclusively for string and
   313  		// other primitive types. Renaming both would cause a collision.
   314  		// We work around this by only renaming the structure shapes.
   315  		if s.Type == "string" {
   316  			continue
   317  		}
   318  
   319  		for mName, member := range s.MemberRefs {
   320  			newName := a.ExportableName(mName)
   321  			if newName != mName {
   322  				delete(s.MemberRefs, mName)
   323  				s.MemberRefs[newName] = member
   324  
   325  				// also apply locationName trait so we keep the old one
   326  				// but only if there's no locationName trait on ref or shape
   327  				if member.LocationName == "" && member.Shape.LocationName == "" {
   328  					member.LocationName = mName
   329  				}
   330  			}
   331  
   332  			if newName == "_" {
   333  				panic("Shape " + s.ShapeName + " uses reserved member name '_'")
   334  			}
   335  		}
   336  
   337  		newName := a.ExportableName(k)
   338  		if s.Type == "structure" && newName == a.StructName() {
   339  			// If struct collides client's struct type name the shape needs to
   340  			// be renamed with a trailing `_` to prevent collision.
   341  			newName += "_"
   342  		}
   343  
   344  		if newName != s.ShapeName {
   345  			s.Rename(newName)
   346  		}
   347  
   348  		s.Payload = a.ExportableName(s.Payload)
   349  
   350  		// fix required trait names
   351  		for i, n := range s.Required {
   352  			s.Required[i] = a.ExportableName(n)
   353  		}
   354  	}
   355  
   356  	for _, s := range a.Shapes {
   357  		// fix enum names
   358  		if s.IsEnum() {
   359  			s.EnumConsts = make([]string, len(s.Enum))
   360  			for i := range s.Enum {
   361  				shape := s.ShapeName
   362  				shape = strings.ToUpper(shape[0:1]) + shape[1:]
   363  				s.EnumConsts[i] = shape + s.EnumName(i)
   364  			}
   365  		}
   366  	}
   367  }
   368  
   369  // renameCollidingFields will rename any fields that uses an SDK or Golang
   370  // specific name.
   371  func (a *API) renameCollidingFields() {
   372  	for _, v := range a.Shapes {
   373  		namesWithSet := map[string]struct{}{}
   374  		for k, field := range v.MemberRefs {
   375  			if _, ok := v.MemberRefs["Set"+k]; ok {
   376  				namesWithSet["Set"+k] = struct{}{}
   377  			}
   378  
   379  			if collides(k) || (v.Exception && exceptionCollides(k)) {
   380  				renameCollidingField(k, v, field)
   381  			}
   382  		}
   383  
   384  		// checks if any field names collide with setters.
   385  		for name := range namesWithSet {
   386  			field := v.MemberRefs[name]
   387  			renameCollidingField(name, v, field)
   388  		}
   389  	}
   390  }
   391  
   392  func renameCollidingField(name string, v *Shape, field *ShapeRef) {
   393  	newName := name + "_"
   394  	debugLogger.Logf("Shape %s's field %q renamed to %q", v.ShapeName, name, newName)
   395  	delete(v.MemberRefs, name)
   396  	v.MemberRefs[newName] = field
   397  	// Set LocationName to the original field name if it is not already set.
   398  	// This is to ensure we correctly serialize to the proper member name
   399  	if len(field.LocationName) == 0 {
   400  		field.LocationName = name
   401  	}
   402  }
   403  
   404  // collides will return true if it is a name used by the SDK or Golang.
   405  func collides(name string) bool {
   406  	switch name {
   407  	case "String",
   408  		"GoString",
   409  		"Validate":
   410  		return true
   411  	}
   412  	return false
   413  }
   414  
   415  func exceptionCollides(name string) bool {
   416  	switch name {
   417  	case "Code",
   418  		"Message",
   419  		"OrigErr",
   420  		"Error",
   421  		"String",
   422  		"GoString",
   423  		"RequestID",
   424  		"StatusCode",
   425  		"RespMetadata":
   426  		return true
   427  	}
   428  	return false
   429  }
   430  
   431  func (a *API) applyShapeNameAliases() {
   432  	service, ok := shapeNameAliases[a.name]
   433  	if !ok {
   434  		return
   435  	}
   436  
   437  	// Generic Shape Aliases
   438  	for name, s := range a.Shapes {
   439  		if alias, ok := service[name]; ok {
   440  			s.Rename(alias)
   441  			s.AliasedShapeName = true
   442  		}
   443  	}
   444  }
   445  
   446  // renameIOSuffixedShapeNames renames shapes that have `Input` or `Output`
   447  // as suffix in their shape names. We add `_` and the end to avoid possible
   448  // conflicts with the generated operation input/output types. SDK has already
   449  // released quite a few shapes with input, output name suffixed with Input or Output.
   450  // This change uses a legacy IO suffixed list and does not rename those legacy shapes.
   451  // We do not rename aliased shapes. We do not rename shapes that are an input or output
   452  // shape of an operation.
   453  func (a *API) renameIOSuffixedShapeNames() {
   454  	// map all input shapes in service enclosure
   455  	inputShapes := make(map[string]*Shape, len(a.Operations))
   456  
   457  	// map all output shapes in service enclosure
   458  	outputShapes := make(map[string]*Shape, len(a.Operations))
   459  
   460  	for _, op := range a.Operations {
   461  		if len(op.InputRef.ShapeName) != 0 {
   462  			inputShapes[op.InputRef.Shape.ShapeName] = op.InputRef.Shape
   463  		}
   464  
   465  		if len(op.OutputRef.ShapeName) != 0 {
   466  			outputShapes[op.OutputRef.Shape.ShapeName] = op.OutputRef.Shape
   467  		}
   468  	}
   469  
   470  	for name, shape := range a.Shapes {
   471  		// skip if this shape is already aliased
   472  		if shape.AliasedShapeName {
   473  			continue
   474  		}
   475  
   476  		// skip if shape name is not suffixed with `Input` or `Output`
   477  		if !strings.HasSuffix(name, "Input") && !strings.HasSuffix(name, "Output") {
   478  			continue
   479  		}
   480  
   481  		// skip if this shape is an input shape
   482  		if s, ok := inputShapes[name]; ok && s == shape {
   483  			continue
   484  		}
   485  
   486  		// skip if this shape is an output shape
   487  		if s, ok := outputShapes[name]; ok && s == shape {
   488  			continue
   489  		}
   490  
   491  		// skip if this shape is a legacy io suffixed shape
   492  		if legacyIOSuffixed.LegacyIOSuffix(a, name) {
   493  			continue
   494  		}
   495  
   496  		// rename the shape to suffix with `_`
   497  		shape.Rename(name + "_")
   498  	}
   499  }
   500  
   501  // createInputOutputShapes creates toplevel input/output shapes if they
   502  // have not been defined in the API. This normalizes all APIs to always
   503  // have an input and output structure in the signature.
   504  func (a *API) createInputOutputShapes() {
   505  	for _, op := range a.Operations {
   506  		createAPIParamShape(a, op.Name, &op.InputRef, op.ExportedName+"Input",
   507  			shamelist.Input,
   508  		)
   509  		op.InputRef.Shape.UsedAsInput = true
   510  
   511  		createAPIParamShape(a, op.Name, &op.OutputRef, op.ExportedName+"Output",
   512  			shamelist.Output,
   513  		)
   514  		op.OutputRef.Shape.UsedAsOutput = true
   515  	}
   516  }
   517  
   518  func (a *API) renameAPIPayloadShapes() {
   519  	for _, op := range a.Operations {
   520  		op.InputRef.Payload = a.ExportableName(op.InputRef.Payload)
   521  		op.OutputRef.Payload = a.ExportableName(op.OutputRef.Payload)
   522  	}
   523  }
   524  
   525  func createAPIParamShape(a *API, opName string, ref *ShapeRef, shapeName string, shamelistLookup func(string, string) bool) {
   526  	if len(ref.ShapeName) == 0 {
   527  		setAsPlacholderShape(ref, shapeName, a)
   528  		return
   529  	}
   530  
   531  	// nothing to do if already the correct name.
   532  	if s := ref.Shape; s.AliasedShapeName || s.ShapeName == shapeName || shamelistLookup(a.name, opName) {
   533  		return
   534  	}
   535  
   536  	if s, ok := a.Shapes[shapeName]; ok {
   537  		panic(fmt.Sprintf(
   538  			"attempting to create duplicate API parameter shape, %v, %v, %v, %v\n",
   539  			shapeName, opName, ref.ShapeName, s.OrigShapeName,
   540  		))
   541  	}
   542  
   543  	ref.Shape.removeRef(ref)
   544  	ref.ShapeName = shapeName
   545  	ref.Shape = ref.Shape.Clone(shapeName)
   546  	ref.Shape.refs = append(ref.Shape.refs, ref)
   547  }
   548  
   549  func setAsPlacholderShape(tgtShapeRef *ShapeRef, name string, a *API) {
   550  	shape := a.makeIOShape(name)
   551  	shape.Placeholder = true
   552  	*tgtShapeRef = ShapeRef{API: a, ShapeName: shape.ShapeName, Shape: shape}
   553  	shape.refs = append(shape.refs, tgtShapeRef)
   554  }
   555  
   556  // makeIOShape returns a pointer to a new Shape initialized by the name provided.
   557  func (a *API) makeIOShape(name string) *Shape {
   558  	shape := &Shape{
   559  		API: a, ShapeName: name, Type: "structure",
   560  		MemberRefs: map[string]*ShapeRef{},
   561  	}
   562  	a.Shapes[name] = shape
   563  	return shape
   564  }
   565  
   566  // removeUnusedShapes removes shapes from the API which are not referenced by
   567  // any other shape in the API.
   568  func (a *API) removeUnusedShapes() {
   569  	for _, s := range a.Shapes {
   570  		if len(s.refs) == 0 {
   571  			a.removeShape(s)
   572  		}
   573  	}
   574  }
   575  
   576  // Sets the EndpointsID field of Metadata  with the value of the
   577  // EndpointPrefix if EndpointsID is not set.
   578  func (a *API) setMetadataEndpointsKey() {
   579  	if len(a.Metadata.EndpointsID) != 0 {
   580  		return
   581  	}
   582  	a.Metadata.EndpointsID = a.Metadata.EndpointPrefix
   583  }
   584  
   585  func (a *API) findEndpointDiscoveryOp() {
   586  	for _, op := range a.Operations {
   587  		if op.IsEndpointDiscoveryOp {
   588  			a.EndpointDiscoveryOp = op
   589  			return
   590  		}
   591  	}
   592  }
   593  
   594  func (a *API) injectUnboundedOutputStreaming() {
   595  	for _, op := range a.Operations {
   596  		if op.AuthType != V4UnsignedBodyAuthType {
   597  			continue
   598  		}
   599  		for _, ref := range op.InputRef.Shape.MemberRefs {
   600  			if ref.Streaming || ref.Shape.Streaming {
   601  				if len(ref.Documentation) != 0 {
   602  					ref.Documentation += `
   603  //`
   604  				}
   605  				ref.Documentation += `
   606  // To use an non-seekable io.Reader for this request wrap the io.Reader with
   607  // "aws.ReadSeekCloser". The SDK will not retry request errors for non-seekable
   608  // readers. This will allow the SDK to send the reader's payload as chunked
   609  // transfer encoding.`
   610  			}
   611  		}
   612  	}
   613  }