shape.go 13 KB

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