reflector.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. // Copyright 2020 Google LLC. 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. //
  15. package generator
  16. import (
  17. "log"
  18. "strings"
  19. "google.golang.org/protobuf/reflect/protoreflect"
  20. wk "git.ikuban.com/server/swagger-api/v2/generator/wellknown"
  21. v3 "github.com/google/gnostic/openapiv3"
  22. )
  23. const (
  24. protobufValueName = "GoogleProtobufValue"
  25. protobufAnyName = "GoogleProtobufAny"
  26. )
  27. type OpenAPIv3Reflector struct {
  28. conf Configuration
  29. requiredSchemas []string // Names of schemas which are used through references.
  30. }
  31. // NewOpenAPIv3Reflector creates a new reflector.
  32. func NewOpenAPIv3Reflector(conf Configuration) *OpenAPIv3Reflector {
  33. return &OpenAPIv3Reflector{
  34. conf: conf,
  35. requiredSchemas: make([]string, 0),
  36. }
  37. }
  38. func (r *OpenAPIv3Reflector) getMessageName(message protoreflect.MessageDescriptor) string {
  39. prefix := ""
  40. parent := message.Parent()
  41. if _, ok := parent.(protoreflect.MessageDescriptor); ok {
  42. prefix = string(parent.Name()) + "_" + prefix
  43. }
  44. return prefix + string(message.Name())
  45. }
  46. func (r *OpenAPIv3Reflector) formatMessageName(message protoreflect.MessageDescriptor) string {
  47. typeName := r.fullMessageTypeName(message)
  48. name := r.getMessageName(message)
  49. if !*r.conf.FQSchemaNaming {
  50. if typeName == ".google.protobuf.Value" {
  51. name = protobufValueName
  52. } else if typeName == ".google.protobuf.Any" {
  53. name = protobufAnyName
  54. }
  55. }
  56. if *r.conf.Naming == "json" {
  57. if len(name) > 1 {
  58. name = strings.ToUpper(name[0:1]) + name[1:]
  59. }
  60. if len(name) == 1 {
  61. name = strings.ToLower(name)
  62. }
  63. }
  64. if *r.conf.FQSchemaNaming {
  65. package_name := string(message.ParentFile().Package())
  66. name = package_name + "." + name
  67. }
  68. return name
  69. }
  70. func (r *OpenAPIv3Reflector) formatFieldName(field protoreflect.FieldDescriptor) string {
  71. if *r.conf.Naming == "proto" {
  72. return string(field.Name())
  73. }
  74. return field.JSONName()
  75. }
  76. // fullMessageTypeName builds the full type name of a message.
  77. func (r *OpenAPIv3Reflector) fullMessageTypeName(message protoreflect.MessageDescriptor) string {
  78. name := r.getMessageName(message)
  79. return "." + string(message.ParentFile().Package()) + "." + name
  80. }
  81. func (r *OpenAPIv3Reflector) responseContentForMessage(message protoreflect.MessageDescriptor) (string, *v3.MediaTypes) {
  82. typeName := r.fullMessageTypeName(message)
  83. if typeName == ".google.protobuf.Empty" {
  84. return "200", wk.NewApplicationJsonMediaType(r.schemaOrReferenceForMessage(message))
  85. }
  86. if typeName == ".google.api.HttpBody" {
  87. return "200", wk.NewGoogleApiHttpBodyMediaType()
  88. }
  89. return "200", wk.NewApplicationJsonMediaType(r.schemaOrReferenceForMessage(message))
  90. }
  91. func (r *OpenAPIv3Reflector) schemaReferenceForMessage(message protoreflect.MessageDescriptor) string {
  92. schemaName := r.formatMessageName(message)
  93. if !contains(r.requiredSchemas, schemaName) {
  94. r.requiredSchemas = append(r.requiredSchemas, schemaName)
  95. }
  96. return "#/components/schemas/" + schemaName
  97. }
  98. // Returns a full schema for simple types, and a schema reference for complex types that reference
  99. // the definition in `#/components/schemas/`
  100. func (r *OpenAPIv3Reflector) schemaOrReferenceForMessage(message protoreflect.MessageDescriptor) *v3.SchemaOrReference {
  101. typeName := r.fullMessageTypeName(message)
  102. switch typeName {
  103. case ".google.api.HttpBody":
  104. return wk.NewGoogleApiHttpBodySchema()
  105. case ".google.protobuf.Timestamp":
  106. return wk.NewGoogleProtobufTimestampSchema()
  107. case ".google.protobuf.Duration":
  108. return wk.NewGoogleProtobufDurationSchema()
  109. case ".google.type.Date":
  110. return wk.NewGoogleTypeDateSchema()
  111. case ".google.type.DateTime":
  112. return wk.NewGoogleTypeDateTimeSchema()
  113. case ".google.protobuf.FieldMask":
  114. return wk.NewGoogleProtobufFieldMaskSchema()
  115. case ".google.protobuf.Struct":
  116. return wk.NewGoogleProtobufStructSchema()
  117. case ".google.protobuf.Empty":
  118. // Empty is closer to JSON undefined than null, so ignore this field
  119. return wk.NewGoogleProtobufStructSchema() //&v3.SchemaOrReference{Oneof: &v3.SchemaOrReference_Schema{Schema: &v3.Schema{Type: "null"}}}
  120. case ".google.protobuf.BoolValue":
  121. return wk.NewBooleanSchema()
  122. case ".google.protobuf.BytesValue":
  123. return wk.NewBytesSchema()
  124. case ".google.protobuf.Int32Value", ".google.protobuf.UInt32Value", ".google.protobuf.Int64Value", ".google.protobuf.UInt64Value":
  125. return wk.NewIntegerSchema(getValueKind(message))
  126. case ".google.protobuf.StringValue":
  127. return wk.NewStringSchema()
  128. case ".google.protobuf.FloatValue", ".google.protobuf.DoubleValue":
  129. return wk.NewNumberSchema(getValueKind(message))
  130. default:
  131. ref := r.schemaReferenceForMessage(message)
  132. return &v3.SchemaOrReference{
  133. Oneof: &v3.SchemaOrReference_Reference{
  134. Reference: &v3.Reference{XRef: ref}}}
  135. }
  136. }
  137. func (r *OpenAPIv3Reflector) schemaOrReferenceForField(field protoreflect.FieldDescriptor) *v3.SchemaOrReference {
  138. var kindSchema *v3.SchemaOrReference
  139. kind := field.Kind()
  140. switch kind {
  141. case protoreflect.MessageKind:
  142. if field.IsMap() {
  143. // This means the field is a map, for example:
  144. // map<string, value_type> map_field = 1;
  145. //
  146. // The map ends up getting converted into something like this:
  147. // message MapFieldEntry {
  148. // string key = 1;
  149. // value_type value = 2;
  150. // }
  151. //
  152. // repeated MapFieldEntry map_field = N;
  153. //
  154. // So we need to find the `value` field in the `MapFieldEntry` message and
  155. // then return a MapFieldEntry schema using the schema for the `value` field
  156. return wk.NewGoogleProtobufMapFieldEntrySchema(r.schemaOrReferenceForField(field.MapValue()))
  157. } else {
  158. kindSchema = r.schemaOrReferenceForMessage(field.Message())
  159. }
  160. case protoreflect.StringKind:
  161. kindSchema = wk.NewStringSchema()
  162. case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Uint32Kind,
  163. protoreflect.Sfixed32Kind, protoreflect.Fixed32Kind:
  164. kindSchema = wk.NewIntegerSchema(kind.String())
  165. case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind,
  166. protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind:
  167. kindSchema = wk.NewIntegerSchema(kind.String())
  168. case protoreflect.EnumKind:
  169. kindSchema = wk.NewEnumSchema(*&r.conf.EnumType, field)
  170. case protoreflect.BoolKind:
  171. kindSchema = wk.NewBooleanSchema()
  172. case protoreflect.FloatKind, protoreflect.DoubleKind:
  173. kindSchema = wk.NewNumberSchema(kind.String())
  174. case protoreflect.BytesKind:
  175. kindSchema = wk.NewBytesSchema()
  176. default:
  177. log.Printf("(TODO) Unsupported field type: %+v", r.fullMessageTypeName(field.Message()))
  178. }
  179. if field.IsList() {
  180. kindSchema = wk.NewListSchema(kindSchema)
  181. }
  182. return kindSchema
  183. }