1// +build codegen 2 3package api 4 5import ( 6 "bytes" 7 "fmt" 8 "path" 9 "regexp" 10 "sort" 11 "strings" 12 "text/template" 13) 14 15// A ShapeRef defines the usage of a shape within the API. 16type ShapeRef struct { 17 API *API `json:"-"` 18 Shape *Shape `json:"-"` 19 Documentation string 20 ShapeName string `json:"shape"` 21 Location string 22 LocationName string 23 QueryName string 24 Flattened bool 25 Streaming bool 26 XMLAttribute bool 27 // Ignore, if set, will not be sent over the wire 28 Ignore bool 29 XMLNamespace XMLInfo 30 Payload string 31 IdempotencyToken bool `json:"idempotencyToken"` 32 JSONValue bool `json:"jsonvalue"` 33 Deprecated bool `json:"deprecated"` 34 35 OrigShapeName string `json:"-"` 36 37 GenerateGetter bool 38} 39 40// ErrorInfo represents the error block of a shape's structure 41type ErrorInfo struct { 42 Code string 43 HTTPStatusCode int 44} 45 46// A XMLInfo defines URL and prefix for Shapes when rendered as XML 47type XMLInfo struct { 48 Prefix string 49 URI string 50} 51 52// A Shape defines the definition of a shape type 53type Shape struct { 54 API *API `json:"-"` 55 ShapeName string 56 Documentation string 57 MemberRefs map[string]*ShapeRef `json:"members"` 58 MemberRef ShapeRef `json:"member"` 59 KeyRef ShapeRef `json:"key"` 60 ValueRef ShapeRef `json:"value"` 61 Required []string 62 Payload string 63 Type string 64 Exception bool 65 Enum []string 66 EnumConsts []string 67 Flattened bool 68 Streaming bool 69 Location string 70 LocationName string 71 IdempotencyToken bool `json:"idempotencyToken"` 72 XMLNamespace XMLInfo 73 Min float64 // optional Minimum length (string, list) or value (number) 74 Max float64 // optional Maximum length (string, list) or value (number) 75 76 refs []*ShapeRef // References to this shape 77 resolvePkg string // use this package in the goType() if present 78 79 OrigShapeName string `json:"-"` 80 81 // Defines if the shape is a placeholder and should not be used directly 82 Placeholder bool 83 84 Deprecated bool `json:"deprecated"` 85 86 Validations ShapeValidations 87 88 // Error information that is set if the shape is an error shape. 89 IsError bool 90 ErrorInfo ErrorInfo `json:"error"` 91} 92 93// ErrorCodeName will return the error shape's name formated for 94// error code const. 95func (s *Shape) ErrorCodeName() string { 96 return "ErrCode" + s.ShapeName 97} 98 99// ErrorName will return the shape's name or error code if available based 100// on the API's protocol. This is the error code string returned by the service. 101func (s *Shape) ErrorName() string { 102 name := s.ShapeName 103 switch s.API.Metadata.Protocol { 104 case "query", "ec2query", "rest-xml": 105 if len(s.ErrorInfo.Code) > 0 { 106 name = s.ErrorInfo.Code 107 } 108 } 109 110 return name 111} 112 113// GoTags returns the struct tags for a shape. 114func (s *Shape) GoTags(root, required bool) string { 115 ref := &ShapeRef{ShapeName: s.ShapeName, API: s.API, Shape: s} 116 return ref.GoTags(root, required) 117} 118 119// Rename changes the name of the Shape to newName. Also updates 120// the associated API's reference to use newName. 121func (s *Shape) Rename(newName string) { 122 for _, r := range s.refs { 123 r.OrigShapeName = r.ShapeName 124 r.ShapeName = newName 125 } 126 127 delete(s.API.Shapes, s.ShapeName) 128 s.OrigShapeName = s.ShapeName 129 s.API.Shapes[newName] = s 130 s.ShapeName = newName 131} 132 133// MemberNames returns a slice of struct member names. 134func (s *Shape) MemberNames() []string { 135 i, names := 0, make([]string, len(s.MemberRefs)) 136 for n := range s.MemberRefs { 137 names[i] = n 138 i++ 139 } 140 sort.Strings(names) 141 return names 142} 143 144// GoTypeWithPkgName returns a shape's type as a string with the package name in 145// <packageName>.<type> format. Package naming only applies to structures. 146func (s *Shape) GoTypeWithPkgName() string { 147 return goType(s, true) 148} 149 150func (s *Shape) GoTypeWithPkgNameElem() string { 151 t := goType(s, true) 152 if strings.HasPrefix(t, "*") { 153 return t[1:] 154 } 155 return t 156} 157 158// GenAccessors returns if the shape's reference should have setters generated. 159func (s *ShapeRef) UseIndirection() bool { 160 switch s.Shape.Type { 161 case "map", "list", "blob", "structure", "jsonvalue": 162 return false 163 } 164 165 if s.Streaming || s.Shape.Streaming { 166 return false 167 } 168 169 if s.JSONValue { 170 return false 171 } 172 173 return true 174} 175 176// GoStructValueType returns the Shape's Go type value instead of a pointer 177// for the type. 178func (s *Shape) GoStructValueType(name string, ref *ShapeRef) string { 179 v := s.GoStructType(name, ref) 180 181 if ref.UseIndirection() && v[0] == '*' { 182 return v[1:] 183 } 184 185 return v 186} 187 188// GoStructType returns the type of a struct field based on the API 189// model definition. 190func (s *Shape) GoStructType(name string, ref *ShapeRef) string { 191 if (ref.Streaming || ref.Shape.Streaming) && s.Payload == name { 192 rtype := "io.ReadSeeker" 193 if strings.HasSuffix(s.ShapeName, "Output") { 194 rtype = "io.ReadCloser" 195 } 196 197 s.API.imports["io"] = true 198 return rtype 199 } 200 201 if ref.JSONValue { 202 s.API.imports["github.com/aws/aws-sdk-go/aws"] = true 203 return "aws.JSONValue" 204 } 205 206 for _, v := range s.Validations { 207 // TODO move this to shape validation resolution 208 if (v.Ref.Shape.Type == "map" || v.Ref.Shape.Type == "list") && v.Type == ShapeValidationNested { 209 s.API.imports["fmt"] = true 210 } 211 } 212 213 return ref.GoType() 214} 215 216// GoType returns a shape's Go type 217func (s *Shape) GoType() string { 218 return goType(s, false) 219} 220 221// GoType returns a shape ref's Go type. 222func (ref *ShapeRef) GoType() string { 223 if ref.Shape == nil { 224 panic(fmt.Errorf("missing shape definition on reference for %#v", ref)) 225 } 226 227 return ref.Shape.GoType() 228} 229 230// GoTypeWithPkgName returns a shape's type as a string with the package name in 231// <packageName>.<type> format. Package naming only applies to structures. 232func (ref *ShapeRef) GoTypeWithPkgName() string { 233 if ref.Shape == nil { 234 panic(fmt.Errorf("missing shape definition on reference for %#v", ref)) 235 } 236 237 return ref.Shape.GoTypeWithPkgName() 238} 239 240// Returns a string version of the Shape's type. 241// If withPkgName is true, the package name will be added as a prefix 242func goType(s *Shape, withPkgName bool) string { 243 switch s.Type { 244 case "structure": 245 if withPkgName || s.resolvePkg != "" { 246 pkg := s.resolvePkg 247 if pkg != "" { 248 s.API.imports[pkg] = true 249 pkg = path.Base(pkg) 250 } else { 251 pkg = s.API.PackageName() 252 } 253 return fmt.Sprintf("*%s.%s", pkg, s.ShapeName) 254 } 255 return "*" + s.ShapeName 256 case "map": 257 return "map[string]" + goType(s.ValueRef.Shape, withPkgName) 258 case "jsonvalue": 259 return "aws.JSONValue" 260 case "list": 261 return "[]" + goType(s.MemberRef.Shape, withPkgName) 262 case "boolean": 263 return "*bool" 264 case "string", "character": 265 return "*string" 266 case "blob": 267 return "[]byte" 268 case "integer", "long": 269 return "*int64" 270 case "float", "double": 271 return "*float64" 272 case "timestamp": 273 s.API.imports["time"] = true 274 return "*time.Time" 275 default: 276 panic("Unsupported shape type: " + s.Type) 277 } 278} 279 280// GoTypeElem returns the Go type for the Shape. If the shape type is a pointer just 281// the type will be returned minus the pointer *. 282func (s *Shape) GoTypeElem() string { 283 t := s.GoType() 284 if strings.HasPrefix(t, "*") { 285 return t[1:] 286 } 287 return t 288} 289 290// GoTypeElem returns the Go type for the Shape. If the shape type is a pointer just 291// the type will be returned minus the pointer *. 292func (ref *ShapeRef) GoTypeElem() string { 293 if ref.Shape == nil { 294 panic(fmt.Errorf("missing shape definition on reference for %#v", ref)) 295 } 296 297 return ref.Shape.GoTypeElem() 298} 299 300// ShapeTag is a struct tag that will be applied to a shape's generated code 301type ShapeTag struct { 302 Key, Val string 303} 304 305// String returns the string representation of the shape tag 306func (s ShapeTag) String() string { 307 return fmt.Sprintf(`%s:"%s"`, s.Key, s.Val) 308} 309 310// ShapeTags is a collection of shape tags and provides serialization of the 311// tags in an ordered list. 312type ShapeTags []ShapeTag 313 314// Join returns an ordered serialization of the shape tags with the provided 315// separator. 316func (s ShapeTags) Join(sep string) string { 317 o := &bytes.Buffer{} 318 for i, t := range s { 319 o.WriteString(t.String()) 320 if i < len(s)-1 { 321 o.WriteString(sep) 322 } 323 } 324 325 return o.String() 326} 327 328// String is an alias for Join with the empty space separator. 329func (s ShapeTags) String() string { 330 return s.Join(" ") 331} 332 333// GoTags returns the rendered tags string for the ShapeRef 334func (ref *ShapeRef) GoTags(toplevel bool, isRequired bool) string { 335 tags := ShapeTags{} 336 337 if ref.Location != "" { 338 tags = append(tags, ShapeTag{"location", ref.Location}) 339 } else if ref.Shape.Location != "" { 340 tags = append(tags, ShapeTag{"location", ref.Shape.Location}) 341 } 342 343 if ref.LocationName != "" { 344 tags = append(tags, ShapeTag{"locationName", ref.LocationName}) 345 } else if ref.Shape.LocationName != "" { 346 tags = append(tags, ShapeTag{"locationName", ref.Shape.LocationName}) 347 } 348 349 if ref.QueryName != "" { 350 tags = append(tags, ShapeTag{"queryName", ref.QueryName}) 351 } 352 if ref.Shape.MemberRef.LocationName != "" { 353 tags = append(tags, ShapeTag{"locationNameList", ref.Shape.MemberRef.LocationName}) 354 } 355 if ref.Shape.KeyRef.LocationName != "" { 356 tags = append(tags, ShapeTag{"locationNameKey", ref.Shape.KeyRef.LocationName}) 357 } 358 if ref.Shape.ValueRef.LocationName != "" { 359 tags = append(tags, ShapeTag{"locationNameValue", ref.Shape.ValueRef.LocationName}) 360 } 361 if ref.Shape.Min > 0 { 362 tags = append(tags, ShapeTag{"min", fmt.Sprintf("%v", ref.Shape.Min)}) 363 } 364 365 if ref.Deprecated || ref.Shape.Deprecated { 366 tags = append(tags, ShapeTag{"deprecated", "true"}) 367 } 368 369 // All shapes have a type 370 tags = append(tags, ShapeTag{"type", ref.Shape.Type}) 371 372 // embed the timestamp type for easier lookups 373 if ref.Shape.Type == "timestamp" { 374 t := ShapeTag{Key: "timestampFormat"} 375 if ref.Location == "header" { 376 t.Val = "rfc822" 377 } else { 378 switch ref.API.Metadata.Protocol { 379 case "json", "rest-json": 380 t.Val = "unix" 381 case "rest-xml", "ec2", "query": 382 t.Val = "iso8601" 383 } 384 } 385 tags = append(tags, t) 386 } 387 388 if ref.Shape.Flattened || ref.Flattened { 389 tags = append(tags, ShapeTag{"flattened", "true"}) 390 } 391 if ref.XMLAttribute { 392 tags = append(tags, ShapeTag{"xmlAttribute", "true"}) 393 } 394 if isRequired { 395 tags = append(tags, ShapeTag{"required", "true"}) 396 } 397 if ref.Shape.IsEnum() { 398 tags = append(tags, ShapeTag{"enum", ref.ShapeName}) 399 } 400 401 if toplevel { 402 if ref.Shape.Payload != "" { 403 tags = append(tags, ShapeTag{"payload", ref.Shape.Payload}) 404 } 405 } 406 407 if ref.XMLNamespace.Prefix != "" { 408 tags = append(tags, ShapeTag{"xmlPrefix", ref.XMLNamespace.Prefix}) 409 } else if ref.Shape.XMLNamespace.Prefix != "" { 410 tags = append(tags, ShapeTag{"xmlPrefix", ref.Shape.XMLNamespace.Prefix}) 411 } 412 413 if ref.XMLNamespace.URI != "" { 414 tags = append(tags, ShapeTag{"xmlURI", ref.XMLNamespace.URI}) 415 } else if ref.Shape.XMLNamespace.URI != "" { 416 tags = append(tags, ShapeTag{"xmlURI", ref.Shape.XMLNamespace.URI}) 417 } 418 419 if ref.IdempotencyToken || ref.Shape.IdempotencyToken { 420 tags = append(tags, ShapeTag{"idempotencyToken", "true"}) 421 } 422 423 if ref.Ignore { 424 tags = append(tags, ShapeTag{"ignore", "true"}) 425 } 426 427 return fmt.Sprintf("`%s`", tags) 428} 429 430// Docstring returns the godocs formated documentation 431func (ref *ShapeRef) Docstring() string { 432 if ref.Documentation != "" { 433 return strings.Trim(ref.Documentation, "\n ") 434 } 435 return ref.Shape.Docstring() 436} 437 438// Docstring returns the godocs formated documentation 439func (s *Shape) Docstring() string { 440 return strings.Trim(s.Documentation, "\n ") 441} 442 443// IndentedDocstring is the indented form of the doc string. 444func (ref *ShapeRef) IndentedDocstring() string { 445 doc := ref.Docstring() 446 return strings.Replace(doc, "// ", "// ", -1) 447} 448 449var goCodeStringerTmpl = template.Must(template.New("goCodeStringerTmpl").Parse(` 450// String returns the string representation 451func (s {{ .ShapeName }}) String() string { 452 return awsutil.Prettify(s) 453} 454// GoString returns the string representation 455func (s {{ .ShapeName }}) GoString() string { 456 return s.String() 457} 458`)) 459 460// GoCodeStringers renders the Stringers for API input/output shapes 461func (s *Shape) GoCodeStringers() string { 462 w := bytes.Buffer{} 463 if err := goCodeStringerTmpl.Execute(&w, s); err != nil { 464 panic(fmt.Sprintln("Unexpected error executing GoCodeStringers template", err)) 465 } 466 467 return w.String() 468} 469 470var enumStrip = regexp.MustCompile(`[^a-zA-Z0-9_:\./-]`) 471var enumDelims = regexp.MustCompile(`[-_:\./]+`) 472var enumCamelCase = regexp.MustCompile(`([a-z])([A-Z])`) 473 474// EnumName returns the Nth enum in the shapes Enum list 475func (s *Shape) EnumName(n int) string { 476 enum := s.Enum[n] 477 enum = enumStrip.ReplaceAllLiteralString(enum, "") 478 enum = enumCamelCase.ReplaceAllString(enum, "$1-$2") 479 parts := enumDelims.Split(enum, -1) 480 for i, v := range parts { 481 v = strings.ToLower(v) 482 parts[i] = "" 483 if len(v) > 0 { 484 parts[i] = strings.ToUpper(v[0:1]) 485 } 486 if len(v) > 1 { 487 parts[i] += v[1:] 488 } 489 } 490 enum = strings.Join(parts, "") 491 enum = strings.ToUpper(enum[0:1]) + enum[1:] 492 return enum 493} 494 495// NestedShape returns the shape pointer value for the shape which is nested 496// under the current shape. If the shape is not nested nil will be returned. 497// 498// strucutures, the current shape is returned 499// map: the value shape of the map is returned 500// list: the element shape of the list is returned 501func (s *Shape) NestedShape() *Shape { 502 var nestedShape *Shape 503 switch s.Type { 504 case "structure": 505 nestedShape = s 506 case "map": 507 nestedShape = s.ValueRef.Shape 508 case "list": 509 nestedShape = s.MemberRef.Shape 510 } 511 512 return nestedShape 513} 514 515var structShapeTmpl = template.Must(template.New("StructShape").Funcs(template.FuncMap{ 516 "GetCrosslinkURL": GetCrosslinkURL, 517}).Parse(` 518{{ .Docstring }} 519{{ if ne $.OrigShapeName "" -}} 520{{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.Metadata.UID $.OrigShapeName -}} 521{{ if ne $crosslinkURL "" -}} 522// Please also see {{ $crosslinkURL }} 523{{ end -}} 524{{ else -}} 525{{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.Metadata.UID $.ShapeName -}} 526{{ if ne $crosslinkURL "" -}} 527// Please also see {{ $crosslinkURL }} 528{{ end -}} 529{{ end -}} 530{{ $context := . -}} 531type {{ .ShapeName }} struct { 532 _ struct{} {{ .GoTags true false }} 533 534 {{ range $_, $name := $context.MemberNames -}} 535 {{ $elem := index $context.MemberRefs $name -}} 536 {{ $isBlob := $context.WillRefBeBase64Encoded $name -}} 537 {{ $isRequired := $context.IsRequired $name -}} 538 {{ $doc := $elem.Docstring -}} 539 540 {{ if $doc -}} 541 {{ $doc }} 542 {{ end -}} 543 {{ if $isBlob -}} 544 {{ if $doc -}} 545 // 546 {{ end -}} 547 // {{ $name }} is automatically base64 encoded/decoded by the SDK. 548 {{ end -}} 549 {{ if $isRequired -}} 550 {{ if or $doc $isBlob -}} 551 // 552 {{ end -}} 553 // {{ $name }} is a required field 554 {{ end -}} 555 {{ $name }} {{ $context.GoStructType $name $elem }} {{ $elem.GoTags false $isRequired }} 556 557 {{ end }} 558} 559{{ if not .API.NoStringerMethods }} 560 {{ .GoCodeStringers }} 561{{ end }} 562{{ if not .API.NoValidataShapeMethods }} 563 {{ if .Validations -}} 564 {{ .Validations.GoCode . }} 565 {{ end }} 566{{ end }} 567 568{{ if not .API.NoGenStructFieldAccessors }} 569 570{{ $builderShapeName := print .ShapeName -}} 571 572{{ range $_, $name := $context.MemberNames -}} 573 {{ $elem := index $context.MemberRefs $name -}} 574 575// Set{{ $name }} sets the {{ $name }} field's value. 576func (s *{{ $builderShapeName }}) Set{{ $name }}(v {{ $context.GoStructValueType $name $elem }}) *{{ $builderShapeName }} { 577 {{ if $elem.UseIndirection -}} 578 s.{{ $name }} = &v 579 {{ else -}} 580 s.{{ $name }} = v 581 {{ end -}} 582 return s 583} 584 585{{ if $elem.GenerateGetter -}} 586func (s *{{ $builderShapeName }}) get{{ $name }}() (v {{ $context.GoStructValueType $name $elem }}) { 587 {{ if $elem.UseIndirection -}} 588 if s.{{ $name }} == nil { 589 return v 590 } 591 return *s.{{ $name }} 592 {{ else -}} 593 return s.{{ $name }} 594 {{ end -}} 595} 596{{- end }} 597 598{{ end }} 599{{ end }} 600`)) 601 602var enumShapeTmpl = template.Must(template.New("EnumShape").Parse(` 603{{ .Docstring }} 604const ( 605 {{ $context := . -}} 606 {{ range $index, $elem := .Enum -}} 607 {{ $name := index $context.EnumConsts $index -}} 608 // {{ $name }} is a {{ $context.ShapeName }} enum value 609 {{ $name }} = "{{ $elem }}" 610 611 {{ end }} 612) 613`)) 614 615// GoCode returns the rendered Go code for the Shape. 616func (s *Shape) GoCode() string { 617 b := &bytes.Buffer{} 618 619 switch { 620 case s.Type == "structure": 621 if err := structShapeTmpl.Execute(b, s); err != nil { 622 panic(fmt.Sprintf("Failed to generate struct shape %s, %v\n", s.ShapeName, err)) 623 } 624 case s.IsEnum(): 625 if err := enumShapeTmpl.Execute(b, s); err != nil { 626 panic(fmt.Sprintf("Failed to generate enum shape %s, %v\n", s.ShapeName, err)) 627 } 628 default: 629 panic(fmt.Sprintln("Cannot generate toplevel shape for", s.Type)) 630 } 631 632 return b.String() 633} 634 635// IsEnum returns whether this shape is an enum list 636func (s *Shape) IsEnum() bool { 637 return s.Type == "string" && len(s.Enum) > 0 638} 639 640// IsRequired returns if member is a required field. 641func (s *Shape) IsRequired(member string) bool { 642 for _, n := range s.Required { 643 if n == member { 644 return true 645 } 646 } 647 return false 648} 649 650// IsInternal returns whether the shape was defined in this package 651func (s *Shape) IsInternal() bool { 652 return s.resolvePkg == "" 653} 654 655// removeRef removes a shape reference from the list of references this 656// shape is used in. 657func (s *Shape) removeRef(ref *ShapeRef) { 658 r := s.refs 659 for i := 0; i < len(r); i++ { 660 if r[i] == ref { 661 j := i + 1 662 copy(r[i:], r[j:]) 663 for k, n := len(r)-j+i, len(r); k < n; k++ { 664 r[k] = nil // free up the end of the list 665 } // for k 666 s.refs = r[:len(r)-j+i] 667 break 668 } 669 } 670} 671 672func (s *Shape) WillRefBeBase64Encoded(refName string) bool { 673 payloadRefName := s.Payload 674 if payloadRefName == refName { 675 return false 676 } 677 678 ref, ok := s.MemberRefs[refName] 679 if !ok { 680 panic(fmt.Sprintf("shape %s does not contain %q refName", s.ShapeName, refName)) 681 } 682 683 return ref.Shape.Type == "blob" 684} 685