1// +build codegen 2 3package api 4 5import ( 6 "fmt" 7 "regexp" 8 "strings" 9) 10 11// updateTopLevelShapeReferences moves resultWrapper, locationName, and 12// xmlNamespace traits from toplevel shape references to the toplevel 13// shapes for easier code generation 14func (a *API) updateTopLevelShapeReferences() { 15 for _, o := range a.Operations { 16 // these are for REST-XML services 17 if o.InputRef.LocationName != "" { 18 o.InputRef.Shape.LocationName = o.InputRef.LocationName 19 } 20 if o.InputRef.Location != "" { 21 o.InputRef.Shape.Location = o.InputRef.Location 22 } 23 if o.InputRef.Payload != "" { 24 o.InputRef.Shape.Payload = o.InputRef.Payload 25 } 26 if o.InputRef.XMLNamespace.Prefix != "" { 27 o.InputRef.Shape.XMLNamespace.Prefix = o.InputRef.XMLNamespace.Prefix 28 } 29 if o.InputRef.XMLNamespace.URI != "" { 30 o.InputRef.Shape.XMLNamespace.URI = o.InputRef.XMLNamespace.URI 31 } 32 } 33 34} 35 36// writeShapeNames sets each shape's API and shape name values. Binding the 37// shape to its parent API. 38func (a *API) writeShapeNames() { 39 for n, s := range a.Shapes { 40 s.API = a 41 s.ShapeName = n 42 } 43} 44 45func (a *API) resolveReferences() { 46 resolver := referenceResolver{API: a, visited: map[*ShapeRef]bool{}} 47 48 for _, s := range a.Shapes { 49 resolver.resolveShape(s) 50 } 51 52 for _, o := range a.Operations { 53 o.API = a // resolve parent reference 54 55 resolver.resolveReference(&o.InputRef) 56 resolver.resolveReference(&o.OutputRef) 57 58 // Resolve references for errors also 59 for i := range o.ErrorRefs { 60 resolver.resolveReference(&o.ErrorRefs[i]) 61 o.ErrorRefs[i].Shape.Exception = true 62 o.ErrorRefs[i].Shape.ErrorInfo.Type = o.ErrorRefs[i].Shape.ShapeName 63 } 64 } 65} 66 67// A referenceResolver provides a way to resolve shape references to 68// shape definitions. 69type referenceResolver struct { 70 *API 71 visited map[*ShapeRef]bool 72} 73 74// resolveReference updates a shape reference to reference the API and 75// its shape definition. All other nested references are also resolved. 76func (r *referenceResolver) resolveReference(ref *ShapeRef) { 77 if ref.ShapeName == "" { 78 return 79 } 80 81 shape, ok := r.API.Shapes[ref.ShapeName] 82 if !ok { 83 panic(fmt.Sprintf("unable resolve reference, %s", ref.ShapeName)) 84 } 85 86 if ref.JSONValue { 87 ref.ShapeName = "JSONValue" 88 if _, ok := r.API.Shapes[ref.ShapeName]; !ok { 89 r.API.Shapes[ref.ShapeName] = &Shape{ 90 API: r.API, 91 ShapeName: "JSONValue", 92 Type: "jsonvalue", 93 ValueRef: ShapeRef{ 94 JSONValue: true, 95 }, 96 } 97 } 98 } 99 100 ref.API = r.API // resolve reference back to API 101 ref.Shape = shape // resolve shape reference 102 103 if r.visited[ref] { 104 return 105 } 106 r.visited[ref] = true 107 108 shape.refs = append(shape.refs, ref) // register the ref 109 110 // resolve shape's references, if it has any 111 r.resolveShape(shape) 112} 113 114// resolveShape resolves a shape's Member Key Value, and nested member 115// shape references. 116func (r *referenceResolver) resolveShape(shape *Shape) { 117 r.resolveReference(&shape.MemberRef) 118 r.resolveReference(&shape.KeyRef) 119 r.resolveReference(&shape.ValueRef) 120 for _, m := range shape.MemberRefs { 121 r.resolveReference(m) 122 } 123} 124 125// fixStutterNames fixes all name struttering based on Go naming conventions. 126// "Stuttering" is when the prefix of a structure or function matches the 127// package name (case insensitive). 128func (a *API) fixStutterNames() { 129 str, end := a.StructName(), "" 130 if len(str) > 1 { 131 l := len(str) - 1 132 str, end = str[0:l], str[l:] 133 } 134 re := regexp.MustCompile(fmt.Sprintf(`\A(?i:%s)%s`, str, end)) 135 136 for name, op := range a.Operations { 137 newName := re.ReplaceAllString(name, "") 138 if newName != name && len(newName) > 0 { 139 delete(a.Operations, name) 140 a.Operations[newName] = op 141 } 142 op.ExportedName = newName 143 } 144 145 for k, s := range a.Shapes { 146 newName := re.ReplaceAllString(k, "") 147 if newName != s.ShapeName && len(newName) > 0 { 148 s.Rename(newName) 149 } 150 } 151} 152 153// renameExportable renames all operation names to be exportable names. 154// All nested Shape names are also updated to the exportable variant. 155func (a *API) renameExportable() { 156 for name, op := range a.Operations { 157 newName := a.ExportableName(name) 158 if newName != name { 159 delete(a.Operations, name) 160 a.Operations[newName] = op 161 } 162 op.ExportedName = newName 163 } 164 165 for k, s := range a.Shapes { 166 // FIXME SNS has lower and uppercased shape names with the same name, 167 // except the lowercased variant is used exclusively for string and 168 // other primitive types. Renaming both would cause a collision. 169 // We work around this by only renaming the structure shapes. 170 if s.Type == "string" { 171 continue 172 } 173 174 for mName, member := range s.MemberRefs { 175 ref := s.MemberRefs[mName] 176 ref.OrigShapeName = mName 177 s.MemberRefs[mName] = ref 178 179 newName := a.ExportableName(mName) 180 if newName != mName { 181 delete(s.MemberRefs, mName) 182 s.MemberRefs[newName] = member 183 184 // also apply locationName trait so we keep the old one 185 // but only if there's no locationName trait on ref or shape 186 if member.LocationName == "" && member.Shape.LocationName == "" { 187 member.LocationName = mName 188 } 189 } 190 191 if newName == "_" { 192 panic("Shape " + s.ShapeName + " uses reserved member name '_'") 193 } 194 } 195 196 newName := a.ExportableName(k) 197 if newName != s.ShapeName { 198 s.Rename(newName) 199 } 200 201 s.Payload = a.ExportableName(s.Payload) 202 203 // fix required trait names 204 for i, n := range s.Required { 205 s.Required[i] = a.ExportableName(n) 206 } 207 } 208 209 for _, s := range a.Shapes { 210 // fix enum names 211 if s.IsEnum() { 212 s.EnumConsts = make([]string, len(s.Enum)) 213 for i := range s.Enum { 214 shape := s.ShapeName 215 shape = strings.ToUpper(shape[0:1]) + shape[1:] 216 s.EnumConsts[i] = shape + s.EnumName(i) 217 } 218 } 219 } 220} 221 222// renameCollidingFields will rename any fields that uses an SDK or Golang 223// specific name. 224func (a *API) renameCollidingFields() { 225 for _, v := range a.Shapes { 226 namesWithSet := map[string]struct{}{} 227 for k, field := range v.MemberRefs { 228 if _, ok := v.MemberRefs["Set"+k]; ok { 229 namesWithSet["Set"+k] = struct{}{} 230 } 231 232 if collides(k) || (v.Exception && exceptionCollides(k)) { 233 renameCollidingField(k, v, field) 234 } 235 } 236 237 // checks if any field names collide with setters. 238 for name := range namesWithSet { 239 field := v.MemberRefs[name] 240 renameCollidingField(name, v, field) 241 } 242 } 243} 244 245func renameCollidingField(name string, v *Shape, field *ShapeRef) { 246 newName := name + "_" 247 debugLogger.Logf("Shape %s's field %q renamed to %q", v.ShapeName, name, newName) 248 delete(v.MemberRefs, name) 249 v.MemberRefs[newName] = field 250} 251 252// collides will return true if it is a name used by the SDK or Golang. 253func collides(name string) bool { 254 switch name { 255 case "String", 256 "GoString", 257 "Validate": 258 return true 259 } 260 return false 261} 262 263func exceptionCollides(name string) bool { 264 switch name { 265 case "Code", 266 "Message", 267 "OrigErr": 268 return true 269 } 270 return false 271} 272 273func (a *API) applyShapeNameAliases() { 274 service, ok := shapeNameAliases[a.name] 275 if !ok { 276 return 277 } 278 279 // Generic Shape Aliases 280 for name, s := range a.Shapes { 281 if alias, ok := service[name]; ok { 282 s.Rename(alias) 283 s.AliasedShapeName = true 284 } 285 } 286} 287 288// createInputOutputShapes creates toplevel input/output shapes if they 289// have not been defined in the API. This normalizes all APIs to always 290// have an input and output structure in the signature. 291func (a *API) createInputOutputShapes() { 292 for _, op := range a.Operations { 293 createAPIParamShape(a, op.Name, &op.InputRef, op.ExportedName+"Input", 294 shamelist.Input, 295 ) 296 createAPIParamShape(a, op.Name, &op.OutputRef, op.ExportedName+"Output", 297 shamelist.Output, 298 ) 299 } 300} 301 302func (a *API) renameAPIPayloadShapes() { 303 for _, op := range a.Operations { 304 op.InputRef.Payload = a.ExportableName(op.InputRef.Payload) 305 op.OutputRef.Payload = a.ExportableName(op.OutputRef.Payload) 306 } 307} 308 309func createAPIParamShape(a *API, opName string, ref *ShapeRef, shapeName string, shamelistLookup func(string, string) bool) { 310 if len(ref.ShapeName) == 0 { 311 setAsPlacholderShape(ref, shapeName, a) 312 return 313 } 314 315 // nothing to do if already the correct name. 316 if s := ref.Shape; s.AliasedShapeName || s.ShapeName == shapeName || shamelistLookup(a.name, opName) { 317 return 318 } 319 320 if s, ok := a.Shapes[shapeName]; ok { 321 panic(fmt.Sprintf( 322 "attempting to create duplicate API parameter shape, %v, %v, %v, %v\n", 323 shapeName, opName, ref.ShapeName, s.OrigShapeName, 324 )) 325 } 326 327 ref.Shape.removeRef(ref) 328 ref.OrigShapeName = shapeName 329 ref.ShapeName = shapeName 330 ref.Shape = ref.Shape.Clone(shapeName) 331 ref.Shape.refs = append(ref.Shape.refs, ref) 332} 333 334func setAsPlacholderShape(tgtShapeRef *ShapeRef, name string, a *API) { 335 shape := a.makeIOShape(name) 336 shape.Placeholder = true 337 *tgtShapeRef = ShapeRef{API: a, ShapeName: shape.ShapeName, Shape: shape} 338 shape.refs = append(shape.refs, tgtShapeRef) 339} 340 341// makeIOShape returns a pointer to a new Shape initialized by the name provided. 342func (a *API) makeIOShape(name string) *Shape { 343 shape := &Shape{ 344 API: a, ShapeName: name, Type: "structure", 345 MemberRefs: map[string]*ShapeRef{}, 346 } 347 a.Shapes[name] = shape 348 return shape 349} 350 351// removeUnusedShapes removes shapes from the API which are not referenced by any 352// other shape in the API. 353func (a *API) removeUnusedShapes() { 354 for _, s := range a.Shapes { 355 if len(s.refs) == 0 { 356 a.removeShape(s) 357 } 358 } 359} 360 361// Represents the service package name to EndpointsID mapping 362var custEndpointsKey = map[string]string{ 363 "applicationautoscaling": "application-autoscaling", 364} 365 366// Sents the EndpointsID field of Metadata with the value of the 367// EndpointPrefix if EndpointsID is not set. Also adds 368// customizations for services if EndpointPrefix is not a valid key. 369func (a *API) setMetadataEndpointsKey() { 370 if len(a.Metadata.EndpointsID) != 0 { 371 return 372 } 373 374 if v, ok := custEndpointsKey[a.PackageName()]; ok { 375 a.Metadata.EndpointsID = v 376 } else { 377 a.Metadata.EndpointsID = a.Metadata.EndpointPrefix 378 } 379} 380 381func (a *API) findEndpointDiscoveryOp() { 382 for _, op := range a.Operations { 383 if op.IsEndpointDiscoveryOp { 384 a.EndpointDiscoveryOp = op 385 return 386 } 387 } 388} 389func (a *API) injectUnboundedOutputStreaming() { 390 for _, op := range a.Operations { 391 if op.AuthType != V4UnsignedBodyAuthType { 392 continue 393 } 394 for _, ref := range op.InputRef.Shape.MemberRefs { 395 if ref.Streaming || ref.Shape.Streaming { 396 if len(ref.Documentation) != 0 { 397 ref.Documentation += ` 398//` 399 } 400 ref.Documentation += ` 401// To use an non-seekable io.Reader for this request wrap the io.Reader with 402// "aws.ReadSeekCloser". The SDK will not retry request errors for non-seekable 403// readers. This will allow the SDK to send the reader's payload as chunked 404// transfer encoding.` 405 } 406 } 407 } 408} 409