shape.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. package api
  2. import (
  3. "bytes"
  4. "fmt"
  5. "path"
  6. "regexp"
  7. "sort"
  8. "strings"
  9. "text/template"
  10. "github.com/aws/aws-sdk-go/internal/util"
  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. }
  27. // A XMLInfo defines URL and prefix for Shapes when rendered as XML
  28. type XMLInfo struct {
  29. Prefix string
  30. URI string
  31. }
  32. // A Shape defines the definition of a shape type
  33. type Shape struct {
  34. API *API `json:"-"`
  35. ShapeName string
  36. Documentation string
  37. MemberRefs map[string]*ShapeRef `json:"members"`
  38. MemberRef ShapeRef `json:"member"`
  39. KeyRef ShapeRef `json:"key"`
  40. ValueRef ShapeRef `json:"value"`
  41. Required []string
  42. Payload string
  43. Type string
  44. Exception bool
  45. Enum []string
  46. EnumConsts []string
  47. Flattened bool
  48. Streaming bool
  49. Location string
  50. LocationName string
  51. XMLNamespace XMLInfo
  52. refs []*ShapeRef // References to this shape
  53. resolvePkg string // use this package in the goType() if present
  54. }
  55. // Rename changes the name of the Shape to newName. Also updates
  56. // the associated API's reference to use newName.
  57. func (s *Shape) Rename(newName string) {
  58. for _, r := range s.refs {
  59. r.ShapeName = newName
  60. }
  61. delete(s.API.Shapes, s.ShapeName)
  62. s.API.Shapes[newName] = s
  63. s.ShapeName = newName
  64. }
  65. // MemberNames returns a slice of struct member names.
  66. func (s *Shape) MemberNames() []string {
  67. i, names := 0, make([]string, len(s.MemberRefs))
  68. for n := range s.MemberRefs {
  69. names[i] = n
  70. i++
  71. }
  72. sort.Strings(names)
  73. return names
  74. }
  75. // GoTypeWithPkgName returns a shape's type as a string with the package name in
  76. // <packageName>.<type> format. Package naming only applies to structures.
  77. func (s *Shape) GoTypeWithPkgName() string {
  78. return goType(s, true)
  79. }
  80. // GoType returns a shape's Go type
  81. func (s *Shape) GoType() string {
  82. return goType(s, false)
  83. }
  84. // GoType returns a shape ref's Go type.
  85. func (ref *ShapeRef) GoType() string {
  86. if ref.Shape == nil {
  87. panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
  88. }
  89. return ref.Shape.GoType()
  90. }
  91. // GoTypeWithPkgName returns a shape's type as a string with the package name in
  92. // <packageName>.<type> format. Package naming only applies to structures.
  93. func (ref *ShapeRef) GoTypeWithPkgName() string {
  94. if ref.Shape == nil {
  95. panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
  96. }
  97. return ref.Shape.GoTypeWithPkgName()
  98. }
  99. // Returns a string version of the Shape's type.
  100. // If withPkgName is true, the package name will be added as a prefix
  101. func goType(s *Shape, withPkgName bool) string {
  102. switch s.Type {
  103. case "structure":
  104. if withPkgName || s.resolvePkg != "" {
  105. pkg := s.resolvePkg
  106. if pkg != "" {
  107. s.API.imports[pkg] = true
  108. pkg = path.Base(pkg)
  109. } else {
  110. pkg = s.API.PackageName()
  111. }
  112. return fmt.Sprintf("*%s.%s", pkg, s.ShapeName)
  113. }
  114. return "*" + s.ShapeName
  115. case "map":
  116. return "map[string]" + s.ValueRef.GoType()
  117. case "list":
  118. return "[]" + s.MemberRef.GoType()
  119. case "boolean":
  120. return "*bool"
  121. case "string", "character":
  122. return "*string"
  123. case "blob":
  124. return "[]byte"
  125. case "integer", "long":
  126. return "*int64"
  127. case "float", "double":
  128. return "*float64"
  129. case "timestamp":
  130. s.API.imports["time"] = true
  131. return "*time.Time"
  132. default:
  133. panic("Unsupported shape type: " + s.Type)
  134. }
  135. }
  136. // GoTypeElem returns the Go type for the Shape. If the shape type is a pointer just
  137. // the type will be returned minus the pointer *.
  138. func (s *Shape) GoTypeElem() string {
  139. t := s.GoType()
  140. if strings.HasPrefix(t, "*") {
  141. return t[1:]
  142. }
  143. return t
  144. }
  145. // GoTypeElem returns the Go type for the Shape. If the shape type is a pointer just
  146. // the type will be returned minus the pointer *.
  147. func (ref *ShapeRef) GoTypeElem() string {
  148. if ref.Shape == nil {
  149. panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
  150. }
  151. return ref.Shape.GoTypeElem()
  152. }
  153. // GoTags returns the rendered tags string for the ShapeRef
  154. func (ref *ShapeRef) GoTags(toplevel bool, isRequired bool) string {
  155. code := "`"
  156. if ref.Location != "" {
  157. code += `location:"` + ref.Location + `" `
  158. } else if ref.Shape.Location != "" {
  159. code += `location:"` + ref.Shape.Location + `" `
  160. }
  161. if ref.LocationName != "" {
  162. code += `locationName:"` + ref.LocationName + `" `
  163. } else if ref.Shape.LocationName != "" {
  164. code += `locationName:"` + ref.Shape.LocationName + `" `
  165. }
  166. if ref.QueryName != "" {
  167. code += `queryName:"` + ref.QueryName + `" `
  168. }
  169. if ref.Shape.MemberRef.LocationName != "" {
  170. code += `locationNameList:"` + ref.Shape.MemberRef.LocationName + `" `
  171. }
  172. if ref.Shape.KeyRef.LocationName != "" {
  173. code += `locationNameKey:"` + ref.Shape.KeyRef.LocationName + `" `
  174. }
  175. if ref.Shape.ValueRef.LocationName != "" {
  176. code += `locationNameValue:"` + ref.Shape.ValueRef.LocationName + `" `
  177. }
  178. code += `type:"` + ref.Shape.Type + `" `
  179. // embed the timestamp type for easier lookups
  180. if ref.Shape.Type == "timestamp" {
  181. code += `timestampFormat:"`
  182. if ref.Location == "header" {
  183. code += "rfc822"
  184. } else {
  185. switch ref.API.Metadata.Protocol {
  186. case "json", "rest-json":
  187. code += "unix"
  188. case "rest-xml", "ec2", "query":
  189. code += "iso8601"
  190. }
  191. }
  192. code += `" `
  193. }
  194. if ref.Shape.Flattened || ref.Flattened {
  195. code += `flattened:"true" `
  196. }
  197. if ref.XMLAttribute {
  198. code += `xmlAttribute:"true" `
  199. }
  200. if isRequired {
  201. code += `required:"true" `
  202. }
  203. if ref.Shape.IsEnum() {
  204. code += `enum:"` + ref.ShapeName + `" `
  205. }
  206. if toplevel {
  207. if ref.Shape.Payload != "" {
  208. code += `payload:"` + ref.Shape.Payload + `" `
  209. }
  210. if ref.XMLNamespace.Prefix != "" {
  211. code += `xmlPrefix:"` + ref.XMLNamespace.Prefix + `" `
  212. } else if ref.Shape.XMLNamespace.Prefix != "" {
  213. code += `xmlPrefix:"` + ref.Shape.XMLNamespace.Prefix + `" `
  214. }
  215. if ref.XMLNamespace.URI != "" {
  216. code += `xmlURI:"` + ref.XMLNamespace.URI + `" `
  217. } else if ref.Shape.XMLNamespace.URI != "" {
  218. code += `xmlURI:"` + ref.Shape.XMLNamespace.URI + `" `
  219. }
  220. }
  221. return strings.TrimSpace(code) + "`"
  222. }
  223. // Docstring returns the godocs formated documentation
  224. func (ref *ShapeRef) Docstring() string {
  225. if ref.Documentation != "" {
  226. return ref.Documentation
  227. }
  228. return ref.Shape.Docstring()
  229. }
  230. // Docstring returns the godocs formated documentation
  231. func (s *Shape) Docstring() string {
  232. return s.Documentation
  233. }
  234. const goCodeStringerTmpl = `
  235. // String returns the string representation
  236. func (s {{ .ShapeName }}) String() string {
  237. return awsutil.Prettify(s)
  238. }
  239. // GoString returns the string representation
  240. func (s {{ .ShapeName }}) GoString() string {
  241. return s.String()
  242. }
  243. `
  244. func (s *Shape) goCodeStringers() string {
  245. tmpl := template.Must(template.New("goCodeStringerTmpl").Parse(goCodeStringerTmpl))
  246. w := bytes.Buffer{}
  247. if err := tmpl.Execute(&w, s); err != nil {
  248. panic(fmt.Sprintln("Unexpected error executing goCodeStringers template", err))
  249. }
  250. return w.String()
  251. }
  252. var enumStrip = regexp.MustCompile(`[^a-zA-Z0-9_:\./-]`)
  253. var enumDelims = regexp.MustCompile(`[-_:\./]+`)
  254. var enumCamelCase = regexp.MustCompile(`([a-z])([A-Z])`)
  255. // EnumName returns the Nth enum in the shapes Enum list
  256. func (s *Shape) EnumName(n int) string {
  257. enum := s.Enum[n]
  258. enum = enumStrip.ReplaceAllLiteralString(enum, "")
  259. enum = enumCamelCase.ReplaceAllString(enum, "$1-$2")
  260. parts := enumDelims.Split(enum, -1)
  261. for i, v := range parts {
  262. v = strings.ToLower(v)
  263. parts[i] = ""
  264. if len(v) > 0 {
  265. parts[i] = strings.ToUpper(v[0:1])
  266. }
  267. if len(v) > 1 {
  268. parts[i] += v[1:]
  269. }
  270. }
  271. enum = strings.Join(parts, "")
  272. enum = strings.ToUpper(enum[0:1]) + enum[1:]
  273. return enum
  274. }
  275. // GoCode returns the rendered Go code for the Shape.
  276. func (s *Shape) GoCode() string {
  277. code := s.Docstring()
  278. if !s.IsEnum() {
  279. code += "type " + s.ShapeName + " "
  280. }
  281. switch {
  282. case s.Type == "structure":
  283. code += "struct {\n"
  284. for _, n := range s.MemberNames() {
  285. m := s.MemberRefs[n]
  286. code += m.Docstring()
  287. if (m.Streaming || m.Shape.Streaming) && s.Payload == n {
  288. rtype := "io.ReadSeeker"
  289. if len(s.refs) > 1 {
  290. rtype = "aws.ReaderSeekCloser"
  291. } else if strings.HasSuffix(s.ShapeName, "Output") {
  292. rtype = "io.ReadCloser"
  293. }
  294. s.API.imports["io"] = true
  295. code += n + " " + rtype + " " + m.GoTags(false, s.IsRequired(n)) + "\n\n"
  296. } else {
  297. code += n + " " + m.GoType() + " " + m.GoTags(false, s.IsRequired(n)) + "\n\n"
  298. }
  299. }
  300. metaStruct := "metadata" + s.ShapeName
  301. ref := &ShapeRef{ShapeName: s.ShapeName, API: s.API, Shape: s}
  302. code += "\n" + metaStruct + " `json:\"-\" xml:\"-\"`\n"
  303. code += "}\n\n"
  304. code += "type " + metaStruct + " struct {\n"
  305. code += "SDKShapeTraits bool " + ref.GoTags(true, false)
  306. code += "}"
  307. if !s.API.NoStringerMethods {
  308. code += s.goCodeStringers()
  309. }
  310. case s.IsEnum():
  311. code += "const (\n"
  312. for n, e := range s.Enum {
  313. code += fmt.Sprintf("\t// @enum %s\n\t%s = %q\n",
  314. s.ShapeName, s.EnumConsts[n], e)
  315. }
  316. code += ")"
  317. default:
  318. panic("Cannot generate toplevel shape for " + s.Type)
  319. }
  320. return util.GoFmt(code)
  321. }
  322. // IsEnum returns whether this shape is an enum list
  323. func (s *Shape) IsEnum() bool {
  324. return s.Type == "string" && len(s.Enum) > 0
  325. }
  326. // IsRequired returns if member is a required field.
  327. func (s *Shape) IsRequired(member string) bool {
  328. for _, n := range s.Required {
  329. if n == member {
  330. return true
  331. }
  332. }
  333. return false
  334. }
  335. // IsInternal returns whether the shape was defined in this package
  336. func (s *Shape) IsInternal() bool {
  337. return s.resolvePkg == ""
  338. }
  339. // removeRef removes a shape reference from the list of references this
  340. // shape is used in.
  341. func (s *Shape) removeRef(ref *ShapeRef) {
  342. r := s.refs
  343. for i := 0; i < len(r); i++ {
  344. if r[i] == ref {
  345. j := i + 1
  346. copy(r[i:], r[j:])
  347. for k, n := len(r)-j+i, len(r); k < n; k++ {
  348. r[k] = nil // free up the end of the list
  349. } // for k
  350. s.refs = r[:len(r)-j+i]
  351. break
  352. }
  353. }
  354. }