shape.go 15 KB

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