template.go 104 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218
  1. package genopenapi
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "math"
  7. "net/textproto"
  8. "os"
  9. "reflect"
  10. "regexp"
  11. "sort"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "text/template"
  16. "time"
  17. "github.com/grpc-ecosystem/grpc-gateway/v2/internal/casing"
  18. "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor"
  19. openapi_options "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
  20. "google.golang.org/genproto/googleapis/api/annotations"
  21. "google.golang.org/genproto/googleapis/api/visibility"
  22. "google.golang.org/grpc/grpclog"
  23. "google.golang.org/protobuf/encoding/protojson"
  24. "google.golang.org/protobuf/proto"
  25. "google.golang.org/protobuf/types/descriptorpb"
  26. "google.golang.org/protobuf/types/known/structpb"
  27. )
  28. // The OpenAPI specification does not allow for more than one endpoint with the same HTTP method and path.
  29. // This prevents multiple gRPC service methods from sharing the same stripped version of the path and method.
  30. // For example: `GET /v1/{name=organizations/*}/roles` and `GET /v1/{name=users/*}/roles` both get stripped to `GET /v1/{name}/roles`.
  31. // We must make the URL unique by adding a suffix and an incrementing index to each path parameter
  32. // to differentiate the endpoints.
  33. // Since path parameter names do not affect the request contents (i.e. they're replaced in the path)
  34. // this will be hidden from the real grpc gateway consumer.
  35. const pathParamUniqueSuffixDeliminator = "_"
  36. const paragraphDeliminator = "\n\n"
  37. // wktSchemas are the schemas of well-known-types.
  38. // The schemas must match with the behavior of the JSON unmarshaler in
  39. // https://github.com/protocolbuffers/protobuf-go/blob/v1.25.0/encoding/protojson/well_known_types.go
  40. var wktSchemas = map[string]schemaCore{
  41. ".google.protobuf.FieldMask": {
  42. Type: "string",
  43. },
  44. ".google.protobuf.Timestamp": {
  45. Type: "string",
  46. Format: "date-time",
  47. },
  48. ".google.protobuf.Duration": {
  49. Type: "string",
  50. },
  51. ".google.protobuf.StringValue": {
  52. Type: "string",
  53. },
  54. ".google.protobuf.BytesValue": {
  55. Type: "string",
  56. Format: "byte",
  57. },
  58. ".google.protobuf.Int32Value": {
  59. Type: "integer",
  60. Format: "int32",
  61. },
  62. ".google.protobuf.UInt32Value": {
  63. Type: "integer",
  64. Format: "int64",
  65. },
  66. ".google.protobuf.Int64Value": {
  67. Type: "string",
  68. Format: "int64",
  69. },
  70. ".google.protobuf.UInt64Value": {
  71. Type: "string",
  72. Format: "uint64",
  73. },
  74. ".google.protobuf.FloatValue": {
  75. Type: "number",
  76. Format: "float",
  77. },
  78. ".google.protobuf.DoubleValue": {
  79. Type: "number",
  80. Format: "double",
  81. },
  82. ".google.protobuf.BoolValue": {
  83. Type: "boolean",
  84. },
  85. ".google.protobuf.Empty": {
  86. Type: "object",
  87. },
  88. ".google.protobuf.Struct": {
  89. Type: "object",
  90. },
  91. ".google.protobuf.Value": {},
  92. ".google.protobuf.ListValue": {
  93. Type: "array",
  94. Items: (*openapiItemsObject)(&openapiSchemaObject{
  95. schemaCore: schemaCore{
  96. Type: "object",
  97. }}),
  98. },
  99. ".google.protobuf.NullValue": {
  100. Type: "string",
  101. },
  102. }
  103. func listEnumNames(reg *descriptor.Registry, enum *descriptor.Enum) interface{} {
  104. var names []string
  105. for _, value := range enum.GetValue() {
  106. if !isVisible(getEnumValueVisibilityOption(value), reg) {
  107. continue
  108. }
  109. if reg.GetOmitEnumDefaultValue() && value.GetNumber() == 0 {
  110. continue
  111. }
  112. names = append(names, value.GetName())
  113. }
  114. if len(names) > 0 {
  115. return names
  116. }
  117. return nil
  118. }
  119. func listEnumNumbers(reg *descriptor.Registry, enum *descriptor.Enum) interface{} {
  120. var numbers []int
  121. for _, value := range enum.GetValue() {
  122. if reg.GetOmitEnumDefaultValue() && value.GetNumber() == 0 {
  123. continue
  124. }
  125. if !isVisible(getEnumValueVisibilityOption(value), reg) {
  126. continue
  127. }
  128. numbers = append(numbers, int(value.GetNumber()))
  129. }
  130. if len(numbers) > 0 {
  131. return numbers
  132. }
  133. return nil
  134. }
  135. func getEnumDefault(reg *descriptor.Registry, enum *descriptor.Enum) interface{} {
  136. if !reg.GetOmitEnumDefaultValue() {
  137. for _, value := range enum.GetValue() {
  138. if value.GetNumber() == 0 {
  139. return value.GetName()
  140. }
  141. }
  142. }
  143. return nil
  144. }
  145. func getEnumDefaultNumber(reg *descriptor.Registry, enum *descriptor.Enum) interface{} {
  146. if !reg.GetOmitEnumDefaultValue() {
  147. for _, value := range enum.GetValue() {
  148. if value.GetNumber() == 0 {
  149. return int(value.GetNumber())
  150. }
  151. }
  152. }
  153. return nil
  154. }
  155. // messageToQueryParameters converts a message to a list of OpenAPI query parameters.
  156. func messageToQueryParameters(message *descriptor.Message, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, httpMethod string) (params []openapiParameterObject, err error) {
  157. for _, field := range message.Fields {
  158. // When body is set to oneof field, we want to skip other fields in the oneof group.
  159. if isBodySameOneOf(body, field) {
  160. continue
  161. }
  162. if !isVisible(getFieldVisibilityOption(field), reg) {
  163. continue
  164. }
  165. if reg.GetAllowPatchFeature() && field.GetTypeName() == ".google.protobuf.FieldMask" && field.GetName() == "update_mask" && httpMethod == "PATCH" && len(body.FieldPath) != 0 {
  166. continue
  167. }
  168. p, err := queryParams(message, field, "", reg, pathParams, body, reg.GetRecursiveDepth())
  169. if err != nil {
  170. return nil, err
  171. }
  172. params = append(params, p...)
  173. }
  174. return params, nil
  175. }
  176. func isBodySameOneOf(body *descriptor.Body, field *descriptor.Field) bool {
  177. if field.OneofIndex == nil {
  178. return false
  179. }
  180. if body == nil || len(body.FieldPath) == 0 {
  181. return false
  182. }
  183. if body.FieldPath[0].Target.OneofIndex == nil {
  184. return false
  185. }
  186. return *body.FieldPath[0].Target.OneofIndex == *field.OneofIndex
  187. }
  188. // queryParams converts a field to a list of OpenAPI query parameters recursively through the use of nestedQueryParams.
  189. func queryParams(message *descriptor.Message, field *descriptor.Field, prefix string, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, recursiveCount int) (params []openapiParameterObject, err error) {
  190. return nestedQueryParams(message, field, prefix, reg, pathParams, body, newCycleChecker(recursiveCount))
  191. }
  192. type cycleChecker struct {
  193. m map[string]int
  194. count int
  195. }
  196. func newCycleChecker(recursive int) *cycleChecker {
  197. return &cycleChecker{
  198. m: make(map[string]int),
  199. count: recursive,
  200. }
  201. }
  202. // Check returns whether name is still within recursion
  203. // toleration
  204. func (c *cycleChecker) Check(name string) bool {
  205. count, ok := c.m[name]
  206. count += 1
  207. isCycle := count > c.count
  208. if isCycle {
  209. return false
  210. }
  211. // provision map entry if not available
  212. if !ok {
  213. c.m[name] = 1
  214. return true
  215. }
  216. c.m[name] = count
  217. return true
  218. }
  219. func (c *cycleChecker) Branch() *cycleChecker {
  220. copy := &cycleChecker{
  221. count: c.count,
  222. m: make(map[string]int, len(c.m)),
  223. }
  224. for k, v := range c.m {
  225. copy.m[k] = v
  226. }
  227. return copy
  228. }
  229. // nestedQueryParams converts a field to a list of OpenAPI query parameters recursively.
  230. // This function is a helper function for queryParams, that keeps track of cyclical message references
  231. // through the use of
  232. //
  233. // touched map[string]int
  234. //
  235. // If a cycle is discovered, an error is returned, as cyclical data structures are dangerous
  236. // in query parameters.
  237. func nestedQueryParams(message *descriptor.Message, field *descriptor.Field, prefix string, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, cycle *cycleChecker) (params []openapiParameterObject, err error) {
  238. // make sure the parameter is not already listed as a path parameter
  239. for _, pathParam := range pathParams {
  240. if pathParam.Target == field {
  241. return nil, nil
  242. }
  243. }
  244. // make sure the parameter is not already listed as a body parameter
  245. if body != nil {
  246. if body.FieldPath == nil {
  247. return nil, nil
  248. }
  249. for _, fieldPath := range body.FieldPath {
  250. if fieldPath.Target == field {
  251. return nil, nil
  252. }
  253. }
  254. }
  255. schema := schemaOfField(field, reg, nil)
  256. fieldType := field.GetTypeName()
  257. if message.File != nil {
  258. comments := fieldProtoComments(reg, message, field)
  259. if err := updateOpenAPIDataFromComments(reg, &schema, message, comments, false); err != nil {
  260. return nil, err
  261. }
  262. }
  263. isEnum := field.GetType() == descriptorpb.FieldDescriptorProto_TYPE_ENUM
  264. items := schema.Items
  265. if schema.Type != "" || isEnum {
  266. if schema.Type == "object" {
  267. location := ""
  268. if ix := strings.LastIndex(field.Message.FQMN(), "."); ix > 0 {
  269. location = field.Message.FQMN()[0:ix]
  270. }
  271. if m, err := reg.LookupMsg(location, field.GetTypeName()); err == nil {
  272. if opt := m.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
  273. k := m.GetField()[0]
  274. kType, err := getMapParamKey(k.GetType())
  275. if err != nil {
  276. return nil, err
  277. }
  278. // This will generate a query in the format map_name[key_type]
  279. fName := fmt.Sprintf("%s[%s]", *field.Name, kType)
  280. field.Name = proto.String(fName)
  281. schema.Type = schema.AdditionalProperties.schemaCore.Type
  282. schema.Description = `This is a request variable of the map type. The query format is "map_name[key]=value", e.g. If the map name is Age, the key type is string, and the value type is integer, the query parameter is expressed as Age["bob"]=18`
  283. }
  284. }
  285. }
  286. if items != nil && (items.Type == "" || items.Type == "object") && !isEnum {
  287. return nil, nil // TODO: currently, mapping object in query parameter is not supported
  288. }
  289. desc := mergeDescription(schema)
  290. // verify if the field is required
  291. required := false
  292. for _, fieldName := range schema.Required {
  293. if fieldName == reg.FieldName(field) {
  294. required = true
  295. break
  296. }
  297. }
  298. // verify if the field is required in message options
  299. if messageSchema, err := extractSchemaOptionFromMessageDescriptor(message.DescriptorProto); err == nil {
  300. for _, fieldName := range messageSchema.GetJsonSchema().GetRequired() {
  301. // Required fields can be field names or json_name values
  302. if fieldName == field.GetJsonName() || fieldName == field.GetName() {
  303. required = true
  304. break
  305. }
  306. }
  307. }
  308. param := openapiParameterObject{
  309. Description: desc,
  310. In: "query",
  311. Default: schema.Default,
  312. Type: schema.Type,
  313. Items: schema.Items,
  314. Format: schema.Format,
  315. Pattern: schema.Pattern,
  316. Required: required,
  317. extensions: schema.extensions,
  318. Enum: schema.Enum,
  319. }
  320. if param.Type == "array" {
  321. param.CollectionFormat = "multi"
  322. }
  323. param.Name = prefix + reg.FieldName(field)
  324. if isEnum {
  325. enum, err := reg.LookupEnum("", fieldType)
  326. if err != nil {
  327. return nil, fmt.Errorf("unknown enum type %s", fieldType)
  328. }
  329. if items != nil { // array
  330. param.Items = &openapiItemsObject{
  331. schemaCore: schemaCore{
  332. Type: "string",
  333. Enum: listEnumNames(reg, enum),
  334. },
  335. }
  336. if reg.GetEnumsAsInts() {
  337. param.Items.Type = "integer"
  338. param.Items.Enum = listEnumNumbers(reg, enum)
  339. }
  340. } else {
  341. param.Type = "string"
  342. param.Enum = listEnumNames(reg, enum)
  343. param.Default = getEnumDefault(reg, enum)
  344. if reg.GetEnumsAsInts() {
  345. param.Type = "integer"
  346. param.Enum = listEnumNumbers(reg, enum)
  347. param.Default = getEnumDefaultNumber(reg, enum)
  348. }
  349. }
  350. valueComments := enumValueProtoComments(reg, enum)
  351. if valueComments != "" {
  352. param.Description = strings.TrimLeft(param.Description+"\n\n "+valueComments, "\n")
  353. }
  354. }
  355. return []openapiParameterObject{param}, nil
  356. }
  357. // nested type, recurse
  358. msg, err := reg.LookupMsg("", fieldType)
  359. if err != nil {
  360. return nil, fmt.Errorf("unknown message type %s", fieldType)
  361. }
  362. // Check for cyclical message reference:
  363. if ok := cycle.Check(*msg.Name); !ok {
  364. return nil, fmt.Errorf("exceeded recursive count (%d) for query parameter %q", cycle.count, fieldType)
  365. }
  366. // Construct a new map with the message name so a cycle further down the recursive path can be detected.
  367. // Do not keep anything in the original touched reference and do not pass that reference along. This will
  368. // prevent clobbering adjacent records while recursing.
  369. touchedOut := cycle.Branch()
  370. for _, nestedField := range msg.Fields {
  371. if !isVisible(getFieldVisibilityOption(nestedField), reg) {
  372. continue
  373. }
  374. fieldName := reg.FieldName(field)
  375. p, err := nestedQueryParams(msg, nestedField, prefix+fieldName+".", reg, pathParams, body, touchedOut)
  376. if err != nil {
  377. return nil, err
  378. }
  379. params = append(params, p...)
  380. }
  381. return params, nil
  382. }
  383. func getMapParamKey(t descriptorpb.FieldDescriptorProto_Type) (string, error) {
  384. tType, f, ok := primitiveSchema(t)
  385. if !ok || f == "byte" || f == "float" || f == "double" {
  386. return "", fmt.Errorf("unsupported type: %q", f)
  387. }
  388. return tType, nil
  389. }
  390. // findServicesMessagesAndEnumerations discovers all messages and enums defined in the RPC methods of the service.
  391. func findServicesMessagesAndEnumerations(s []*descriptor.Service, reg *descriptor.Registry, m messageMap, ms messageMap, e enumMap, refs refMap) {
  392. for _, svc := range s {
  393. if !isVisible(getServiceVisibilityOption(svc), reg) {
  394. continue
  395. }
  396. for _, meth := range svc.Methods {
  397. // Request may be fully included in query
  398. {
  399. if !isVisible(getMethodVisibilityOption(meth), reg) {
  400. continue
  401. }
  402. swgReqName, ok := fullyQualifiedNameToOpenAPIName(meth.RequestType.FQMN(), reg)
  403. if !ok {
  404. grpclog.Errorf("couldn't resolve OpenAPI name for FQMN %q", meth.RequestType.FQMN())
  405. continue
  406. }
  407. if _, ok := refs[fmt.Sprintf("#/definitions/%s", swgReqName)]; ok {
  408. if !skipRenderingRef(meth.RequestType.FQMN()) {
  409. m[swgReqName] = meth.RequestType
  410. }
  411. }
  412. }
  413. swgRspName, ok := fullyQualifiedNameToOpenAPIName(meth.ResponseType.FQMN(), reg)
  414. if !ok && !skipRenderingRef(meth.ResponseType.FQMN()) {
  415. grpclog.Errorf("couldn't resolve OpenAPI name for FQMN %q", meth.ResponseType.FQMN())
  416. continue
  417. }
  418. findNestedMessagesAndEnumerations(meth.RequestType, reg, m, e)
  419. if !skipRenderingRef(meth.ResponseType.FQMN()) {
  420. m[swgRspName] = meth.ResponseType
  421. }
  422. findNestedMessagesAndEnumerations(meth.ResponseType, reg, m, e)
  423. }
  424. }
  425. }
  426. // findNestedMessagesAndEnumerations those can be generated by the services.
  427. func findNestedMessagesAndEnumerations(message *descriptor.Message, reg *descriptor.Registry, m messageMap, e enumMap) {
  428. // Iterate over all the fields that
  429. for _, t := range message.Fields {
  430. if !isVisible(getFieldVisibilityOption(t), reg) {
  431. continue
  432. }
  433. fieldType := t.GetTypeName()
  434. // If the type is an empty string then it is a proto primitive
  435. if fieldType != "" {
  436. if _, ok := m[fieldType]; !ok {
  437. msg, err := reg.LookupMsg("", fieldType)
  438. if err != nil {
  439. enum, err := reg.LookupEnum("", fieldType)
  440. if err != nil {
  441. panic(err)
  442. }
  443. e[fieldType] = enum
  444. continue
  445. }
  446. m[fieldType] = msg
  447. findNestedMessagesAndEnumerations(msg, reg, m, e)
  448. }
  449. }
  450. }
  451. }
  452. func skipRenderingRef(refName string) bool {
  453. _, ok := wktSchemas[refName]
  454. return ok
  455. }
  456. func renderMessageAsDefinition(msg *descriptor.Message, reg *descriptor.Registry, customRefs refMap, pathParams []descriptor.Parameter) (openapiSchemaObject, error) {
  457. schema := openapiSchemaObject{
  458. schemaCore: schemaCore{
  459. Type: "object",
  460. },
  461. }
  462. msgComments := protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index))
  463. if err := updateOpenAPIDataFromComments(reg, &schema, msg, msgComments, false); err != nil {
  464. return openapiSchemaObject{}, err
  465. }
  466. opts, err := getMessageOpenAPIOption(reg, msg)
  467. if err != nil {
  468. return openapiSchemaObject{}, err
  469. }
  470. if opts != nil {
  471. protoSchema := openapiSchemaFromProtoSchema(opts, reg, customRefs, msg)
  472. // Warning: Make sure not to overwrite any fields already set on the schema type.
  473. schema.ExternalDocs = protoSchema.ExternalDocs
  474. schema.ReadOnly = protoSchema.ReadOnly
  475. schema.MultipleOf = protoSchema.MultipleOf
  476. schema.Maximum = protoSchema.Maximum
  477. schema.ExclusiveMaximum = protoSchema.ExclusiveMaximum
  478. schema.Minimum = protoSchema.Minimum
  479. schema.ExclusiveMinimum = protoSchema.ExclusiveMinimum
  480. schema.MaxLength = protoSchema.MaxLength
  481. schema.MinLength = protoSchema.MinLength
  482. schema.Pattern = protoSchema.Pattern
  483. schema.Default = protoSchema.Default
  484. schema.MaxItems = protoSchema.MaxItems
  485. schema.MinItems = protoSchema.MinItems
  486. schema.UniqueItems = protoSchema.UniqueItems
  487. schema.MaxProperties = protoSchema.MaxProperties
  488. schema.MinProperties = protoSchema.MinProperties
  489. schema.Required = protoSchema.Required
  490. schema.XNullable = protoSchema.XNullable
  491. schema.extensions = protoSchema.extensions
  492. if protoSchema.schemaCore.Type != "" || protoSchema.schemaCore.Ref != "" {
  493. schema.schemaCore = protoSchema.schemaCore
  494. }
  495. if protoSchema.Title != "" {
  496. schema.Title = protoSchema.Title
  497. }
  498. if protoSchema.Description != "" {
  499. schema.Description = protoSchema.Description
  500. }
  501. if protoSchema.Example != nil {
  502. schema.Example = protoSchema.Example
  503. }
  504. }
  505. schema.Required = filterOutExcludedFields(schema.Required, pathParams)
  506. for _, f := range msg.Fields {
  507. if !isVisible(getFieldVisibilityOption(f), reg) {
  508. continue
  509. }
  510. if shouldExcludeField(f.GetName(), pathParams) {
  511. continue
  512. }
  513. subPathParams := subPathParams(f.GetName(), pathParams)
  514. fieldSchema, err := renderFieldAsDefinition(f, reg, customRefs, subPathParams)
  515. if err != nil {
  516. return openapiSchemaObject{}, err
  517. }
  518. comments := fieldProtoComments(reg, msg, f)
  519. if err := updateOpenAPIDataFromComments(reg, &fieldSchema, f, comments, false); err != nil {
  520. return openapiSchemaObject{}, err
  521. }
  522. if requiredIdx := find(schema.Required, *f.Name); requiredIdx != -1 && reg.GetUseJSONNamesForFields() {
  523. schema.Required[requiredIdx] = f.GetJsonName()
  524. }
  525. if fieldSchema.Required != nil {
  526. schema.Required = getUniqueFields(schema.Required, fieldSchema.Required)
  527. schema.Required = append(schema.Required, fieldSchema.Required...)
  528. // To avoid populating both the field schema require and message schema require, unset the field schema require.
  529. // See issue #2635.
  530. fieldSchema.Required = nil
  531. }
  532. if reg.GetUseAllOfForRefs() {
  533. if fieldSchema.Ref != "" {
  534. // Per the JSON Reference syntax: Any members other than "$ref" in a JSON Reference object SHALL be ignored.
  535. // https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03#section-3
  536. // However, use allOf to specify Title/Description/Example/readOnly fields.
  537. if fieldSchema.Title != "" || fieldSchema.Description != "" || len(fieldSchema.Example) > 0 || fieldSchema.ReadOnly {
  538. fieldSchema = openapiSchemaObject{
  539. Title: fieldSchema.Title,
  540. Description: fieldSchema.Description,
  541. schemaCore: schemaCore{
  542. Example: fieldSchema.Example,
  543. },
  544. ReadOnly: fieldSchema.ReadOnly,
  545. AllOf: []allOfEntry{{Ref: fieldSchema.Ref}},
  546. }
  547. } else {
  548. fieldSchema = openapiSchemaObject{schemaCore: schemaCore{Ref: fieldSchema.Ref}}
  549. }
  550. }
  551. }
  552. kv := keyVal{Value: fieldSchema}
  553. kv.Key = reg.FieldName(f)
  554. if schema.Properties == nil {
  555. schema.Properties = &openapiSchemaObjectProperties{}
  556. }
  557. *schema.Properties = append(*schema.Properties, kv)
  558. }
  559. if msg.FQMN() == ".google.protobuf.Any" {
  560. transformAnyForJSON(&schema, reg.GetUseJSONNamesForFields())
  561. }
  562. return schema, nil
  563. }
  564. func renderFieldAsDefinition(f *descriptor.Field, reg *descriptor.Registry, refs refMap, pathParams []descriptor.Parameter) (openapiSchemaObject, error) {
  565. if len(pathParams) == 0 {
  566. return schemaOfField(f, reg, refs), nil
  567. }
  568. location := ""
  569. if ix := strings.LastIndex(f.Message.FQMN(), "."); ix > 0 {
  570. location = f.Message.FQMN()[0:ix]
  571. }
  572. msg, err := reg.LookupMsg(location, f.GetTypeName())
  573. if err != nil {
  574. return openapiSchemaObject{}, err
  575. }
  576. schema, err := renderMessageAsDefinition(msg, reg, refs, pathParams)
  577. if err != nil {
  578. return openapiSchemaObject{}, err
  579. }
  580. comments := fieldProtoComments(reg, f.Message, f)
  581. if len(comments) > 0 {
  582. // Use title and description from field instead of nested message if present.
  583. paragraphs := strings.Split(comments, paragraphDeliminator)
  584. schema.Title = strings.TrimSpace(paragraphs[0])
  585. schema.Description = strings.TrimSpace(strings.Join(paragraphs[1:], paragraphDeliminator))
  586. }
  587. return schema, nil
  588. }
  589. // transformAnyForJSON should be called when the schema object represents a google.protobuf.Any, and will replace the
  590. // Properties slice with a single value for '@type'. We mutate the incorrectly named field so that we inherit the same
  591. // documentation as specified on the original field in the protobuf descriptors.
  592. func transformAnyForJSON(schema *openapiSchemaObject, useJSONNames bool) {
  593. var typeFieldName string
  594. if useJSONNames {
  595. typeFieldName = "typeUrl"
  596. } else {
  597. typeFieldName = "type_url"
  598. }
  599. for _, property := range *schema.Properties {
  600. if property.Key == typeFieldName {
  601. schema.AdditionalProperties = &openapiSchemaObject{}
  602. schema.Properties = &openapiSchemaObjectProperties{keyVal{
  603. Key: "@type",
  604. Value: property.Value,
  605. }}
  606. break
  607. }
  608. }
  609. }
  610. func renderMessagesAsDefinition(messages messageMap, d openapiDefinitionsObject, reg *descriptor.Registry, customRefs refMap, pathParams []descriptor.Parameter) error {
  611. for name, msg := range messages {
  612. swgName, ok := fullyQualifiedNameToOpenAPIName(msg.FQMN(), reg)
  613. if !ok {
  614. return fmt.Errorf("can't resolve OpenAPI name from %q", msg.FQMN())
  615. }
  616. if skipRenderingRef(name) {
  617. continue
  618. }
  619. if opt := msg.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
  620. continue
  621. }
  622. var err error
  623. d[swgName], err = renderMessageAsDefinition(msg, reg, customRefs, pathParams)
  624. if err != nil {
  625. return err
  626. }
  627. }
  628. return nil
  629. }
  630. // isVisible checks if a field/RPC is visible based on the visibility restriction
  631. // combined with the `visibility_restriction_selectors`.
  632. // Elements with an overlap on `visibility_restriction_selectors` are visible, those without are not visible.
  633. // Elements without `google.api.VisibilityRule` annotations entirely are always visible.
  634. func isVisible(r *visibility.VisibilityRule, reg *descriptor.Registry) bool {
  635. if r == nil {
  636. return true
  637. }
  638. restrictions := strings.Split(strings.TrimSpace(r.Restriction), ",")
  639. // No restrictions results in the element always being visible
  640. if len(restrictions) == 0 {
  641. return true
  642. }
  643. for _, restriction := range restrictions {
  644. if reg.GetVisibilityRestrictionSelectors()[strings.TrimSpace(restriction)] {
  645. return true
  646. }
  647. }
  648. return false
  649. }
  650. func shouldExcludeField(name string, excluded []descriptor.Parameter) bool {
  651. for _, p := range excluded {
  652. if len(p.FieldPath) == 1 && name == p.FieldPath[0].Name {
  653. return true
  654. }
  655. }
  656. return false
  657. }
  658. func filterOutExcludedFields(fields []string, excluded []descriptor.Parameter) []string {
  659. var filtered []string
  660. for _, f := range fields {
  661. if !shouldExcludeField(f, excluded) {
  662. filtered = append(filtered, f)
  663. }
  664. }
  665. return filtered
  666. }
  667. // schemaOfField returns a OpenAPI Schema Object for a protobuf field.
  668. func schemaOfField(f *descriptor.Field, reg *descriptor.Registry, refs refMap) openapiSchemaObject {
  669. const (
  670. singular = 0
  671. array = 1
  672. object = 2
  673. )
  674. var (
  675. core schemaCore
  676. aggregate int
  677. )
  678. fd := f.FieldDescriptorProto
  679. location := ""
  680. if ix := strings.LastIndex(f.Message.FQMN(), "."); ix > 0 {
  681. location = f.Message.FQMN()[0:ix]
  682. }
  683. if m, err := reg.LookupMsg(location, f.GetTypeName()); err == nil {
  684. if opt := m.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
  685. fd = m.GetField()[1]
  686. aggregate = object
  687. }
  688. }
  689. if fd.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
  690. aggregate = array
  691. }
  692. var props *openapiSchemaObjectProperties
  693. switch ft := fd.GetType(); ft {
  694. case descriptorpb.FieldDescriptorProto_TYPE_ENUM, descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
  695. if wktSchema, ok := wktSchemas[fd.GetTypeName()]; ok {
  696. core = wktSchema
  697. if fd.GetTypeName() == ".google.protobuf.Empty" {
  698. props = &openapiSchemaObjectProperties{}
  699. }
  700. } else {
  701. swgRef, ok := fullyQualifiedNameToOpenAPIName(fd.GetTypeName(), reg)
  702. if !ok {
  703. panic(fmt.Sprintf("can't resolve OpenAPI ref from typename %q", fd.GetTypeName()))
  704. }
  705. core = schemaCore{
  706. Ref: "#/definitions/" + swgRef,
  707. }
  708. if refs != nil {
  709. refs[fd.GetTypeName()] = struct{}{}
  710. }
  711. }
  712. default:
  713. ftype, format, ok := primitiveSchema(ft)
  714. if ok {
  715. core = schemaCore{Type: ftype, Format: format}
  716. } else {
  717. core = schemaCore{Type: ft.String(), Format: "UNKNOWN"}
  718. }
  719. }
  720. ret := openapiSchemaObject{}
  721. switch aggregate {
  722. case array:
  723. if _, ok := wktSchemas[fd.GetTypeName()]; !ok && fd.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE {
  724. core.Type = "object"
  725. }
  726. ret = openapiSchemaObject{
  727. schemaCore: schemaCore{
  728. Type: "array",
  729. Items: (*openapiItemsObject)(&openapiSchemaObject{schemaCore: core}),
  730. },
  731. }
  732. case object:
  733. ret = openapiSchemaObject{
  734. schemaCore: schemaCore{
  735. Type: "object",
  736. },
  737. AdditionalProperties: &openapiSchemaObject{Properties: props, schemaCore: core},
  738. }
  739. default:
  740. ret = openapiSchemaObject{
  741. schemaCore: core,
  742. Properties: props,
  743. }
  744. }
  745. if j, err := getFieldOpenAPIOption(reg, f); err == nil {
  746. updateswaggerObjectFromJSONSchema(&ret, j, reg, f)
  747. }
  748. if j, err := getFieldBehaviorOption(reg, f); err == nil {
  749. updateSwaggerObjectFromFieldBehavior(&ret, j, reg, f)
  750. }
  751. for i, required := range ret.Required {
  752. if required == f.GetName() {
  753. ret.Required[i] = reg.FieldName(f)
  754. }
  755. }
  756. if reg.GetProto3OptionalNullable() && f.GetProto3Optional() {
  757. ret.XNullable = true
  758. }
  759. return ret
  760. }
  761. // primitiveSchema returns a pair of "Type" and "Format" in JSON Schema for
  762. // the given primitive field type.
  763. // The last return parameter is true iff the field type is actually primitive.
  764. func primitiveSchema(t descriptorpb.FieldDescriptorProto_Type) (ftype, format string, ok bool) {
  765. switch t {
  766. case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:
  767. return "number", "double", true
  768. case descriptorpb.FieldDescriptorProto_TYPE_FLOAT:
  769. return "number", "float", true
  770. case descriptorpb.FieldDescriptorProto_TYPE_INT64:
  771. return "string", "int64", true
  772. case descriptorpb.FieldDescriptorProto_TYPE_UINT64:
  773. // 64bit integer types are marshaled as string in the default JSONPb marshaler.
  774. // TODO(yugui) Add an option to declare 64bit integers as int64.
  775. //
  776. // NOTE: uint64 is not a predefined format of integer type in OpenAPI spec.
  777. // So we cannot expect that uint64 is commonly supported by OpenAPI processor.
  778. return "string", "uint64", true
  779. case descriptorpb.FieldDescriptorProto_TYPE_INT32:
  780. return "integer", "int32", true
  781. case descriptorpb.FieldDescriptorProto_TYPE_FIXED64:
  782. // Ditto.
  783. return "string", "uint64", true
  784. case descriptorpb.FieldDescriptorProto_TYPE_FIXED32:
  785. // Ditto.
  786. return "integer", "int64", true
  787. case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
  788. // NOTE: in OpenAPI specification, format should be empty on boolean type
  789. return "boolean", "", true
  790. case descriptorpb.FieldDescriptorProto_TYPE_STRING:
  791. // NOTE: in OpenAPI specification, can be empty on string type
  792. // see: https://swagger.io/specification/v2/#data-types
  793. return "string", "", true
  794. case descriptorpb.FieldDescriptorProto_TYPE_BYTES:
  795. return "string", "byte", true
  796. case descriptorpb.FieldDescriptorProto_TYPE_UINT32:
  797. // Ditto.
  798. return "integer", "int64", true
  799. case descriptorpb.FieldDescriptorProto_TYPE_SFIXED32:
  800. return "integer", "int32", true
  801. case descriptorpb.FieldDescriptorProto_TYPE_SFIXED64:
  802. return "string", "int64", true
  803. case descriptorpb.FieldDescriptorProto_TYPE_SINT32:
  804. return "integer", "int32", true
  805. case descriptorpb.FieldDescriptorProto_TYPE_SINT64:
  806. return "string", "int64", true
  807. default:
  808. return "", "", false
  809. }
  810. }
  811. // renderEnumerationsAsDefinition inserts enums into the definitions object.
  812. func renderEnumerationsAsDefinition(enums enumMap, d openapiDefinitionsObject, reg *descriptor.Registry) {
  813. for _, enum := range enums {
  814. swgName, ok := fullyQualifiedNameToOpenAPIName(enum.FQEN(), reg)
  815. if !ok {
  816. panic(fmt.Sprintf("can't resolve OpenAPI name from FQEN %q", enum.FQEN()))
  817. }
  818. enumComments := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index))
  819. // it may be necessary to sort the result of the GetValue function.
  820. enumNames := listEnumNames(reg, enum)
  821. defaultValue := getEnumDefault(reg, enum)
  822. valueComments := enumValueProtoComments(reg, enum)
  823. if valueComments != "" {
  824. enumComments = strings.TrimLeft(enumComments+"\n\n "+valueComments, "\n")
  825. }
  826. enumSchemaObject := openapiSchemaObject{
  827. schemaCore: schemaCore{
  828. Type: "string",
  829. Enum: enumNames,
  830. Default: defaultValue,
  831. },
  832. }
  833. if reg.GetEnumsAsInts() {
  834. enumSchemaObject.Type = "integer"
  835. enumSchemaObject.Format = "int32"
  836. enumSchemaObject.Default = getEnumDefaultNumber(reg, enum)
  837. enumSchemaObject.Enum = listEnumNumbers(reg, enum)
  838. }
  839. if err := updateOpenAPIDataFromComments(reg, &enumSchemaObject, enum, enumComments, false); err != nil {
  840. panic(err)
  841. }
  842. d[swgName] = enumSchemaObject
  843. }
  844. }
  845. // Take in a FQMN or FQEN and return a OpenAPI safe version of the FQMN and
  846. // a boolean indicating if FQMN was properly resolved.
  847. func fullyQualifiedNameToOpenAPIName(fqn string, reg *descriptor.Registry) (string, bool) {
  848. registriesSeenMutex.Lock()
  849. defer registriesSeenMutex.Unlock()
  850. if mapping, present := registriesSeen[reg]; present {
  851. ret, ok := mapping[fqn]
  852. return ret, ok
  853. }
  854. mapping := resolveFullyQualifiedNameToOpenAPINames(append(reg.GetAllFQMNs(), append(reg.GetAllFQENs(), reg.GetAllFQMethNs()...)...), reg.GetOpenAPINamingStrategy())
  855. registriesSeen[reg] = mapping
  856. ret, ok := mapping[fqn]
  857. return ret, ok
  858. }
  859. // Lookup message type by location.name and return a openapiv2-safe version
  860. // of its FQMN.
  861. func lookupMsgAndOpenAPIName(location, name string, reg *descriptor.Registry) (*descriptor.Message, string, error) {
  862. msg, err := reg.LookupMsg(location, name)
  863. if err != nil {
  864. return nil, "", err
  865. }
  866. swgName, ok := fullyQualifiedNameToOpenAPIName(msg.FQMN(), reg)
  867. if !ok {
  868. return nil, "", fmt.Errorf("can't map OpenAPI name from FQMN %q", msg.FQMN())
  869. }
  870. return msg, swgName, nil
  871. }
  872. // registriesSeen is used to memoise calls to resolveFullyQualifiedNameToOpenAPINames so
  873. // we don't repeat it unnecessarily, since it can take some time.
  874. var registriesSeen = map[*descriptor.Registry]map[string]string{}
  875. var registriesSeenMutex sync.Mutex
  876. // Take the names of every proto message and generate a unique reference for each, according to the given strategy.
  877. func resolveFullyQualifiedNameToOpenAPINames(messages []string, namingStrategy string) map[string]string {
  878. strategyFn := LookupNamingStrategy(namingStrategy)
  879. if strategyFn == nil {
  880. return nil
  881. }
  882. return strategyFn(messages)
  883. }
  884. var canRegexp = regexp.MustCompile("{([a-zA-Z][a-zA-Z0-9_.]*)([^}]*)}")
  885. // templateToParts will split a URL template as defined by https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
  886. // into a string slice with each part as an element of the slice for use by `partsToOpenAPIPath` and `partsToRegexpMap`.
  887. func templateToParts(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message) []string {
  888. // It seems like the right thing to do here is to just use
  889. // strings.Split(path, "/") but that breaks badly when you hit a url like
  890. // /{my_field=prefix/*}/ and end up with 2 sections representing my_field.
  891. // Instead do the right thing and write a small pushdown (counter) automata
  892. // for it.
  893. var parts []string
  894. depth := 0
  895. buffer := ""
  896. jsonBuffer := ""
  897. pathLoop:
  898. for i, char := range path {
  899. switch char {
  900. case '{':
  901. // Push on the stack
  902. depth++
  903. buffer += string(char)
  904. jsonBuffer = ""
  905. jsonBuffer += string(char)
  906. case '}':
  907. if depth == 0 {
  908. panic("Encountered } without matching { before it.")
  909. }
  910. // Pop from the stack
  911. depth--
  912. buffer += string(char)
  913. if reg.GetUseJSONNamesForFields() &&
  914. len(jsonBuffer) > 1 {
  915. jsonSnakeCaseName := jsonBuffer[1:]
  916. jsonCamelCaseName := lowerCamelCase(jsonSnakeCaseName, fields, msgs)
  917. prev := buffer[:len(buffer)-len(jsonSnakeCaseName)-2]
  918. buffer = strings.Join([]string{prev, "{", jsonCamelCaseName, "}"}, "")
  919. jsonBuffer = ""
  920. }
  921. case '/':
  922. if depth == 0 {
  923. parts = append(parts, buffer)
  924. buffer = ""
  925. // Since the stack was empty when we hit the '/' we are done with this
  926. // section.
  927. continue
  928. }
  929. buffer += string(char)
  930. jsonBuffer += string(char)
  931. case ':':
  932. if depth == 0 {
  933. // As soon as we find a ":" outside a variable,
  934. // everything following is a verb
  935. parts = append(parts, buffer)
  936. buffer = path[i:]
  937. break pathLoop
  938. }
  939. buffer += string(char)
  940. jsonBuffer += string(char)
  941. default:
  942. buffer += string(char)
  943. jsonBuffer += string(char)
  944. }
  945. }
  946. // Now append the last element to parts
  947. parts = append(parts, buffer)
  948. return parts
  949. }
  950. // partsToOpenAPIPath converts each path part of the form /path/{string_value=strprefix/*} which is defined in
  951. // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto to the OpenAPI expected form /path/{string_value}.
  952. // For example this would replace the path segment of "{foo=bar/*}" with "{foo}" or "prefix{bang=bash/**}" with "prefix{bang}".
  953. // OpenAPI 2 only allows simple path parameters with the constraints on that parameter specified in the OpenAPI
  954. // schema's "pattern" instead of in the path parameter itself.
  955. func partsToOpenAPIPath(parts []string, overrides map[string]string) string {
  956. for index, part := range parts {
  957. part = canRegexp.ReplaceAllString(part, "{$1}")
  958. if override, ok := overrides[part]; ok {
  959. part = override
  960. }
  961. parts[index] = part
  962. }
  963. if last := len(parts) - 1; strings.HasPrefix(parts[last], ":") {
  964. // Last item is a verb (":" LITERAL).
  965. return strings.Join(parts[:last], "/") + parts[last]
  966. }
  967. return strings.Join(parts, "/")
  968. }
  969. // partsToRegexpMap returns a map of parameter name to ECMA 262 patterns
  970. // which is what the "pattern" field on an OpenAPI parameter expects.
  971. // See https://swagger.io/specification/v2/ (Parameter Object) and
  972. // https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3.
  973. // The expression is generated based on expressions defined by https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
  974. // "Path Template Syntax" section which allow for a "param_name=foobar/*/bang/**" style expressions inside
  975. // the path parameter placeholders that indicate constraints on the values of those parameters.
  976. // This function will scan the split parts of a path template for parameters and
  977. // outputs a map of the name of the parameter to a ECMA regular expression. See the http.proto file for descriptions
  978. // of the supported syntax. This function will ignore any path parameters that don't contain a "=" after the
  979. // parameter name. For supported parameters, we assume "*" represent all characters except "/" as it's
  980. // intended to match a single path element and we assume "**" matches any character as it's intended to match multiple
  981. // path elements.
  982. // For example "{name=organizations/*/roles/*}" would produce the regular expression for the "name" parameter of
  983. // "organizations/[^/]+/roles/[^/]+" or "{bar=bing/*/bang/**}" would produce the regular expression for the "bar"
  984. // parameter of "bing/[^/]+/bang/.+".
  985. //
  986. // Note that OpenAPI does not actually support path parameters with "/", see https://github.com/OAI/OpenAPI-Specification/issues/892
  987. func partsToRegexpMap(parts []string) map[string]string {
  988. regExps := make(map[string]string)
  989. for _, part := range parts {
  990. if strings.Contains(part, "/") {
  991. grpclog.Warningf("Path parameter %q contains '/', which is not supported in OpenAPI", part)
  992. }
  993. if submatch := canRegexp.FindStringSubmatch(part); len(submatch) > 2 {
  994. if strings.HasPrefix(submatch[2], "=") { // this part matches the standard and should be made into a regular expression
  995. // assume the string's characters other than "**" and "*" are literals (not necessarily a good assumption 100% of the times, but it will support most use cases)
  996. regex := submatch[2][1:]
  997. regex = strings.ReplaceAll(regex, "**", ".+") // ** implies any character including "/"
  998. regex = strings.ReplaceAll(regex, "*", "[^/]+") // * implies any character except "/"
  999. regExps[submatch[1]] = regex
  1000. }
  1001. }
  1002. }
  1003. return regExps
  1004. }
  1005. func renderServiceTags(services []*descriptor.Service, reg *descriptor.Registry) []openapiTagObject {
  1006. var tags []openapiTagObject
  1007. for _, svc := range services {
  1008. if !isVisible(getServiceVisibilityOption(svc), reg) {
  1009. continue
  1010. }
  1011. tagName := svc.GetName()
  1012. if pkg := svc.File.GetPackage(); pkg != "" && reg.IsIncludePackageInTags() {
  1013. tagName = pkg + "." + tagName
  1014. }
  1015. tag := openapiTagObject{
  1016. Name: tagName,
  1017. }
  1018. opts, err := getServiceOpenAPIOption(reg, svc)
  1019. if err != nil {
  1020. grpclog.Error(err)
  1021. return nil
  1022. }
  1023. if opts != nil {
  1024. tag.Description = opts.Description
  1025. if reg.GetUseGoTemplate() {
  1026. tag.Description = goTemplateComments(tag.Description, svc, reg)
  1027. }
  1028. if opts.ExternalDocs != nil {
  1029. tag.ExternalDocs = &openapiExternalDocumentationObject{
  1030. Description: opts.ExternalDocs.Description,
  1031. URL: opts.ExternalDocs.Url,
  1032. }
  1033. if reg.GetUseGoTemplate() {
  1034. tag.ExternalDocs.Description = goTemplateComments(opts.ExternalDocs.Description, svc, reg)
  1035. }
  1036. }
  1037. if opts.GetName() != "" {
  1038. tag.Name = opts.GetName()
  1039. }
  1040. }
  1041. tags = append(tags, tag)
  1042. }
  1043. return tags
  1044. }
  1045. func renderServices(services []*descriptor.Service, paths *openapiPathsObject, reg *descriptor.Registry, requestResponseRefs, customRefs refMap, msgs []*descriptor.Message, defs openapiDefinitionsObject) error {
  1046. // Correctness of svcIdx and methIdx depends on 'services' containing the services in the same order as the 'file.Service' array.
  1047. svcBaseIdx := 0
  1048. var lastFile *descriptor.File = nil
  1049. for svcIdx, svc := range services {
  1050. if svc.File != lastFile {
  1051. lastFile = svc.File
  1052. svcBaseIdx = svcIdx
  1053. }
  1054. if !isVisible(getServiceVisibilityOption(svc), reg) {
  1055. continue
  1056. }
  1057. for methIdx, meth := range svc.Methods {
  1058. if !isVisible(getMethodVisibilityOption(meth), reg) {
  1059. continue
  1060. }
  1061. for bIdx, b := range meth.Bindings {
  1062. operationFunc := operationForMethod(b.HTTPMethod)
  1063. // Iterate over all the OpenAPI parameters
  1064. parameters := openapiParametersObject{}
  1065. // split the path template into its parts
  1066. parts := templateToParts(b.PathTmpl.Template, reg, meth.RequestType.Fields, msgs)
  1067. // extract any constraints specified in the path placeholders into ECMA regular expressions
  1068. pathParamRegexpMap := partsToRegexpMap(parts)
  1069. // Keep track of path parameter overrides
  1070. var pathParamNames = make(map[string]string)
  1071. for _, parameter := range b.PathParams {
  1072. var paramType, paramFormat, desc, collectionFormat string
  1073. var defaultValue interface{}
  1074. var enumNames interface{}
  1075. var items *openapiItemsObject
  1076. var minItems *int
  1077. var extensions []extension
  1078. switch pt := parameter.Target.GetType(); pt {
  1079. case descriptorpb.FieldDescriptorProto_TYPE_GROUP, descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
  1080. if descriptor.IsWellKnownType(parameter.Target.GetTypeName()) {
  1081. if parameter.IsRepeated() {
  1082. return errors.New("only primitive and enum types are allowed in repeated path parameters")
  1083. }
  1084. schema := schemaOfField(parameter.Target, reg, customRefs)
  1085. paramType = schema.Type
  1086. paramFormat = schema.Format
  1087. desc = schema.Description
  1088. defaultValue = schema.Default
  1089. extensions = schema.extensions
  1090. } else {
  1091. return errors.New("only primitive and well-known types are allowed in path parameters")
  1092. }
  1093. case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
  1094. enum, err := reg.LookupEnum("", parameter.Target.GetTypeName())
  1095. if err != nil {
  1096. return err
  1097. }
  1098. paramType = "string"
  1099. paramFormat = ""
  1100. enumNames = listEnumNames(reg, enum)
  1101. if reg.GetEnumsAsInts() {
  1102. paramType = "integer"
  1103. paramFormat = ""
  1104. enumNames = listEnumNumbers(reg, enum)
  1105. }
  1106. schema := schemaOfField(parameter.Target, reg, customRefs)
  1107. desc = schema.Description
  1108. defaultValue = schema.Default
  1109. extensions = schema.extensions
  1110. default:
  1111. var ok bool
  1112. paramType, paramFormat, ok = primitiveSchema(pt)
  1113. if !ok {
  1114. return fmt.Errorf("unknown field type %v", pt)
  1115. }
  1116. schema := schemaOfField(parameter.Target, reg, customRefs)
  1117. desc = schema.Description
  1118. defaultValue = schema.Default
  1119. extensions = schema.extensions
  1120. // If there is no mandatory format based on the field,
  1121. // allow it to be overridden by the user
  1122. if paramFormat == "" {
  1123. paramFormat = schema.Format
  1124. }
  1125. }
  1126. if parameter.IsRepeated() {
  1127. core := schemaCore{Type: paramType, Format: paramFormat}
  1128. if parameter.IsEnum() {
  1129. core.Enum = enumNames
  1130. enumNames = nil
  1131. }
  1132. items = (*openapiItemsObject)(&openapiSchemaObject{schemaCore: core})
  1133. paramType = "array"
  1134. paramFormat = ""
  1135. collectionFormat = reg.GetRepeatedPathParamSeparatorName()
  1136. minItems = new(int)
  1137. *minItems = 1
  1138. }
  1139. if desc == "" {
  1140. desc = fieldProtoComments(reg, parameter.Target.Message, parameter.Target)
  1141. }
  1142. parameterString := parameter.String()
  1143. if reg.GetUseJSONNamesForFields() {
  1144. parameterString = lowerCamelCase(parameterString, meth.RequestType.Fields, msgs)
  1145. }
  1146. var pattern string
  1147. if regExp, ok := pathParamRegexpMap[parameterString]; ok {
  1148. pattern = regExp
  1149. }
  1150. if fc := getFieldConfiguration(reg, parameter.Target); fc != nil {
  1151. pathParamName := fc.GetPathParamName()
  1152. if pathParamName != "" && pathParamName != parameterString {
  1153. pathParamNames["{"+parameterString+"}"] = "{" + pathParamName + "}"
  1154. parameterString, _, _ = strings.Cut(pathParamName, "=")
  1155. }
  1156. }
  1157. parameters = append(parameters, openapiParameterObject{
  1158. Name: parameterString,
  1159. Description: desc,
  1160. In: "path",
  1161. Required: true,
  1162. Default: defaultValue,
  1163. // Parameters in gRPC-Gateway can only be strings?
  1164. Type: paramType,
  1165. Format: paramFormat,
  1166. Enum: enumNames,
  1167. Items: items,
  1168. CollectionFormat: collectionFormat,
  1169. MinItems: minItems,
  1170. Pattern: pattern,
  1171. extensions: extensions,
  1172. })
  1173. }
  1174. // Now check if there is a body parameter
  1175. if b.Body != nil {
  1176. // Recursively render fields as definitions as long as they contain path parameters.
  1177. // Special case for top level body if we don't have a body field.
  1178. var schema openapiSchemaObject
  1179. desc := ""
  1180. var bodyFieldName string
  1181. schema = openapiSchemaObject{
  1182. schemaCore: schemaCore{},
  1183. }
  1184. if len(b.Body.FieldPath) == 0 {
  1185. // No field for body, use type.
  1186. bodyFieldName = "body"
  1187. wknSchemaCore, isWkn := wktSchemas[meth.RequestType.FQMN()]
  1188. if isWkn {
  1189. schema.schemaCore = wknSchemaCore
  1190. // Special workaround for Empty: it's well-known type but wknSchemas only returns schema.schemaCore; but we need to set schema.Properties which is a level higher.
  1191. if meth.RequestType.FQMN() == ".google.protobuf.Empty" {
  1192. schema.Properties = &openapiSchemaObjectProperties{}
  1193. }
  1194. } else {
  1195. messageSchema, err := renderMessageAsDefinition(meth.RequestType, reg, customRefs, b.PathParams)
  1196. if err != nil {
  1197. return err
  1198. }
  1199. if len(b.PathParams) == 0 {
  1200. if err := schema.setRefFromFQN(meth.RequestType.FQMN(), reg); err != nil {
  1201. return err
  1202. }
  1203. desc = messageSchema.Description
  1204. } else {
  1205. if meth.Name != nil {
  1206. methFQN, ok := fullyQualifiedNameToOpenAPIName(meth.FQMN(), reg)
  1207. if !ok {
  1208. panic(fmt.Errorf("failed to resolve method FQN: '%s'", meth.FQMN()))
  1209. }
  1210. defName := methFQN + "Body"
  1211. schema.Ref = fmt.Sprintf("#/definitions/%s", defName)
  1212. defs[defName] = messageSchema
  1213. } else {
  1214. schema = messageSchema
  1215. if schema.Properties == nil || len(*schema.Properties) == 0 {
  1216. grpclog.Warningf("created a body with 0 properties in the message, this might be unintended: %s", *meth.RequestType)
  1217. }
  1218. }
  1219. }
  1220. }
  1221. } else {
  1222. // Body field path is limited to one path component. From google.api.HttpRule.body:
  1223. // "NOTE: the referred field must be present at the top-level of the request message type."
  1224. // Ref: https://github.com/googleapis/googleapis/blob/b3397f5febbf21dfc69b875ddabaf76bee765058/google/api/http.proto#L350-L352
  1225. if len(b.Body.FieldPath) > 1 {
  1226. return fmt.Errorf("body of request %q is not a top level field: '%v'", meth.Service.GetName(), b.Body.FieldPath)
  1227. }
  1228. bodyField := b.Body.FieldPath[0]
  1229. if reg.GetUseJSONNamesForFields() {
  1230. bodyFieldName = lowerCamelCase(bodyField.Name, meth.RequestType.Fields, msgs)
  1231. } else {
  1232. bodyFieldName = bodyField.Name
  1233. }
  1234. // Align pathParams with body field path.
  1235. pathParams := subPathParams(bodyField.Name, b.PathParams)
  1236. var err error
  1237. schema, err = renderFieldAsDefinition(bodyField.Target, reg, customRefs, pathParams)
  1238. if err != nil {
  1239. return err
  1240. }
  1241. if schema.Title != "" {
  1242. desc = mergeDescription(schema)
  1243. } else {
  1244. desc = fieldProtoComments(reg, bodyField.Target.Message, bodyField.Target)
  1245. }
  1246. }
  1247. if meth.GetClientStreaming() {
  1248. desc += " (streaming inputs)"
  1249. }
  1250. parameters = append(parameters, openapiParameterObject{
  1251. Name: bodyFieldName,
  1252. Description: desc,
  1253. In: "body",
  1254. Required: true,
  1255. Schema: &schema,
  1256. })
  1257. }
  1258. // add the parameters to the query string
  1259. queryParams, err := messageToQueryParameters(meth.RequestType, reg, b.PathParams, b.Body, b.HTTPMethod)
  1260. if err != nil {
  1261. return err
  1262. }
  1263. parameters = append(parameters, queryParams...)
  1264. path := partsToOpenAPIPath(parts, pathParamNames)
  1265. pathItemObject, ok := getPathItemObject(*paths, path)
  1266. if !ok {
  1267. pathItemObject = openapiPathItemObject{}
  1268. } else {
  1269. // handle case where we have an existing mapping for the same path and method
  1270. existingOperationObject := operationFunc(&pathItemObject)
  1271. if existingOperationObject != nil {
  1272. var firstPathParameter *openapiParameterObject
  1273. var firstParamIndex int
  1274. for index, param := range parameters {
  1275. param := param
  1276. if param.In == "path" {
  1277. firstPathParameter = &param
  1278. firstParamIndex = index
  1279. break
  1280. }
  1281. }
  1282. if firstPathParameter == nil {
  1283. // Without a path parameter, there is nothing to vary to support multiple mappings of the same path/method.
  1284. // Previously this did not log an error and only overwrote the mapping, we now log the error but
  1285. // still overwrite the mapping
  1286. grpclog.Errorf("Duplicate mapping for path %s %s", b.HTTPMethod, path)
  1287. } else {
  1288. newPathCount := 0
  1289. var newPath string
  1290. var newPathElement string
  1291. // Iterate until there is not an existing operation that matches the same escaped path.
  1292. // Most of the time this will only be a single iteration, but a large API could technically have
  1293. // a pretty large amount of these if it used similar patterns for all its functions.
  1294. for existingOperationObject != nil {
  1295. newPathCount += 1
  1296. newPathElement = firstPathParameter.Name + pathParamUniqueSuffixDeliminator + strconv.Itoa(newPathCount)
  1297. newPath = strings.ReplaceAll(path, "{"+firstPathParameter.Name+"}", "{"+newPathElement+"}")
  1298. if newPathItemObject, ok := getPathItemObject(*paths, newPath); ok {
  1299. existingOperationObject = operationFunc(&newPathItemObject)
  1300. } else {
  1301. existingOperationObject = nil
  1302. }
  1303. }
  1304. // update the pathItemObject we are adding to with the new path
  1305. pathItemObject, _ = getPathItemObject(*paths, newPath)
  1306. firstPathParameter.Name = newPathElement
  1307. path = newPath
  1308. parameters[firstParamIndex] = *firstPathParameter
  1309. }
  1310. }
  1311. }
  1312. methProtoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.ServiceDescriptorProto)(nil)), "Method")
  1313. desc := "A successful response."
  1314. var responseSchema openapiSchemaObject
  1315. if b.ResponseBody == nil || len(b.ResponseBody.FieldPath) == 0 {
  1316. responseSchema = openapiSchemaObject{
  1317. schemaCore: schemaCore{},
  1318. }
  1319. // Don't link to a full definition for
  1320. // empty; it's overly verbose.
  1321. // schema.Properties{} renders it as
  1322. // well, without a definition
  1323. wknSchemaCore, isWkn := wktSchemas[meth.ResponseType.FQMN()]
  1324. if !isWkn {
  1325. if err := responseSchema.setRefFromFQN(meth.ResponseType.FQMN(), reg); err != nil {
  1326. return err
  1327. }
  1328. } else {
  1329. responseSchema.schemaCore = wknSchemaCore
  1330. // Special workaround for Empty: it's well-known type but wknSchemas only returns schema.schemaCore; but we need to set schema.Properties which is a level higher.
  1331. if meth.ResponseType.FQMN() == ".google.protobuf.Empty" {
  1332. responseSchema.Properties = &openapiSchemaObjectProperties{}
  1333. }
  1334. }
  1335. } else {
  1336. // This is resolving the value of response_body in the google.api.HttpRule
  1337. lastField := b.ResponseBody.FieldPath[len(b.ResponseBody.FieldPath)-1]
  1338. responseSchema = schemaOfField(lastField.Target, reg, customRefs)
  1339. if responseSchema.Description != "" {
  1340. desc = responseSchema.Description
  1341. } else {
  1342. desc = fieldProtoComments(reg, lastField.Target.Message, lastField.Target)
  1343. }
  1344. }
  1345. if meth.GetServerStreaming() {
  1346. desc += "(streaming responses)"
  1347. responseSchema.Type = "object"
  1348. swgRef, _ := fullyQualifiedNameToOpenAPIName(meth.ResponseType.FQMN(), reg)
  1349. responseSchema.Title = "Stream result of " + swgRef
  1350. props := openapiSchemaObjectProperties{
  1351. keyVal{
  1352. Key: "result",
  1353. Value: openapiSchemaObject{
  1354. schemaCore: schemaCore{
  1355. Ref: responseSchema.Ref,
  1356. },
  1357. },
  1358. },
  1359. }
  1360. if !reg.GetDisableDefaultErrors() {
  1361. statusDef, hasStatus := fullyQualifiedNameToOpenAPIName(".google.rpc.Status", reg)
  1362. if hasStatus {
  1363. props = append(props, keyVal{
  1364. Key: "error",
  1365. Value: openapiSchemaObject{
  1366. schemaCore: schemaCore{
  1367. Ref: fmt.Sprintf("#/definitions/%s", statusDef)},
  1368. },
  1369. })
  1370. }
  1371. }
  1372. // Special case HttpBody responses, they will be unformatted bytes
  1373. if meth.ResponseType.FQMN() == ".google.api.HttpBody" {
  1374. responseSchema.Type = "string"
  1375. responseSchema.Format = "binary"
  1376. responseSchema.Title = "Free form byte stream"
  1377. // The error response is still JSON, but technically the full response
  1378. // is still unformatted, so don't include the error response structure.
  1379. props = nil
  1380. }
  1381. responseSchema.Properties = &props
  1382. responseSchema.Ref = ""
  1383. }
  1384. operationObject := &openapiOperationObject{
  1385. Parameters: parameters,
  1386. Responses: openapiResponsesObject{},
  1387. }
  1388. if !reg.GetDisableDefaultResponses() {
  1389. operationObject.Responses["200"] = openapiResponseObject{
  1390. Description: desc,
  1391. Schema: responseSchema,
  1392. Headers: openapiHeadersObject{},
  1393. }
  1394. }
  1395. if !reg.GetDisableServiceTags() {
  1396. tag := svc.GetName()
  1397. if pkg := svc.File.GetPackage(); pkg != "" && reg.IsIncludePackageInTags() {
  1398. tag = pkg + "." + tag
  1399. }
  1400. operationObject.Tags = []string{tag}
  1401. }
  1402. if !reg.GetDisableDefaultErrors() {
  1403. errDef, hasErrDef := fullyQualifiedNameToOpenAPIName(".google.rpc.Status", reg)
  1404. if hasErrDef {
  1405. // https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#responses-object
  1406. operationObject.Responses["default"] = openapiResponseObject{
  1407. Description: "An unexpected error response.",
  1408. Schema: openapiSchemaObject{
  1409. schemaCore: schemaCore{
  1410. Ref: fmt.Sprintf("#/definitions/%s", errDef),
  1411. },
  1412. },
  1413. }
  1414. }
  1415. }
  1416. operationObject.OperationID = fmt.Sprintf("%s_%s", svc.GetName(), meth.GetName())
  1417. if reg.GetSimpleOperationIDs() {
  1418. operationObject.OperationID = meth.GetName()
  1419. }
  1420. if bIdx != 0 {
  1421. // OperationID must be unique in an OpenAPI v2 definition.
  1422. operationObject.OperationID += strconv.Itoa(bIdx + 1)
  1423. }
  1424. // Fill reference map with referenced request messages
  1425. for _, param := range operationObject.Parameters {
  1426. if param.Schema != nil && param.Schema.Ref != "" {
  1427. requestResponseRefs[param.Schema.Ref] = struct{}{}
  1428. }
  1429. }
  1430. methComments := protoComments(reg, svc.File, nil, "Service", int32(svcIdx-svcBaseIdx), methProtoPath, int32(methIdx))
  1431. if err := updateOpenAPIDataFromComments(reg, operationObject, meth, methComments, false); err != nil {
  1432. panic(err)
  1433. }
  1434. svcOpts, err := getServiceOpenAPIOption(reg, svc)
  1435. if err != nil {
  1436. grpclog.Error(err)
  1437. return err
  1438. }
  1439. opts, err := getMethodOpenAPIOption(reg, meth)
  1440. if opts != nil {
  1441. if err != nil {
  1442. panic(err)
  1443. }
  1444. operationObject.ExternalDocs = protoExternalDocumentationToOpenAPIExternalDocumentation(opts.ExternalDocs, reg, meth)
  1445. // TODO(ivucica): this would be better supported by looking whether the method is deprecated in the proto file
  1446. operationObject.Deprecated = opts.Deprecated
  1447. if opts.Summary != "" {
  1448. operationObject.Summary = opts.Summary
  1449. }
  1450. if opts.Description != "" {
  1451. operationObject.Description = opts.Description
  1452. }
  1453. if len(opts.Tags) > 0 {
  1454. operationObject.Tags = make([]string, len(opts.Tags))
  1455. copy(operationObject.Tags, opts.Tags)
  1456. } else if svcOpts.GetName() != "" {
  1457. operationObject.Tags = []string{svcOpts.GetName()}
  1458. }
  1459. if opts.OperationId != "" {
  1460. operationObject.OperationID = opts.OperationId
  1461. }
  1462. if opts.Security != nil {
  1463. newSecurity := []openapiSecurityRequirementObject{}
  1464. if operationObject.Security != nil {
  1465. newSecurity = *operationObject.Security
  1466. }
  1467. for _, secReq := range opts.Security {
  1468. newSecReq := openapiSecurityRequirementObject{}
  1469. for secReqKey, secReqValue := range secReq.SecurityRequirement {
  1470. if secReqValue == nil {
  1471. continue
  1472. }
  1473. newSecReqValue := make([]string, len(secReqValue.Scope))
  1474. copy(newSecReqValue, secReqValue.Scope)
  1475. newSecReq[secReqKey] = newSecReqValue
  1476. }
  1477. if len(newSecReq) > 0 {
  1478. newSecurity = append(newSecurity, newSecReq)
  1479. }
  1480. }
  1481. operationObject.Security = &newSecurity
  1482. }
  1483. if opts.Responses != nil {
  1484. for name, resp := range opts.Responses {
  1485. // Merge response data into default response if available.
  1486. respObj := operationObject.Responses[name]
  1487. if resp.Description != "" {
  1488. respObj.Description = resp.Description
  1489. }
  1490. if resp.Schema != nil {
  1491. respObj.Schema = openapiSchemaFromProtoSchema(resp.Schema, reg, customRefs, meth)
  1492. }
  1493. if resp.Examples != nil {
  1494. respObj.Examples = openapiExamplesFromProtoExamples(resp.Examples)
  1495. }
  1496. if resp.Headers != nil {
  1497. hdrs, err := processHeaders(resp.Headers)
  1498. if err != nil {
  1499. return err
  1500. }
  1501. respObj.Headers = hdrs
  1502. }
  1503. if resp.Extensions != nil {
  1504. exts, err := processExtensions(resp.Extensions)
  1505. if err != nil {
  1506. return err
  1507. }
  1508. respObj.extensions = exts
  1509. }
  1510. operationObject.Responses[name] = respObj
  1511. }
  1512. }
  1513. if opts.Extensions != nil {
  1514. exts, err := processExtensions(opts.Extensions)
  1515. if err != nil {
  1516. return err
  1517. }
  1518. operationObject.extensions = exts
  1519. }
  1520. if len(opts.Consumes) > 0 {
  1521. operationObject.Consumes = make([]string, len(opts.Consumes))
  1522. copy(operationObject.Consumes, opts.Consumes)
  1523. }
  1524. if len(opts.Produces) > 0 {
  1525. operationObject.Produces = make([]string, len(opts.Produces))
  1526. copy(operationObject.Produces, opts.Produces)
  1527. }
  1528. if params := opts.Parameters; params != nil && len(params.Headers) > 0 {
  1529. for _, header := range params.Headers {
  1530. param := openapiParameterObject{
  1531. In: "header",
  1532. Name: header.Name,
  1533. Description: header.Description,
  1534. Required: header.Required,
  1535. Format: header.Format,
  1536. }
  1537. switch header.Type {
  1538. case openapi_options.HeaderParameter_STRING:
  1539. param.Type = "string"
  1540. case openapi_options.HeaderParameter_NUMBER:
  1541. param.Type = "number"
  1542. case openapi_options.HeaderParameter_INTEGER:
  1543. param.Type = "integer"
  1544. case openapi_options.HeaderParameter_BOOLEAN:
  1545. param.Type = "boolean"
  1546. default:
  1547. return fmt.Errorf("invalid header parameter type: %+v", header.Type)
  1548. }
  1549. operationObject.Parameters = append(operationObject.Parameters, param)
  1550. }
  1551. }
  1552. // TODO(ivucica): add remaining fields of operation object
  1553. }
  1554. switch b.HTTPMethod {
  1555. case "DELETE":
  1556. pathItemObject.Delete = operationObject
  1557. case "GET":
  1558. pathItemObject.Get = operationObject
  1559. case "POST":
  1560. pathItemObject.Post = operationObject
  1561. case "PUT":
  1562. pathItemObject.Put = operationObject
  1563. case "PATCH":
  1564. pathItemObject.Patch = operationObject
  1565. case "HEAD":
  1566. pathItemObject.Head = operationObject
  1567. case "OPTIONS":
  1568. pathItemObject.Options = operationObject
  1569. }
  1570. updatePaths(paths, path, pathItemObject)
  1571. }
  1572. }
  1573. }
  1574. // Success! return nil on the error object
  1575. return nil
  1576. }
  1577. // Returns the openapiPathItemObject associated with a path. If path is not present, returns
  1578. // empty openapiPathItemObject and false.
  1579. func getPathItemObject(paths openapiPathsObject, path string) (openapiPathItemObject, bool) {
  1580. for _, pathData := range paths {
  1581. if pathData.Path == path {
  1582. return pathData.PathItemObject, true
  1583. }
  1584. }
  1585. return openapiPathItemObject{}, false
  1586. }
  1587. // If a path already exists in openapiPathsObject, updates that path's openapiPathItemObject. If not,
  1588. // appends a new path and openapiPathItemObject to the openapiPathsObject.
  1589. func updatePaths(paths *openapiPathsObject, path string, pathItemObject openapiPathItemObject) {
  1590. for i, p := range *paths {
  1591. if p.Path == path {
  1592. (*paths)[i].PathItemObject = pathItemObject
  1593. return
  1594. }
  1595. }
  1596. *paths = append(*paths, pathData{
  1597. Path: path,
  1598. PathItemObject: pathItemObject,
  1599. })
  1600. }
  1601. func mergeDescription(schema openapiSchemaObject) string {
  1602. desc := schema.Description
  1603. if schema.Title != "" { // join title because title of parameter object will be ignored
  1604. desc = strings.TrimSpace(schema.Title + paragraphDeliminator + schema.Description)
  1605. }
  1606. return desc
  1607. }
  1608. func operationForMethod(httpMethod string) func(*openapiPathItemObject) *openapiOperationObject {
  1609. switch httpMethod {
  1610. case "GET":
  1611. return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Get }
  1612. case "POST":
  1613. return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Post }
  1614. case "PUT":
  1615. return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Put }
  1616. case "DELETE":
  1617. return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Delete }
  1618. case "PATCH":
  1619. return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Patch }
  1620. case "HEAD":
  1621. return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Head }
  1622. case "OPTIONS":
  1623. return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Options }
  1624. default:
  1625. return func(obj *openapiPathItemObject) *openapiOperationObject { return nil }
  1626. }
  1627. }
  1628. // This function is called with a param which contains the entire definition of a method.
  1629. func applyTemplate(p param) (*openapiSwaggerObject, error) {
  1630. // Create the basic template object. This is the object that everything is
  1631. // defined off of.
  1632. s := openapiSwaggerObject{
  1633. // OpenAPI 2.0 is the version of this document
  1634. Swagger: "2.0",
  1635. Consumes: []string{"application/json"},
  1636. Produces: []string{"application/json"},
  1637. Paths: openapiPathsObject{},
  1638. Definitions: make(openapiDefinitionsObject),
  1639. Info: openapiInfoObject{
  1640. Title: *p.File.Name,
  1641. Version: "version not set",
  1642. },
  1643. }
  1644. // Loops through all the services and their exposed GET/POST/PUT/DELETE definitions
  1645. // and create entries for all of them.
  1646. // Also adds custom user specified references to second map.
  1647. requestResponseRefs, customRefs := refMap{}, refMap{}
  1648. if err := renderServices(p.Services, &s.Paths, p.reg, requestResponseRefs, customRefs, p.Messages, s.Definitions); err != nil {
  1649. panic(err)
  1650. }
  1651. messages := messageMap{}
  1652. streamingMessages := messageMap{}
  1653. enums := enumMap{}
  1654. if !p.reg.GetDisableDefaultErrors() {
  1655. // Add the error type to the message map
  1656. runtimeError, swgRef, err := lookupMsgAndOpenAPIName("google.rpc", "Status", p.reg)
  1657. if err == nil {
  1658. messages[swgRef] = runtimeError
  1659. } else {
  1660. // just in case there is an error looking up runtimeError
  1661. grpclog.Error(err)
  1662. }
  1663. }
  1664. // Find all the service's messages and enumerations that are defined (recursively)
  1665. // and write request, response and other custom (but referenced) types out as definition objects.
  1666. findServicesMessagesAndEnumerations(p.Services, p.reg, messages, streamingMessages, enums, requestResponseRefs)
  1667. if err := renderMessagesAsDefinition(messages, s.Definitions, p.reg, customRefs, nil); err != nil {
  1668. return nil, err
  1669. }
  1670. renderEnumerationsAsDefinition(enums, s.Definitions, p.reg)
  1671. // File itself might have some comments and metadata.
  1672. packageProtoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Package")
  1673. packageComments := protoComments(p.reg, p.File, nil, "Package", packageProtoPath)
  1674. if err := updateOpenAPIDataFromComments(p.reg, &s, p, packageComments, true); err != nil {
  1675. return nil, err
  1676. }
  1677. // There may be additional options in the OpenAPI option in the proto.
  1678. spb, err := getFileOpenAPIOption(p.reg, p.File)
  1679. if err != nil {
  1680. return nil, err
  1681. }
  1682. if spb != nil {
  1683. if spb.Swagger != "" {
  1684. s.Swagger = spb.Swagger
  1685. }
  1686. if spb.Info != nil {
  1687. if spb.Info.Title != "" {
  1688. s.Info.Title = spb.Info.Title
  1689. }
  1690. if spb.Info.Description != "" {
  1691. s.Info.Description = spb.Info.Description
  1692. }
  1693. if spb.Info.TermsOfService != "" {
  1694. s.Info.TermsOfService = spb.Info.TermsOfService
  1695. }
  1696. if spb.Info.Version != "" {
  1697. s.Info.Version = spb.Info.Version
  1698. }
  1699. if spb.Info.Contact != nil {
  1700. if s.Info.Contact == nil {
  1701. s.Info.Contact = &openapiContactObject{}
  1702. }
  1703. if spb.Info.Contact.Name != "" {
  1704. s.Info.Contact.Name = spb.Info.Contact.Name
  1705. }
  1706. if spb.Info.Contact.Url != "" {
  1707. s.Info.Contact.URL = spb.Info.Contact.Url
  1708. }
  1709. if spb.Info.Contact.Email != "" {
  1710. s.Info.Contact.Email = spb.Info.Contact.Email
  1711. }
  1712. }
  1713. if spb.Info.License != nil {
  1714. if s.Info.License == nil {
  1715. s.Info.License = &openapiLicenseObject{}
  1716. }
  1717. if spb.Info.License.Name != "" {
  1718. s.Info.License.Name = spb.Info.License.Name
  1719. }
  1720. if spb.Info.License.Url != "" {
  1721. s.Info.License.URL = spb.Info.License.Url
  1722. }
  1723. }
  1724. if spb.Info.Extensions != nil {
  1725. exts, err := processExtensions(spb.Info.Extensions)
  1726. if err != nil {
  1727. return nil, err
  1728. }
  1729. s.Info.extensions = exts
  1730. }
  1731. }
  1732. if spb.Host != "" {
  1733. s.Host = spb.Host
  1734. }
  1735. if spb.BasePath != "" {
  1736. s.BasePath = spb.BasePath
  1737. }
  1738. if len(spb.Schemes) > 0 {
  1739. s.Schemes = make([]string, len(spb.Schemes))
  1740. for i, scheme := range spb.Schemes {
  1741. s.Schemes[i] = strings.ToLower(scheme.String())
  1742. }
  1743. }
  1744. if len(spb.Consumes) > 0 {
  1745. s.Consumes = make([]string, len(spb.Consumes))
  1746. copy(s.Consumes, spb.Consumes)
  1747. }
  1748. if len(spb.Produces) > 0 {
  1749. s.Produces = make([]string, len(spb.Produces))
  1750. copy(s.Produces, spb.Produces)
  1751. }
  1752. if spb.SecurityDefinitions != nil && spb.SecurityDefinitions.Security != nil {
  1753. if s.SecurityDefinitions == nil {
  1754. s.SecurityDefinitions = openapiSecurityDefinitionsObject{}
  1755. }
  1756. for secDefKey, secDefValue := range spb.SecurityDefinitions.Security {
  1757. var newSecDefValue openapiSecuritySchemeObject
  1758. if oldSecDefValue, ok := s.SecurityDefinitions[secDefKey]; !ok {
  1759. newSecDefValue = openapiSecuritySchemeObject{}
  1760. } else {
  1761. newSecDefValue = oldSecDefValue
  1762. }
  1763. if secDefValue.Type != openapi_options.SecurityScheme_TYPE_INVALID {
  1764. switch secDefValue.Type {
  1765. case openapi_options.SecurityScheme_TYPE_BASIC:
  1766. newSecDefValue.Type = "basic"
  1767. case openapi_options.SecurityScheme_TYPE_API_KEY:
  1768. newSecDefValue.Type = "apiKey"
  1769. case openapi_options.SecurityScheme_TYPE_OAUTH2:
  1770. newSecDefValue.Type = "oauth2"
  1771. }
  1772. }
  1773. if secDefValue.Description != "" {
  1774. newSecDefValue.Description = secDefValue.Description
  1775. }
  1776. if secDefValue.Name != "" {
  1777. newSecDefValue.Name = secDefValue.Name
  1778. }
  1779. if secDefValue.In != openapi_options.SecurityScheme_IN_INVALID {
  1780. switch secDefValue.In {
  1781. case openapi_options.SecurityScheme_IN_QUERY:
  1782. newSecDefValue.In = "query"
  1783. case openapi_options.SecurityScheme_IN_HEADER:
  1784. newSecDefValue.In = "header"
  1785. }
  1786. }
  1787. if secDefValue.Flow != openapi_options.SecurityScheme_FLOW_INVALID {
  1788. switch secDefValue.Flow {
  1789. case openapi_options.SecurityScheme_FLOW_IMPLICIT:
  1790. newSecDefValue.Flow = "implicit"
  1791. case openapi_options.SecurityScheme_FLOW_PASSWORD:
  1792. newSecDefValue.Flow = "password"
  1793. case openapi_options.SecurityScheme_FLOW_APPLICATION:
  1794. newSecDefValue.Flow = "application"
  1795. case openapi_options.SecurityScheme_FLOW_ACCESS_CODE:
  1796. newSecDefValue.Flow = "accessCode"
  1797. }
  1798. }
  1799. if secDefValue.AuthorizationUrl != "" {
  1800. newSecDefValue.AuthorizationURL = secDefValue.AuthorizationUrl
  1801. }
  1802. if secDefValue.TokenUrl != "" {
  1803. newSecDefValue.TokenURL = secDefValue.TokenUrl
  1804. }
  1805. if secDefValue.Scopes != nil {
  1806. if newSecDefValue.Scopes == nil {
  1807. newSecDefValue.Scopes = openapiScopesObject{}
  1808. }
  1809. for scopeKey, scopeDesc := range secDefValue.Scopes.Scope {
  1810. newSecDefValue.Scopes[scopeKey] = scopeDesc
  1811. }
  1812. }
  1813. if secDefValue.Extensions != nil {
  1814. exts, err := processExtensions(secDefValue.Extensions)
  1815. if err != nil {
  1816. return nil, err
  1817. }
  1818. newSecDefValue.extensions = exts
  1819. }
  1820. s.SecurityDefinitions[secDefKey] = newSecDefValue
  1821. }
  1822. }
  1823. if spb.Security != nil {
  1824. var newSecurity []openapiSecurityRequirementObject
  1825. if s.Security != nil {
  1826. newSecurity = s.Security
  1827. }
  1828. for _, secReq := range spb.Security {
  1829. newSecReq := openapiSecurityRequirementObject{}
  1830. for secReqKey, secReqValue := range secReq.SecurityRequirement {
  1831. if secReqValue == nil {
  1832. return nil, fmt.Errorf("malformed security requirement spec for key %q; value is required", secReqKey)
  1833. }
  1834. newSecReqValue := make([]string, len(secReqValue.Scope))
  1835. copy(newSecReqValue, secReqValue.Scope)
  1836. newSecReq[secReqKey] = newSecReqValue
  1837. }
  1838. newSecurity = append(newSecurity, newSecReq)
  1839. }
  1840. s.Security = newSecurity
  1841. }
  1842. s.ExternalDocs = protoExternalDocumentationToOpenAPIExternalDocumentation(spb.ExternalDocs, p.reg, spb)
  1843. // Populate all Paths with Responses set at top level,
  1844. // preferring Responses already set over those at the top level.
  1845. if spb.Responses != nil {
  1846. for _, verbs := range s.Paths {
  1847. var maps []openapiResponsesObject
  1848. if verbs.PathItemObject.Delete != nil {
  1849. maps = append(maps, verbs.PathItemObject.Delete.Responses)
  1850. }
  1851. if verbs.PathItemObject.Get != nil {
  1852. maps = append(maps, verbs.PathItemObject.Get.Responses)
  1853. }
  1854. if verbs.PathItemObject.Post != nil {
  1855. maps = append(maps, verbs.PathItemObject.Post.Responses)
  1856. }
  1857. if verbs.PathItemObject.Put != nil {
  1858. maps = append(maps, verbs.PathItemObject.Put.Responses)
  1859. }
  1860. if verbs.PathItemObject.Patch != nil {
  1861. maps = append(maps, verbs.PathItemObject.Patch.Responses)
  1862. }
  1863. for k, v := range spb.Responses {
  1864. for _, respMap := range maps {
  1865. if _, ok := respMap[k]; ok {
  1866. // Don't overwrite already existing Responses
  1867. continue
  1868. }
  1869. respMap[k] = openapiResponseObject{
  1870. Description: v.Description,
  1871. Schema: openapiSchemaFromProtoSchema(v.Schema, p.reg, customRefs, nil),
  1872. Examples: openapiExamplesFromProtoExamples(v.Examples),
  1873. }
  1874. }
  1875. }
  1876. }
  1877. }
  1878. if spb.Extensions != nil {
  1879. exts, err := processExtensions(spb.Extensions)
  1880. if err != nil {
  1881. return nil, err
  1882. }
  1883. s.extensions = exts
  1884. }
  1885. if spb.Tags != nil {
  1886. for _, v := range spb.Tags {
  1887. newTag := openapiTagObject{}
  1888. newTag.Name = v.Name
  1889. newTag.Description = v.Description
  1890. if p.reg.GetUseGoTemplate() {
  1891. newTag.Description = goTemplateComments(newTag.Description, nil, p.reg)
  1892. }
  1893. if v.ExternalDocs != nil {
  1894. newTag.ExternalDocs = &openapiExternalDocumentationObject{
  1895. Description: v.ExternalDocs.Description,
  1896. URL: v.ExternalDocs.Url,
  1897. }
  1898. if p.reg.GetUseGoTemplate() {
  1899. newTag.ExternalDocs.Description = goTemplateComments(v.ExternalDocs.Description, nil, p.reg)
  1900. }
  1901. }
  1902. if v.Extensions != nil {
  1903. exts, err := processExtensions(v.Extensions)
  1904. if err != nil {
  1905. return nil, err
  1906. }
  1907. newTag.extensions = exts
  1908. }
  1909. s.Tags = append(s.Tags, newTag)
  1910. }
  1911. }
  1912. // Additional fields on the OpenAPI v2 spec's "OpenAPI" object
  1913. // should be added here, once supported in the proto.
  1914. }
  1915. if !p.reg.GetDisableServiceTags() {
  1916. s.Tags = mergeTags(s.Tags, renderServiceTags(p.Services, p.reg))
  1917. }
  1918. // Finally add any references added by users that aren't
  1919. // otherwise rendered.
  1920. if err := addCustomRefs(s.Definitions, p.reg, customRefs); err != nil {
  1921. return nil, err
  1922. }
  1923. return &s, nil
  1924. }
  1925. func mergeTags(existingTags []openapiTagObject, tags []openapiTagObject) []openapiTagObject {
  1926. for _, tag := range tags {
  1927. matched := false
  1928. for i, existingTag := range existingTags {
  1929. if existingTag.Name == tag.Name {
  1930. if existingTag.Description == "" {
  1931. existingTags[i].Description = tag.Description
  1932. }
  1933. if existingTag.ExternalDocs == nil {
  1934. existingTags[i].ExternalDocs = tag.ExternalDocs
  1935. } else if tag.ExternalDocs != nil {
  1936. if existingTag.ExternalDocs.Description == "" {
  1937. existingTags[i].ExternalDocs.Description = tag.ExternalDocs.Description
  1938. }
  1939. if existingTag.ExternalDocs.URL == "" {
  1940. existingTags[i].ExternalDocs.URL = tag.ExternalDocs.URL
  1941. }
  1942. }
  1943. if existingTag.extensions == nil {
  1944. existingTags[i].extensions = tag.extensions
  1945. } else if tag.extensions != nil {
  1946. for _, ext := range tag.extensions {
  1947. matchedExt := false
  1948. for _, existingExt := range existingTag.extensions {
  1949. if existingExt.key == ext.key {
  1950. matchedExt = true
  1951. break
  1952. }
  1953. }
  1954. if !matchedExt {
  1955. existingTags[i].extensions = append(existingTags[i].extensions, ext)
  1956. }
  1957. }
  1958. }
  1959. matched = true
  1960. break
  1961. }
  1962. }
  1963. if !matched {
  1964. existingTags = append(existingTags, tag)
  1965. }
  1966. }
  1967. return existingTags
  1968. }
  1969. func processExtensions(inputExts map[string]*structpb.Value) ([]extension, error) {
  1970. exts := make([]extension, 0, len(inputExts))
  1971. for k, v := range inputExts {
  1972. if !strings.HasPrefix(k, "x-") {
  1973. return nil, fmt.Errorf("extension keys need to start with \"x-\": %q", k)
  1974. }
  1975. ext, err := (&protojson.MarshalOptions{Indent: " "}).Marshal(v)
  1976. if err != nil {
  1977. return nil, err
  1978. }
  1979. exts = append(exts, extension{key: k, value: ext})
  1980. }
  1981. sort.Slice(exts, func(i, j int) bool { return exts[i].key < exts[j].key })
  1982. return exts, nil
  1983. }
  1984. func validateHeaderTypeAndFormat(headerType, format string) error {
  1985. // The type of the object. The value MUST be one of "string", "number", "integer", "boolean", or "array"
  1986. // See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#headerObject
  1987. // Note: currently not implementing array as we are only implementing this in the operation response context
  1988. switch headerType {
  1989. // the format property is an open string-valued property, and can have any value to support documentation needs
  1990. // primary check for format is to ensure that the number/integer formats are extensions of the specified type
  1991. // See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#dataTypeFormat
  1992. case "string":
  1993. return nil
  1994. case "number":
  1995. switch format {
  1996. case "uint",
  1997. "uint8",
  1998. "uint16",
  1999. "uint32",
  2000. "uint64",
  2001. "int",
  2002. "int8",
  2003. "int16",
  2004. "int32",
  2005. "int64",
  2006. "float",
  2007. "float32",
  2008. "float64",
  2009. "complex64",
  2010. "complex128",
  2011. "double",
  2012. "byte",
  2013. "rune",
  2014. "uintptr",
  2015. "":
  2016. return nil
  2017. default:
  2018. return fmt.Errorf("the provided format %q is not a valid extension of the type %q", format, headerType)
  2019. }
  2020. case "integer":
  2021. switch format {
  2022. case "uint",
  2023. "uint8",
  2024. "uint16",
  2025. "uint32",
  2026. "uint64",
  2027. "int",
  2028. "int8",
  2029. "int16",
  2030. "int32",
  2031. "int64",
  2032. "":
  2033. return nil
  2034. default:
  2035. return fmt.Errorf("the provided format %q is not a valid extension of the type %q", format, headerType)
  2036. }
  2037. case "boolean":
  2038. return nil
  2039. }
  2040. return fmt.Errorf("the provided header type %q is not supported", headerType)
  2041. }
  2042. func validateDefaultValueTypeAndFormat(headerType string, defaultValue string, format string) error {
  2043. switch headerType {
  2044. case "string":
  2045. if !isQuotedString(defaultValue) {
  2046. return fmt.Errorf("the provided default value %q does not match provider type %q, or is not properly quoted with escaped quotations", defaultValue, headerType)
  2047. }
  2048. switch format {
  2049. case "date-time":
  2050. unquoteTime := strings.Trim(defaultValue, `"`)
  2051. if _, err := time.Parse(time.RFC3339, unquoteTime); err != nil {
  2052. return fmt.Errorf("the provided default value %q is not a valid RFC3339 date-time string", defaultValue)
  2053. }
  2054. case "date":
  2055. const layoutRFC3339Date = "2006-01-02"
  2056. unquoteDate := strings.Trim(defaultValue, `"`)
  2057. if _, err := time.Parse(layoutRFC3339Date, unquoteDate); err != nil {
  2058. return fmt.Errorf("the provided default value %q is not a valid RFC3339 date-time string", defaultValue)
  2059. }
  2060. }
  2061. case "number":
  2062. if err := isJSONNumber(defaultValue, headerType); err != nil {
  2063. return err
  2064. }
  2065. case "integer":
  2066. switch format {
  2067. case "int32":
  2068. if _, err := strconv.ParseInt(defaultValue, 0, 32); err != nil {
  2069. return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  2070. }
  2071. case "uint32":
  2072. if _, err := strconv.ParseUint(defaultValue, 0, 32); err != nil {
  2073. return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  2074. }
  2075. case "int64":
  2076. if _, err := strconv.ParseInt(defaultValue, 0, 64); err != nil {
  2077. return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  2078. }
  2079. case "uint64":
  2080. if _, err := strconv.ParseUint(defaultValue, 0, 64); err != nil {
  2081. return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  2082. }
  2083. default:
  2084. if _, err := strconv.ParseInt(defaultValue, 0, 64); err != nil {
  2085. return fmt.Errorf("the provided default value %q does not match provided type %q", defaultValue, headerType)
  2086. }
  2087. }
  2088. case "boolean":
  2089. if !isBool(defaultValue) {
  2090. return fmt.Errorf("the provided default value %q does not match provider type %q", defaultValue, headerType)
  2091. }
  2092. }
  2093. return nil
  2094. }
  2095. func isQuotedString(s string) bool {
  2096. return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"'
  2097. }
  2098. func isJSONNumber(s string, t string) error {
  2099. val, err := strconv.ParseFloat(s, 64)
  2100. if err != nil {
  2101. return fmt.Errorf("the provided default value %q does not match provider type %q", s, t)
  2102. }
  2103. // Floating point values that cannot be represented as sequences of digits (such as Infinity and NaN) are not permitted.
  2104. // See: https://tools.ietf.org/html/rfc4627#section-2.4
  2105. if math.IsInf(val, 0) || math.IsNaN(val) {
  2106. return fmt.Errorf("the provided number %q is not a valid JSON number", s)
  2107. }
  2108. return nil
  2109. }
  2110. func isBool(s string) bool {
  2111. // Unable to use strconv.ParseBool because it returns truthy values https://golang.org/pkg/strconv/#example_ParseBool
  2112. // per https://swagger.io/specification/v2/#data-types
  2113. // type: boolean represents two values: true and false. Note that truthy and falsy values such as "true", "", 0 or null are not considered boolean values.
  2114. return s == "true" || s == "false"
  2115. }
  2116. func processHeaders(inputHdrs map[string]*openapi_options.Header) (openapiHeadersObject, error) {
  2117. hdrs := make(map[string]openapiHeaderObject, len(inputHdrs))
  2118. for k, v := range inputHdrs {
  2119. header := textproto.CanonicalMIMEHeaderKey(k)
  2120. ret := openapiHeaderObject{
  2121. Description: v.Description,
  2122. Format: v.Format,
  2123. Pattern: v.Pattern,
  2124. }
  2125. if err := validateHeaderTypeAndFormat(v.Type, v.Format); err != nil {
  2126. return nil, err
  2127. }
  2128. ret.Type = v.Type
  2129. if v.Default != "" {
  2130. if err := validateDefaultValueTypeAndFormat(v.Type, v.Default, v.Format); err != nil {
  2131. return nil, err
  2132. }
  2133. ret.Default = RawExample(v.Default)
  2134. }
  2135. hdrs[header] = ret
  2136. }
  2137. return hdrs, nil
  2138. }
  2139. func removeInternalComments(comment string) string {
  2140. c := []string{}
  2141. for len(comment) > 0 {
  2142. open := strings.SplitN(comment, "(--", 2)
  2143. if len(open) == 1 {
  2144. c = append(c, open[0])
  2145. break
  2146. }
  2147. ex := strings.TrimRight(open[0], " \t")
  2148. // Trim only one line prior to all spaces
  2149. switch {
  2150. case strings.HasSuffix(ex, "\r\n"):
  2151. ex = strings.TrimSuffix(ex, "\r\n")
  2152. case strings.HasSuffix(ex, "\n"):
  2153. ex = strings.TrimSuffix(ex, "\n")
  2154. }
  2155. if ex != "" {
  2156. c = append(c, ex)
  2157. }
  2158. comment = open[1]
  2159. close := strings.SplitN(comment, "--)", 2)
  2160. if len(close) > 1 {
  2161. comment = close[1]
  2162. } else {
  2163. break
  2164. }
  2165. }
  2166. return strings.Join(c, "")
  2167. }
  2168. // updateOpenAPIDataFromComments updates a OpenAPI object based on a comment
  2169. // from the proto file.
  2170. //
  2171. // First paragraph of a comment is used for summary. Remaining paragraphs of
  2172. // a comment are used for description. If 'Summary' field is not present on
  2173. // the passed swaggerObject, the summary and description are joined by \n\n.
  2174. //
  2175. // If there is a field named 'Info', its 'Summary' and 'Description' fields
  2176. // will be updated instead.
  2177. //
  2178. // If there is no 'Summary', the same behavior will be attempted on 'Title',
  2179. // but only if the last character is not a period.
  2180. func updateOpenAPIDataFromComments(reg *descriptor.Registry, swaggerObject interface{}, data interface{}, comment string, isPackageObject bool) error {
  2181. if len(comment) == 0 {
  2182. return nil
  2183. }
  2184. // Checks whether the "ignore_comments" flag is set to true
  2185. if reg.GetIgnoreComments() {
  2186. return nil
  2187. }
  2188. // Checks whether the "remove_internal_comments" flag is set to true
  2189. if reg.GetRemoveInternalComments() {
  2190. comment = removeInternalComments(comment)
  2191. }
  2192. // Checks whether the "use_go_templates" flag is set to true
  2193. if reg.GetUseGoTemplate() {
  2194. comment = goTemplateComments(comment, data, reg)
  2195. }
  2196. // Figure out what to apply changes to.
  2197. swaggerObjectValue := reflect.ValueOf(swaggerObject)
  2198. infoObjectValue := swaggerObjectValue.Elem().FieldByName("Info")
  2199. if !infoObjectValue.CanSet() {
  2200. // No such field? Apply summary and description directly to
  2201. // passed object.
  2202. infoObjectValue = swaggerObjectValue.Elem()
  2203. }
  2204. // Figure out which properties to update.
  2205. summaryValue := infoObjectValue.FieldByName("Summary")
  2206. descriptionValue := infoObjectValue.FieldByName("Description")
  2207. readOnlyValue := infoObjectValue.FieldByName("ReadOnly")
  2208. if readOnlyValue.Kind() == reflect.Bool && readOnlyValue.CanSet() && strings.Contains(comment, "Output only.") {
  2209. readOnlyValue.Set(reflect.ValueOf(true))
  2210. }
  2211. usingTitle := false
  2212. if !summaryValue.CanSet() {
  2213. summaryValue = infoObjectValue.FieldByName("Title")
  2214. usingTitle = true
  2215. }
  2216. paragraphs := strings.Split(comment, paragraphDeliminator)
  2217. // If there is a summary (or summary-equivalent) and it's empty, use the first
  2218. // paragraph as summary, and the rest as description.
  2219. if summaryValue.CanSet() {
  2220. summary := strings.TrimSpace(paragraphs[0])
  2221. description := strings.TrimSpace(strings.Join(paragraphs[1:], paragraphDeliminator))
  2222. if !usingTitle || (len(summary) > 0 && summary[len(summary)-1] != '.') {
  2223. // overrides the schema value only if it's empty
  2224. // keep the comment precedence when updating the package definition
  2225. if summaryValue.Len() == 0 || isPackageObject {
  2226. summaryValue.Set(reflect.ValueOf(summary))
  2227. }
  2228. if len(description) > 0 {
  2229. if !descriptionValue.CanSet() {
  2230. return errors.New("encountered object type with a summary, but no description")
  2231. }
  2232. // overrides the schema value only if it's empty
  2233. // keep the comment precedence when updating the package definition
  2234. if descriptionValue.Len() == 0 || isPackageObject {
  2235. descriptionValue.Set(reflect.ValueOf(description))
  2236. }
  2237. }
  2238. return nil
  2239. }
  2240. }
  2241. // There was no summary field on the swaggerObject. Try to apply the
  2242. // whole comment into description if the OpenAPI object description is empty.
  2243. if descriptionValue.CanSet() {
  2244. if descriptionValue.Len() == 0 || isPackageObject {
  2245. descriptionValue.Set(reflect.ValueOf(strings.Join(paragraphs, paragraphDeliminator)))
  2246. }
  2247. return nil
  2248. }
  2249. return errors.New("no description nor summary property")
  2250. }
  2251. func fieldProtoComments(reg *descriptor.Registry, msg *descriptor.Message, field *descriptor.Field) string {
  2252. protoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.DescriptorProto)(nil)), "Field")
  2253. for i, f := range msg.Fields {
  2254. if f == field {
  2255. return protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index), protoPath, int32(i))
  2256. }
  2257. }
  2258. return ""
  2259. }
  2260. func enumValueProtoComments(reg *descriptor.Registry, enum *descriptor.Enum) string {
  2261. protoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.EnumDescriptorProto)(nil)), "Value")
  2262. var comments []string
  2263. for idx, value := range enum.GetValue() {
  2264. if reg.GetOmitEnumDefaultValue() && value.GetNumber() == 0 {
  2265. continue
  2266. }
  2267. if !isVisible(getEnumValueVisibilityOption(value), reg) {
  2268. continue
  2269. }
  2270. name := value.GetName()
  2271. if reg.GetEnumsAsInts() {
  2272. name = strconv.Itoa(int(value.GetNumber()))
  2273. }
  2274. if str := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index), protoPath, int32(idx)); str != "" {
  2275. comments = append(comments, name+": "+str)
  2276. }
  2277. }
  2278. if len(comments) > 0 {
  2279. return "- " + strings.Join(comments, "\n - ")
  2280. }
  2281. return ""
  2282. }
  2283. func protoComments(reg *descriptor.Registry, file *descriptor.File, outers []string, typeName string, typeIndex int32, fieldPaths ...int32) string {
  2284. if file.SourceCodeInfo == nil {
  2285. fmt.Fprintln(os.Stderr, file.GetName(), "descriptor.File should not contain nil SourceCodeInfo")
  2286. return ""
  2287. }
  2288. outerPaths := make([]int32, len(outers))
  2289. for i := range outers {
  2290. location := ""
  2291. if file.Package != nil {
  2292. location = file.GetPackage()
  2293. }
  2294. msg, err := reg.LookupMsg(location, strings.Join(outers[:i+1], "."))
  2295. if err != nil {
  2296. panic(err)
  2297. }
  2298. outerPaths[i] = int32(msg.Index)
  2299. }
  2300. for _, loc := range file.SourceCodeInfo.Location {
  2301. if !isProtoPathMatches(loc.Path, outerPaths, typeName, typeIndex, fieldPaths) {
  2302. continue
  2303. }
  2304. comments := ""
  2305. if loc.LeadingComments != nil {
  2306. comments = strings.TrimRight(*loc.LeadingComments, "\n")
  2307. comments = strings.TrimSpace(comments)
  2308. // TODO(ivucica): this is a hack to fix "// " being interpreted as "//".
  2309. // perhaps we should:
  2310. // - split by \n
  2311. // - determine if every (but first and last) line begins with " "
  2312. // - trim every line only if that is the case
  2313. // - join by \n
  2314. comments = strings.ReplaceAll(comments, "\n ", "\n")
  2315. comments = removeInternalComments(comments)
  2316. }
  2317. if loc.TrailingComments != nil {
  2318. trailing := strings.TrimSpace(*loc.TrailingComments)
  2319. if comments == "" {
  2320. comments = trailing
  2321. } else {
  2322. comments += "\n\n" + trailing
  2323. }
  2324. }
  2325. return comments
  2326. }
  2327. return ""
  2328. }
  2329. func goTemplateComments(comment string, data interface{}, reg *descriptor.Registry) string {
  2330. var temp bytes.Buffer
  2331. tpl, err := template.New("").Funcs(template.FuncMap{
  2332. // Allows importing documentation from a file
  2333. "import": func(name string) string {
  2334. file, err := os.ReadFile(name)
  2335. if err != nil {
  2336. return err.Error()
  2337. }
  2338. // Runs template over imported file
  2339. return goTemplateComments(string(file), data, reg)
  2340. },
  2341. // Grabs title and description from a field
  2342. "fieldcomments": func(msg *descriptor.Message, field *descriptor.Field) string {
  2343. return strings.ReplaceAll(fieldProtoComments(reg, msg, field), "\n", "<br>")
  2344. },
  2345. "arg": func(name string) string {
  2346. if v, f := reg.GetGoTemplateArgs()[name]; f {
  2347. return v
  2348. }
  2349. return fmt.Sprintf("goTemplateArg %s not found", name)
  2350. },
  2351. }).Parse(comment)
  2352. if err != nil {
  2353. // If there is an error parsing the templating insert the error as string in the comment
  2354. // to make it easier to debug the template error
  2355. return err.Error()
  2356. }
  2357. if err := tpl.Execute(&temp, data); err != nil {
  2358. // If there is an error executing the templating insert the error as string in the comment
  2359. // to make it easier to debug the error
  2360. return err.Error()
  2361. }
  2362. return temp.String()
  2363. }
  2364. var messageProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "MessageType")
  2365. var nestedProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.DescriptorProto)(nil)), "NestedType")
  2366. var packageProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Package")
  2367. var serviceProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Service")
  2368. var methodProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.ServiceDescriptorProto)(nil)), "Method")
  2369. func isProtoPathMatches(paths []int32, outerPaths []int32, typeName string, typeIndex int32, fieldPaths []int32) bool {
  2370. if typeName == "Package" && typeIndex == packageProtoPath {
  2371. // path for package comments is just [2], and all the other processing
  2372. // is too complex for it.
  2373. if len(paths) == 0 || typeIndex != paths[0] {
  2374. return false
  2375. }
  2376. return true
  2377. }
  2378. if len(paths) != len(outerPaths)*2+2+len(fieldPaths) {
  2379. return false
  2380. }
  2381. if typeName == "Method" {
  2382. if paths[0] != serviceProtoPath || paths[2] != methodProtoPath {
  2383. return false
  2384. }
  2385. paths = paths[2:]
  2386. } else {
  2387. typeNameDescriptor := reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil))
  2388. if len(outerPaths) > 0 {
  2389. if paths[0] != messageProtoPath || paths[1] != outerPaths[0] {
  2390. return false
  2391. }
  2392. paths = paths[2:]
  2393. outerPaths = outerPaths[1:]
  2394. for i, v := range outerPaths {
  2395. if paths[i*2] != nestedProtoPath || paths[i*2+1] != v {
  2396. return false
  2397. }
  2398. }
  2399. paths = paths[len(outerPaths)*2:]
  2400. if typeName == "MessageType" {
  2401. typeName = "NestedType"
  2402. }
  2403. typeNameDescriptor = reflect.TypeOf((*descriptorpb.DescriptorProto)(nil))
  2404. }
  2405. if paths[0] != protoPathIndex(typeNameDescriptor, typeName) || paths[1] != typeIndex {
  2406. return false
  2407. }
  2408. paths = paths[2:]
  2409. }
  2410. for i, v := range fieldPaths {
  2411. if paths[i] != v {
  2412. return false
  2413. }
  2414. }
  2415. return true
  2416. }
  2417. // protoPathIndex returns a path component for google.protobuf.descriptor.SourceCode_Location.
  2418. //
  2419. // Specifically, it returns an id as generated from descriptor proto which
  2420. // can be used to determine what type the id following it in the path is.
  2421. // For example, if we are trying to locate comments related to a field named
  2422. // `Address` in a message named `Person`, the path will be:
  2423. //
  2424. // [4, a, 2, b]
  2425. //
  2426. // While `a` gets determined by the order in which the messages appear in
  2427. // the proto file, and `b` is the field index specified in the proto
  2428. // file itself, the path actually needs to specify that `a` refers to a
  2429. // message and not, say, a service; and that `b` refers to a field and not
  2430. // an option.
  2431. //
  2432. // protoPathIndex figures out the values 4 and 2 in the above example. Because
  2433. // messages are top level objects, the value of 4 comes from field id for
  2434. // `MessageType` inside `google.protobuf.descriptor.FileDescriptor` message.
  2435. // This field has a message type `google.protobuf.descriptor.DescriptorProto`.
  2436. // And inside message `DescriptorProto`, there is a field named `Field` with id
  2437. // 2.
  2438. //
  2439. // Some code generators seem to be hardcoding these values; this method instead
  2440. // interprets them from `descriptor.proto`-derived Go source as necessary.
  2441. func protoPathIndex(descriptorType reflect.Type, what string) int32 {
  2442. field, ok := descriptorType.Elem().FieldByName(what)
  2443. if !ok {
  2444. panic(fmt.Errorf("could not find protobuf descriptor type id for %s", what))
  2445. }
  2446. pbtag := field.Tag.Get("protobuf")
  2447. if pbtag == "" {
  2448. panic(fmt.Errorf("no Go tag 'protobuf' on protobuf descriptor for %s", what))
  2449. }
  2450. path, err := strconv.ParseInt(strings.Split(pbtag, ",")[1], 10, 32)
  2451. if err != nil {
  2452. panic(fmt.Errorf("protobuf descriptor id for %s cannot be converted to a number: %s", what, err.Error()))
  2453. }
  2454. return int32(path)
  2455. }
  2456. // extractOperationOptionFromMethodDescriptor extracts the message of type
  2457. // openapi_options.Operation from a given proto method's descriptor.
  2458. func extractOperationOptionFromMethodDescriptor(meth *descriptorpb.MethodDescriptorProto) (*openapi_options.Operation, error) {
  2459. if meth.Options == nil {
  2460. return nil, nil
  2461. }
  2462. if !proto.HasExtension(meth.Options, openapi_options.E_Openapiv2Operation) {
  2463. return nil, nil
  2464. }
  2465. ext := proto.GetExtension(meth.Options, openapi_options.E_Openapiv2Operation)
  2466. opts, ok := ext.(*openapi_options.Operation)
  2467. if !ok {
  2468. return nil, fmt.Errorf("extension is %T; want an Operation", ext)
  2469. }
  2470. return opts, nil
  2471. }
  2472. // extractSchemaOptionFromMessageDescriptor extracts the message of type
  2473. // openapi_options.Schema from a given proto message's descriptor.
  2474. func extractSchemaOptionFromMessageDescriptor(msg *descriptorpb.DescriptorProto) (*openapi_options.Schema, error) {
  2475. if msg.Options == nil {
  2476. return nil, nil
  2477. }
  2478. if !proto.HasExtension(msg.Options, openapi_options.E_Openapiv2Schema) {
  2479. return nil, nil
  2480. }
  2481. ext := proto.GetExtension(msg.Options, openapi_options.E_Openapiv2Schema)
  2482. opts, ok := ext.(*openapi_options.Schema)
  2483. if !ok {
  2484. return nil, fmt.Errorf("extension is %T; want a Schema", ext)
  2485. }
  2486. return opts, nil
  2487. }
  2488. // extractTagOptionFromServiceDescriptor extracts the tag of type
  2489. // openapi_options.Tag from a given proto service's descriptor.
  2490. func extractTagOptionFromServiceDescriptor(svc *descriptorpb.ServiceDescriptorProto) (*openapi_options.Tag, error) {
  2491. if svc.Options == nil {
  2492. return nil, nil
  2493. }
  2494. if !proto.HasExtension(svc.Options, openapi_options.E_Openapiv2Tag) {
  2495. return nil, nil
  2496. }
  2497. ext := proto.GetExtension(svc.Options, openapi_options.E_Openapiv2Tag)
  2498. opts, ok := ext.(*openapi_options.Tag)
  2499. if !ok {
  2500. return nil, fmt.Errorf("extension is %T; want a Tag", ext)
  2501. }
  2502. return opts, nil
  2503. }
  2504. // extractOpenAPIOptionFromFileDescriptor extracts the message of type
  2505. // openapi_options.OpenAPI from a given proto method's descriptor.
  2506. func extractOpenAPIOptionFromFileDescriptor(file *descriptorpb.FileDescriptorProto) (*openapi_options.Swagger, error) {
  2507. if file.Options == nil {
  2508. return nil, nil
  2509. }
  2510. if !proto.HasExtension(file.Options, openapi_options.E_Openapiv2Swagger) {
  2511. return nil, nil
  2512. }
  2513. ext := proto.GetExtension(file.Options, openapi_options.E_Openapiv2Swagger)
  2514. opts, ok := ext.(*openapi_options.Swagger)
  2515. if !ok {
  2516. return nil, fmt.Errorf("extension is %T; want a OpenAPI object", ext)
  2517. }
  2518. return opts, nil
  2519. }
  2520. func extractJSONSchemaFromFieldDescriptor(fd *descriptorpb.FieldDescriptorProto) (*openapi_options.JSONSchema, error) {
  2521. if fd.Options == nil {
  2522. return nil, nil
  2523. }
  2524. if !proto.HasExtension(fd.Options, openapi_options.E_Openapiv2Field) {
  2525. return nil, nil
  2526. }
  2527. ext := proto.GetExtension(fd.Options, openapi_options.E_Openapiv2Field)
  2528. opts, ok := ext.(*openapi_options.JSONSchema)
  2529. if !ok {
  2530. return nil, fmt.Errorf("extension is %T; want a JSONSchema object", ext)
  2531. }
  2532. return opts, nil
  2533. }
  2534. func extractFieldBehaviorFromFieldDescriptor(fd *descriptorpb.FieldDescriptorProto) ([]annotations.FieldBehavior, error) {
  2535. if fd.Options == nil {
  2536. return nil, nil
  2537. }
  2538. if !proto.HasExtension(fd.Options, annotations.E_FieldBehavior) {
  2539. return nil, nil
  2540. }
  2541. ext := proto.GetExtension(fd.Options, annotations.E_FieldBehavior)
  2542. opts, ok := ext.([]annotations.FieldBehavior)
  2543. if !ok {
  2544. return nil, fmt.Errorf("extension is %T; want a []FieldBehavior object", ext)
  2545. }
  2546. return opts, nil
  2547. }
  2548. func getFieldVisibilityOption(fd *descriptor.Field) *visibility.VisibilityRule {
  2549. if fd.Options == nil {
  2550. return nil
  2551. }
  2552. if !proto.HasExtension(fd.Options, visibility.E_FieldVisibility) {
  2553. return nil
  2554. }
  2555. ext := proto.GetExtension(fd.Options, visibility.E_FieldVisibility)
  2556. opts, ok := ext.(*visibility.VisibilityRule)
  2557. if !ok {
  2558. return nil
  2559. }
  2560. return opts
  2561. }
  2562. func getServiceVisibilityOption(fd *descriptor.Service) *visibility.VisibilityRule {
  2563. if fd.Options == nil {
  2564. return nil
  2565. }
  2566. if !proto.HasExtension(fd.Options, visibility.E_ApiVisibility) {
  2567. return nil
  2568. }
  2569. ext := proto.GetExtension(fd.Options, visibility.E_ApiVisibility)
  2570. opts, ok := ext.(*visibility.VisibilityRule)
  2571. if !ok {
  2572. return nil
  2573. }
  2574. return opts
  2575. }
  2576. func getMethodVisibilityOption(fd *descriptor.Method) *visibility.VisibilityRule {
  2577. if fd.Options == nil {
  2578. return nil
  2579. }
  2580. if !proto.HasExtension(fd.Options, visibility.E_MethodVisibility) {
  2581. return nil
  2582. }
  2583. ext := proto.GetExtension(fd.Options, visibility.E_MethodVisibility)
  2584. opts, ok := ext.(*visibility.VisibilityRule)
  2585. if !ok {
  2586. return nil
  2587. }
  2588. return opts
  2589. }
  2590. func getEnumValueVisibilityOption(fd *descriptorpb.EnumValueDescriptorProto) *visibility.VisibilityRule {
  2591. if fd.Options == nil {
  2592. return nil
  2593. }
  2594. if !proto.HasExtension(fd.Options, visibility.E_ValueVisibility) {
  2595. return nil
  2596. }
  2597. ext := proto.GetExtension(fd.Options, visibility.E_ValueVisibility)
  2598. opts, ok := ext.(*visibility.VisibilityRule)
  2599. if !ok {
  2600. return nil
  2601. }
  2602. return opts
  2603. }
  2604. func getMethodOpenAPIOption(reg *descriptor.Registry, meth *descriptor.Method) (*openapi_options.Operation, error) {
  2605. opts, err := extractOperationOptionFromMethodDescriptor(meth.MethodDescriptorProto)
  2606. if err != nil {
  2607. return nil, err
  2608. }
  2609. if opts != nil {
  2610. return opts, nil
  2611. }
  2612. opts, ok := reg.GetOpenAPIMethodOption(meth.FQMN())
  2613. if !ok {
  2614. return nil, nil
  2615. }
  2616. return opts, nil
  2617. }
  2618. func getMessageOpenAPIOption(reg *descriptor.Registry, msg *descriptor.Message) (*openapi_options.Schema, error) {
  2619. opts, err := extractSchemaOptionFromMessageDescriptor(msg.DescriptorProto)
  2620. if err != nil {
  2621. return nil, err
  2622. }
  2623. if opts != nil {
  2624. return opts, nil
  2625. }
  2626. opts, ok := reg.GetOpenAPIMessageOption(msg.FQMN())
  2627. if !ok {
  2628. return nil, nil
  2629. }
  2630. return opts, nil
  2631. }
  2632. func getServiceOpenAPIOption(reg *descriptor.Registry, svc *descriptor.Service) (*openapi_options.Tag, error) {
  2633. if opts, ok := reg.GetOpenAPIServiceOption(svc.FQSN()); ok {
  2634. return opts, nil
  2635. }
  2636. opts, err := extractTagOptionFromServiceDescriptor(svc.ServiceDescriptorProto)
  2637. if err != nil {
  2638. return nil, err
  2639. }
  2640. return opts, nil
  2641. }
  2642. func getFileOpenAPIOption(reg *descriptor.Registry, file *descriptor.File) (*openapi_options.Swagger, error) {
  2643. opts, err := extractOpenAPIOptionFromFileDescriptor(file.FileDescriptorProto)
  2644. if err != nil {
  2645. return nil, err
  2646. }
  2647. if opts != nil {
  2648. return opts, nil
  2649. }
  2650. opts, ok := reg.GetOpenAPIFileOption(*file.Name)
  2651. if !ok {
  2652. return nil, nil
  2653. }
  2654. return opts, nil
  2655. }
  2656. func getFieldOpenAPIOption(reg *descriptor.Registry, fd *descriptor.Field) (*openapi_options.JSONSchema, error) {
  2657. opts, err := extractJSONSchemaFromFieldDescriptor(fd.FieldDescriptorProto)
  2658. if err != nil {
  2659. return nil, err
  2660. }
  2661. if opts != nil {
  2662. return opts, nil
  2663. }
  2664. opts, ok := reg.GetOpenAPIFieldOption(fd.FQFN())
  2665. if !ok {
  2666. return nil, nil
  2667. }
  2668. return opts, nil
  2669. }
  2670. func getFieldBehaviorOption(reg *descriptor.Registry, fd *descriptor.Field) ([]annotations.FieldBehavior, error) {
  2671. opts, err := extractFieldBehaviorFromFieldDescriptor(fd.FieldDescriptorProto)
  2672. if err != nil {
  2673. return nil, err
  2674. }
  2675. if opts != nil {
  2676. return opts, nil
  2677. }
  2678. return opts, nil
  2679. }
  2680. func protoJSONSchemaToOpenAPISchemaCore(j *openapi_options.JSONSchema, reg *descriptor.Registry, refs refMap) schemaCore {
  2681. ret := schemaCore{}
  2682. if j.GetRef() != "" {
  2683. openapiName, ok := fullyQualifiedNameToOpenAPIName(j.GetRef(), reg)
  2684. if ok {
  2685. ret.Ref = "#/definitions/" + openapiName
  2686. if refs != nil {
  2687. refs[j.GetRef()] = struct{}{}
  2688. }
  2689. } else {
  2690. ret.Ref += j.GetRef()
  2691. }
  2692. } else {
  2693. f, t := protoJSONSchemaTypeToFormat(j.GetType())
  2694. ret.Format = f
  2695. ret.Type = t
  2696. }
  2697. return ret
  2698. }
  2699. func updateswaggerObjectFromJSONSchema(s *openapiSchemaObject, j *openapi_options.JSONSchema, reg *descriptor.Registry, data interface{}) {
  2700. s.Title = j.GetTitle()
  2701. s.Description = j.GetDescription()
  2702. if reg.GetUseGoTemplate() {
  2703. s.Title = goTemplateComments(s.Title, data, reg)
  2704. s.Description = goTemplateComments(s.Description, data, reg)
  2705. }
  2706. if s.Type == "array" {
  2707. s.Items.MaxLength = j.GetMaxLength()
  2708. s.Items.MinLength = j.GetMinLength()
  2709. s.Items.Pattern = j.GetPattern()
  2710. s.Items.Default = j.GetDefault()
  2711. s.Items.UniqueItems = j.GetUniqueItems()
  2712. s.Items.MaxProperties = j.GetMaxProperties()
  2713. s.Items.MinProperties = j.GetMinProperties()
  2714. s.Items.Required = j.GetRequired()
  2715. s.Items.Minimum = j.GetMinimum()
  2716. s.Items.Maximum = j.GetMaximum()
  2717. s.Items.ReadOnly = j.GetReadOnly()
  2718. s.Items.MultipleOf = j.GetMultipleOf()
  2719. s.Items.ExclusiveMaximum = j.GetExclusiveMaximum()
  2720. s.Items.ExclusiveMinimum = j.GetExclusiveMinimum()
  2721. s.Items.Enum = j.GetEnum()
  2722. if j.GetDefault() == "" {
  2723. s.Items.Default = nil
  2724. }
  2725. if len(j.GetEnum()) == 0 {
  2726. s.Items.Enum = nil
  2727. }
  2728. if j.GetFormat() != "" {
  2729. s.Items.Format = j.GetFormat()
  2730. }
  2731. } else {
  2732. s.MaxLength = j.GetMaxLength()
  2733. s.MinLength = j.GetMinLength()
  2734. s.Pattern = j.GetPattern()
  2735. s.Default = j.GetDefault()
  2736. s.UniqueItems = j.GetUniqueItems()
  2737. s.MaxProperties = j.GetMaxProperties()
  2738. s.MinProperties = j.GetMinProperties()
  2739. s.Required = j.GetRequired()
  2740. s.Minimum = j.GetMinimum()
  2741. s.Maximum = j.GetMaximum()
  2742. s.ReadOnly = j.GetReadOnly()
  2743. s.MultipleOf = j.GetMultipleOf()
  2744. s.ExclusiveMaximum = j.GetExclusiveMaximum()
  2745. s.ExclusiveMinimum = j.GetExclusiveMinimum()
  2746. s.Enum = j.GetEnum()
  2747. if j.GetDefault() == "" {
  2748. s.Default = nil
  2749. }
  2750. if len(j.GetEnum()) == 0 {
  2751. s.Enum = nil
  2752. }
  2753. if j.GetFormat() != "" {
  2754. s.Format = j.GetFormat()
  2755. }
  2756. }
  2757. s.MaxItems = j.GetMaxItems()
  2758. s.MinItems = j.GetMinItems()
  2759. if j.GetExtensions() != nil {
  2760. exts, err := processExtensions(j.GetExtensions())
  2761. if err != nil {
  2762. panic(err)
  2763. }
  2764. s.extensions = exts
  2765. }
  2766. if overrideType := j.GetType(); len(overrideType) > 0 {
  2767. s.Type = strings.ToLower(overrideType[0].String())
  2768. }
  2769. if j.GetExample() != "" {
  2770. s.Example = RawExample(j.GetExample())
  2771. }
  2772. }
  2773. func updateSwaggerObjectFromFieldBehavior(s *openapiSchemaObject, j []annotations.FieldBehavior, reg *descriptor.Registry, field *descriptor.Field) {
  2774. for _, fb := range j {
  2775. switch fb {
  2776. case annotations.FieldBehavior_REQUIRED:
  2777. if reg.GetUseJSONNamesForFields() {
  2778. s.Required = append(s.Required, *field.JsonName)
  2779. } else {
  2780. s.Required = append(s.Required, *field.Name)
  2781. }
  2782. case annotations.FieldBehavior_OUTPUT_ONLY:
  2783. s.ReadOnly = true
  2784. case annotations.FieldBehavior_FIELD_BEHAVIOR_UNSPECIFIED:
  2785. case annotations.FieldBehavior_OPTIONAL:
  2786. case annotations.FieldBehavior_INPUT_ONLY:
  2787. // OpenAPI v3 supports a writeOnly property, but this is not supported in Open API v2
  2788. case annotations.FieldBehavior_IMMUTABLE:
  2789. }
  2790. }
  2791. }
  2792. func openapiSchemaFromProtoSchema(s *openapi_options.Schema, reg *descriptor.Registry, refs refMap, data interface{}) openapiSchemaObject {
  2793. ret := openapiSchemaObject{
  2794. ExternalDocs: protoExternalDocumentationToOpenAPIExternalDocumentation(s.GetExternalDocs(), reg, data),
  2795. }
  2796. ret.schemaCore = protoJSONSchemaToOpenAPISchemaCore(s.GetJsonSchema(), reg, refs)
  2797. updateswaggerObjectFromJSONSchema(&ret, s.GetJsonSchema(), reg, data)
  2798. if s != nil && s.Example != "" {
  2799. ret.Example = RawExample(s.Example)
  2800. }
  2801. return ret
  2802. }
  2803. func openapiExamplesFromProtoExamples(in map[string]string) map[string]interface{} {
  2804. if len(in) == 0 {
  2805. return nil
  2806. }
  2807. out := make(map[string]interface{}, len(in))
  2808. for mimeType, exampleStr := range in {
  2809. switch mimeType {
  2810. case "application/json":
  2811. // JSON example objects are rendered raw.
  2812. out[mimeType] = RawExample(exampleStr)
  2813. default:
  2814. // All other mimetype examples are rendered as strings.
  2815. out[mimeType] = exampleStr
  2816. }
  2817. }
  2818. return out
  2819. }
  2820. func protoJSONSchemaTypeToFormat(in []openapi_options.JSONSchema_JSONSchemaSimpleTypes) (string, string) {
  2821. if len(in) == 0 {
  2822. return "", ""
  2823. }
  2824. // Can't support more than 1 type, just return the first element.
  2825. // This is due to an inconsistency in the design of the openapiv2 proto
  2826. // and that used in schemaCore. schemaCore uses the v3 definition of types,
  2827. // which only allows a single string, while the openapiv2 proto uses the OpenAPI v2
  2828. // definition, which defers to the JSON schema definition, which allows a string or an array.
  2829. // Sources:
  2830. // https://swagger.io/specification/#itemsObject
  2831. // https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.2
  2832. switch in[0] {
  2833. case openapi_options.JSONSchema_UNKNOWN, openapi_options.JSONSchema_NULL:
  2834. return "", ""
  2835. case openapi_options.JSONSchema_OBJECT:
  2836. return "object", ""
  2837. case openapi_options.JSONSchema_ARRAY:
  2838. return "array", ""
  2839. case openapi_options.JSONSchema_BOOLEAN:
  2840. // NOTE: in OpenAPI specification, format should be empty on boolean type
  2841. return "boolean", ""
  2842. case openapi_options.JSONSchema_INTEGER:
  2843. return "integer", "int32"
  2844. case openapi_options.JSONSchema_NUMBER:
  2845. return "number", "double"
  2846. case openapi_options.JSONSchema_STRING:
  2847. // NOTE: in OpenAPI specification, format should be empty on string type
  2848. return "string", ""
  2849. default:
  2850. // Maybe panic?
  2851. return "", ""
  2852. }
  2853. }
  2854. func protoExternalDocumentationToOpenAPIExternalDocumentation(in *openapi_options.ExternalDocumentation, reg *descriptor.Registry, data interface{}) *openapiExternalDocumentationObject {
  2855. if in == nil {
  2856. return nil
  2857. }
  2858. if reg.GetUseGoTemplate() {
  2859. in.Description = goTemplateComments(in.Description, data, reg)
  2860. }
  2861. return &openapiExternalDocumentationObject{
  2862. Description: in.Description,
  2863. URL: in.Url,
  2864. }
  2865. }
  2866. func addCustomRefs(d openapiDefinitionsObject, reg *descriptor.Registry, refs refMap) error {
  2867. if len(refs) == 0 {
  2868. return nil
  2869. }
  2870. msgMap := make(messageMap)
  2871. enumMap := make(enumMap)
  2872. for ref := range refs {
  2873. swgName, swgOk := fullyQualifiedNameToOpenAPIName(ref, reg)
  2874. if !swgOk {
  2875. grpclog.Errorf("can't resolve OpenAPI name from CustomRef %q", ref)
  2876. continue
  2877. }
  2878. if _, ok := d[swgName]; ok {
  2879. // Skip already existing definitions
  2880. delete(refs, ref)
  2881. continue
  2882. }
  2883. msg, err := reg.LookupMsg("", ref)
  2884. if err == nil {
  2885. msgMap[swgName] = msg
  2886. continue
  2887. }
  2888. enum, err := reg.LookupEnum("", ref)
  2889. if err == nil {
  2890. enumMap[swgName] = enum
  2891. continue
  2892. }
  2893. // ?? Should be either enum or msg
  2894. }
  2895. if err := renderMessagesAsDefinition(msgMap, d, reg, refs, nil); err != nil {
  2896. return err
  2897. }
  2898. renderEnumerationsAsDefinition(enumMap, d, reg)
  2899. // Run again in case any new refs were added
  2900. return addCustomRefs(d, reg, refs)
  2901. }
  2902. func lowerCamelCase(fieldName string, fields []*descriptor.Field, msgs []*descriptor.Message) string {
  2903. for _, oneField := range fields {
  2904. if oneField.GetName() == fieldName {
  2905. return oneField.GetJsonName()
  2906. }
  2907. }
  2908. messageNameToFieldsToJSONName := make(map[string]map[string]string, len(msgs))
  2909. fieldNameToType := make(map[string]string)
  2910. for _, msg := range msgs {
  2911. fieldNameToJSONName := make(map[string]string)
  2912. for _, oneField := range msg.GetField() {
  2913. fieldNameToJSONName[oneField.GetName()] = oneField.GetJsonName()
  2914. fieldNameToType[oneField.GetName()] = oneField.GetTypeName()
  2915. }
  2916. messageNameToFieldsToJSONName[msg.GetName()] = fieldNameToJSONName
  2917. }
  2918. if strings.Contains(fieldName, ".") {
  2919. fieldNames := strings.Split(fieldName, ".")
  2920. fieldNamesWithCamelCase := make([]string, 0)
  2921. for i := 0; i < len(fieldNames)-1; i++ {
  2922. fieldNamesWithCamelCase = append(fieldNamesWithCamelCase, casing.JSONCamelCase(fieldNames[i]))
  2923. }
  2924. prefix := strings.Join(fieldNamesWithCamelCase, ".")
  2925. reservedJSONName := getReservedJSONName(fieldName, messageNameToFieldsToJSONName, fieldNameToType)
  2926. if reservedJSONName != "" {
  2927. return prefix + "." + reservedJSONName
  2928. }
  2929. }
  2930. return casing.JSONCamelCase(fieldName)
  2931. }
  2932. func getReservedJSONName(fieldName string, messageNameToFieldsToJSONName map[string]map[string]string, fieldNameToType map[string]string) string {
  2933. if len(strings.Split(fieldName, ".")) == 2 {
  2934. fieldNames := strings.Split(fieldName, ".")
  2935. firstVariable := fieldNames[0]
  2936. firstType := fieldNameToType[firstVariable]
  2937. firstTypeShortNames := strings.Split(firstType, ".")
  2938. firstTypeShortName := firstTypeShortNames[len(firstTypeShortNames)-1]
  2939. return messageNameToFieldsToJSONName[firstTypeShortName][fieldNames[1]]
  2940. }
  2941. fieldNames := strings.Split(fieldName, ".")
  2942. return getReservedJSONName(strings.Join(fieldNames[1:], "."), messageNameToFieldsToJSONName, fieldNameToType)
  2943. }
  2944. func find(a []string, x string) int {
  2945. // This is a linear search but we are dealing with a small number of fields
  2946. for i, n := range a {
  2947. if x == n {
  2948. return i
  2949. }
  2950. }
  2951. return -1
  2952. }
  2953. // Make a deep copy of the outer parameters that has paramName as the first component,
  2954. // but remove the first component of the field path.
  2955. func subPathParams(paramName string, outerParams []descriptor.Parameter) []descriptor.Parameter {
  2956. var innerParams []descriptor.Parameter
  2957. for _, p := range outerParams {
  2958. if len(p.FieldPath) > 1 && p.FieldPath[0].Name == paramName {
  2959. subParam := descriptor.Parameter{
  2960. FieldPath: p.FieldPath[1:],
  2961. Target: p.Target,
  2962. Method: p.Method,
  2963. }
  2964. innerParams = append(innerParams, subParam)
  2965. }
  2966. }
  2967. return innerParams
  2968. }
  2969. func getFieldConfiguration(reg *descriptor.Registry, fd *descriptor.Field) *openapi_options.JSONSchema_FieldConfiguration {
  2970. if j, err := getFieldOpenAPIOption(reg, fd); err == nil {
  2971. return j.GetFieldConfiguration()
  2972. }
  2973. return nil
  2974. }