generator.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. package genopenapi
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "path/filepath"
  8. "reflect"
  9. "sort"
  10. "strings"
  11. "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor"
  12. gen "github.com/grpc-ecosystem/grpc-gateway/v2/internal/generator"
  13. openapioptions "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
  14. statuspb "google.golang.org/genproto/googleapis/rpc/status"
  15. "google.golang.org/grpc/grpclog"
  16. "google.golang.org/protobuf/proto"
  17. "google.golang.org/protobuf/reflect/protodesc"
  18. "google.golang.org/protobuf/types/descriptorpb"
  19. "google.golang.org/protobuf/types/known/anypb"
  20. "google.golang.org/protobuf/types/pluginpb"
  21. "gopkg.in/yaml.v3"
  22. )
  23. var errNoTargetService = errors.New("no target service defined in the file")
  24. type generator struct {
  25. reg *descriptor.Registry
  26. format Format
  27. }
  28. type wrapper struct {
  29. fileName string
  30. swagger *openapiSwaggerObject
  31. }
  32. type GeneratorOptions struct {
  33. Registry *descriptor.Registry
  34. RecursiveDepth int
  35. }
  36. // New returns a new generator which generates grpc gateway files.
  37. func New(reg *descriptor.Registry, format Format) gen.Generator {
  38. return &generator{
  39. reg: reg,
  40. format: format,
  41. }
  42. }
  43. // Merge a lot of OpenAPI file (wrapper) to single one OpenAPI file
  44. func mergeTargetFile(targets []*wrapper, mergeFileName string) *wrapper {
  45. var mergedTarget *wrapper
  46. for _, f := range targets {
  47. if mergedTarget == nil {
  48. mergedTarget = &wrapper{
  49. fileName: mergeFileName,
  50. swagger: f.swagger,
  51. }
  52. } else {
  53. for k, v := range f.swagger.Definitions {
  54. mergedTarget.swagger.Definitions[k] = v
  55. }
  56. for k, v := range f.swagger.SecurityDefinitions {
  57. mergedTarget.swagger.SecurityDefinitions[k] = v
  58. }
  59. copy(mergedTarget.swagger.Paths, f.swagger.Paths)
  60. mergedTarget.swagger.Security = append(mergedTarget.swagger.Security, f.swagger.Security...)
  61. }
  62. }
  63. return mergedTarget
  64. }
  65. // Q: What's up with the alias types here?
  66. // A: We don't want to completely override how these structs are marshaled into
  67. // JSON, we only want to add fields (see below, extensionMarshalJSON).
  68. // An infinite recursion would happen if we'd call json.Marshal on the struct
  69. // that has swaggerObject as an embedded field. To avoid that, we'll create
  70. // type aliases, and those don't have the custom MarshalJSON methods defined
  71. // on them. See http://choly.ca/post/go-json-marshalling/ (or, if it ever
  72. // goes away, use
  73. // https://web.archive.org/web/20190806073003/http://choly.ca/post/go-json-marshalling/).
  74. func (so openapiSwaggerObject) MarshalJSON() ([]byte, error) {
  75. type alias openapiSwaggerObject
  76. return extensionMarshalJSON(alias(so), so.extensions)
  77. }
  78. // MarshalYAML implements yaml.Marshaler interface.
  79. //
  80. // It is required in order to pass extensions inline.
  81. //
  82. // Example:
  83. //
  84. // extensions: {x-key: x-value}
  85. // type: string
  86. //
  87. // It will be rendered as:
  88. //
  89. // x-key: x-value
  90. // type: string
  91. //
  92. // Use generics when the project will be upgraded to go 1.18+.
  93. func (so openapiSwaggerObject) MarshalYAML() (interface{}, error) {
  94. type Alias openapiSwaggerObject
  95. return struct {
  96. Extension map[string]interface{} `yaml:",inline"`
  97. Alias `yaml:",inline"`
  98. }{
  99. Extension: extensionsToMap(so.extensions),
  100. Alias: Alias(so),
  101. }, nil
  102. }
  103. // Custom json marshaller for openapiPathsObject. Ensures
  104. // openapiPathsObject is marshalled into expected format in generated
  105. // swagger.json.
  106. func (po openapiPathsObject) MarshalJSON() ([]byte, error) {
  107. var buf bytes.Buffer
  108. buf.WriteString("{")
  109. for i, pd := range po {
  110. if i != 0 {
  111. buf.WriteString(",")
  112. }
  113. // marshal key
  114. key, err := json.Marshal(pd.Path)
  115. if err != nil {
  116. return nil, err
  117. }
  118. buf.Write(key)
  119. buf.WriteString(":")
  120. // marshal value
  121. val, err := json.Marshal(pd.PathItemObject)
  122. if err != nil {
  123. return nil, err
  124. }
  125. buf.Write(val)
  126. }
  127. buf.WriteString("}")
  128. return buf.Bytes(), nil
  129. }
  130. // Custom yaml marshaller for openapiPathsObject. Ensures
  131. // openapiPathsObject is marshalled into expected format in generated
  132. // swagger.yaml.
  133. func (po openapiPathsObject) MarshalYAML() (interface{}, error) {
  134. var pathObjectNode yaml.Node
  135. pathObjectNode.Kind = yaml.MappingNode
  136. for _, pathData := range po {
  137. var pathNode yaml.Node
  138. pathNode.SetString(pathData.Path)
  139. pathItemObjectNode, err := pathData.PathItemObject.toYAMLNode()
  140. if err != nil {
  141. return nil, err
  142. }
  143. pathObjectNode.Content = append(pathObjectNode.Content, &pathNode, pathItemObjectNode)
  144. }
  145. return pathObjectNode, nil
  146. }
  147. // We can simplify this implementation once the go-yaml bug is resolved. See: https://github.com/go-yaml/yaml/issues/643.
  148. //
  149. // func (pio *openapiPathItemObject) toYAMLNode() (*yaml.Node, error) {
  150. // var node yaml.Node
  151. // if err := node.Encode(pio); err != nil {
  152. // return nil, err
  153. // }
  154. // return &node, nil
  155. // }
  156. func (pio *openapiPathItemObject) toYAMLNode() (*yaml.Node, error) {
  157. var doc yaml.Node
  158. var buf bytes.Buffer
  159. ec := yaml.NewEncoder(&buf)
  160. ec.SetIndent(2)
  161. if err := ec.Encode(pio); err != nil {
  162. return nil, err
  163. }
  164. if err := yaml.Unmarshal(buf.Bytes(), &doc); err != nil {
  165. return nil, err
  166. }
  167. if len(doc.Content) == 0 {
  168. return nil, errors.New("unexpected number of yaml nodes")
  169. }
  170. return doc.Content[0], nil
  171. }
  172. func (so openapiInfoObject) MarshalJSON() ([]byte, error) {
  173. type alias openapiInfoObject
  174. return extensionMarshalJSON(alias(so), so.extensions)
  175. }
  176. func (so openapiInfoObject) MarshalYAML() (interface{}, error) {
  177. type Alias openapiInfoObject
  178. return struct {
  179. Extension map[string]interface{} `yaml:",inline"`
  180. Alias `yaml:",inline"`
  181. }{
  182. Extension: extensionsToMap(so.extensions),
  183. Alias: Alias(so),
  184. }, nil
  185. }
  186. func (so openapiSecuritySchemeObject) MarshalJSON() ([]byte, error) {
  187. type alias openapiSecuritySchemeObject
  188. return extensionMarshalJSON(alias(so), so.extensions)
  189. }
  190. func (so openapiSecuritySchemeObject) MarshalYAML() (interface{}, error) {
  191. type Alias openapiSecuritySchemeObject
  192. return struct {
  193. Extension map[string]interface{} `yaml:",inline"`
  194. Alias `yaml:",inline"`
  195. }{
  196. Extension: extensionsToMap(so.extensions),
  197. Alias: Alias(so),
  198. }, nil
  199. }
  200. func (so openapiOperationObject) MarshalJSON() ([]byte, error) {
  201. type alias openapiOperationObject
  202. return extensionMarshalJSON(alias(so), so.extensions)
  203. }
  204. func (so openapiOperationObject) MarshalYAML() (interface{}, error) {
  205. type Alias openapiOperationObject
  206. return struct {
  207. Extension map[string]interface{} `yaml:",inline"`
  208. Alias `yaml:",inline"`
  209. }{
  210. Extension: extensionsToMap(so.extensions),
  211. Alias: Alias(so),
  212. }, nil
  213. }
  214. func (so openapiResponseObject) MarshalJSON() ([]byte, error) {
  215. type alias openapiResponseObject
  216. return extensionMarshalJSON(alias(so), so.extensions)
  217. }
  218. func (so openapiResponseObject) MarshalYAML() (interface{}, error) {
  219. type Alias openapiResponseObject
  220. return struct {
  221. Extension map[string]interface{} `yaml:",inline"`
  222. Alias `yaml:",inline"`
  223. }{
  224. Extension: extensionsToMap(so.extensions),
  225. Alias: Alias(so),
  226. }, nil
  227. }
  228. func (so openapiSchemaObject) MarshalJSON() ([]byte, error) {
  229. type alias openapiSchemaObject
  230. return extensionMarshalJSON(alias(so), so.extensions)
  231. }
  232. func (so openapiSchemaObject) MarshalYAML() (interface{}, error) {
  233. type Alias openapiSchemaObject
  234. return struct {
  235. Extension map[string]interface{} `yaml:",inline"`
  236. Alias `yaml:",inline"`
  237. }{
  238. Extension: extensionsToMap(so.extensions),
  239. Alias: Alias(so),
  240. }, nil
  241. }
  242. func (so openapiParameterObject) MarshalJSON() ([]byte, error) {
  243. type alias openapiParameterObject
  244. return extensionMarshalJSON(alias(so), so.extensions)
  245. }
  246. func (so openapiParameterObject) MarshalYAML() (interface{}, error) {
  247. type Alias openapiParameterObject
  248. return struct {
  249. Extension map[string]interface{} `yaml:",inline"`
  250. Alias `yaml:",inline"`
  251. }{
  252. Extension: extensionsToMap(so.extensions),
  253. Alias: Alias(so),
  254. }, nil
  255. }
  256. func (so openapiTagObject) MarshalJSON() ([]byte, error) {
  257. type alias openapiTagObject
  258. return extensionMarshalJSON(alias(so), so.extensions)
  259. }
  260. func (so openapiTagObject) MarshalYAML() (interface{}, error) {
  261. type Alias openapiTagObject
  262. return struct {
  263. Extension map[string]interface{} `yaml:",inline"`
  264. Alias `yaml:",inline"`
  265. }{
  266. Extension: extensionsToMap(so.extensions),
  267. Alias: Alias(so),
  268. }, nil
  269. }
  270. func extensionMarshalJSON(so interface{}, extensions []extension) ([]byte, error) {
  271. // To append arbitrary keys to the struct we'll render into json,
  272. // we're creating another struct that embeds the original one, and
  273. // its extra fields:
  274. //
  275. // The struct will look like
  276. // struct {
  277. // *openapiCore
  278. // XGrpcGatewayFoo json.RawMessage `json:"x-grpc-gateway-foo"`
  279. // XGrpcGatewayBar json.RawMessage `json:"x-grpc-gateway-bar"`
  280. // }
  281. // and thus render into what we want -- the JSON of openapiCore with the
  282. // extensions appended.
  283. fields := []reflect.StructField{
  284. { // embedded
  285. Name: "Embedded",
  286. Type: reflect.TypeOf(so),
  287. Anonymous: true,
  288. },
  289. }
  290. for _, ext := range extensions {
  291. fields = append(fields, reflect.StructField{
  292. Name: fieldName(ext.key),
  293. Type: reflect.TypeOf(ext.value),
  294. Tag: reflect.StructTag(fmt.Sprintf("json:\"%s\"", ext.key)),
  295. })
  296. }
  297. t := reflect.StructOf(fields)
  298. s := reflect.New(t).Elem()
  299. s.Field(0).Set(reflect.ValueOf(so))
  300. for _, ext := range extensions {
  301. s.FieldByName(fieldName(ext.key)).Set(reflect.ValueOf(ext.value))
  302. }
  303. return json.Marshal(s.Interface())
  304. }
  305. // encodeOpenAPI converts OpenAPI file obj to pluginpb.CodeGeneratorResponse_File
  306. func encodeOpenAPI(file *wrapper, format Format) (*descriptor.ResponseFile, error) {
  307. var contentBuf bytes.Buffer
  308. enc, err := format.NewEncoder(&contentBuf)
  309. if err != nil {
  310. return nil, err
  311. }
  312. if err := enc.Encode(*file.swagger); err != nil {
  313. return nil, err
  314. }
  315. name := file.fileName
  316. ext := filepath.Ext(name)
  317. base := strings.TrimSuffix(name, ext)
  318. output := fmt.Sprintf("%s.swagger."+string(format), base)
  319. return &descriptor.ResponseFile{
  320. CodeGeneratorResponse_File: &pluginpb.CodeGeneratorResponse_File{
  321. Name: proto.String(output),
  322. Content: proto.String(contentBuf.String()),
  323. },
  324. }, nil
  325. }
  326. func (g *generator) Generate(targets []*descriptor.File) ([]*descriptor.ResponseFile, error) {
  327. var files []*descriptor.ResponseFile
  328. if g.reg.IsAllowMerge() {
  329. var mergedTarget *descriptor.File
  330. // try to find proto leader
  331. for _, f := range targets {
  332. if proto.HasExtension(f.Options, openapioptions.E_Openapiv2Swagger) {
  333. mergedTarget = f
  334. break
  335. }
  336. }
  337. // merge protos to leader
  338. for _, f := range targets {
  339. if mergedTarget == nil {
  340. mergedTarget = f
  341. } else if mergedTarget != f {
  342. mergedTarget.Enums = append(mergedTarget.Enums, f.Enums...)
  343. mergedTarget.Messages = append(mergedTarget.Messages, f.Messages...)
  344. mergedTarget.Services = append(mergedTarget.Services, f.Services...)
  345. }
  346. }
  347. targets = nil
  348. targets = append(targets, mergedTarget)
  349. }
  350. var openapis []*wrapper
  351. for _, file := range targets {
  352. if grpclog.V(1) {
  353. grpclog.Infof("Processing %s", file.GetName())
  354. }
  355. swagger, err := applyTemplate(param{File: file, reg: g.reg})
  356. if errors.Is(err, errNoTargetService) {
  357. if grpclog.V(1) {
  358. grpclog.Infof("%s: %v", file.GetName(), err)
  359. }
  360. continue
  361. }
  362. if err != nil {
  363. return nil, err
  364. }
  365. openapis = append(openapis, &wrapper{
  366. fileName: file.GetName(),
  367. swagger: swagger,
  368. })
  369. }
  370. if g.reg.IsAllowMerge() {
  371. targetOpenAPI := mergeTargetFile(openapis, g.reg.GetMergeFileName())
  372. if !g.reg.IsPreserveRPCOrder() {
  373. targetOpenAPI.swagger.sortPathsAlphabetically()
  374. }
  375. f, err := encodeOpenAPI(targetOpenAPI, g.format)
  376. if err != nil {
  377. return nil, fmt.Errorf("failed to encode OpenAPI for %s: %w", g.reg.GetMergeFileName(), err)
  378. }
  379. files = append(files, f)
  380. if grpclog.V(1) {
  381. grpclog.Infof("New OpenAPI file will emit")
  382. }
  383. } else {
  384. for _, file := range openapis {
  385. if !g.reg.IsPreserveRPCOrder() {
  386. file.swagger.sortPathsAlphabetically()
  387. }
  388. f, err := encodeOpenAPI(file, g.format)
  389. if err != nil {
  390. return nil, fmt.Errorf("failed to encode OpenAPI for %s: %w", file.fileName, err)
  391. }
  392. files = append(files, f)
  393. if grpclog.V(1) {
  394. grpclog.Infof("New OpenAPI file will emit")
  395. }
  396. }
  397. }
  398. return files, nil
  399. }
  400. func (so openapiSwaggerObject) sortPathsAlphabetically() {
  401. sort.Slice(so.Paths, func(i, j int) bool {
  402. return so.Paths[i].Path < so.Paths[j].Path
  403. })
  404. }
  405. // AddErrorDefs Adds google.rpc.Status and google.protobuf.Any
  406. // to registry (used for error-related API responses)
  407. func AddErrorDefs(reg *descriptor.Registry) error {
  408. // load internal protos
  409. any := protodesc.ToFileDescriptorProto((&anypb.Any{}).ProtoReflect().Descriptor().ParentFile())
  410. any.SourceCodeInfo = new(descriptorpb.SourceCodeInfo)
  411. status := protodesc.ToFileDescriptorProto((&statuspb.Status{}).ProtoReflect().Descriptor().ParentFile())
  412. status.SourceCodeInfo = new(descriptorpb.SourceCodeInfo)
  413. return reg.Load(&pluginpb.CodeGeneratorRequest{
  414. ProtoFile: []*descriptorpb.FileDescriptorProto{
  415. any,
  416. status,
  417. },
  418. })
  419. }
  420. func extensionsToMap(extensions []extension) map[string]interface{} {
  421. m := make(map[string]interface{}, len(extensions))
  422. for _, v := range extensions {
  423. m[v.key] = RawExample(v.value)
  424. }
  425. return m
  426. }