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 }