shape.go 17 KB

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