renderer.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. // Copyright 2017 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "bytes"
  17. "encoding/base64"
  18. "fmt"
  19. "log"
  20. _ "os"
  21. "path"
  22. "path/filepath"
  23. "strings"
  24. "text/template"
  25. "unicode"
  26. "unicode/utf8"
  27. openapi "github.com/googleapis/gnostic/OpenAPIv2"
  28. plugins "github.com/googleapis/gnostic/plugins"
  29. )
  30. // ServiceType typically corresponds to a definition, parameter,
  31. // or response in the API and is represented by a type in generated code.
  32. type ServiceType struct {
  33. Name string
  34. Kind string
  35. Fields []*ServiceTypeField
  36. }
  37. func (s *ServiceType) hasFieldNamed(name string) bool {
  38. for _, f := range s.Fields {
  39. if f.Name == name {
  40. return true
  41. }
  42. }
  43. return false
  44. }
  45. // ServiceTypeField is a field in a definition and can be
  46. // associated with a position in a request structure.
  47. type ServiceTypeField struct {
  48. Name string // the name as specified
  49. Type string // the specified type of the field
  50. NativeType string // the programming-language native type of the field
  51. FieldName string // the name to use for data structure fields
  52. ParameterName string // the name to use for parameters
  53. JSONName string // the name to use in JSON serialization
  54. Position string // "body", "header", "formdata", "query", or "path"
  55. }
  56. // ServiceMethod is an operation of an API and typically
  57. // has associated client and server code.
  58. type ServiceMethod struct {
  59. Name string // Operation name, possibly generated from method and path
  60. Path string // HTTP path
  61. Method string // HTTP method name
  62. Description string // description of method
  63. HandlerName string // name of the generated handler
  64. ProcessorName string // name of the processing function in the service interface
  65. ClientName string // name of client
  66. ResultTypeName string // native type name for the result structure
  67. ParametersTypeName string // native type name for the input parameters structure
  68. ResponsesTypeName string // native type name for the responses
  69. ParametersType *ServiceType // parameters (input)
  70. ResponsesType *ServiceType // responses (output)
  71. }
  72. // ServiceRenderer reads an OpenAPI document and generates code.
  73. type ServiceRenderer struct {
  74. Templates map[string]*template.Template
  75. Name string
  76. Package string
  77. Types []*ServiceType
  78. Methods []*ServiceMethod
  79. }
  80. // NewServiceRenderer creates a renderer.
  81. func NewServiceRenderer(document *openapi.Document, packageName string) (renderer *ServiceRenderer, err error) {
  82. renderer = &ServiceRenderer{}
  83. // Load templates.
  84. err = renderer.loadTemplates(templates())
  85. if err != nil {
  86. return nil, err
  87. }
  88. // Set renderer properties from passed-in document.
  89. renderer.Name = document.Info.Title
  90. renderer.Package = packageName // Set package name from argument.
  91. renderer.Types = make([]*ServiceType, 0)
  92. renderer.Methods = make([]*ServiceMethod, 0)
  93. err = renderer.loadService(document)
  94. if err != nil {
  95. return nil, err
  96. }
  97. return renderer, nil
  98. }
  99. // Load templates that will be used by the renderer.
  100. func (renderer *ServiceRenderer) loadTemplates(files map[string]string) (err error) {
  101. helpers := templateHelpers()
  102. renderer.Templates = make(map[string]*template.Template, 0)
  103. for filename, encoding := range files {
  104. templateData, err := base64.StdEncoding.DecodeString(encoding)
  105. if err != nil {
  106. return err
  107. }
  108. t, err := template.New(filename).Funcs(helpers).Parse(string(templateData))
  109. if err != nil {
  110. return err
  111. }
  112. renderer.Templates[filename] = t
  113. }
  114. return err
  115. }
  116. // Preprocess the types and methods of the API.
  117. func (renderer *ServiceRenderer) loadService(document *openapi.Document) (err error) {
  118. // Collect service type descriptions from Definitions section.
  119. if document.Definitions != nil {
  120. for _, pair := range document.Definitions.AdditionalProperties {
  121. var t ServiceType
  122. t.Fields = make([]*ServiceTypeField, 0)
  123. schema := pair.Value
  124. if schema.Properties != nil {
  125. if len(schema.Properties.AdditionalProperties) > 0 {
  126. // If the schema has properties, generate a struct.
  127. t.Kind = "struct"
  128. }
  129. for _, pair2 := range schema.Properties.AdditionalProperties {
  130. var f ServiceTypeField
  131. f.Name = strings.Title(pair2.Name)
  132. f.Type = typeForSchema(pair2.Value)
  133. f.JSONName = pair2.Name
  134. t.Fields = append(t.Fields, &f)
  135. }
  136. }
  137. t.Name = strings.Title(filteredTypeName(pair.Name))
  138. if len(t.Fields) == 0 {
  139. if schema.AdditionalProperties != nil {
  140. // If the schema has no fixed properties and additional properties of a specified type,
  141. // generate a map pointing to objects of that type.
  142. mapType := typeForRef(schema.AdditionalProperties.GetSchema().XRef)
  143. t.Kind = "map[string]" + mapType
  144. }
  145. }
  146. renderer.Types = append(renderer.Types, &t)
  147. }
  148. }
  149. // Collect service method descriptions from Paths section.
  150. for _, pair := range document.Paths.Path {
  151. v := pair.Value
  152. if v.Get != nil {
  153. renderer.loadOperation(v.Get, "GET", pair.Name)
  154. }
  155. if v.Post != nil {
  156. renderer.loadOperation(v.Post, "POST", pair.Name)
  157. }
  158. if v.Put != nil {
  159. renderer.loadOperation(v.Put, "PUT", pair.Name)
  160. }
  161. if v.Delete != nil {
  162. renderer.loadOperation(v.Delete, "DELETE", pair.Name)
  163. }
  164. }
  165. return err
  166. }
  167. // convert the first character of a string to upper case
  168. func upperFirst(s string) string {
  169. if s == "" {
  170. return ""
  171. }
  172. r, n := utf8.DecodeRuneInString(s)
  173. return string(unicode.ToUpper(r)) + strings.ToLower(s[n:])
  174. }
  175. func generateOperationName(method, path string) string {
  176. filteredPath := strings.Replace(path, "/", "_", -1)
  177. filteredPath = strings.Replace(filteredPath, ".", "_", -1)
  178. filteredPath = strings.Replace(filteredPath, "{", "", -1)
  179. filteredPath = strings.Replace(filteredPath, "}", "", -1)
  180. return upperFirst(method) + filteredPath
  181. }
  182. func (renderer *ServiceRenderer) loadOperation(op *openapi.Operation, method string, path string) (err error) {
  183. var m ServiceMethod
  184. m.Name = strings.Title(op.OperationId)
  185. m.Path = path
  186. m.Method = method
  187. if m.Name == "" {
  188. m.Name = generateOperationName(method, path)
  189. }
  190. m.Description = op.Description
  191. m.HandlerName = "Handle" + m.Name
  192. m.ProcessorName = m.Name
  193. m.ClientName = m.Name
  194. m.ParametersType, err = renderer.loadServiceTypeFromParameters(m.Name+"Parameters", op.Parameters)
  195. if m.ParametersType != nil {
  196. m.ParametersTypeName = m.ParametersType.Name
  197. }
  198. m.ResponsesType, err = renderer.loadServiceTypeFromResponses(&m, m.Name+"Responses", op.Responses)
  199. if m.ResponsesType != nil {
  200. m.ResponsesTypeName = m.ResponsesType.Name
  201. }
  202. renderer.Methods = append(renderer.Methods, &m)
  203. return err
  204. }
  205. func (renderer *ServiceRenderer) loadServiceTypeFromParameters(name string, parameters []*openapi.ParametersItem) (t *ServiceType, err error) {
  206. t = &ServiceType{}
  207. t.Kind = "struct"
  208. t.Fields = make([]*ServiceTypeField, 0)
  209. for _, parametersItem := range parameters {
  210. var f ServiceTypeField
  211. f.Type = fmt.Sprintf("%+v", parametersItem)
  212. parameter := parametersItem.GetParameter()
  213. if parameter != nil {
  214. bodyParameter := parameter.GetBodyParameter()
  215. if bodyParameter != nil {
  216. f.Name = bodyParameter.Name
  217. f.FieldName = strings.Replace(f.Name, "-", "_", -1)
  218. if bodyParameter.Schema != nil {
  219. f.Type = typeForSchema(bodyParameter.Schema)
  220. f.NativeType = f.Type
  221. f.Position = "body"
  222. }
  223. }
  224. nonBodyParameter := parameter.GetNonBodyParameter()
  225. if nonBodyParameter != nil {
  226. headerParameter := nonBodyParameter.GetHeaderParameterSubSchema()
  227. if headerParameter != nil {
  228. f.Name = headerParameter.Name
  229. f.FieldName = strings.Replace(f.Name, "-", "_", -1)
  230. f.Type = headerParameter.Type
  231. f.NativeType = f.Type
  232. f.Position = "header"
  233. }
  234. formDataParameter := nonBodyParameter.GetFormDataParameterSubSchema()
  235. if formDataParameter != nil {
  236. f.Name = formDataParameter.Name
  237. f.FieldName = strings.Replace(f.Name, "-", "_", -1)
  238. f.Type = formDataParameter.Type
  239. f.NativeType = f.Type
  240. f.Position = "formdata"
  241. }
  242. queryParameter := nonBodyParameter.GetQueryParameterSubSchema()
  243. if queryParameter != nil {
  244. f.Name = queryParameter.Name
  245. f.FieldName = strings.Replace(f.Name, "-", "_", -1)
  246. f.Type = queryParameter.Type
  247. f.NativeType = f.Type
  248. f.Position = "query"
  249. }
  250. pathParameter := nonBodyParameter.GetPathParameterSubSchema()
  251. if pathParameter != nil {
  252. f.Name = pathParameter.Name
  253. f.FieldName = strings.Replace(f.Name, "-", "_", -1)
  254. f.Type = pathParameter.Type
  255. f.NativeType = f.Type
  256. f.Position = "path"
  257. f.Type = typeForName(pathParameter.Type, pathParameter.Format)
  258. }
  259. }
  260. f.JSONName = f.Name
  261. f.ParameterName = replaceReservedWords(f.FieldName)
  262. f.Name = strings.Title(f.Name)
  263. t.Fields = append(t.Fields, &f)
  264. if f.NativeType == "integer" {
  265. f.NativeType = "int64"
  266. }
  267. }
  268. }
  269. t.Name = name
  270. if len(t.Fields) > 0 {
  271. renderer.Types = append(renderer.Types, t)
  272. return t, err
  273. }
  274. return nil, err
  275. }
  276. func (renderer *ServiceRenderer) loadServiceTypeFromResponses(m *ServiceMethod, name string, responses *openapi.Responses) (t *ServiceType, err error) {
  277. t = &ServiceType{}
  278. t.Kind = "struct"
  279. t.Fields = make([]*ServiceTypeField, 0)
  280. for _, responseCode := range responses.ResponseCode {
  281. var f ServiceTypeField
  282. f.Name = propertyNameForResponseCode(responseCode.Name)
  283. f.JSONName = ""
  284. response := responseCode.Value.GetResponse()
  285. if response != nil && response.Schema != nil && response.Schema.GetSchema() != nil {
  286. f.Type = "*" + typeForSchema(response.Schema.GetSchema())
  287. t.Fields = append(t.Fields, &f)
  288. if f.Name == "OK" {
  289. m.ResultTypeName = typeForSchema(response.Schema.GetSchema())
  290. }
  291. }
  292. }
  293. t.Name = name
  294. if len(t.Fields) > 0 {
  295. renderer.Types = append(renderer.Types, t)
  296. return t, err
  297. }
  298. return nil, err
  299. }
  300. func filteredTypeName(typeName string) (name string) {
  301. // first take the last path segment
  302. parts := strings.Split(typeName, "/")
  303. name = parts[len(parts)-1]
  304. // then take the last part of a dotted name
  305. parts = strings.Split(name, ".")
  306. name = parts[len(parts)-1]
  307. return name
  308. }
  309. func typeForName(name string, format string) (typeName string) {
  310. switch name {
  311. case "integer":
  312. if format == "int32" {
  313. return "int32"
  314. } else if format == "int64" {
  315. return "int64"
  316. } else {
  317. return "int32"
  318. }
  319. default:
  320. return name
  321. }
  322. }
  323. func typeForSchema(schema *openapi.Schema) (typeName string) {
  324. ref := schema.XRef
  325. if ref != "" {
  326. return typeForRef(ref)
  327. }
  328. if schema.Type != nil {
  329. types := schema.Type.Value
  330. format := schema.Format
  331. if len(types) == 1 && types[0] == "string" {
  332. return "string"
  333. }
  334. if len(types) == 1 && types[0] == "integer" && format == "int32" {
  335. return "int32"
  336. }
  337. if len(types) == 1 && types[0] == "integer" {
  338. return "int"
  339. }
  340. if len(types) == 1 && types[0] == "number" {
  341. return "int"
  342. }
  343. if len(types) == 1 && types[0] == "array" && schema.Items != nil {
  344. // we have an array.., but of what?
  345. items := schema.Items.Schema
  346. if len(items) == 1 && items[0].XRef != "" {
  347. return "[]" + typeForRef(items[0].XRef)
  348. }
  349. }
  350. if len(types) == 1 && types[0] == "object" && schema.AdditionalProperties == nil {
  351. return "map[string]interface{}"
  352. }
  353. }
  354. if schema.AdditionalProperties != nil {
  355. additionalProperties := schema.AdditionalProperties
  356. if propertySchema := additionalProperties.GetSchema(); propertySchema != nil {
  357. if ref := propertySchema.XRef; ref != "" {
  358. return "map[string]" + typeForRef(ref)
  359. }
  360. }
  361. }
  362. // this function is incomplete... so return a string representing anything that we don't handle
  363. return fmt.Sprintf("%v", schema)
  364. }
  365. func typeForRef(ref string) (typeName string) {
  366. return strings.Replace(strings.Title(path.Base(ref)), "-", "_", -1)
  367. }
  368. func propertyNameForResponseCode(code string) string {
  369. if code == "200" {
  370. return "OK"
  371. }
  372. return strings.Title(code)
  373. }
  374. // Generate runs the renderer to generate the named files.
  375. func (renderer *ServiceRenderer) Generate(response *plugins.Response, files []string) (err error) {
  376. for _, filename := range files {
  377. file := &plugins.File{}
  378. file.Name = filename
  379. f := new(bytes.Buffer)
  380. t := renderer.Templates[filename]
  381. log.Printf("Generating %s", filename)
  382. err = t.Execute(f, struct {
  383. Renderer *ServiceRenderer
  384. }{
  385. renderer,
  386. })
  387. if err != nil {
  388. response.Errors = append(response.Errors, fmt.Sprintf("ERROR %v", err))
  389. }
  390. inputBytes := f.Bytes()
  391. // run generated Go files through gofmt
  392. if filepath.Ext(file.Name) == ".go" {
  393. strippedBytes := stripMarkers(inputBytes)
  394. file.Data, err = gofmt(file.Name, strippedBytes)
  395. } else {
  396. file.Data = inputBytes
  397. }
  398. response.Files = append(response.Files, file)
  399. }
  400. return
  401. }
  402. func replaceReservedWords(name string) string {
  403. log.Printf("replacing %s\n", name)
  404. if name == "type" {
  405. return "ttttype"
  406. }
  407. return name
  408. }