shape.go 16 KB

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