| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791 |
- package genopenapi
- import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "math"
- "os"
- "reflect"
- "strings"
- "testing"
- "github.com/google/go-cmp/cmp"
- "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor"
- "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor/openapiconfig"
- "github.com/grpc-ecosystem/grpc-gateway/v2/internal/httprule"
- openapi_options "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
- "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
- "google.golang.org/genproto/googleapis/api/annotations"
- "google.golang.org/genproto/googleapis/api/visibility"
- "google.golang.org/protobuf/encoding/protojson"
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/reflect/protodesc"
- "google.golang.org/protobuf/reflect/protoreflect"
- "google.golang.org/protobuf/reflect/protoregistry"
- "google.golang.org/protobuf/types/descriptorpb"
- "google.golang.org/protobuf/types/known/anypb"
- "google.golang.org/protobuf/types/known/durationpb"
- "google.golang.org/protobuf/types/known/emptypb"
- field_mask "google.golang.org/protobuf/types/known/fieldmaskpb"
- "google.golang.org/protobuf/types/known/structpb"
- "google.golang.org/protobuf/types/known/timestamppb"
- "google.golang.org/protobuf/types/known/wrapperspb"
- "google.golang.org/protobuf/types/pluginpb"
- )
- var marshaler = &runtime.JSONPb{}
- func TestOpenapiExamplesFromProtoExamples(t *testing.T) {
- examples := openapiExamplesFromProtoExamples(map[string]string{
- "application/json": `{"Hello": "Worldr!"}`,
- "plain/text": "Hello, World!",
- })
- testCases := map[Format]string{
- FormatJSON: `
- {
- "application/json": {
- "Hello": "Worldr!"
- },
- "plain/text": "Hello, World!"
- }
- `,
- FormatYAML: `
- application/json:
- Hello: Worldr!
- plain/text: Hello, World!
- `,
- }
- spaceRemover := strings.NewReplacer(" ", "", "\t", "", "\n", "")
- for format, expected := range testCases {
- t.Run(string(format), func(t *testing.T) {
- var buf bytes.Buffer
- encoder, err := format.NewEncoder(&buf)
- if err != nil {
- t.Fatalf("creating encoder: %s", err)
- }
- err = encoder.Encode(examples)
- if err != nil {
- t.Fatalf("encoding: %s", err)
- }
- actual := spaceRemover.Replace(buf.String())
- expected = spaceRemover.Replace(expected)
- if expected != actual {
- t.Fatalf("expected:\n%s\nactual:\n%s", expected, actual)
- }
- })
- }
- }
- func crossLinkFixture(f *descriptor.File) *descriptor.File {
- for _, m := range f.Messages {
- m.File = f
- }
- for _, svc := range f.Services {
- svc.File = f
- for _, m := range svc.Methods {
- m.Service = svc
- for _, b := range m.Bindings {
- b.Method = m
- for _, param := range b.PathParams {
- param.Method = m
- }
- }
- }
- }
- return f
- }
- func reqFromFile(f *descriptor.File) *pluginpb.CodeGeneratorRequest {
- return &pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{
- f.FileDescriptorProto,
- },
- FileToGenerate: []string{f.GetName()},
- }
- }
- func TestMessageToQueryParametersWithEnumAsInt(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- Message string
- Params []openapiParameterObject
- }
- tests := []test{
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
- Number: proto.Int32(2),
- },
- {
- Name: proto.String("c"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Number: proto.Int32(3),
- },
- },
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "a",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "b",
- In: "query",
- Required: false,
- Type: "number",
- Format: "double",
- },
- {
- Name: "c",
- In: "query",
- Required: false,
- Type: "array",
- CollectionFormat: "multi",
- },
- },
- },
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Nested"),
- Number: proto.Int32(1),
- },
- },
- },
- {
- Name: proto.String("Nested"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("deep"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Nested.DeepNested"),
- Number: proto.Int32(2),
- },
- },
- NestedType: []*descriptorpb.DescriptorProto{{
- Name: proto.String("DeepNested"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("c"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
- TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"),
- Number: proto.Int32(2),
- },
- },
- EnumType: []*descriptorpb.EnumDescriptorProto{
- {
- Name: proto.String("DeepEnum"),
- Value: []*descriptorpb.EnumValueDescriptorProto{
- {Name: proto.String("FALSE"), Number: proto.Int32(0)},
- {Name: proto.String("TRUE"), Number: proto.Int32(1)},
- },
- },
- },
- }},
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "nested.a",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "nested.deep.b",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "nested.deep.c",
- In: "query",
- Required: false,
- Type: "integer",
- Enum: []int{0, 1},
- Default: 0,
- },
- },
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- reg.SetEnumsAsInts(true)
- var msgs []*descriptor.Message
- for _, msgdesc := range test.MsgDescs {
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err != nil {
- t.Fatalf("failed to convert message to query parameters: %s", err)
- }
- // avoid checking Items for array types
- for i := range params {
- params[i].Items = nil
- }
- if !reflect.DeepEqual(params, test.Params) {
- t.Errorf("expected %v, got %v", test.Params, params)
- }
- }
- }
- func TestMessageToQueryParametersWithOmitEnumDefaultValue(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- Message string
- Params []openapiParameterObject
- }
- tests := []test{
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
- Number: proto.Int32(2),
- },
- {
- Name: proto.String("c"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Number: proto.Int32(3),
- },
- },
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "a",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "b",
- In: "query",
- Required: false,
- Type: "number",
- Format: "double",
- },
- {
- Name: "c",
- In: "query",
- Required: false,
- Type: "array",
- CollectionFormat: "multi",
- },
- },
- },
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Nested"),
- Number: proto.Int32(1),
- },
- },
- },
- {
- Name: proto.String("Nested"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("deep"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Nested.DeepNested"),
- Number: proto.Int32(2),
- },
- },
- NestedType: []*descriptorpb.DescriptorProto{{
- Name: proto.String("DeepNested"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("c"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
- TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"),
- Number: proto.Int32(2),
- },
- },
- EnumType: []*descriptorpb.EnumDescriptorProto{
- {
- Name: proto.String("DeepEnum"),
- Value: []*descriptorpb.EnumValueDescriptorProto{
- {Name: proto.String("FALSE"), Number: proto.Int32(0)},
- {Name: proto.String("TRUE"), Number: proto.Int32(1)},
- },
- },
- },
- }},
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "nested.a",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "nested.deep.b",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "nested.deep.c",
- In: "query",
- Required: false,
- Type: "string",
- Enum: []string{"TRUE"},
- },
- },
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- reg.SetOmitEnumDefaultValue(true)
- var msgs []*descriptor.Message
- for _, msgdesc := range test.MsgDescs {
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err != nil {
- t.Fatalf("failed to convert message to query parameters: %s", err)
- }
- // avoid checking Items for array types
- for i := range params {
- params[i].Items = nil
- }
- if !reflect.DeepEqual(params, test.Params) {
- t.Errorf("expected %v, got %v", test.Params, params)
- }
- }
- }
- func TestMessageToQueryParameters(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- Message string
- Params []openapiParameterObject
- }
- tests := []test{
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
- Number: proto.Int32(2),
- },
- {
- Name: proto.String("c"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Number: proto.Int32(3),
- },
- },
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "a",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "b",
- In: "query",
- Required: false,
- Type: "number",
- Format: "double",
- },
- {
- Name: "c",
- In: "query",
- Required: false,
- Type: "array",
- CollectionFormat: "multi",
- },
- },
- },
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Nested"),
- Number: proto.Int32(1),
- },
- },
- },
- {
- Name: proto.String("Nested"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("deep"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Nested.DeepNested"),
- Number: proto.Int32(2),
- },
- },
- NestedType: []*descriptorpb.DescriptorProto{{
- Name: proto.String("DeepNested"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("c"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
- TypeName: proto.String(".example.Nested.DeepNested.DeepEnum"),
- Number: proto.Int32(2),
- },
- },
- EnumType: []*descriptorpb.EnumDescriptorProto{
- {
- Name: proto.String("DeepEnum"),
- Value: []*descriptorpb.EnumValueDescriptorProto{
- {Name: proto.String("FALSE"), Number: proto.Int32(0)},
- {Name: proto.String("TRUE"), Number: proto.Int32(1)},
- },
- },
- },
- }},
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "nested.a",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "nested.deep.b",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "nested.deep.c",
- In: "query",
- Required: false,
- Type: "string",
- Enum: []string{"FALSE", "TRUE"},
- Default: "FALSE",
- },
- },
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- msgs := []*descriptor.Message{}
- for _, msgdesc := range test.MsgDescs {
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err != nil {
- t.Fatalf("failed to convert message to query parameters: %s", err)
- }
- // avoid checking Items for array types
- for i := range params {
- params[i].Items = nil
- }
- if !reflect.DeepEqual(params, test.Params) {
- t.Errorf("expected %v, got %v", test.Params, params)
- }
- }
- }
- // TestMessagetoQueryParametersNoRecursive, is a check that cyclical references between messages
- // are not falsely detected given previous known edge-cases.
- func TestMessageToQueryParametersNoRecursive(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- Message string
- }
- tests := []test{
- // First test:
- // Here is a message that has two of another message adjacent to one another in a nested message.
- // There is no loop but this was previouly falsely flagged as a cycle.
- // Example proto:
- // message NonRecursiveMessage {
- // string field = 1;
- // }
- // message BaseMessage {
- // NonRecursiveMessage first = 1;
- // NonRecursiveMessage second = 2;
- // }
- // message QueryMessage {
- // BaseMessage first = 1;
- // string second = 2;
- // }
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("QueryMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("first"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.BaseMessage"),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("second"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- },
- {
- Name: proto.String("BaseMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("first"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.NonRecursiveMessage"),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("second"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.NonRecursiveMessage"),
- Number: proto.Int32(2),
- },
- },
- },
- // Note there is no recursive nature to this message
- {
- Name: proto.String("NonRecursiveMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- // Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- },
- },
- Message: "QueryMessage",
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- msgs := []*descriptor.Message{}
- for _, msgdesc := range test.MsgDescs {
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- _, err = messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err != nil {
- t.Fatalf("No recursion error should be thrown: %s", err)
- }
- }
- }
- // TestMessagetoQueryParametersRecursive, is a check that cyclical references between messages
- // are handled gracefully. The goal is to insure that attempts to add messages with cyclical
- // references to query-parameters returns an error message.
- func TestMessageToQueryParametersRecursive(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- Message string
- }
- tests := []test{
- // First test:
- // Here we test that a message that references it self through a field will return an error.
- // Example proto:
- // message DirectRecursiveMessage {
- // DirectRecursiveMessage nested = 1;
- // }
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("DirectRecursiveMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.DirectRecursiveMessage"),
- Number: proto.Int32(1),
- },
- },
- },
- },
- Message: "DirectRecursiveMessage",
- },
- // Second test:
- // Here we test that a cycle through multiple messages is detected and that an error is returned.
- // Sample:
- // message Root { NodeMessage nested = 1; }
- // message NodeMessage { CycleMessage nested = 1; }
- // message CycleMessage { Root nested = 1; }
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("RootMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.NodeMessage"),
- Number: proto.Int32(1),
- },
- },
- },
- {
- Name: proto.String("NodeMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.CycleMessage"),
- Number: proto.Int32(1),
- },
- },
- },
- {
- Name: proto.String("CycleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.RootMessage"),
- Number: proto.Int32(1),
- },
- },
- },
- },
- Message: "RootMessage",
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- msgs := []*descriptor.Message{}
- for _, msgdesc := range test.MsgDescs {
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- _, err = messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err == nil {
- t.Fatalf("It should not be allowed to have recursive query parameters")
- }
- }
- }
- func TestMessageToQueryParametersWithJsonName(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- Message string
- Params []openapiParameterObject
- }
- var requiredField = []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
- var requiredFieldOptions = new(descriptorpb.FieldOptions)
- proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, requiredField)
- messageSchema := &openapi_options.Schema{
- JsonSchema: &openapi_options.JSONSchema{
- Required: []string{"test_field_b"},
- },
- }
- messageOption := &descriptorpb.MessageOptions{}
- proto.SetExtension(messageOption, openapi_options.E_Openapiv2Schema, messageSchema)
- tests := []test{
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("test_field_a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- JsonName: proto.String("testFieldA"),
- },
- },
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "testFieldA",
- In: "query",
- Required: false,
- Type: "string",
- },
- },
- },
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("SubMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("test_field_a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- JsonName: proto.String("testFieldA"),
- },
- },
- },
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("sub_message"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.SubMessage"),
- Number: proto.Int32(1),
- JsonName: proto.String("subMessage"),
- },
- },
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "subMessage.testFieldA",
- In: "query",
- Required: false,
- Type: "string",
- },
- },
- },
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("test_field_a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- JsonName: proto.String("testFieldACustom"),
- Options: requiredFieldOptions,
- },
- {
- Name: proto.String("test_field_b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- JsonName: proto.String("testFieldBCustom"),
- },
- },
- Options: messageOption,
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "testFieldACustom",
- In: "query",
- Required: true,
- Type: "string",
- },
- {
- Name: "testFieldBCustom",
- In: "query",
- Required: true,
- Type: "string",
- },
- },
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- msgs := []*descriptor.Message{}
- for _, msgdesc := range test.MsgDescs {
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err != nil {
- t.Fatalf("failed to convert message to query parameters: %s", err)
- }
- if !reflect.DeepEqual(params, test.Params) {
- t.Errorf("expected %#v, got %#v", test.Params, params)
- }
- }
- }
- func TestMessageToQueryParametersWellKnownTypes(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- WellKnownMsgDescs []*descriptorpb.DescriptorProto
- Message string
- Params []openapiParameterObject
- }
- tests := []test{
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a_field_mask"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".google.protobuf.FieldMask"),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("a_timestamp"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".google.protobuf.Timestamp"),
- Number: proto.Int32(2),
- },
- },
- },
- },
- WellKnownMsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("FieldMask"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("paths"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Number: proto.Int32(1),
- },
- },
- },
- {
- Name: proto.String("Timestamp"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("seconds"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("nanos"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
- Number: proto.Int32(2),
- },
- },
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "a_field_mask",
- In: "query",
- Required: false,
- Type: "string",
- },
- {
- Name: "a_timestamp",
- In: "query",
- Required: false,
- Type: "string",
- Format: "date-time",
- },
- },
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- reg.SetEnumsAsInts(true)
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{
- {
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("google/well_known.proto"),
- Package: proto.String("google.protobuf"),
- Dependency: []string{},
- MessageType: test.WellKnownMsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("google/well_known"),
- },
- },
- {
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("acme/example.proto"),
- Package: proto.String("example"),
- Dependency: []string{"google/well_known.proto"},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("acme/example"),
- },
- },
- },
- })
- if err != nil {
- t.Fatalf("failed to load CodeGeneratorRequest: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err != nil {
- t.Fatalf("failed to convert message to query parameters: %s", err)
- }
- if !reflect.DeepEqual(params, test.Params) {
- t.Errorf("expected %v, got %v", test.Params, params)
- }
- }
- }
- func TestMessageToQueryParametersWithRequiredField(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- Message string
- Params []openapiParameterObject
- }
- messageSchema := &openapi_options.Schema{
- JsonSchema: &openapi_options.JSONSchema{
- Required: []string{"a"},
- },
- }
- messageOption := &descriptorpb.MessageOptions{}
- proto.SetExtension(messageOption, openapi_options.E_Openapiv2Schema, messageSchema)
- fieldSchema := &openapi_options.JSONSchema{Required: []string{"b"}}
- fieldOption := &descriptorpb.FieldOptions{}
- proto.SetExtension(fieldOption, openapi_options.E_Openapiv2Field, fieldSchema)
- // TODO(makdon): is nested field's test case necessary here?
- tests := []test{
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_DOUBLE.Enum(),
- Number: proto.Int32(2),
- Options: fieldOption,
- },
- {
- Name: proto.String("c"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Number: proto.Int32(3),
- },
- },
- Options: messageOption,
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "a",
- In: "query",
- Required: true,
- Type: "string",
- },
- {
- Name: "b",
- In: "query",
- Required: true,
- Type: "number",
- Format: "double",
- },
- {
- Name: "c",
- In: "query",
- Required: false,
- Type: "array",
- CollectionFormat: "multi",
- },
- },
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- msgs := []*descriptor.Message{}
- for _, msgdesc := range test.MsgDescs {
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err != nil {
- t.Fatalf("failed to convert message to query parameters: %s", err)
- }
- // avoid checking Items for array types
- for i := range params {
- params[i].Items = nil
- }
- if !reflect.DeepEqual(params, test.Params) {
- t.Errorf("expected %v, got %v", test.Params, params)
- }
- }
- }
- func TestMessageToQueryParametersWithEnumFieldOption(t *testing.T) {
- type test struct {
- MsgDescs []*descriptorpb.DescriptorProto
- Message string
- Params []openapiParameterObject
- }
- fieldSchema := &openapi_options.JSONSchema{Enum: []string{"enum1", "enum2"}}
- fieldOption := &descriptorpb.FieldOptions{}
- proto.SetExtension(fieldOption, openapi_options.E_Openapiv2Field, fieldSchema)
- tests := []test{
- {
- MsgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("a"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- Options: fieldOption,
- },
- {
- Name: proto.String("b"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- {
- Name: proto.String("c"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
- TypeName: proto.String(".example.ExampleMessage.EnabledEnum"),
- Number: proto.Int32(3),
- },
- {
- Name: proto.String("d"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
- TypeName: proto.String(".example.ExampleMessage.EnabledEnum"),
- Number: proto.Int32(4),
- Options: fieldOption,
- },
- },
- EnumType: []*descriptorpb.EnumDescriptorProto{
- {
- Name: proto.String("EnabledEnum"),
- Value: []*descriptorpb.EnumValueDescriptorProto{
- {Name: proto.String("FALSE"), Number: proto.Int32(0)},
- {Name: proto.String("TRUE"), Number: proto.Int32(1)},
- },
- },
- },
- },
- },
- Message: "ExampleMessage",
- Params: []openapiParameterObject{
- {
- Name: "a",
- In: "query",
- Type: "string",
- Enum: []string{"enum1", "enum2"},
- },
- {
- Name: "b",
- In: "query",
- Type: "string",
- },
- {
- Name: "c",
- In: "query",
- Type: "string",
- Enum: []string{"FALSE", "TRUE"},
- Default: "FALSE",
- },
- {
- Name: "d",
- In: "query",
- Type: "string",
- Enum: []string{"FALSE", "TRUE"},
- Default: "FALSE",
- },
- },
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- msgs := []*descriptor.Message{}
- for _, msgdesc := range test.MsgDescs {
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.MsgDescs,
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- message, err := reg.LookupMsg("", ".example."+test.Message)
- if err != nil {
- t.Fatalf("failed to lookup message: %s", err)
- }
- params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
- if err != nil {
- t.Fatalf("failed to convert message to query parameters: %s", err)
- }
- // avoid checking Items for array types
- for i := range params {
- params[i].Items = nil
- }
- if !reflect.DeepEqual(params, test.Params) {
- t.Errorf("expected %v, got %v", test.Params, params)
- }
- }
- }
- func TestApplyTemplateSimple(t *testing.T) {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- Body: &descriptor.Body{FieldPath: nil},
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(&file)
- err := reg.Load(reqFromFile(fileCL))
- if err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
- return
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := "", result.BasePath, "BasePath"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := ([]string)(nil), result.Schemes, "Schemes"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := []string{"application/json"}, result.Consumes, "Consumes"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestApplyTemplateMultiService(t *testing.T) {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- // Create two services that have the same method name. We will test that the
- // operation IDs are different
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- svc2 := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("OtherService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- Body: &descriptor.Body{FieldPath: nil},
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo",
- },
- },
- },
- },
- },
- },
- {
- ServiceDescriptorProto: svc2,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- Body: &descriptor.Body{FieldPath: nil},
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/ping",
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(&file)
- err := reg.Load(reqFromFile(fileCL))
- if err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
- return
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- // Check that the two services have unique operation IDs even though they
- // have the same method name.
- if want, is := "ExampleService_Example", result.getPathItemObject("/v1/echo").Get.OperationID; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want)
- }
- if want, is := "OtherService_Example", result.getPathItemObject("/v1/ping").Get.OperationID; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", file, is, want)
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestApplyTemplateOpenAPIConfigFromYAML(t *testing.T) {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- Body: &descriptor.Body{FieldPath: nil},
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(&file)
- err := reg.Load(reqFromFile(fileCL))
- if err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
- return
- }
- openapiOptions := &openapiconfig.OpenAPIOptions{
- Service: []*openapiconfig.OpenAPIServiceOption{
- {
- Service: "example.ExampleService",
- Option: &openapi_options.Tag{
- Description: "ExampleService description",
- ExternalDocs: &openapi_options.ExternalDocumentation{
- Description: "Find out more about ExampleService",
- },
- },
- },
- },
- }
- if err := reg.RegisterOpenAPIOptions(openapiOptions); err != nil {
- t.Errorf("reg.RegisterOpenAPIOptions for Service %#v failed with %v; want success", openapiOptions.Service, err)
- return
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, is, name := "ExampleService description", result.Tags[0].Description, "Tags[0].Description"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := "Find out more about ExampleService", result.Tags[0].ExternalDocs.Description, "Tags[0].ExternalDocs.Description"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- reg.SetDisableServiceTags(true)
- res, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if got, want := len(res.Tags), 0; got != want {
- t.Fatalf("len(applyTemplate(%#v).Tags) = %d want to be %d", file, got, want)
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestApplyTemplateOverrideWithOperation(t *testing.T) {
- newFile := func() *descriptor.File {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- Options: &descriptorpb.MethodOptions{},
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- return &descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- Body: &descriptor.Body{FieldPath: nil},
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
- },
- },
- },
- },
- },
- },
- },
- }
- }
- verifyTemplateFromReq := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File, opts *openapiconfig.OpenAPIOptions) {
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(file)
- err := reg.Load(reqFromFile(fileCL))
- if err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", *file, err)
- return
- }
- if opts != nil {
- if err := reg.RegisterOpenAPIOptions(opts); err != nil {
- t.Fatalf("failed to register OpenAPI options: %s", err)
- }
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", *file, err)
- return
- }
- if want, is := "MyExample", result.getPathItemObject("/v1/echo").Get.OperationID; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).Paths[0].Get.OperationID = %s want to be %s", *file, is, want)
- }
- if want, is := []string{"application/xml"}, result.getPathItemObject("/v1/echo").Get.Consumes; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).Paths[0].Get.Consumes = %s want to be %s", *file, is, want)
- }
- if want, is := []string{"application/json", "application/xml"}, result.getPathItemObject("/v1/echo").Get.Produces; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).Paths[0].Get.Produces = %s want to be %s", *file, is, want)
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", *file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- openapiOperation := openapi_options.Operation{
- OperationId: "MyExample",
- Consumes: []string{"application/xml"},
- Produces: []string{"application/json", "application/xml"},
- }
- t.Run("verify override via method option", func(t *testing.T) {
- file := newFile()
- proto.SetExtension(proto.Message(file.Services[0].Methods[0].MethodDescriptorProto.Options),
- openapi_options.E_Openapiv2Operation, &openapiOperation)
- reg := descriptor.NewRegistry()
- verifyTemplateFromReq(t, reg, file, nil)
- })
- t.Run("verify override options annotations", func(t *testing.T) {
- file := newFile()
- reg := descriptor.NewRegistry()
- opts := &openapiconfig.OpenAPIOptions{
- Method: []*openapiconfig.OpenAPIMethodOption{
- {
- Method: "example.ExampleService.Example",
- Option: &openapiOperation,
- },
- },
- }
- verifyTemplateFromReq(t, reg, file, opts)
- })
- }
- func TestApplyTemplateExtensions(t *testing.T) {
- newFile := func() *descriptor.File {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- Options: &descriptorpb.MethodOptions{},
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- return &descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- Body: &descriptor.Body{FieldPath: nil},
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
- },
- },
- },
- },
- },
- },
- },
- }
- }
- swagger := openapi_options.Swagger{
- Info: &openapi_options.Info{
- Title: "test",
- Extensions: map[string]*structpb.Value{
- "x-info-extension": {Kind: &structpb.Value_StringValue{StringValue: "bar"}},
- },
- },
- Extensions: map[string]*structpb.Value{
- "x-foo": {Kind: &structpb.Value_StringValue{StringValue: "bar"}},
- "x-bar": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{
- Values: []*structpb.Value{{Kind: &structpb.Value_StringValue{StringValue: "baz"}}},
- }}},
- },
- SecurityDefinitions: &openapi_options.SecurityDefinitions{
- Security: map[string]*openapi_options.SecurityScheme{
- "somescheme": {
- Extensions: map[string]*structpb.Value{
- "x-security-baz": {Kind: &structpb.Value_BoolValue{BoolValue: true}},
- },
- },
- },
- },
- Tags: []*openapi_options.Tag{
- {
- Name: "test tag",
- Description: "test tag description",
- Extensions: map[string]*structpb.Value{
- "x-traitTag": {Kind: &structpb.Value_BoolValue{BoolValue: true}},
- },
- },
- },
- }
- openapiOperation := openapi_options.Operation{
- Responses: map[string]*openapi_options.Response{
- "200": {
- Extensions: map[string]*structpb.Value{
- "x-resp-id": {Kind: &structpb.Value_StringValue{StringValue: "resp1000"}},
- },
- },
- },
- Extensions: map[string]*structpb.Value{
- "x-op-foo": {Kind: &structpb.Value_StringValue{StringValue: "baz"}},
- },
- }
- verifyTemplateExtensions := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File,
- opts *openapiconfig.OpenAPIOptions) {
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(file)
- err := reg.Load(reqFromFile(fileCL))
- if err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
- return
- }
- if opts != nil {
- if err := reg.RegisterOpenAPIOptions(opts); err != nil {
- t.Fatalf("failed to register OpenAPI annotations: %s", err)
- }
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if got, want := len(result.extensions), 2; got != want {
- t.Fatalf("len(applyTemplate(%#v).Extensions) = %d want to be %d", file, got, want)
- }
- if got, want := result.extensions[0].key, "x-bar"; got != want {
- t.Errorf("applyTemplate(%#v).Extensions[0].key = %s want to be %s", file, got, want)
- }
- if got, want := result.extensions[1].key, "x-foo"; got != want {
- t.Errorf("applyTemplate(%#v).Extensions[1].key = %s want to be %s", file, got, want)
- }
- {
- var got []string
- err = marshaler.Unmarshal(result.extensions[0].value, &got)
- if err != nil {
- t.Fatalf("marshaler.Unmarshal failed: %v", err)
- }
- want := []string{"baz"}
- if diff := cmp.Diff(got, want); diff != "" {
- t.Errorf(diff)
- }
- }
- {
- var got string
- err = marshaler.Unmarshal(result.extensions[1].value, &got)
- if err != nil {
- t.Fatalf("marshaler.Unmarshal failed: %v", err)
- }
- want := "bar"
- if diff := cmp.Diff(got, want); diff != "" {
- t.Errorf(diff)
- }
- }
- var scheme openapiSecuritySchemeObject
- for _, v := range result.SecurityDefinitions {
- scheme = v
- }
- if want, is, name := []extension{
- {key: "x-security-baz", value: json.RawMessage("true")},
- }, scheme.extensions, "SecurityScheme.Extensions"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := []extension{
- {key: "x-info-extension", value: json.RawMessage("\"bar\"")},
- }, result.Info.extensions, "Info.Extensions"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- var operation *openapiOperationObject
- var response openapiResponseObject
- for _, v := range result.Paths {
- operation = v.PathItemObject.Get
- response = v.PathItemObject.Get.Responses["200"]
- }
- if want, is, name := []extension{
- {key: "x-op-foo", value: json.RawMessage("\"baz\"")},
- }, operation.extensions, "operation.Extensions"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := []extension{
- {key: "x-resp-id", value: json.RawMessage("\"resp1000\"")},
- }, response.extensions, "response.Extensions"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if len(result.Tags) == 0 {
- t.Errorf("No tags found in result")
- return
- }
- tag := result.Tags[0]
- if want, is, name := []extension{
- {key: "x-traitTag", value: json.RawMessage("true")},
- }, tag.extensions, "Tags[0].Extensions"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- }
- t.Run("verify template options set via proto options", func(t *testing.T) {
- file := newFile()
- proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), openapi_options.E_Openapiv2Swagger, &swagger)
- proto.SetExtension(proto.Message(file.Services[0].Methods[0].Options), openapi_options.E_Openapiv2Operation, &openapiOperation)
- reg := descriptor.NewRegistry()
- verifyTemplateExtensions(t, reg, file, nil)
- })
- t.Run("verify template options set via annotations", func(t *testing.T) {
- file := newFile()
- opts := &openapiconfig.OpenAPIOptions{
- File: []*openapiconfig.OpenAPIFileOption{
- {
- File: "example.proto",
- Option: &swagger,
- },
- },
- Method: []*openapiconfig.OpenAPIMethodOption{
- {
- Method: "example.ExampleService.Example",
- Option: &openapiOperation,
- },
- },
- }
- reg := descriptor.NewRegistry()
- verifyTemplateExtensions(t, reg, file, opts)
- })
- }
- func TestApplyTemplateHeaders(t *testing.T) {
- newFile := func() *descriptor.File {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- Options: &descriptorpb.MethodOptions{},
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- return &descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- Body: &descriptor.Body{FieldPath: nil},
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
- },
- },
- },
- },
- },
- },
- },
- }
- }
- openapiOperation := openapi_options.Operation{
- Responses: map[string]*openapi_options.Response{
- "200": {
- Description: "Testing Headers",
- Headers: map[string]*openapi_options.Header{
- "string": {
- Description: "string header description",
- Type: "string",
- Format: "uuid",
- Pattern: "",
- },
- "boolean": {
- Description: "boolean header description",
- Type: "boolean",
- Default: "true",
- Pattern: "^true|false$",
- },
- "integer": {
- Description: "integer header description",
- Type: "integer",
- Default: "0",
- Pattern: "^[0-9]$",
- },
- "number": {
- Description: "number header description",
- Type: "number",
- Default: "1.2",
- Pattern: "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$",
- },
- },
- },
- },
- }
- verifyTemplateHeaders := func(t *testing.T, reg *descriptor.Registry, file *descriptor.File,
- opts *openapiconfig.OpenAPIOptions) {
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(file)
- err := reg.Load(reqFromFile(fileCL))
- if err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
- return
- }
- if opts != nil {
- if err := reg.RegisterOpenAPIOptions(opts); err != nil {
- t.Fatalf("failed to register OpenAPI annotations: %s", err)
- }
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- var response openapiResponseObject
- for _, v := range result.Paths {
- response = v.PathItemObject.Get.Responses["200"]
- }
- if want, is, name := []openapiHeadersObject{
- {
- "String": openapiHeaderObject{
- Description: "string header description",
- Type: "string",
- Format: "uuid",
- Pattern: "",
- },
- "Boolean": openapiHeaderObject{
- Description: "boolean header description",
- Type: "boolean",
- Default: RawExample("true"),
- Pattern: "^true|false$",
- },
- "Integer": openapiHeaderObject{
- Description: "integer header description",
- Type: "integer",
- Default: RawExample("0"),
- Pattern: "^[0-9]$",
- },
- "Number": openapiHeaderObject{
- Description: "number header description",
- Type: "number",
- Default: RawExample("1.2"),
- Pattern: "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$",
- },
- },
- }[0], response.Headers, "response.Headers"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- }
- t.Run("verify template options set via proto options", func(t *testing.T) {
- file := newFile()
- proto.SetExtension(proto.Message(file.Services[0].Methods[0].Options), openapi_options.E_Openapiv2Operation, &openapiOperation)
- reg := descriptor.NewRegistry()
- verifyTemplateHeaders(t, reg, file, nil)
- })
- }
- func TestValidateHeaderType(t *testing.T) {
- type test struct {
- Type string
- Format string
- expectedError error
- }
- tests := []test{
- {
- "string",
- "date-time",
- nil,
- },
- {
- "boolean",
- "",
- nil,
- },
- {
- "integer",
- "uint",
- nil,
- },
- {
- "integer",
- "uint8",
- nil,
- },
- {
- "integer",
- "uint16",
- nil,
- },
- {
- "integer",
- "uint32",
- nil,
- },
- {
- "integer",
- "uint64",
- nil,
- },
- {
- "integer",
- "int",
- nil,
- },
- {
- "integer",
- "int8",
- nil,
- },
- {
- "integer",
- "int16",
- nil,
- },
- {
- "integer",
- "int32",
- nil,
- },
- {
- "integer",
- "int64",
- nil,
- },
- {
- "integer",
- "float64",
- errors.New("the provided format \"float64\" is not a valid extension of the type \"integer\""),
- },
- {
- "integer",
- "uuid",
- errors.New("the provided format \"uuid\" is not a valid extension of the type \"integer\""),
- },
- {
- "number",
- "uint",
- nil,
- },
- {
- "number",
- "uint8",
- nil,
- },
- {
- "number",
- "uint16",
- nil,
- },
- {
- "number",
- "uint32",
- nil,
- },
- {
- "number",
- "uint64",
- nil,
- },
- {
- "number",
- "int",
- nil,
- },
- {
- "number",
- "int8",
- nil,
- },
- {
- "number",
- "int16",
- nil,
- },
- {
- "number",
- "int32",
- nil,
- },
- {
- "number",
- "int64",
- nil,
- },
- {
- "number",
- "float",
- nil,
- },
- {
- "number",
- "float32",
- nil,
- },
- {
- "number",
- "float64",
- nil,
- },
- {
- "number",
- "complex64",
- nil,
- },
- {
- "number",
- "complex128",
- nil,
- },
- {
- "number",
- "double",
- nil,
- },
- {
- "number",
- "byte",
- nil,
- },
- {
- "number",
- "rune",
- nil,
- },
- {
- "number",
- "uintptr",
- nil,
- },
- {
- "number",
- "date",
- errors.New("the provided format \"date\" is not a valid extension of the type \"number\""),
- },
- {
- "array",
- "",
- errors.New("the provided header type \"array\" is not supported"),
- },
- {
- "foo",
- "",
- errors.New("the provided header type \"foo\" is not supported"),
- },
- }
- for _, v := range tests {
- err := validateHeaderTypeAndFormat(v.Type, v.Format)
- if v.expectedError == nil {
- if err != nil {
- t.Errorf("unexpected error %v", err)
- }
- } else {
- if err == nil {
- t.Fatal("expected header error not returned")
- }
- if err.Error() != v.expectedError.Error() {
- t.Errorf("expected error malformed, expected %q, got %q", v.expectedError.Error(), err.Error())
- }
- }
- }
- }
- func TestValidateDefaultValueType(t *testing.T) {
- type test struct {
- Type string
- Value string
- Format string
- expectedError error
- }
- tests := []test{
- {
- "string",
- `"string"`,
- "",
- nil,
- },
- {
- "string",
- "\"2012-11-01T22:08:41+00:00\"",
- "date-time",
- nil,
- },
- {
- "string",
- "\"2012-11-01\"",
- "date",
- nil,
- },
- {
- "string",
- "0",
- "",
- errors.New("the provided default value \"0\" does not match provider type \"string\", or is not properly quoted with escaped quotations"),
- },
- {
- "string",
- "false",
- "",
- errors.New("the provided default value \"false\" does not match provider type \"string\", or is not properly quoted with escaped quotations"),
- },
- {
- "boolean",
- "true",
- "",
- nil,
- },
- {
- "boolean",
- "0",
- "",
- errors.New("the provided default value \"0\" does not match provider type \"boolean\""),
- },
- {
- "boolean",
- `"string"`,
- "",
- errors.New("the provided default value \"\\\"string\\\"\" does not match provider type \"boolean\""),
- },
- {
- "number",
- "1.2",
- "",
- nil,
- },
- {
- "number",
- "123",
- "",
- nil,
- },
- {
- "number",
- "nan",
- "",
- errors.New("the provided number \"nan\" is not a valid JSON number"),
- },
- {
- "number",
- "NaN",
- "",
- errors.New("the provided number \"NaN\" is not a valid JSON number"),
- },
- {
- "number",
- "-459.67",
- "",
- nil,
- },
- {
- "number",
- "inf",
- "",
- errors.New("the provided number \"inf\" is not a valid JSON number"),
- },
- {
- "number",
- "infinity",
- "",
- errors.New("the provided number \"infinity\" is not a valid JSON number"),
- },
- {
- "number",
- "Inf",
- "",
- errors.New("the provided number \"Inf\" is not a valid JSON number"),
- },
- {
- "number",
- "Infinity",
- "",
- errors.New("the provided number \"Infinity\" is not a valid JSON number"),
- },
- {
- "number",
- "false",
- "",
- errors.New("the provided default value \"false\" does not match provider type \"number\""),
- },
- {
- "number",
- `"string"`,
- "",
- errors.New("the provided default value \"\\\"string\\\"\" does not match provider type \"number\""),
- },
- {
- "integer",
- "2",
- "",
- nil,
- },
- {
- "integer",
- fmt.Sprint(math.MaxInt32),
- "int32",
- nil,
- },
- {
- "integer",
- fmt.Sprint(math.MaxInt32 + 1),
- "int32",
- errors.New("the provided default value \"2147483648\" does not match provided format \"int32\""),
- },
- {
- "integer",
- fmt.Sprint(math.MaxInt64),
- "int64",
- nil,
- },
- {
- "integer",
- "9223372036854775808",
- "int64",
- errors.New("the provided default value \"9223372036854775808\" does not match provided format \"int64\""),
- },
- {
- "integer",
- "18446744073709551615",
- "uint64",
- nil,
- },
- {
- "integer",
- "false",
- "",
- errors.New("the provided default value \"false\" does not match provided type \"integer\""),
- },
- {
- "integer",
- "1.2",
- "",
- errors.New("the provided default value \"1.2\" does not match provided type \"integer\""),
- },
- {
- "integer",
- `"string"`,
- "",
- errors.New("the provided default value \"\\\"string\\\"\" does not match provided type \"integer\""),
- },
- }
- for _, v := range tests {
- err := validateDefaultValueTypeAndFormat(v.Type, v.Value, v.Format)
- if v.expectedError == nil {
- if err != nil {
- t.Errorf("unexpected error '%v'", err)
- }
- } else {
- if err == nil {
- t.Error("expected update error not returned")
- }
- if err.Error() != v.expectedError.Error() {
- t.Errorf("expected error malformed, expected %q, got %q", v.expectedError.Error(), err.Error())
- }
- }
- }
- }
- func TestApplyTemplateRequestWithoutClientStreaming(t *testing.T) {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String("NestedMessage"),
- Number: proto.Int32(1),
- },
- },
- }
- nesteddesc := &descriptorpb.DescriptorProto{
- Name: proto.String("NestedMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("int32"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("bool"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(),
- Number: proto.Int32(2),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Echo"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- ClientStreaming: proto.Bool(false),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- meth.ServerStreaming = proto.Bool(false)
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- nested := &descriptor.Message{
- DescriptorProto: nesteddesc,
- }
- nestedField := &descriptor.Field{
- Message: msg,
- FieldDescriptorProto: msg.GetField()[0],
- }
- intField := &descriptor.Field{
- Message: nested,
- FieldDescriptorProto: nested.GetField()[0],
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc, nesteddesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg, nested},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo", // TODO(achew): Figure out what this should really be
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "nested",
- Target: nestedField,
- },
- {
- Name: "int32",
- Target: intField,
- },
- }),
- Target: intField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "nested",
- Target: nestedField,
- },
- }),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fmt.Fprintln(os.Stderr, "fd", file.FileDescriptorProto)
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- fmt.Fprintln(os.Stderr, "AllFQMNs", reg.GetAllFQMNs())
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, got := "2.0", result.Swagger; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).Swagger = %s want to be %s", file, got, want)
- }
- if want, got := "", result.BasePath; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).BasePath = %s want to be %s", file, got, want)
- }
- if want, got := ([]string)(nil), result.Schemes; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).Schemes = %s want to be %s", file, got, want)
- }
- if want, got := []string{"application/json"}, result.Consumes; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).Consumes = %s want to be %s", file, got, want)
- }
- if want, got := []string{"application/json"}, result.Produces; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).Produces = %s want to be %s", file, got, want)
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestApplyTemplateRequestWithClientStreaming(t *testing.T) {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String("NestedMessage"),
- Number: proto.Int32(1),
- },
- },
- }
- nesteddesc := &descriptorpb.DescriptorProto{
- Name: proto.String("NestedMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("int32"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("bool"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(),
- Number: proto.Int32(2),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Echo"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- ClientStreaming: proto.Bool(true),
- ServerStreaming: proto.Bool(true),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- nested := &descriptor.Message{
- DescriptorProto: nesteddesc,
- }
- nestedField := &descriptor.Field{
- Message: msg,
- FieldDescriptorProto: msg.GetField()[0],
- }
- intField := &descriptor.Field{
- Message: nested,
- FieldDescriptorProto: nested.GetField()[0],
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc, nesteddesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg, nested},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo", // TODO(achew): Figure out what this should really be
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "nested",
- Target: nestedField,
- },
- {
- Name: "int32",
- Target: intField,
- },
- }),
- Target: intField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "nested",
- Target: nestedField,
- },
- }),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- // Only ExampleMessage must be present, not NestedMessage
- if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
- }
- if _, ok := result.getPathItemObject("/v1/echo").Post.Responses["200"]; !ok {
- t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.getPathItemObject("/v1/echo").Post.Responses["200"]`)
- } else {
- if want, got, name := "A successful response.(streaming responses)", result.getPathItemObject("/v1/echo").Post.Responses["200"].Description, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- streamExampleExampleMessage := result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema
- if want, got, name := "object", streamExampleExampleMessage.Type, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- if want, got, name := "Stream result of exampleExampleMessage", streamExampleExampleMessage.Title, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- streamExampleExampleMessageProperties := *(streamExampleExampleMessage.Properties)
- if want, got, name := 2, len(streamExampleExampleMessageProperties), `len(StreamDefinitions["exampleExampleMessage"].Properties)`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
- } else {
- resultProperty := streamExampleExampleMessageProperties[0]
- if want, got, name := "result", resultProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- result := resultProperty.Value.(openapiSchemaObject)
- if want, got, name := "#/definitions/exampleExampleMessage", result.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(openapiSchemaObject)).Ref`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- errorProperty := streamExampleExampleMessageProperties[1]
- if want, got, name := "error", errorProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- err := errorProperty.Value.(openapiSchemaObject)
- if want, got, name := "#/definitions/rpcStatus", err.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(openapiSchemaObject)).Ref`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- }
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestApplyTemplateRequestWithServerStreamingAndNoStandardErrors(t *testing.T) {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String("NestedMessage"),
- Number: proto.Int32(1),
- },
- },
- }
- nesteddesc := &descriptorpb.DescriptorProto{
- Name: proto.String("NestedMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("int32"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("bool"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(),
- Number: proto.Int32(2),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Echo"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- ClientStreaming: proto.Bool(false),
- ServerStreaming: proto.Bool(true),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- nested := &descriptor.Message{
- DescriptorProto: nesteddesc,
- }
- nestedField := &descriptor.Field{
- Message: msg,
- FieldDescriptorProto: msg.GetField()[0],
- }
- intField := &descriptor.Field{
- Message: nested,
- FieldDescriptorProto: nested.GetField()[0],
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc, nesteddesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg, nested},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "nested",
- Target: nestedField,
- },
- {
- Name: "int32",
- Target: intField,
- },
- }),
- Target: intField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "nested",
- Target: nestedField,
- },
- }),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- reg.SetDisableDefaultErrors(true)
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- // Should only include the message, no status or any type
- if want, got, name := 1, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
- }
- if _, ok := result.getPathItemObject("/v1/echo").Post.Responses["200"]; !ok {
- t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.getPathItemObject("/v1/echo").Post.Responses["200"]`)
- } else {
- if want, got, name := "A successful response.(streaming responses)", result.getPathItemObject("/v1/echo").Post.Responses["200"].Description, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- streamExampleExampleMessage := result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema
- if want, got, name := "object", streamExampleExampleMessage.Type, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- if want, got, name := "Stream result of exampleExampleMessage", streamExampleExampleMessage.Title, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- streamExampleExampleMessageProperties := *(streamExampleExampleMessage.Properties)
- if want, got, name := 1, len(streamExampleExampleMessageProperties), `len(StreamDefinitions["exampleExampleMessage"].Properties)`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
- } else {
- resultProperty := streamExampleExampleMessageProperties[0]
- if want, got, name := "result", resultProperty.Key, `(*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Key`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- result := resultProperty.Value.(openapiSchemaObject)
- if want, got, name := "#/definitions/exampleExampleMessage", result.Ref, `((*(StreamDefinitions["exampleExampleMessage"].Properties))[0].Value.(openapiSchemaObject)).Ref`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- }
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestApplyTemplateRequestWithUnusedReferences(t *testing.T) {
- reqdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("string"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- respdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("EmptyMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("EmptyMessage"),
- ClientStreaming: proto.Bool(false),
- ServerStreaming: proto.Bool(false),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- req := &descriptor.Message{
- DescriptorProto: reqdesc,
- }
- resp := &descriptor.Message{
- DescriptorProto: respdesc,
- }
- stringField := &descriptor.Field{
- Message: req,
- FieldDescriptorProto: req.GetField()[0],
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{reqdesc, respdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{req, resp},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: req,
- ResponseType: resp,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/example",
- },
- },
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/example/{string}",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "string",
- Target: stringField,
- },
- }),
- Target: stringField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "string",
- Target: stringField,
- },
- }),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- // Only EmptyMessage must be present, not ExampleMessage (plus error status)
- if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestApplyTemplateRequestWithBodyQueryParameters(t *testing.T) {
- bookDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("Book"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("name"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("id"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- }
- createDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("CreateBookRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("parent"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("book"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- {
- Name: proto.String("book_id"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(3),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("CreateBook"),
- InputType: proto.String("CreateBookRequest"),
- OutputType: proto.String("Book"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("BookService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- bookMsg := &descriptor.Message{
- DescriptorProto: bookDesc,
- }
- createMsg := &descriptor.Message{
- DescriptorProto: createDesc,
- }
- parentField := &descriptor.Field{
- Message: createMsg,
- FieldDescriptorProto: createMsg.GetField()[0],
- }
- bookField := &descriptor.Field{
- Message: createMsg,
- FieldMessage: bookMsg,
- FieldDescriptorProto: createMsg.GetField()[1],
- }
- bookIDField := &descriptor.Field{
- Message: createMsg,
- FieldDescriptorProto: createMsg.GetField()[2],
- }
- createMsg.Fields = []*descriptor.Field{parentField, bookField, bookIDField}
- newFile := func() descriptor.File {
- return descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("book.proto"),
- MessageType: []*descriptorpb.DescriptorProto{bookDesc, createDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/book.pb",
- Name: "book_pb",
- },
- Messages: []*descriptor.Message{bookMsg, createMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: createMsg,
- ResponseType: bookMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{parent=publishers/*}/books",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "parent",
- Target: parentField,
- },
- }),
- Target: parentField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: []descriptor.FieldPathComponent{
- {
- Name: "book",
- Target: bookField,
- },
- },
- },
- },
- },
- },
- },
- },
- },
- }
- }
- type args struct {
- file descriptor.File
- }
- type paramOut struct {
- Name string
- In string
- Required bool
- }
- tests := []struct {
- name string
- args args
- want []paramOut
- }{
- {
- name: "book_in_body",
- args: args{file: newFile()},
- want: []paramOut{
- {"parent", "path", true},
- {"book", "body", true},
- {"book_id", "query", false},
- },
- },
- {
- name: "book_in_query",
- args: args{file: func() descriptor.File {
- f := newFile()
- f.Services[0].Methods[0].Bindings[0].Body = nil
- return f
- }()},
- want: []paramOut{
- {"parent", "path", true},
- {"book", "query", false},
- {"book_id", "query", false},
- },
- },
- }
- for _, tt := range tests {
- tt := tt
- t.Run(tt.name, func(t *testing.T) {
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{tt.args.file.FileDescriptorProto}})
- if err != nil {
- t.Errorf("Registry.Load() failed with %v; want success", err)
- return
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&tt.args.file), reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", tt.args.file, err)
- return
- }
- if _, ok := result.getPathItemObject("/v1/{parent}/books").Post.Responses["200"]; !ok {
- t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", tt.args.file, `result.getPathItemObject("/v1/{parent}/books").Post.Responses["200"]`)
- } else {
- if want, got, name := 3, len(result.getPathItemObject("/v1/{parent}/books").Post.Parameters), `len(result.getPathItemObject("/v1/{parent}/books").Post.Parameters)`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %d want to be %d", tt.args.file, name, got, want)
- }
- for i, want := range tt.want {
- p := result.getPathItemObject("/v1/{parent}/books").Post.Parameters[i]
- if got, name := (paramOut{p.Name, p.In, p.Required}), `result.getPathItemObject("/v1/{parent}/books").Post.Parameters[0]`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %v want to be %v", tt.args.file, name, got, want)
- }
- }
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", tt.args.file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- })
- }
- }
- func TestApplyTemplateWithRequestAndBodyParameters(t *testing.T) {
- bookDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("Book"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("name"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("id"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- }
- createDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("CreateBookRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("parent"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("book"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- {
- Name: proto.String("book_id"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REQUIRED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(3),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("CreateBook"),
- InputType: proto.String("CreateBookRequest"),
- OutputType: proto.String("Book"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("BookService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- bookMsg := &descriptor.Message{
- DescriptorProto: bookDesc,
- }
- createMsg := &descriptor.Message{
- DescriptorProto: createDesc,
- }
- parentField := &descriptor.Field{
- Message: createMsg,
- FieldDescriptorProto: createMsg.GetField()[0],
- }
- bookField := &descriptor.Field{
- Message: createMsg,
- FieldMessage: bookMsg,
- FieldDescriptorProto: createMsg.GetField()[1],
- }
- bookIDField := &descriptor.Field{
- Message: createMsg,
- FieldDescriptorProto: createMsg.GetField()[2],
- }
- createMsg.Fields = []*descriptor.Field{parentField, bookField, bookIDField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("book.proto"),
- MessageType: []*descriptorpb.DescriptorProto{bookDesc, createDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/book.pb",
- Name: "book_pb",
- },
- Messages: []*descriptor.Message{bookMsg, createMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: createMsg,
- ResponseType: bookMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{parent=publishers/*}/books",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "parent",
- Target: parentField,
- },
- }),
- Target: parentField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: []descriptor.FieldPathComponent{},
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(&file)
- err := reg.Load(reqFromFile(fileCL))
- if err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
- return
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, is, name := "2.0", result.Swagger, "Swagger"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := "", result.BasePath, "BasePath"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := ([]string)(nil), result.Schemes, "Schemes"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := []string{"application/json"}, result.Consumes, "Consumes"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := 1, len(result.Paths), "len(result.Paths)"; !reflect.DeepEqual(is, want) {
- t.Errorf("%s = %d want to be %d", name, want, is)
- }
- if want, is, name := 4, len(result.Paths[0].PathItemObject.Post.Parameters), "len(result.Paths[0].PathItemObject.Post.Parameters)"; !reflect.DeepEqual(is, want) {
- t.Errorf("%s = %d want to be %d", name, want, is)
- }
- if want, is, name := "#/definitions/BookServiceCreateBookBody", result.Paths[0].PathItemObject.Post.Parameters[1].Schema.schemaCore.Ref, "result.Paths[0].PathItemObject.Post.Parameters[1].Schema.schemaCore.Ref"; !reflect.DeepEqual(is, want) {
- t.Errorf("%s = %s want to be %s", name, want, is)
- }
- _, found := result.Definitions["BookServiceCreateBookBody"]
- if !found {
- t.Error("expecting definition to contain BookServiceCreateBookBody")
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- // TestApplyTemplateProtobufAny tests that the protobufAny definition is correctly rendered with the @type field and
- // allowing additional properties.
- func TestApplyTemplateProtobufAny(t *testing.T) {
- // checkProtobufAnyFormat verifies the only property should be @type and additional properties are allowed
- checkProtobufAnyFormat := func(t *testing.T, protobufAny openapiSchemaObject) {
- anyPropsJSON, err := protobufAny.Properties.MarshalJSON()
- if err != nil {
- t.Errorf("protobufAny.Properties.MarshalJSON(), got error = %v", err)
- }
- var anyPropsMap map[string]interface{}
- if err := json.Unmarshal(anyPropsJSON, &anyPropsMap); err != nil {
- t.Errorf("json.Unmarshal(), got error = %v", err)
- }
- // @type should exist
- if _, ok := anyPropsMap["@type"]; !ok {
- t.Errorf("protobufAny.Properties missing key, \"@type\". got = %#v", anyPropsMap)
- }
- // and @type should be the only property
- if len(anyPropsMap) > 1 {
- t.Errorf("len(protobufAny.Properties) = %v, want = %v", len(anyPropsMap), 1)
- }
- // protobufAny should have additionalProperties allowed
- if protobufAny.AdditionalProperties == nil {
- t.Errorf("protobufAny.AdditionalProperties = nil, want not-nil")
- }
- }
- type args struct {
- regConfig func(registry *descriptor.Registry)
- msgContainsAny bool
- }
- tests := []struct {
- name string
- args args
- wantNumDefinitions int
- }{
- {
- // our proto schema doesn't directly use protobufAny, but it is implicitly used by rpcStatus being
- // automatically rendered
- name: "default_protobufAny_from_rpcStatus",
- args: args{
- msgContainsAny: false,
- },
- wantNumDefinitions: 4,
- },
- {
- // we have a protobufAny in a message, it should contain a ref inside the custom message
- name: "protobufAny_referenced_in_message",
- args: args{
- msgContainsAny: true,
- },
- wantNumDefinitions: 4,
- },
- {
- // we have a protobufAny in a message but with automatic rendering of rpcStatus disabled
- name: "protobufAny_referenced_in_message_with_default_errors_disabled",
- args: args{
- msgContainsAny: true,
- regConfig: func(reg *descriptor.Registry) {
- reg.SetDisableDefaultErrors(true)
- },
- },
- wantNumDefinitions: 3,
- },
- {
- // we have a protobufAny in a message but with automatic rendering of responses disabled
- name: "protobufAny_referenced_in_message_with_default_responses_disabled",
- args: args{
- msgContainsAny: true,
- regConfig: func(reg *descriptor.Registry) {
- reg.SetDisableDefaultResponses(true)
- },
- },
- wantNumDefinitions: 4,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- reqdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("name"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- respdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("EmptyMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("EmptyMessage"),
- ClientStreaming: proto.Bool(false),
- ServerStreaming: proto.Bool(false),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- req := &descriptor.Message{
- DescriptorProto: reqdesc,
- }
- resp := &descriptor.Message{
- DescriptorProto: respdesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{reqdesc, respdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{req, resp},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: req,
- ResponseType: resp,
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetGenerateUnboundMethods(true)
- if tt.args.regConfig != nil {
- tt.args.regConfig(reg)
- }
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- protoFiles := []*descriptorpb.FileDescriptorProto{
- file.FileDescriptorProto,
- }
- if tt.args.msgContainsAny {
- // add an Any field to the request message
- reqdesc.Field = append(reqdesc.Field, &descriptorpb.FieldDescriptorProto{
- Name: proto.String("any_value"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".google.protobuf.Any"),
- Number: proto.Int32(2),
- })
- // update the dependencies to import it
- file.Dependency = append(file.Dependency, "google/protobuf/any.proto")
- anyDescriptorProto := protodesc.ToFileDescriptorProto((&anypb.Any{}).ProtoReflect().Descriptor().ParentFile())
- anyDescriptorProto.SourceCodeInfo = &descriptorpb.SourceCodeInfo{}
- // prepend the anyDescriptorProto to the protoFiles slice so that the dependency can be resolved
- protoFiles = append(append(make([]*descriptorpb.FileDescriptorProto, 0, len(protoFiles)+1), anyDescriptorProto), protoFiles[0:]...)
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: protoFiles,
- FileToGenerate: []string{file.GetName()},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- target, err := reg.LookupFile(file.GetName())
- if err != nil {
- t.Fatalf("failed to lookup file from reg: %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(target), reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, got, name := tt.wantNumDefinitions, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
- }
- protobufAny, ok := result.Definitions["protobufAny"]
- if !ok {
- t.Error("expecting Definitions to contain protobufAny")
- }
- checkProtobufAnyFormat(t, protobufAny)
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- resultJSON, _ := json.Marshal(result)
- t.Errorf("got: %s", resultJSON)
- }
- })
- }
- }
- func generateFieldsForJSONReservedName() []*descriptor.Field {
- fields := make([]*descriptor.Field, 0)
- fieldName := "json_name"
- fieldJSONName := "jsonNAME"
- fieldDescriptor := descriptorpb.FieldDescriptorProto{Name: &fieldName, JsonName: &fieldJSONName}
- field := &descriptor.Field{FieldDescriptorProto: &fieldDescriptor}
- return append(fields, field)
- }
- func generateMsgsForJSONReservedName() []*descriptor.Message {
- result := make([]*descriptor.Message, 0)
- // The first message, its field is field_abc and its type is NewType
- // NewType field_abc
- fieldName := "field_abc"
- fieldJSONName := "fieldAbc"
- messageName1 := "message1"
- messageType := "pkg.a.NewType"
- pfd := descriptorpb.FieldDescriptorProto{Name: &fieldName, JsonName: &fieldJSONName, TypeName: &messageType}
- result = append(result,
- &descriptor.Message{
- DescriptorProto: &descriptorpb.DescriptorProto{
- Name: &messageName1, Field: []*descriptorpb.FieldDescriptorProto{&pfd},
- },
- })
- // The second message, its name is NewName, its type is string
- // message NewType {
- // string field_newName [json_name = RESERVEDJSONNAME]
- // }
- messageName := "NewType"
- field := "field_newName"
- fieldJSONName2 := "RESERVEDJSONNAME"
- pfd2 := descriptorpb.FieldDescriptorProto{Name: &field, JsonName: &fieldJSONName2}
- result = append(result, &descriptor.Message{
- DescriptorProto: &descriptorpb.DescriptorProto{
- Name: &messageName, Field: []*descriptorpb.FieldDescriptorProto{&pfd2},
- },
- })
- return result
- }
- func TestTemplateWithJsonCamelCase(t *testing.T) {
- var tests = []struct {
- input string
- expected string
- }{
- {"/test/{test_id}", "/test/{testId}"},
- {"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1Id}/test2/{test2Id}"},
- {"/test1/{test1_id}/{test2_id}", "/test1/{test1Id}/{test2Id}"},
- {"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1Id}/{test2Id}"},
- {"/test1/{test1_id1_id2}", "/test1/{test1Id1Id2}"},
- {"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1Id1Id2}/test2/{test2Id3Id4}"},
- {"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1Id1Id2}/{test2Id3Id4}"},
- {"test/{a}", "test/{a}"},
- {"test/{ab}", "test/{ab}"},
- {"test/{a_a}", "test/{aA}"},
- {"test/{ab_c}", "test/{abC}"},
- {"test/{json_name}", "test/{jsonNAME}"},
- {"test/{field_abc.field_newName}", "test/{fieldAbc.RESERVEDJSONNAME}"},
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- for _, data := range tests {
- actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string))
- if data.expected != actual {
- t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
- }
- }
- }
- func TestTemplateWithoutJsonCamelCase(t *testing.T) {
- var tests = []struct {
- input string
- expected string
- }{
- {"/test/{test_id}", "/test/{test_id}"},
- {"/test1/{test1_id}/test2/{test2_id}", "/test1/{test1_id}/test2/{test2_id}"},
- {"/test1/{test1_id}/{test2_id}", "/test1/{test1_id}/{test2_id}"},
- {"/test1/test2/{test1_id}/{test2_id}", "/test1/test2/{test1_id}/{test2_id}"},
- {"/test1/{test1_id1_id2}", "/test1/{test1_id1_id2}"},
- {"/test1/{test1_id1_id2}/test2/{test2_id3_id4}", "/test1/{test1_id1_id2}/test2/{test2_id3_id4}"},
- {"/test1/test2/{test1_id1_id2}/{test2_id3_id4}", "/test1/test2/{test1_id1_id2}/{test2_id3_id4}"},
- {"test/{a}", "test/{a}"},
- {"test/{ab}", "test/{ab}"},
- {"test/{a_a}", "test/{a_a}"},
- {"test/{json_name}", "test/{json_name}"},
- {"test/{field_abc.field_newName}", "test/{field_abc.field_newName}"},
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(false)
- for _, data := range tests {
- actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string))
- if data.expected != actual {
- t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
- }
- }
- }
- func TestTemplateToOpenAPIPath(t *testing.T) {
- var tests = []struct {
- input string
- expected string
- }{
- {"/test", "/test"},
- {"/{test}", "/{test}"},
- {"/{test=prefix/*}", "/{test}"},
- {"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"},
- {"/{test1}/{test2}", "/{test1}/{test2}"},
- {"/{test1}/{test2}/", "/{test1}/{test2}/"},
- {"/{name=prefix/*}", "/{name}"},
- {"/{name=prefix1/*/prefix2/*}", "/{name}"},
- {"/{user.name=prefix/*}", "/{user.name}"},
- {"/{user.name=prefix1/*/prefix2/*}", "/{user.name}"},
- {"/{parent=prefix/*}/children", "/{parent}/children"},
- {"/{name=prefix/*}:customMethod", "/{name}:customMethod"},
- {"/{name=prefix1/*/prefix2/*}:customMethod", "/{name}:customMethod"},
- {"/{user.name=prefix/*}:customMethod", "/{user.name}:customMethod"},
- {"/{user.name=prefix1/*/prefix2/*}:customMethod", "/{user.name}:customMethod"},
- {"/{parent=prefix/*}/children:customMethod", "/{parent}/children:customMethod"},
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(false)
- for _, data := range tests {
- actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string))
- if data.expected != actual {
- t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
- }
- }
- reg.SetUseJSONNamesForFields(true)
- for _, data := range tests {
- actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string))
- if data.expected != actual {
- t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
- }
- }
- }
- func BenchmarkTemplateToOpenAPIPath(b *testing.B) {
- const input = "/{user.name=prefix1/*/prefix2/*}:customMethod"
- b.Run("with JSON names", func(b *testing.B) {
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(false)
- for i := 0; i < b.N; i++ {
- _ = templateToOpenAPIPath(input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string))
- }
- })
- b.Run("without JSON names", func(b *testing.B) {
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- for i := 0; i < b.N; i++ {
- _ = templateToOpenAPIPath(input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string))
- }
- })
- }
- func TestResolveFullyQualifiedNameToOpenAPIName(t *testing.T) {
- var tests = []struct {
- input string
- output string
- listOfFQMNs []string
- namingStrategy string
- }{
- {
- ".a.b.C",
- "C",
- []string{
- ".a.b.C",
- },
- "legacy",
- },
- {
- ".a.b.C",
- "C",
- []string{
- ".a.b.C",
- },
- "simple",
- },
- {
- ".a.b.C",
- "abC",
- []string{
- ".a.C",
- ".a.b.C",
- },
- "legacy",
- },
- {
- ".a.b.C",
- "b.C",
- []string{
- ".a.C",
- ".a.b.C",
- },
- "simple",
- },
- {
- ".a.b.C",
- "abC",
- []string{
- ".C",
- ".a.C",
- ".a.b.C",
- },
- "legacy",
- },
- {
- ".a.b.C",
- "b.C",
- []string{
- ".C",
- ".a.C",
- ".a.b.C",
- },
- "simple",
- },
- {
- ".a.b.C",
- "a.b.C",
- []string{
- ".C",
- ".a.C",
- ".a.b.C",
- },
- "fqn",
- },
- }
- for _, data := range tests {
- names := resolveFullyQualifiedNameToOpenAPINames(data.listOfFQMNs, data.namingStrategy)
- output := names[data.input]
- if output != data.output {
- t.Errorf("Expected fullyQualifiedNameToOpenAPIName(%v, %s) to be %s but got %s",
- data.input, data.namingStrategy, data.output, output)
- }
- }
- }
- func templateToOpenAPIPath(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message, pathParamNames map[string]string) string {
- return partsToOpenAPIPath(templateToParts(path, reg, fields, msgs), pathParamNames)
- }
- func templateToRegexpMap(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message) map[string]string {
- return partsToRegexpMap(templateToParts(path, reg, fields, msgs))
- }
- func TestFQMNToRegexpMap(t *testing.T) {
- var tests = []struct {
- input string
- expected map[string]string
- }{
- {"/test", map[string]string{}},
- {"/{test}", map[string]string{}},
- {"/{test" + pathParamUniqueSuffixDeliminator + "1=prefix/*}", map[string]string{"test" + pathParamUniqueSuffixDeliminator + "1": "prefix/[^/]+"}},
- {"/{test=prefix/that/has/multiple/parts/to/it/**}", map[string]string{"test": "prefix/that/has/multiple/parts/to/it/.+"}},
- {"/{test1=organizations/*}/{test2=divisions/*}", map[string]string{
- "test1": "organizations/[^/]+",
- "test2": "divisions/[^/]+",
- }},
- {"/v1/{name=projects/*/topics/*}:delete", map[string]string{"name": "projects/[^/]+/topics/[^/]+"}},
- }
- reg := descriptor.NewRegistry()
- for _, data := range tests {
- actual := templateToRegexpMap(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName())
- if !reflect.DeepEqual(data.expected, actual) {
- t.Errorf("Expected partsToRegexpMap(%v) = %v, actual: %v", data.input, data.expected, actual)
- }
- }
- }
- func TestFQMNtoOpenAPIName(t *testing.T) {
- var tests = []struct {
- input string
- expected string
- }{
- {"/test", "/test"},
- {"/{test}", "/{test}"},
- {"/{test=prefix/*}", "/{test}"},
- {"/{test=prefix/that/has/multiple/parts/to/it/*}", "/{test}"},
- {"/{test1}/{test2}", "/{test1}/{test2}"},
- {"/{test1}/{test2}/", "/{test1}/{test2}/"},
- {"/v1/{name=tests/*}/tests", "/v1/{name}/tests"},
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(false)
- for _, data := range tests {
- actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string))
- if data.expected != actual {
- t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
- }
- }
- reg.SetUseJSONNamesForFields(true)
- for _, data := range tests {
- actual := templateToOpenAPIPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), make(map[string]string))
- if data.expected != actual {
- t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actual)
- }
- }
- }
- func TestSchemaOfField(t *testing.T) {
- type test struct {
- field *descriptor.Field
- refs refMap
- expected openapiSchemaObject
- openAPIOptions *openapiconfig.OpenAPIOptions
- useJSONNamesForFields bool
- }
- jsonSchema := &openapi_options.JSONSchema{
- Title: "field title",
- Description: "field description",
- }
- jsonSchemaWithOptions := &openapi_options.JSONSchema{
- Title: "field title",
- Description: "field description",
- MultipleOf: 100,
- Maximum: 101,
- ExclusiveMaximum: true,
- Minimum: 1,
- ExclusiveMinimum: true,
- MaxLength: 10,
- MinLength: 3,
- Pattern: "[a-z]+",
- MaxItems: 20,
- MinItems: 2,
- UniqueItems: true,
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- }
- jsonSchemaRequired := &openapi_options.JSONSchema{
- Required: []string{"required_via_json_schema"},
- }
- jsonSchemaWithFormat := &openapi_options.JSONSchema{
- Format: "uuid",
- }
- var fieldOptions = new(descriptorpb.FieldOptions)
- proto.SetExtension(fieldOptions, openapi_options.E_Openapiv2Field, jsonSchema)
- var requiredField = []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
- var requiredFieldOptions = new(descriptorpb.FieldOptions)
- proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, requiredField)
- var outputOnlyField = []annotations.FieldBehavior{annotations.FieldBehavior_OUTPUT_ONLY}
- var outputOnlyOptions = new(descriptorpb.FieldOptions)
- proto.SetExtension(outputOnlyOptions, annotations.E_FieldBehavior, outputOnlyField)
- tests := []test{
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("primitive_field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("repeated_primitive_field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("empty_field"),
- TypeName: proto.String(".google.protobuf.Empty"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "object",
- },
- Properties: &openapiSchemaObjectProperties{},
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.FieldMask"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.Timestamp"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- Format: "date-time",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.Duration"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.StringValue"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("repeated_wrapped_field"),
- TypeName: proto.String(".google.protobuf.StringValue"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.BytesValue"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- Format: "byte",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.Int32Value"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "integer",
- Format: "int32",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.UInt32Value"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "integer",
- Format: "int64",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.Int64Value"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- Format: "int64",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.UInt64Value"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- Format: "uint64",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.FloatValue"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "number",
- Format: "float",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.DoubleValue"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "number",
- Format: "double",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.BoolValue"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "boolean",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.Struct"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "object",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.Value"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{},
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.ListValue"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{schemaCore: schemaCore{
- Type: "object",
- }},
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("wrapped_field"),
- TypeName: proto.String(".google.protobuf.NullValue"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("message_field"),
- TypeName: proto.String(".example.Message"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- },
- },
- refs: refMap{".example.Message": struct{}{}},
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Ref: "#/definitions/exampleMessage",
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("map_field"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Message.MapFieldEntry"),
- Options: fieldOptions,
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "object",
- },
- AdditionalProperties: &openapiSchemaObject{
- schemaCore: schemaCore{Type: "string"},
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("array_field"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Options: fieldOptions,
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{schemaCore: schemaCore{
- Type: "string",
- }},
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("primitive_field"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
- Options: fieldOptions,
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "integer",
- Format: "int32",
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("message_field"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Empty"),
- Options: fieldOptions,
- },
- },
- refs: refMap{".example.Empty": struct{}{}},
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Ref: "#/definitions/exampleEmpty",
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("map_field"), // should be called map_field_option but it's not valid map field name
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Message.MapFieldEntry"),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.map_field",
- Option: jsonSchema,
- },
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "object",
- },
- AdditionalProperties: &openapiSchemaObject{
- schemaCore: schemaCore{Type: "string"},
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("array_field_option"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.array_field_option",
- Option: jsonSchema,
- },
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{schemaCore: schemaCore{
- Type: "string",
- }},
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("primitive_field_option"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.primitive_field_option",
- Option: jsonSchema,
- },
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "integer",
- Format: "int32",
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("primitive_field_option"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum().Enum(),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.primitive_field_option",
- Option: &openapi_options.JSONSchema{
- Title: "field title",
- Description: "field description",
- Format: "uuid",
- },
- },
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- Format: "uuid",
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("message_field_option"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Empty"),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.message_field_option",
- Option: jsonSchema,
- },
- },
- },
- refs: refMap{".example.Empty": struct{}{}},
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Ref: "#/definitions/exampleEmpty",
- },
- Title: "field title",
- Description: "field description",
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("required_via_field_behavior_field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Options: requiredFieldOptions,
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- Required: []string{"required_via_field_behavior_field"},
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("readonly_via_field_behavior_field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Options: outputOnlyOptions,
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- ReadOnly: true,
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("required_message_field"),
- TypeName: proto.String(".example.Message"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- Options: requiredFieldOptions,
- },
- },
- refs: refMap{".example.Message": struct{}{}},
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Ref: "#/definitions/exampleMessage",
- },
- Required: []string{"required_message_field"},
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("array_field_option"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.array_field_option",
- Option: jsonSchemaWithOptions,
- },
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- MultipleOf: 100,
- Maximum: 101,
- ExclusiveMaximum: true,
- Minimum: 1,
- ExclusiveMinimum: true,
- MaxLength: 10,
- MinLength: 3,
- Pattern: "[a-z]+",
- UniqueItems: true,
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- },
- },
- Title: "field title",
- Description: "field description",
- MaxItems: 20,
- MinItems: 2,
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("array_field_option"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT64.Enum(),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.array_field_option",
- Option: jsonSchemaWithOptions,
- },
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{
- schemaCore: schemaCore{
- Type: "string",
- Format: "int64",
- },
- MultipleOf: 100,
- Maximum: 101,
- ExclusiveMaximum: true,
- Minimum: 1,
- ExclusiveMinimum: true,
- MaxLength: 10,
- MinLength: 3,
- Pattern: "[a-z]+",
- UniqueItems: true,
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- },
- },
- Title: "field title",
- Description: "field description",
- MaxItems: 20,
- MinItems: 2,
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("array_field_format"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.array_field_format",
- Option: jsonSchemaWithFormat,
- },
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{
- schemaCore: schemaCore{
- Type: "string",
- Format: "uuid",
- },
- },
- },
- },
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("required_via_field_behavior_field_json_name"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- JsonName: proto.String("required_field_custom_name"),
- Options: requiredFieldOptions,
- },
- },
- refs: make(refMap),
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- Required: []string{"required_field_custom_name"},
- },
- useJSONNamesForFields: true,
- },
- {
- field: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String("required_via_json_schema"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- JsonName: proto.String("required_via_json_schema_json_name"),
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.required_via_json_schema",
- Option: jsonSchemaRequired,
- },
- },
- },
- refs: make(refMap),
- useJSONNamesForFields: true,
- expected: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- Required: []string{"required_via_json_schema_json_name"},
- },
- },
- }
- for _, test := range tests {
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(test.useJSONNamesForFields)
- req := &pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{
- {
- Name: proto.String("third_party/google.proto"),
- Package: proto.String("google.protobuf"),
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("third_party/google"),
- },
- MessageType: []*descriptorpb.DescriptorProto{
- protodesc.ToDescriptorProto((&emptypb.Empty{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&structpb.Struct{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&structpb.Value{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&structpb.ListValue{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&field_mask.FieldMask{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((×tamppb.Timestamp{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&durationpb.Duration{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.StringValue{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.BytesValue{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.Int32Value{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.UInt32Value{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.Int64Value{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.UInt64Value{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.FloatValue{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.DoubleValue{}).ProtoReflect().Descriptor()),
- protodesc.ToDescriptorProto((&wrapperspb.BoolValue{}).ProtoReflect().Descriptor()),
- },
- EnumType: []*descriptorpb.EnumDescriptorProto{
- protodesc.ToEnumDescriptorProto(structpb.NullValue(0).Descriptor()),
- },
- },
- {
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{"third_party/google.proto"},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- MessageType: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("value"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- func() *descriptorpb.FieldDescriptorProto {
- fd := test.field.FieldDescriptorProto
- fd.Number = proto.Int32(2)
- return fd
- }(),
- },
- NestedType: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("MapFieldEntry"),
- Options: &descriptorpb.MessageOptions{MapEntry: proto.Bool(true)},
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("key"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("value"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- },
- },
- },
- {
- Name: proto.String("Empty"),
- },
- },
- EnumType: []*descriptorpb.EnumDescriptorProto{
- {
- Name: proto.String("MessageType"),
- Value: []*descriptorpb.EnumValueDescriptorProto{
- {
- Name: proto.String("MESSAGE_TYPE_1"),
- Number: proto.Int32(0),
- },
- },
- },
- },
- Service: []*descriptorpb.ServiceDescriptorProto{},
- },
- },
- }
- err := reg.Load(req)
- if err != nil {
- t.Errorf("failed to reg.Load(req): %v", err)
- }
- // set field's parent message pointer to message so field can resolve its FQFN
- test.field.Message = &descriptor.Message{
- DescriptorProto: req.ProtoFile[1].MessageType[0],
- File: &descriptor.File{
- FileDescriptorProto: req.ProtoFile[1],
- },
- }
- if test.openAPIOptions != nil {
- if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil {
- t.Fatalf("failed to register OpenAPI options: %s", err)
- }
- }
- refs := make(refMap)
- actual := schemaOfField(test.field, reg, refs)
- expectedSchemaObject := test.expected
- if e, a := expectedSchemaObject, actual; !reflect.DeepEqual(a, e) {
- t.Errorf("Expected schemaOfField(%v) = \n%#+v, actual: \n%#+v", test.field, e, a)
- }
- if !reflect.DeepEqual(refs, test.refs) {
- t.Errorf("Expected schemaOfField(%v) to add refs %v, not %v", test.field, test.refs, refs)
- }
- }
- }
- func TestRenderMessagesAsDefinition(t *testing.T) {
- jsonSchema := &openapi_options.JSONSchema{
- Title: "field title",
- Description: "field description",
- Required: []string{"aRequiredField"},
- }
- var requiredField = new(descriptorpb.FieldOptions)
- proto.SetExtension(requiredField, openapi_options.E_Openapiv2Field, jsonSchema)
- var fieldBehaviorRequired = []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
- var requiredFieldOptions = new(descriptorpb.FieldOptions)
- proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, fieldBehaviorRequired)
- var fieldBehaviorOutputOnlyField = []annotations.FieldBehavior{annotations.FieldBehavior_OUTPUT_ONLY}
- var fieldBehaviorOutputOnlyOptions = new(descriptorpb.FieldOptions)
- proto.SetExtension(fieldBehaviorOutputOnlyOptions, annotations.E_FieldBehavior, fieldBehaviorOutputOnlyField)
- var fieldVisibilityFieldInternal = &visibility.VisibilityRule{Restriction: "INTERNAL"}
- var fieldVisibilityInternalOption = new(descriptorpb.FieldOptions)
- proto.SetExtension(fieldVisibilityInternalOption, visibility.E_FieldVisibility, fieldVisibilityFieldInternal)
- var fieldVisibilityFieldPreview = &visibility.VisibilityRule{Restriction: "INTERNAL,PREVIEW"}
- var fieldVisibilityPreviewOption = new(descriptorpb.FieldOptions)
- proto.SetExtension(fieldVisibilityPreviewOption, visibility.E_FieldVisibility, fieldVisibilityFieldPreview)
- tests := []struct {
- descr string
- msgDescs []*descriptorpb.DescriptorProto
- schema map[string]*openapi_options.Schema // per-message schema to add
- defs openapiDefinitionsObject
- openAPIOptions *openapiconfig.OpenAPIOptions
- pathParams []descriptor.Parameter
- UseJSONNamesForFields bool
- UseAllOfForRefs bool
- }{
- {
- descr: "no OpenAPI options",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- schema: map[string]*openapi_options.Schema{},
- defs: map[string]openapiSchemaObject{
- "Message": {schemaCore: schemaCore{Type: "object"}},
- },
- },
- {
- descr: "example option",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- Example: `{"foo":"bar"}`,
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {schemaCore: schemaCore{
- Type: "object",
- Example: RawExample(`{"foo":"bar"}`),
- }},
- },
- },
- {
- descr: "example option with something non-json",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- Example: `XXXX anything goes XXXX`,
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {schemaCore: schemaCore{
- Type: "object",
- Example: RawExample(`XXXX anything goes XXXX`),
- }},
- },
- },
- {
- descr: "external docs option",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- ExternalDocs: &openapi_options.ExternalDocumentation{
- Description: "glorious docs",
- Url: "https://nada",
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "glorious docs",
- URL: "https://nada",
- },
- },
- },
- },
- {
- descr: "JSONSchema options",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- MultipleOf: 100,
- Maximum: 101,
- ExclusiveMaximum: true,
- Minimum: 1,
- ExclusiveMinimum: true,
- MaxLength: 10,
- MinLength: 3,
- Pattern: "[a-z]+",
- MaxItems: 20,
- MinItems: 2,
- UniqueItems: true,
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- MultipleOf: 100,
- Maximum: 101,
- ExclusiveMaximum: true,
- Minimum: 1,
- ExclusiveMinimum: true,
- MaxLength: 10,
- MinLength: 3,
- Pattern: "[a-z]+",
- MaxItems: 20,
- MinItems: 2,
- UniqueItems: true,
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- },
- },
- },
- {
- descr: "JSONSchema options from registry",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Message: []*openapiconfig.OpenAPIMessageOption{
- {
- Message: "example.Message",
- Option: &openapi_options.Schema{
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- MultipleOf: 100,
- Maximum: 101,
- ExclusiveMaximum: true,
- Minimum: 1,
- ExclusiveMinimum: true,
- MaxLength: 10,
- MinLength: 3,
- Pattern: "[a-z]+",
- MaxItems: 20,
- MinItems: 2,
- UniqueItems: true,
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- },
- },
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- MultipleOf: 100,
- Maximum: 101,
- ExclusiveMaximum: true,
- Minimum: 1,
- ExclusiveMinimum: true,
- MaxLength: 10,
- MinLength: 3,
- Pattern: "[a-z]+",
- MaxItems: 20,
- MinItems: 2,
- UniqueItems: true,
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- },
- },
- },
- {
- descr: "JSONSchema with required properties",
- msgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("FieldOne"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("FieldTwo"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- Options: requiredFieldOptions,
- },
- {
- Name: proto.String("FieldThree"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(3),
- Options: requiredFieldOptions,
- },
- },
- },
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- Required: []string{"FieldOne", "FieldTwo"},
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- Required: []string{"FieldOne", "FieldTwo", "FieldThree"},
- Properties: &openapiSchemaObjectProperties{
- {
- Key: "FieldOne",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- Key: "FieldTwo",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- Key: "FieldThree",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- },
- },
- },
- },
- {
- descr: "JSONSchema with required properties",
- msgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("FieldOne"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(3),
- Options: requiredFieldOptions,
- },
- },
- },
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- Required: []string{"FieldOne"},
- Properties: &openapiSchemaObjectProperties{
- {
- Key: "FieldOne",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- },
- },
- },
- },
- {
- descr: "JSONSchema with required properties by using annotations",
- msgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("FieldOne"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- Options: requiredFieldOptions,
- },
- },
- },
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- Required: []string{"FieldOne"},
- Properties: &openapiSchemaObjectProperties{
- {
- Key: "FieldOne",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- },
- },
- },
- },
- {
- descr: "JSONSchema with hidden properties",
- msgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("aInternalField"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- Options: fieldVisibilityInternalOption,
- },
- {
- Name: proto.String("aPreviewField"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- Options: fieldVisibilityPreviewOption,
- },
- {
- Name: proto.String("aVisibleField"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(3),
- },
- },
- },
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- Required: []string{"req"},
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- Required: []string{"req"},
- Properties: &openapiSchemaObjectProperties{
- {
- Key: "aPreviewField",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- Key: "aVisibleField",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- },
- },
- },
- },
- {
- descr: "JSONSchema with path parameters",
- msgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("aRequiredField"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- Options: requiredField,
- },
- {
- Name: proto.String("aPathParameter"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- },
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- Required: []string{"req"},
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- Required: []string{"req", "aRequiredField"},
- Properties: &openapiSchemaObjectProperties{
- {
- Key: "aRequiredField",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- Description: "field description",
- Title: "field title",
- },
- },
- },
- },
- },
- pathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath{
- descriptor.FieldPathComponent{
- Name: ("aPathParameter"),
- },
- },
- },
- },
- },
- {
- descr: "JSONSchema with required properties via field_behavior",
- msgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("aRequiredField"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- Options: requiredFieldOptions,
- },
- {
- Name: proto.String("aOutputOnlyField"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- Options: fieldBehaviorOutputOnlyOptions,
- },
- },
- },
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- Required: []string{"req"},
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- Required: []string{"req", "aRequiredField"},
- Properties: &openapiSchemaObjectProperties{
- {
- Key: "aRequiredField",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- Key: "aOutputOnlyField",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- ReadOnly: true,
- },
- },
- },
- },
- },
- },
- {
- descr: "JSONSchema with required properties and fields with json_name",
- msgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("FieldOne"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- JsonName: proto.String("custom_json_1"),
- },
- {
- Name: proto.String("FieldTwo"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- JsonName: proto.String("custom_json_2"),
- Options: requiredFieldOptions,
- },
- {
- Name: proto.String("FieldThree"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(3),
- JsonName: proto.String("custom_json_3"),
- Options: requiredFieldOptions,
- },
- },
- },
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- Required: []string{"FieldOne", "FieldTwo"},
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- Required: []string{"custom_json_1", "custom_json_2", "custom_json_3"},
- Properties: &openapiSchemaObjectProperties{
- {
- Key: "custom_json_1",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- Key: "custom_json_2",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- {
- Key: "custom_json_3",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- },
- },
- },
- UseJSONNamesForFields: true,
- },
- {
- descr: "JSONSchema with a read_only nested field",
- msgDescs: []*descriptorpb.DescriptorProto{
- {
- Name: proto.String("Message"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("nested"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.Message.Nested"),
- Number: proto.Int32(1),
- Options: fieldBehaviorOutputOnlyOptions,
- },
- },
- NestedType: []*descriptorpb.DescriptorProto{{
- Name: proto.String("Nested"),
- }},
- },
- },
- UseAllOfForRefs: true,
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "title",
- Description: "desc",
- Required: []string{},
- },
- },
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Field: []*openapiconfig.OpenAPIFieldOption{
- {
- Field: "example.Message.nested",
- Option: &openapi_options.JSONSchema{
- Title: "nested field title",
- Description: "nested field desc",
- Example: `"ok":"TRUE"`,
- },
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "exampleMessage": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "title",
- Description: "desc",
- Required: nil,
- Properties: &openapiSchemaObjectProperties{
- {
- Key: "nested",
- Value: openapiSchemaObject{
- AllOf: []allOfEntry{{Ref: "#/definitions/MessageNested"}},
- ReadOnly: true,
- schemaCore: schemaCore{
- Example: RawExample(`"ok":"TRUE"`),
- },
- Title: "nested field title",
- Description: "nested field desc",
- },
- },
- },
- },
- },
- },
- }
- for _, test := range tests {
- t.Run(test.descr, func(t *testing.T) {
- msgs := []*descriptor.Message{}
- for _, msgdesc := range test.msgDescs {
- msgdesc.Options = &descriptorpb.MessageOptions{}
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- reg := descriptor.NewRegistry()
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.msgDescs,
- EnumType: []*descriptorpb.EnumDescriptorProto{},
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- reg.SetVisibilityRestrictionSelectors([]string{"PREVIEW"})
- if test.UseJSONNamesForFields {
- reg.SetUseJSONNamesForFields(true)
- }
- if test.UseAllOfForRefs {
- reg.SetUseAllOfForRefs(true)
- }
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- msgMap := map[string]*descriptor.Message{}
- for _, d := range test.msgDescs {
- name := d.GetName()
- msg, err := reg.LookupMsg("example", name)
- if err != nil {
- t.Fatalf("lookup message %v: %v", name, err)
- }
- msgMap[msg.FQMN()] = msg
- if schema, ok := test.schema[name]; ok {
- proto.SetExtension(d.Options, openapi_options.E_Openapiv2Schema, schema)
- }
- }
- if test.openAPIOptions != nil {
- if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil {
- t.Fatalf("failed to register OpenAPI options: %s", err)
- }
- }
- refs := make(refMap)
- actual := make(openapiDefinitionsObject)
- if err := renderMessagesAsDefinition(msgMap, actual, reg, refs, test.pathParams); err != nil {
- t.Errorf("renderMessagesAsDefinition failed with: %s", err)
- }
- if !reflect.DeepEqual(actual, test.defs) {
- t.Errorf("Expected renderMessagesAsDefinition() to add defs %+v, not %+v", test.defs, actual)
- }
- })
- }
- }
- func TestUpdateOpenAPIDataFromComments(t *testing.T) {
- tests := []struct {
- descr string
- openapiSwaggerObject interface{}
- comments string
- expectedError error
- expectedOpenAPIObject interface{}
- useGoTemplate bool
- goTemplateArgs []string
- }{
- {
- descr: "empty comments",
- openapiSwaggerObject: nil,
- expectedOpenAPIObject: nil,
- comments: "",
- expectedError: nil,
- },
- {
- descr: "set field to read only",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- ReadOnly: true,
- Description: "... Output only. ...",
- },
- comments: "... Output only. ...",
- expectedError: nil,
- },
- {
- descr: "set title",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Title: "Comment with no trailing dot",
- },
- comments: "Comment with no trailing dot",
- expectedError: nil,
- },
- {
- descr: "set description",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Description: "Comment with trailing dot.",
- },
- comments: "Comment with trailing dot.",
- expectedError: nil,
- },
- {
- descr: "use info object",
- openapiSwaggerObject: &openapiSwaggerObject{
- Info: openapiInfoObject{},
- },
- expectedOpenAPIObject: &openapiSwaggerObject{
- Info: openapiInfoObject{
- Description: "Comment with trailing dot.",
- },
- },
- comments: "Comment with trailing dot.",
- expectedError: nil,
- },
- {
- descr: "multi line comment with title",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Title: "First line",
- Description: "Second line",
- },
- comments: "First line\n\nSecond line",
- expectedError: nil,
- },
- {
- descr: "multi line comment no title",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Description: "First line.\n\nSecond line",
- },
- comments: "First line.\n\nSecond line",
- expectedError: nil,
- },
- {
- descr: "multi line comment with summary with dot",
- openapiSwaggerObject: &openapiOperationObject{},
- expectedOpenAPIObject: &openapiOperationObject{
- Summary: "First line.",
- Description: "Second line",
- },
- comments: "First line.\n\nSecond line",
- expectedError: nil,
- },
- {
- descr: "multi line comment with summary no dot",
- openapiSwaggerObject: &openapiOperationObject{},
- expectedOpenAPIObject: &openapiOperationObject{
- Summary: "First line",
- Description: "Second line",
- },
- comments: "First line\n\nSecond line",
- expectedError: nil,
- },
- {
- descr: "multi line comment with summary no dot",
- openapiSwaggerObject: &schemaCore{},
- expectedOpenAPIObject: &schemaCore{},
- comments: "Any comment",
- expectedError: errors.New("no description nor summary property"),
- },
- {
- descr: "without use_go_template",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Title: "First line",
- Description: "{{import \"documentation.md\"}}",
- },
- comments: "First line\n\n{{import \"documentation.md\"}}",
- expectedError: nil,
- },
- {
- descr: "error with use_go_template",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Title: "First line",
- Description: "open noneexistingfile.txt: no such file or directory",
- },
- comments: "First line\n\n{{import \"noneexistingfile.txt\"}}",
- expectedError: nil,
- useGoTemplate: true,
- },
- {
- descr: "template with use_go_template",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Title: "Template",
- Description: `Description "which means nothing"`,
- },
- comments: "Template\n\nDescription {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- expectedError: nil,
- useGoTemplate: true,
- },
- {
- descr: "template with use_go_template and go_template_args",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Title: "Template",
- Description: `Description "which means nothing" for environment test with value my_value`,
- },
- comments: "Template\n\nDescription {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} for " +
- "environment {{arg \"environment\"}} with value {{arg \"my_key\"}}",
- expectedError: nil,
- useGoTemplate: true,
- goTemplateArgs: []string{"my_key=my_value", "environment=test"},
- },
- {
- descr: "template with use_go_template and undefined go_template_args",
- openapiSwaggerObject: &openapiSchemaObject{},
- expectedOpenAPIObject: &openapiSchemaObject{
- Title: "Template",
- Description: `Description "which means nothing" for environment test with value ` +
- `goTemplateArg something_undefined not found`,
- },
- comments: "Template\n\nDescription {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} for " +
- "environment {{arg \"environment\"}} with value {{arg \"something_undefined\"}}",
- expectedError: nil,
- useGoTemplate: true,
- goTemplateArgs: []string{"environment=test"},
- },
- }
- for _, test := range tests {
- t.Run(test.descr, func(t *testing.T) {
- reg := descriptor.NewRegistry()
- if test.useGoTemplate {
- reg.SetUseGoTemplate(true)
- }
- if len(test.goTemplateArgs) > 0 {
- reg.SetGoTemplateArgs(test.goTemplateArgs)
- }
- err := updateOpenAPIDataFromComments(reg, test.openapiSwaggerObject, nil, test.comments, false)
- if test.expectedError == nil {
- if err != nil {
- t.Errorf("unexpected error '%v'", err)
- }
- if !reflect.DeepEqual(test.openapiSwaggerObject, test.expectedOpenAPIObject) {
- t.Errorf("openapiSwaggerObject was not updated correctly, expected '%+v', got '%+v'", test.expectedOpenAPIObject, test.openapiSwaggerObject)
- }
- } else {
- if err == nil {
- t.Error("expected update error not returned")
- }
- if !reflect.DeepEqual(test.openapiSwaggerObject, test.expectedOpenAPIObject) {
- t.Errorf("openapiSwaggerObject was not updated correctly, expected '%+v', got '%+v'", test.expectedOpenAPIObject, test.openapiSwaggerObject)
- }
- if err.Error() != test.expectedError.Error() {
- t.Errorf("expected error malformed, expected %q, got %q", test.expectedError.Error(), err.Error())
- }
- }
- })
- }
- }
- func TestMessageOptionsWithGoTemplate(t *testing.T) {
- tests := []struct {
- descr string
- msgDescs []*descriptorpb.DescriptorProto
- schema map[string]*openapi_options.Schema // per-message schema to add
- defs openapiDefinitionsObject
- openAPIOptions *openapiconfig.OpenAPIOptions
- useGoTemplate bool
- goTemplateArgs []string
- }{
- {
- descr: "external docs option",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "{{.Name}}",
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- },
- ExternalDocs: &openapi_options.ExternalDocumentation{
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "Message",
- Description: `Description "which means nothing"`,
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: `Description "which means nothing"`,
- },
- },
- },
- useGoTemplate: true,
- },
- {
- descr: "external docs option",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "{{.Name}}",
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- },
- ExternalDocs: &openapi_options.ExternalDocumentation{
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "{{.Name}}",
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- },
- },
- },
- useGoTemplate: false,
- },
- {
- descr: "external docs option with go template args",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- schema: map[string]*openapi_options.Schema{
- "Message": {
- JsonSchema: &openapi_options.JSONSchema{
- Title: "{{.Name}}",
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} " +
- "{{arg \"my_key\"}}",
- },
- ExternalDocs: &openapi_options.ExternalDocumentation{
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} " +
- "{{arg \"my_key\"}}",
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "Message",
- Description: `Description "which means nothing" too`,
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: `Description "which means nothing" too`,
- },
- },
- },
- useGoTemplate: true,
- goTemplateArgs: []string{"my_key=too"},
- },
- {
- descr: "registered OpenAPIOption",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Message: []*openapiconfig.OpenAPIMessageOption{
- {
- Message: "example.Message",
- Option: &openapi_options.Schema{
- JsonSchema: &openapi_options.JSONSchema{
- Title: "{{.Name}}",
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- },
- ExternalDocs: &openapi_options.ExternalDocumentation{
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}}",
- },
- },
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "Message",
- Description: `Description "which means nothing"`,
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: `Description "which means nothing"`,
- },
- },
- },
- useGoTemplate: true,
- },
- {
- descr: "registered OpenAPIOption with go template args",
- msgDescs: []*descriptorpb.DescriptorProto{
- {Name: proto.String("Message")},
- },
- openAPIOptions: &openapiconfig.OpenAPIOptions{
- Message: []*openapiconfig.OpenAPIMessageOption{
- {
- Message: "example.Message",
- Option: &openapi_options.Schema{
- JsonSchema: &openapi_options.JSONSchema{
- Title: "{{.Name}}",
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} " +
- "{{arg \"my_key\"}}",
- },
- ExternalDocs: &openapi_options.ExternalDocumentation{
- Description: "Description {{with \"which means nothing\"}}{{printf \"%q\" .}}{{end}} " +
- "{{arg \"my_key\"}}",
- },
- },
- },
- },
- },
- defs: map[string]openapiSchemaObject{
- "Message": {
- schemaCore: schemaCore{
- Type: "object",
- },
- Title: "Message",
- Description: `Description "which means nothing" too`,
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: `Description "which means nothing" too`,
- },
- },
- },
- useGoTemplate: true,
- goTemplateArgs: []string{"my_key=too"},
- },
- }
- for _, test := range tests {
- t.Run(test.descr, func(t *testing.T) {
- msgs := []*descriptor.Message{}
- for _, msgdesc := range test.msgDescs {
- msgdesc.Options = &descriptorpb.MessageOptions{}
- msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
- }
- reg := descriptor.NewRegistry()
- reg.SetUseGoTemplate(test.useGoTemplate)
- reg.SetGoTemplateArgs(test.goTemplateArgs)
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: test.msgDescs,
- EnumType: []*descriptorpb.EnumDescriptorProto{},
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- Messages: msgs,
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- msgMap := map[string]*descriptor.Message{}
- for _, d := range test.msgDescs {
- name := d.GetName()
- msg, err := reg.LookupMsg("example", name)
- if err != nil {
- t.Fatalf("lookup message %v: %v", name, err)
- }
- msgMap[msg.FQMN()] = msg
- if schema, ok := test.schema[name]; ok {
- proto.SetExtension(d.Options, openapi_options.E_Openapiv2Schema, schema)
- }
- }
- if test.openAPIOptions != nil {
- if err := reg.RegisterOpenAPIOptions(test.openAPIOptions); err != nil {
- t.Fatalf("failed to register OpenAPI options: %s", err)
- }
- }
- refs := make(refMap)
- actual := make(openapiDefinitionsObject)
- if err := renderMessagesAsDefinition(msgMap, actual, reg, refs, nil); err != nil {
- t.Errorf("renderMessagesAsDefinition failed with: %s", err)
- }
- if !reflect.DeepEqual(actual, test.defs) {
- t.Errorf("Expected renderMessagesAsDefinition() to add defs %+v, not %+v", test.defs, actual)
- }
- })
- }
- }
- func TestTagsWithGoTemplate(t *testing.T) {
- reg := descriptor.NewRegistry()
- reg.SetUseGoTemplate(true)
- reg.SetGoTemplateArgs([]string{"my_key=my_value"})
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Options: &descriptorpb.ServiceOptions{},
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{},
- MessageType: []*descriptorpb.DescriptorProto{},
- EnumType: []*descriptorpb.EnumDescriptorProto{},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- Messages: []*descriptor.Message{},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- },
- },
- }
- // Set tag through service extension
- proto.SetExtension(file.GetService()[0].Options, openapi_options.E_Openapiv2Tag, &openapi_options.Tag{
- Name: "service tag",
- Description: "{{ .Name }}!"})
- // Set tags through file extension
- swagger := openapi_options.Swagger{
- Tags: []*openapi_options.Tag{
- {
- Name: "not a service tag",
- Description: "{{ import \"file\" }}",
- },
- {
- Name: "ExampleService",
- Description: "ExampleService!",
- },
- {
- Name: "not a service tag 2",
- Description: "{{ import \"file\" }}",
- },
- {
- Name: "Service with my_key",
- Description: "the {{arg \"my_key\"}}",
- },
- },
- }
- proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), openapi_options.E_Openapiv2Swagger, &swagger)
- actual, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- expectedTags := []openapiTagObject{
- {
- Name: "not a service tag",
- Description: "open file: no such file or directory",
- },
- {
- Name: "ExampleService",
- Description: "ExampleService!",
- },
- {
- Name: "not a service tag 2",
- Description: "open file: no such file or directory",
- },
- {
- Name: "Service with my_key",
- Description: "the my_value",
- },
- {
- Name: "service tag",
- Description: "ExampleService!",
- },
- }
- if !reflect.DeepEqual(actual.Tags, expectedTags) {
- t.Errorf("Expected tags %+v, not %+v", expectedTags, actual.Tags)
- }
- }
- func TestTemplateWithoutErrorDefinition(t *testing.T) {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{},
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Echo"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo",
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
- if err != nil {
- t.Errorf("failed to reg.Load(): %v", err)
- return
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- defRsp, ok := result.getPathItemObject("/v1/echo").Post.Responses["default"]
- if !ok {
- return
- }
- ref := defRsp.Schema.schemaCore.Ref
- refName := strings.TrimPrefix(ref, "#/definitions/")
- if refName == "" {
- t.Fatal("created default Error response with empty reflink")
- }
- if _, ok := result.Definitions[refName]; !ok {
- t.Errorf("default Error response with reflink '%v', but its definition was not found", refName)
- }
- }
- func TestSingleServiceTemplateWithDuplicateHttp1Operations(t *testing.T) {
- fieldType := descriptorpb.FieldDescriptorProto_TYPE_STRING
- field1 := &descriptorpb.FieldDescriptorProto{
- Name: proto.String("name"),
- Number: proto.Int32(1),
- Type: &fieldType,
- }
- getFooMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("GetFooRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- },
- }
- getFooMsg := &descriptor.Message{
- DescriptorProto: getFooMsgDesc,
- }
- deleteFooMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("DeleteFooRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- },
- }
- deleteFooMsg := &descriptor.Message{
- DescriptorProto: deleteFooMsgDesc,
- }
- getFoo := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("GetFoo"),
- InputType: proto.String("GetFooRequest"),
- OutputType: proto.String("EmptyMessage"),
- }
- deleteFoo := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("DeleteFoo"),
- InputType: proto.String("DeleteFooRequest"),
- OutputType: proto.String("EmptyMessage"),
- }
- getBarMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("GetBarRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- },
- }
- getBarMsg := &descriptor.Message{
- DescriptorProto: getBarMsgDesc,
- }
- deleteBarMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("DeleteBarRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- },
- }
- deleteBarMsg := &descriptor.Message{
- DescriptorProto: deleteBarMsgDesc,
- }
- getBar := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("GetBar"),
- InputType: proto.String("GetBarRequest"),
- OutputType: proto.String("EmptyMessage"),
- }
- deleteBar := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("DeleteBar"),
- InputType: proto.String("DeleteBarRequest"),
- OutputType: proto.String("EmptyMessage"),
- }
- svc1 := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("Service1"),
- Method: []*descriptorpb.MethodDescriptorProto{getFoo, deleteFoo, getBar, deleteBar},
- }
- emptyMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("EmptyMessage"),
- }
- emptyMsg := &descriptor.Message{
- DescriptorProto: emptyMsgDesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("service1.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{getBarMsgDesc, deleteBarMsgDesc, getFooMsgDesc, deleteFooMsgDesc, emptyMsgDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc1},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{getFooMsg, deleteFooMsg, getBarMsg, deleteBarMsg, emptyMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc1,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: getFoo,
- RequestType: getFooMsg,
- ResponseType: getFooMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=foos/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: getFooMsg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- {
- MethodDescriptorProto: deleteFoo,
- RequestType: deleteFooMsg,
- ResponseType: emptyMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "DELETE",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=foos/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: deleteFooMsg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- {
- MethodDescriptorProto: getBar,
- RequestType: getBarMsg,
- ResponseType: getBarMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=bars/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: getBarMsg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- {
- MethodDescriptorProto: deleteBar,
- RequestType: deleteBarMsg,
- ResponseType: emptyMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "DELETE",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=bars/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: deleteBarMsg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- err := reg.Load(reqFromFile(&file))
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- if got, want := len(result.Paths), 2; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- firstOpGet := result.getPathItemObject("/v1/{name}").Get
- if got, want := firstOpGet.OperationID, "Service1_GetFoo"; got != want {
- t.Fatalf("First operation GET id differed, got %s want %s", got, want)
- }
- if got, want := len(firstOpGet.Parameters), 2; got != want {
- t.Fatalf("First operation GET params length differed, got %d want %d", got, want)
- }
- if got, want := firstOpGet.Parameters[0].Name, "name"; got != want {
- t.Fatalf("First operation GET first param name differed, got %s want %s", got, want)
- }
- if got, want := firstOpGet.Parameters[0].Pattern, "foos/[^/]+"; got != want {
- t.Fatalf("First operation GET first param pattern differed, got %s want %s", got, want)
- }
- if got, want := firstOpGet.Parameters[1].In, "body"; got != want {
- t.Fatalf("First operation GET second param 'in' differed, got %s want %s", got, want)
- }
- firstOpDelete := result.getPathItemObject("/v1/{name}").Delete
- if got, want := firstOpDelete.OperationID, "Service1_DeleteFoo"; got != want {
- t.Fatalf("First operation id DELETE differed, got %s want %s", got, want)
- }
- if got, want := len(firstOpDelete.Parameters), 2; got != want {
- t.Fatalf("First operation DELETE params length differed, got %d want %d", got, want)
- }
- if got, want := firstOpDelete.Parameters[0].Name, "name"; got != want {
- t.Fatalf("First operation DELETE first param name differed, got %s want %s", got, want)
- }
- if got, want := firstOpDelete.Parameters[0].Pattern, "foos/[^/]+"; got != want {
- t.Fatalf("First operation DELETE first param pattern differed, got %s want %s", got, want)
- }
- if got, want := firstOpDelete.Parameters[1].In, "body"; got != want {
- t.Fatalf("First operation DELETE second param 'in' differed, got %s want %s", got, want)
- }
- secondOpGet := result.getPathItemObject("/v1/{name" + pathParamUniqueSuffixDeliminator + "1}").Get
- if got, want := secondOpGet.OperationID, "Service1_GetBar"; got != want {
- t.Fatalf("Second operation id GET differed, got %s want %s", got, want)
- }
- if got, want := len(secondOpGet.Parameters), 2; got != want {
- t.Fatalf("Second operation GET params length differed, got %d want %d", got, want)
- }
- if got, want := secondOpGet.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"1"; got != want {
- t.Fatalf("Second operation GET first param name differed, got %s want %s", got, want)
- }
- if got, want := secondOpGet.Parameters[0].Pattern, "bars/[^/]+"; got != want {
- t.Fatalf("Second operation GET first param pattern differed, got %s want %s", got, want)
- }
- if got, want := secondOpGet.Parameters[1].In, "body"; got != want {
- t.Fatalf("Second operation GET second param 'in' differed, got %s want %s", got, want)
- }
- secondOpDelete := result.getPathItemObject("/v1/{name" + pathParamUniqueSuffixDeliminator + "1}").Delete
- if got, want := secondOpDelete.OperationID, "Service1_DeleteBar"; got != want {
- t.Fatalf("Second operation id differed, got %s want %s", got, want)
- }
- if got, want := len(secondOpDelete.Parameters), 2; got != want {
- t.Fatalf("Second operation params length differed, got %d want %d", got, want)
- }
- if got, want := secondOpDelete.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"1"; got != want {
- t.Fatalf("Second operation first param name differed, got %s want %s", got, want)
- }
- if got, want := secondOpDelete.Parameters[0].Pattern, "bars/[^/]+"; got != want {
- t.Fatalf("Second operation first param pattern differed, got %s want %s", got, want)
- }
- if got, want := secondOpDelete.Parameters[1].In, "body"; got != want {
- t.Fatalf("Second operation third param 'in' differed, got %s want %s", got, want)
- }
- }
- func getOperation(pathItem openapiPathItemObject, httpMethod string) *openapiOperationObject {
- switch httpMethod {
- case "GET":
- return pathItem.Get
- case "POST":
- return pathItem.Post
- case "PUT":
- return pathItem.Put
- case "DELETE":
- return pathItem.Delete
- case "PATCH":
- return pathItem.Patch
- case "HEAD":
- return pathItem.Head
- case "OPTIONS":
- return pathItem.Options
- default:
- return nil
- }
- }
- func TestSingleServiceTemplateWithDuplicateInAllSupportedHttp1Operations(t *testing.T) {
- supportedMethods := []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}
- for _, method := range supportedMethods {
- fieldType := descriptorpb.FieldDescriptorProto_TYPE_STRING
- field1 := &descriptorpb.FieldDescriptorProto{
- Name: proto.String("name"),
- Number: proto.Int32(1),
- Type: &fieldType,
- }
- methodFooMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String(method + "FooRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- },
- }
- methodFooMsg := &descriptor.Message{
- DescriptorProto: methodFooMsgDesc,
- }
- methodFoo := &descriptorpb.MethodDescriptorProto{
- Name: proto.String(method + "Foo"),
- InputType: proto.String(method + "FooRequest"),
- OutputType: proto.String("EmptyMessage"),
- }
- methodBarMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String(method + "BarRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- },
- }
- methodBarMsg := &descriptor.Message{
- DescriptorProto: methodBarMsgDesc,
- }
- methodBar := &descriptorpb.MethodDescriptorProto{
- Name: proto.String(method + "Bar"),
- InputType: proto.String(method + "BarRequest"),
- OutputType: proto.String("EmptyMessage"),
- }
- svc1 := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("Service1"),
- Method: []*descriptorpb.MethodDescriptorProto{methodFoo, methodBar},
- }
- emptyMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("EmptyMessage"),
- }
- emptyMsg := &descriptor.Message{
- DescriptorProto: emptyMsgDesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("service1.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{methodBarMsgDesc, methodFooMsgDesc, emptyMsgDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc1},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{methodFooMsg, methodBarMsg, emptyMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc1,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: methodFoo,
- RequestType: methodFooMsg,
- ResponseType: methodFooMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: method,
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=foos/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: methodFooMsg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- {
- MethodDescriptorProto: methodBar,
- RequestType: methodBarMsg,
- ResponseType: methodBarMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: method,
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=bars/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: methodBarMsg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- err := reg.Load(reqFromFile(&file))
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- if got, want := len(result.Paths), 2; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- firstOpMethod := getOperation(result.getPathItemObject("/v1/{name}"), method)
- if got, want := firstOpMethod.OperationID, "Service1_"+method+"Foo"; got != want {
- t.Fatalf("First operation %s id differed, got %s want %s", method, got, want)
- }
- if got, want := len(firstOpMethod.Parameters), 2; got != want {
- t.Fatalf("First operation %s params length differed, got %d want %d", method, got, want)
- }
- if got, want := firstOpMethod.Parameters[0].Name, "name"; got != want {
- t.Fatalf("First operation %s first param name differed, got %s want %s", method, got, want)
- }
- if got, want := firstOpMethod.Parameters[0].Pattern, "foos/[^/]+"; got != want {
- t.Fatalf("First operation %s first param pattern differed, got %s want %s", method, got, want)
- }
- if got, want := firstOpMethod.Parameters[1].In, "body"; got != want {
- t.Fatalf("First operation %s second param 'in' differed, got %s want %s", method, got, want)
- }
- secondOpMethod := getOperation(result.getPathItemObject("/v1/{name"+pathParamUniqueSuffixDeliminator+"1}"), method)
- if got, want := secondOpMethod.OperationID, "Service1_"+method+"Bar"; got != want {
- t.Fatalf("Second operation id %s differed, got %s want %s", method, got, want)
- }
- if got, want := len(secondOpMethod.Parameters), 2; got != want {
- t.Fatalf("Second operation %s params length differed, got %d want %d", method, got, want)
- }
- if got, want := secondOpMethod.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"1"; got != want {
- t.Fatalf("Second operation %s first param name differed, got %s want %s", method, got, want)
- }
- if got, want := secondOpMethod.Parameters[0].Pattern, "bars/[^/]+"; got != want {
- t.Fatalf("Second operation %s first param pattern differed, got %s want %s", method, got, want)
- }
- if got, want := secondOpMethod.Parameters[1].In, "body"; got != want {
- t.Fatalf("Second operation %s second param 'in' differed, got %s want %s", method, got, want)
- }
- }
- }
- func TestSingleServiceTemplateWithDuplicateHttp1UnsupportedOperations(t *testing.T) {
- fieldType := descriptorpb.FieldDescriptorProto_TYPE_STRING
- field1 := &descriptorpb.FieldDescriptorProto{
- Name: proto.String("name"),
- Number: proto.Int32(1),
- Type: &fieldType,
- }
- unsupportedFooMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("UnsupportedFooRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- },
- }
- unsupportedFooMsg := &descriptor.Message{
- DescriptorProto: unsupportedFooMsgDesc,
- }
- unsupportedFoo := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("UnsupportedFoo"),
- InputType: proto.String("UnsupportedFooRequest"),
- OutputType: proto.String("EmptyMessage"),
- }
- unsupportedBarMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("UnsupportedBarRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- },
- }
- unsupportedBarMsg := &descriptor.Message{
- DescriptorProto: unsupportedBarMsgDesc,
- }
- unsupportedBar := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("UnsupportedBar"),
- InputType: proto.String("UnsupportedBarRequest"),
- OutputType: proto.String("EmptyMessage"),
- }
- svc1 := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("Service1"),
- Method: []*descriptorpb.MethodDescriptorProto{unsupportedFoo, unsupportedBar},
- }
- emptyMsgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("EmptyMessage"),
- }
- emptyMsg := &descriptor.Message{
- DescriptorProto: emptyMsgDesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("service1.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{unsupportedBarMsgDesc, unsupportedFooMsgDesc, emptyMsgDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc1},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{unsupportedFooMsg, unsupportedBarMsg, emptyMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc1,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: unsupportedFoo,
- RequestType: unsupportedFooMsg,
- ResponseType: unsupportedFooMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "UNSUPPORTED",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=foos/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: unsupportedFooMsg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- {
- MethodDescriptorProto: unsupportedBar,
- RequestType: unsupportedBarMsg,
- ResponseType: unsupportedBarMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "UNSUPPORTED",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=bars/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: unsupportedBarMsg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- err := reg.Load(reqFromFile(&file))
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- // Just should not crash, no special handling of unsupported HTTP methods
- if got, want := len(result.Paths), 1; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- }
- func TestTemplateWithDuplicateHttp1Operations(t *testing.T) {
- fieldType := descriptorpb.FieldDescriptorProto_TYPE_STRING
- field1 := &descriptorpb.FieldDescriptorProto{
- Name: proto.String("name"),
- Number: proto.Int32(1),
- Type: &fieldType,
- }
- field2 := &descriptorpb.FieldDescriptorProto{
- Name: proto.String("role"),
- Number: proto.Int32(2),
- Type: &fieldType,
- }
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- field1,
- field2,
- },
- }
- meth1 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Method1"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- meth2 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Method2"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- svc1 := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("Service1"),
- Method: []*descriptorpb.MethodDescriptorProto{meth1, meth2},
- }
- meth3 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Method3"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- meth4 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Method4"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- svc2 := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("Service2"),
- Method: []*descriptorpb.MethodDescriptorProto{meth3, meth4},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("service1.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc1, svc2},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc1,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth1,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=organizations/*}/{role=roles/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: msg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field2,
- Message: msg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "role",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- {
- MethodDescriptorProto: meth2,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=users/*}/{role=roles/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: msg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field2,
- Message: msg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "role",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- {
- ServiceDescriptorProto: svc2,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth3,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=users/*}/roles",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: msg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- {
- MethodDescriptorProto: meth4,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/{name=groups/*}/{role=roles/*}",
- },
- PathParams: []descriptor.Parameter{
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field1,
- Message: msg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "name",
- },
- },
- },
- {
- Target: &descriptor.Field{
- FieldDescriptorProto: field2,
- Message: msg,
- },
- FieldPath: descriptor.FieldPath{
- {
- Name: "role",
- },
- },
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- err := reg.Load(reqFromFile(&file))
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- if got, want := len(result.Paths), 4; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- firstOp := result.getPathItemObject("/v1/{name}/{role}").Get
- if got, want := firstOp.OperationID, "Service1_Method1"; got != want {
- t.Fatalf("First operation id differed, got %s want %s", got, want)
- }
- if got, want := len(firstOp.Parameters), 3; got != want {
- t.Fatalf("First operation params length differed, got %d want %d", got, want)
- }
- if got, want := firstOp.Parameters[0].Name, "name"; got != want {
- t.Fatalf("First operation first param name differed, got %s want %s", got, want)
- }
- if got, want := firstOp.Parameters[0].Pattern, "organizations/[^/]+"; got != want {
- t.Fatalf("First operation first param pattern differed, got %s want %s", got, want)
- }
- if got, want := firstOp.Parameters[1].Name, "role"; got != want {
- t.Fatalf("First operation second param name differed, got %s want %s", got, want)
- }
- if got, want := firstOp.Parameters[1].Pattern, "roles/[^/]+"; got != want {
- t.Fatalf("First operation second param pattern differed, got %s want %s", got, want)
- }
- if got, want := firstOp.Parameters[2].In, "body"; got != want {
- t.Fatalf("First operation third param 'in' differed, got %s want %s", got, want)
- }
- secondOp := result.getPathItemObject("/v1/{name" + pathParamUniqueSuffixDeliminator + "1}/{role}").Get
- if got, want := secondOp.OperationID, "Service1_Method2"; got != want {
- t.Fatalf("Second operation id differed, got %s want %s", got, want)
- }
- if got, want := len(secondOp.Parameters), 3; got != want {
- t.Fatalf("Second operation params length differed, got %d want %d", got, want)
- }
- if got, want := secondOp.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"1"; got != want {
- t.Fatalf("Second operation first param name differed, got %s want %s", got, want)
- }
- if got, want := secondOp.Parameters[0].Pattern, "users/[^/]+"; got != want {
- t.Fatalf("Second operation first param pattern differed, got %s want %s", got, want)
- }
- if got, want := secondOp.Parameters[1].Name, "role"; got != want {
- t.Fatalf("Second operation second param name differed, got %s want %s", got, want)
- }
- if got, want := secondOp.Parameters[1].Pattern, "roles/[^/]+"; got != want {
- t.Fatalf("Second operation second param pattern differed, got %s want %s", got, want)
- }
- if got, want := secondOp.Parameters[2].In, "body"; got != want {
- t.Fatalf("Second operation third param 'in' differed, got %s want %s", got, want)
- }
- thirdOp := result.getPathItemObject("/v1/{name}/roles").Get
- if got, want := thirdOp.OperationID, "Service2_Method3"; got != want {
- t.Fatalf("Third operation id differed, got %s want %s", got, want)
- }
- if got, want := len(thirdOp.Parameters), 2; got != want {
- t.Fatalf("Third operation params length differed, got %d want %d", got, want)
- }
- if got, want := thirdOp.Parameters[0].Name, "name"; got != want {
- t.Fatalf("Third operation first param name differed, got %s want %s", got, want)
- }
- if got, want := thirdOp.Parameters[0].Pattern, "users/[^/]+"; got != want {
- t.Fatalf("Third operation first param pattern differed, got %s want %s", got, want)
- }
- if got, want := thirdOp.Parameters[1].In, "body"; got != want {
- t.Fatalf("Third operation second param 'in' differed, got %s want %s", got, want)
- }
- forthOp := result.getPathItemObject("/v1/{name" + pathParamUniqueSuffixDeliminator + "2}/{role}").Get
- if got, want := forthOp.OperationID, "Service2_Method4"; got != want {
- t.Fatalf("Fourth operation id differed, got %s want %s", got, want)
- }
- if got, want := len(forthOp.Parameters), 3; got != want {
- t.Fatalf("Fourth operation params length differed, got %d want %d", got, want)
- }
- if got, want := forthOp.Parameters[0].Name, "name"+pathParamUniqueSuffixDeliminator+"2"; got != want {
- t.Fatalf("Fourth operation first param name differed, got %s want %s", got, want)
- }
- if got, want := forthOp.Parameters[0].Pattern, "groups/[^/]+"; got != want {
- t.Fatalf("Fourth operation first param pattern differed, got %s want %s", got, want)
- }
- if got, want := forthOp.Parameters[1].Name, "role"; got != want {
- t.Fatalf("Fourth operation second param name differed, got %s want %s", got, want)
- }
- if got, want := forthOp.Parameters[1].Pattern, "roles/[^/]+"; got != want {
- t.Fatalf("Fourth operation second param pattern differed, got %s want %s", got, want)
- }
- if got, want := forthOp.Parameters[2].In, "body"; got != want {
- t.Fatalf("Fourth operation second param 'in' differed, got %s want %s", got, want)
- }
- }
- func Test_getReservedJsonName(t *testing.T) {
- type args struct {
- fieldName string
- messageNameToFieldsToJSONName map[string]map[string]string
- fieldNameToType map[string]string
- }
- tests := []struct {
- name string
- args args
- want string
- }{
- {
- "test case 1: single dot use case",
- args{
- fieldName: "abc.a_1",
- messageNameToFieldsToJSONName: map[string]map[string]string{
- "Msg": {
- "a_1": "a1JSONNAME",
- "b_1": "b1JSONNAME",
- },
- },
- fieldNameToType: map[string]string{
- "abc": "pkg1.test.Msg",
- "bcd": "pkg1.test.Msg",
- },
- },
- "a1JSONNAME",
- },
- {
- "test case 2: single dot use case with no existing field",
- args{
- fieldName: "abc.d_1",
- messageNameToFieldsToJSONName: map[string]map[string]string{
- "Msg": {
- "a_1": "a1JSONNAME",
- "b_1": "b1JSONNAME",
- },
- },
- fieldNameToType: map[string]string{
- "abc": "pkg1.test.Msg",
- "bcd": "pkg1.test.Msg",
- },
- },
- "",
- },
- {
- "test case 3: double dot use case",
- args{
- fieldName: "pkg.abc.a_1",
- messageNameToFieldsToJSONName: map[string]map[string]string{
- "Msg": {
- "a_1": "a1JSONNAME",
- "b_1": "b1JSONNAME",
- },
- },
- fieldNameToType: map[string]string{
- "abc": "pkg1.test.Msg",
- "bcd": "pkg1.test.Msg",
- },
- },
- "a1JSONNAME",
- },
- {
- "test case 4: double dot use case with a not existed field",
- args{
- fieldName: "pkg.abc.c_1",
- messageNameToFieldsToJSONName: map[string]map[string]string{
- "Msg": {
- "a_1": "a1JSONNAME",
- "b_1": "b1JSONNAME",
- },
- },
- fieldNameToType: map[string]string{
- "abc": "pkg1.test.Msg",
- "bcd": "pkg1.test.Msg",
- },
- },
- "",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := getReservedJSONName(tt.args.fieldName, tt.args.messageNameToFieldsToJSONName, tt.args.fieldNameToType); got != tt.want {
- t.Errorf("getReservedJSONName() = %v, want %v", got, tt.want)
- }
- })
- }
- }
- func TestParseIncompleteSecurityRequirement(t *testing.T) {
- swagger := openapi_options.Swagger{
- Security: []*openapi_options.SecurityRequirement{
- {
- SecurityRequirement: map[string]*openapi_options.SecurityRequirement_SecurityRequirementValue{
- "key": nil,
- },
- },
- },
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- }
- proto.SetExtension(proto.Message(file.FileDescriptorProto.Options), openapi_options.E_Openapiv2Swagger, &swagger)
- reg := descriptor.NewRegistry()
- err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
- if err != nil {
- t.Errorf("failed to reg.Load(): %v", err)
- return
- }
- _, err = applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err == nil {
- t.Errorf("applyTemplate(%#v) did not error as expected", file)
- return
- }
- }
- func TestSubPathParams(t *testing.T) {
- outerParams := []descriptor.Parameter{
- {
- FieldPath: []descriptor.FieldPathComponent{
- {
- Name: "prefix",
- },
- {
- Name: "first",
- },
- },
- },
- {
- FieldPath: []descriptor.FieldPathComponent{
- {
- Name: "prefix",
- },
- {
- Name: "second",
- },
- {
- Name: "deeper",
- },
- },
- },
- {
- FieldPath: []descriptor.FieldPathComponent{
- {
- Name: "otherprefix",
- },
- {
- Name: "third",
- },
- },
- },
- }
- subParams := subPathParams("prefix", outerParams)
- if got, want := len(subParams), 2; got != want {
- t.Fatalf("Wrong number of path params, got %d want %d", got, want)
- }
- if got, want := len(subParams[0].FieldPath), 1; got != want {
- t.Fatalf("Wrong length of path param 0, got %d want %d", got, want)
- }
- if got, want := subParams[0].FieldPath[0].Name, "first"; got != want {
- t.Fatalf("Wrong path param 0, element 0, got %s want %s", got, want)
- }
- if got, want := len(subParams[1].FieldPath), 2; got != want {
- t.Fatalf("Wrong length of path param 1 got %d want %d", got, want)
- }
- if got, want := subParams[1].FieldPath[0].Name, "second"; got != want {
- t.Fatalf("Wrong path param 1, element 0, got %s want %s", got, want)
- }
- if got, want := subParams[1].FieldPath[1].Name, "deeper"; got != want {
- t.Fatalf("Wrong path param 1, element 1, got %s want %s", got, want)
- }
- }
- func TestRenderServicesParameterDescriptionNoFieldBody(t *testing.T) {
- optionsRaw :=
- `{
- "[grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema]": {
- "jsonSchema": {
- "title": "aMessage title",
- "description": "aMessage description"
- }
- }
- }`
- options := &descriptorpb.MessageOptions{}
- err := protojson.Unmarshal([]byte(optionsRaw), options)
- if err != nil {
- t.Fatalf("Error while unmarshalling options: %s", err.Error())
- }
- aMessageDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("AMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("project_id"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("other_field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- Options: options,
- }
- someResponseDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("SomeResponse"),
- }
- aMeth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("AMethod"),
- InputType: proto.String("AMessage"),
- OutputType: proto.String("SomeResponse"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("Test"),
- Method: []*descriptorpb.MethodDescriptorProto{aMeth},
- }
- aMessage := &descriptor.Message{
- DescriptorProto: aMessageDesc,
- }
- someResponseMessage := &descriptor.Message{
- DescriptorProto: someResponseDesc,
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("api"),
- Name: proto.String("test.proto"),
- MessageType: []*descriptorpb.DescriptorProto{aMessageDesc, someResponseDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{aMessage, someResponseMessage},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: aMeth,
- RequestType: aMessage,
- ResponseType: someResponseMessage,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/projects/someotherpath",
- },
- Body: &descriptor.Body{},
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- err = reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- got := result.getPathItemObject("/v1/projects/someotherpath").Post.Parameters[0].Description
- want := "aMessage description"
- if got != want {
- t.Fatalf("Wrong description for body parameter, got %s want %s", got, want)
- }
- }
- func TestRenderServicesWithBodyFieldNameInCamelCase(t *testing.T) {
- userDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("User"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("name"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("role"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- }
- updateDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("UpdateUserRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("user_object"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.User"),
- Number: proto.Int32(1),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("UpdateUser"),
- InputType: proto.String("UpdateUserRequest"),
- OutputType: proto.String("User"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("UserService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- userMsg := &descriptor.Message{
- DescriptorProto: userDesc,
- }
- updateMsg := &descriptor.Message{
- DescriptorProto: updateDesc,
- }
- nameField := &descriptor.Field{
- Message: userMsg,
- FieldDescriptorProto: userMsg.GetField()[0],
- }
- nameField.JsonName = proto.String("name")
- roleField := &descriptor.Field{
- Message: userMsg,
- FieldDescriptorProto: userMsg.GetField()[1],
- }
- roleField.JsonName = proto.String("role")
- userMsg.Fields = []*descriptor.Field{nameField, roleField}
- userField := &descriptor.Field{
- Message: updateMsg,
- FieldMessage: userMsg,
- FieldDescriptorProto: updateMsg.GetField()[0],
- }
- userField.JsonName = proto.String("userObject")
- updateMsg.Fields = []*descriptor.Field{userField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String("user_service.proto"),
- MessageType: []*descriptorpb.DescriptorProto{userDesc, updateDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{userMsg, updateMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: updateMsg,
- ResponseType: userMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/users/{user_object.name}",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "user_object",
- },
- {
- Name: "name",
- },
- }),
- Target: nameField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: []descriptor.FieldPathComponent{
- {
- Name: "user_object",
- Target: userField,
- },
- },
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := GetPaths(result)
- if got, want := len(paths), 1; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- if got, want := paths[0], "/v1/users/{userObject.name}"; got != want {
- t.Fatalf("Wrong results path, got %s want %s", got, want)
- }
- var operation = *result.getPathItemObject("/v1/users/{userObject.name}").Post
- if got, want := len(operation.Parameters), 2; got != want {
- t.Fatalf("Parameters length differed, got %d want %d", got, want)
- }
- if got, want := operation.Parameters[0].Name, "userObject.name"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].In, "path"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].Name, "userObject"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].In, "body"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- // The body parameter should be inlined and not contain 'name', as this is a path parameter.
- schema := operation.Parameters[1].Schema
- if got, want := schema.Ref, ""; got != want {
- t.Fatalf("Wrong reference, got %s want %s", got, want)
- }
- props := schema.Properties
- if props == nil {
- t.Fatal("No properties on body parameter")
- }
- if got, want := len(*props), 1; got != want {
- t.Fatalf("Properties length differed, got %d want %d", got, want)
- }
- for _, v := range *props {
- if got, want := v.Key, "role"; got != want {
- t.Fatalf("Wrong key for property, got %s want %s", got, want)
- }
- }
- }
- func TestRenderServicesWithBodyFieldHasFieldMask(t *testing.T) {
- userDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("User"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("name"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("role"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- }
- updateDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("UpdateUserRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("user_object"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.User"),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("update_mask"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".google.protobuf.FieldMask"),
- Number: proto.Int32(2),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("UpdateUser"),
- InputType: proto.String("UpdateUserRequest"),
- OutputType: proto.String("User"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("UserService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- userMsg := &descriptor.Message{
- DescriptorProto: userDesc,
- }
- updateMsg := &descriptor.Message{
- DescriptorProto: updateDesc,
- }
- nameField := &descriptor.Field{
- Message: userMsg,
- FieldDescriptorProto: userMsg.GetField()[0],
- }
- nameField.JsonName = proto.String("name")
- roleField := &descriptor.Field{
- Message: userMsg,
- FieldDescriptorProto: userMsg.GetField()[1],
- }
- roleField.JsonName = proto.String("role")
- userMsg.Fields = []*descriptor.Field{nameField, roleField}
- userField := &descriptor.Field{
- Message: updateMsg,
- FieldMessage: userMsg,
- FieldDescriptorProto: updateMsg.GetField()[0],
- }
- userField.JsonName = proto.String("userObject")
- updateMaskField := &descriptor.Field{
- Message: updateMsg,
- FieldDescriptorProto: updateMsg.GetField()[1],
- }
- updateMaskField.JsonName = proto.String("updateMask")
- updateMsg.Fields = []*descriptor.Field{userField, updateMaskField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String("user_service.proto"),
- Dependency: []string{"google/well_known.proto"},
- MessageType: []*descriptorpb.DescriptorProto{userDesc, updateDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{userMsg, updateMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: updateMsg,
- ResponseType: userMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "PATCH",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/users/{user_object.name}",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "user_object",
- },
- {
- Name: "name",
- },
- }),
- Target: nameField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: []descriptor.FieldPathComponent{
- {
- Name: "user_object",
- Target: userField,
- },
- },
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- reg.SetAllowPatchFeature(true)
- err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{
- {
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("google/well_known.proto"),
- Package: proto.String("google.protobuf"),
- Dependency: []string{},
- MessageType: []*descriptorpb.DescriptorProto{
- protodesc.ToDescriptorProto((&field_mask.FieldMask{}).ProtoReflect().Descriptor()),
- },
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("google/well_known"),
- },
- },
- file.FileDescriptorProto,
- }})
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := GetPaths(result)
- if got, want := len(paths), 1; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- if got, want := paths[0], "/v1/users/{userObject.name}"; got != want {
- t.Fatalf("Wrong results path, got %s want %s", got, want)
- }
- var operation = *result.getPathItemObject("/v1/users/{userObject.name}").Patch
- if got, want := len(operation.Parameters), 2; got != want {
- t.Fatalf("Parameters length differed, got %d want %d", got, want)
- }
- if got, want := operation.Parameters[0].Name, "userObject.name"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].In, "path"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].Name, "userObject"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].In, "body"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- }
- func TestRenderServicesWithColonInPath(t *testing.T) {
- jsonSchema := &openapi_options.JSONSchema{
- FieldConfiguration: &openapi_options.JSONSchema_FieldConfiguration{
- PathParamName: "overrideField",
- },
- }
- var fieldOptions = new(descriptorpb.FieldOptions)
- proto.SetExtension(fieldOptions, openapi_options.E_Openapiv2Field, jsonSchema)
- reqDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- Options: fieldOptions,
- },
- },
- }
- resDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyResponse"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("MyService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- reqMsg := &descriptor.Message{
- DescriptorProto: reqDesc,
- }
- resMsg := &descriptor.Message{
- DescriptorProto: resDesc,
- }
- reqField := &descriptor.Field{
- Message: reqMsg,
- FieldDescriptorProto: reqMsg.GetField()[0],
- }
- resField := &descriptor.Field{
- Message: resMsg,
- FieldDescriptorProto: resMsg.GetField()[0],
- }
- reqField.JsonName = proto.String("field")
- resField.JsonName = proto.String("field")
- reqMsg.Fields = []*descriptor.Field{reqField}
- resMsg.Fields = []*descriptor.Field{resField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String(",my_service.proto"),
- MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{reqMsg, resMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/my/{field}:foo",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "field",
- },
- }),
- Target: reqField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- err := reg.Load(reqFromFile(&file))
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := GetPaths(result)
- if got, want := len(paths), 1; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- if got, want := paths[0], "/my/{overrideField}:foo"; got != want {
- t.Fatalf("Wrong results path, got %s want %s", got, want)
- }
- var operation = *result.getPathItemObject("/my/{overrideField}:foo").Post
- if got, want := len(operation.Parameters), 2; got != want {
- t.Fatalf("Parameters length differed, got %d want %d", got, want)
- }
- if got, want := operation.Parameters[0].Name, "overrideField"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].In, "path"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].Type, "string"; got != want {
- t.Fatalf("Wrong parameter type, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].Name, "body"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].In, "body"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- }
- func TestRenderServicesWithDoubleColonInPath(t *testing.T) {
- reqDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- resDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyResponse"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("MyService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- reqMsg := &descriptor.Message{
- DescriptorProto: reqDesc,
- }
- resMsg := &descriptor.Message{
- DescriptorProto: resDesc,
- }
- reqField := &descriptor.Field{
- Message: reqMsg,
- FieldDescriptorProto: reqMsg.GetField()[0],
- }
- resField := &descriptor.Field{
- Message: resMsg,
- FieldDescriptorProto: resMsg.GetField()[0],
- }
- reqField.JsonName = proto.String("field")
- resField.JsonName = proto.String("field")
- reqMsg.Fields = []*descriptor.Field{reqField}
- resMsg.Fields = []*descriptor.Field{resField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String(",my_service.proto"),
- MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{reqMsg, resMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/my/{field}:foo:bar",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "field",
- },
- }),
- Target: reqField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- err := reg.Load(reqFromFile(&file))
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := GetPaths(result)
- if got, want := len(paths), 1; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- if got, want := paths[0], "/my/{field}:foo:bar"; got != want {
- t.Fatalf("Wrong results path, got %s want %s", got, want)
- }
- var operation = *result.getPathItemObject("/my/{field}:foo:bar").Post
- if got, want := len(operation.Parameters), 2; got != want {
- t.Fatalf("Parameters length differed, got %d want %d", got, want)
- }
- if got, want := operation.Parameters[0].Name, "field"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].In, "path"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].Type, "string"; got != want {
- t.Fatalf("Wrong parameter type, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].Name, "body"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].In, "body"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- }
- func TestRenderServicesWithColonLastInPath(t *testing.T) {
- reqDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- resDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyResponse"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("MyService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- reqMsg := &descriptor.Message{
- DescriptorProto: reqDesc,
- }
- resMsg := &descriptor.Message{
- DescriptorProto: resDesc,
- }
- reqField := &descriptor.Field{
- Message: reqMsg,
- FieldDescriptorProto: reqMsg.GetField()[0],
- }
- resField := &descriptor.Field{
- Message: resMsg,
- FieldDescriptorProto: resMsg.GetField()[0],
- }
- reqField.JsonName = proto.String("field")
- resField.JsonName = proto.String("field")
- reqMsg.Fields = []*descriptor.Field{reqField}
- resMsg.Fields = []*descriptor.Field{resField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String(",my_service.proto"),
- MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{reqMsg, resMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/my/{field}:",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "field",
- },
- }),
- Target: reqField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- err := reg.Load(reqFromFile(&file))
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := GetPaths(result)
- if got, want := len(paths), 1; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- if got, want := paths[0], "/my/{field}:"; got != want {
- t.Fatalf("Wrong results path, got %s want %s", got, want)
- }
- var operation = *result.getPathItemObject("/my/{field}:").Post
- if got, want := len(operation.Parameters), 2; got != want {
- t.Fatalf("Parameters length differed, got %d want %d", got, want)
- }
- if got, want := operation.Parameters[0].Name, "field"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].In, "path"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].Type, "string"; got != want {
- t.Fatalf("Wrong parameter type, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].Name, "body"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].In, "body"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- }
- func TestRenderServicesWithColonInSegment(t *testing.T) {
- reqDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- resDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyResponse"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("MyService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- reqMsg := &descriptor.Message{
- DescriptorProto: reqDesc,
- }
- resMsg := &descriptor.Message{
- DescriptorProto: resDesc,
- }
- reqField := &descriptor.Field{
- Message: reqMsg,
- FieldDescriptorProto: reqMsg.GetField()[0],
- }
- resField := &descriptor.Field{
- Message: resMsg,
- FieldDescriptorProto: resMsg.GetField()[0],
- }
- reqField.JsonName = proto.String("field")
- resField.JsonName = proto.String("field")
- reqMsg.Fields = []*descriptor.Field{reqField}
- resMsg.Fields = []*descriptor.Field{resField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String(",my_service.proto"),
- MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{reqMsg, resMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/my/{field=segment/wi:th}",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "field",
- },
- }),
- Target: reqField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- err := reg.Load(reqFromFile(&file))
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := GetPaths(result)
- if got, want := len(paths), 1; got != want {
- t.Fatalf("Results path length differed, got %d want %d", got, want)
- }
- if got, want := paths[0], "/my/{field}"; got != want {
- t.Fatalf("Wrong results path, got %s want %s", got, want)
- }
- var operation = *result.getPathItemObject("/my/{field}").Post
- if got, want := len(operation.Parameters), 2; got != want {
- t.Fatalf("Parameters length differed, got %d want %d", got, want)
- }
- if got, want := operation.Parameters[0].Name, "field"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].In, "path"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].Type, "string"; got != want {
- t.Fatalf("Wrong parameter type, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].Name, "body"; got != want {
- t.Fatalf("Wrong parameter name, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].In, "body"; got != want {
- t.Fatalf("Wrong parameter location, got %s want %s", got, want)
- }
- }
- func TestRenderServiceWithHeaderParameters(t *testing.T) {
- file := func() descriptor.File {
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- Options: &descriptorpb.MethodOptions{},
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- return descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo",
- },
- },
- },
- },
- },
- },
- },
- }
- }
- type test struct {
- file func() descriptor.File
- openapiOperation *openapi_options.Operation
- parameters openapiParametersObject
- }
- tests := map[string]*test{
- "type string": {
- file: file,
- openapiOperation: &openapi_options.Operation{
- Parameters: &openapi_options.Parameters{
- Headers: []*openapi_options.HeaderParameter{
- {
- Name: "X-Custom-Header",
- Type: openapi_options.HeaderParameter_STRING,
- },
- },
- },
- },
- parameters: openapiParametersObject{
- {
- Name: "X-Custom-Header",
- In: "header",
- Type: "string",
- },
- },
- },
- "type string with format": {
- file: file,
- openapiOperation: &openapi_options.Operation{
- Parameters: &openapi_options.Parameters{
- Headers: []*openapi_options.HeaderParameter{
- {
- Name: "X-Custom-Header",
- Type: openapi_options.HeaderParameter_STRING,
- Format: "uuid",
- },
- },
- },
- },
- parameters: openapiParametersObject{
- {
- Name: "X-Custom-Header",
- In: "header",
- Type: "string",
- Format: "uuid",
- },
- },
- },
- "type integer": {
- file: file,
- openapiOperation: &openapi_options.Operation{
- Parameters: &openapi_options.Parameters{
- Headers: []*openapi_options.HeaderParameter{
- {
- Name: "X-Custom-Header",
- Type: openapi_options.HeaderParameter_INTEGER,
- },
- },
- },
- },
- parameters: openapiParametersObject{
- {
- Name: "X-Custom-Header",
- In: "header",
- Type: "integer",
- },
- },
- },
- "type number": {
- file: file,
- openapiOperation: &openapi_options.Operation{
- Parameters: &openapi_options.Parameters{
- Headers: []*openapi_options.HeaderParameter{
- {
- Name: "X-Custom-Header",
- Type: openapi_options.HeaderParameter_NUMBER,
- },
- },
- },
- },
- parameters: openapiParametersObject{
- {
- Name: "X-Custom-Header",
- In: "header",
- Type: "number",
- },
- },
- },
- "type boolean": {
- file: file,
- openapiOperation: &openapi_options.Operation{
- Parameters: &openapi_options.Parameters{
- Headers: []*openapi_options.HeaderParameter{
- {
- Name: "X-Custom-Header",
- Type: openapi_options.HeaderParameter_BOOLEAN,
- },
- },
- },
- },
- parameters: openapiParametersObject{
- {
- Name: "X-Custom-Header",
- In: "header",
- Type: "boolean",
- },
- },
- },
- "header required": {
- file: file,
- openapiOperation: &openapi_options.Operation{
- Parameters: &openapi_options.Parameters{
- Headers: []*openapi_options.HeaderParameter{
- {
- Name: "X-Custom-Header",
- Required: true,
- Type: openapi_options.HeaderParameter_STRING,
- },
- },
- },
- },
- parameters: openapiParametersObject{
- {
- Name: "X-Custom-Header",
- In: "header",
- Required: true,
- Type: "string",
- },
- },
- },
- }
- for name, test := range tests {
- test := test
- t.Run(name, func(t *testing.T) {
- file := test.file()
- proto.SetExtension(
- proto.Message(file.Services[0].Methods[0].Options),
- openapi_options.E_Openapiv2Operation,
- test.openapiOperation)
- reg := descriptor.NewRegistry()
- fileCL := crossLinkFixture(&file)
- err := reg.Load(reqFromFile(fileCL))
- if err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- params := result.getPathItemObject("/v1/echo").Get.Parameters
- if !reflect.DeepEqual(params, test.parameters) {
- t.Errorf("expected %+v, got %+v", test.parameters, params)
- }
- })
- }
- }
- func GetPaths(req *openapiSwaggerObject) []string {
- paths := make([]string, len(req.Paths))
- i := 0
- for _, k := range req.Paths {
- paths[i] = k.Path
- i++
- }
- return paths
- }
- func TestRenderServicesOpenapiPathsOrderPreserved(t *testing.T) {
- reqDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- resDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyResponse"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- meth1 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod1"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- meth2 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod2"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("MyService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth1, meth2},
- }
- reqMsg := &descriptor.Message{
- DescriptorProto: reqDesc,
- }
- resMsg := &descriptor.Message{
- DescriptorProto: resDesc,
- }
- reqField := &descriptor.Field{
- Message: reqMsg,
- FieldDescriptorProto: reqMsg.GetField()[0],
- }
- resField := &descriptor.Field{
- Message: resMsg,
- FieldDescriptorProto: resMsg.GetField()[0],
- }
- reqField.JsonName = proto.String("field")
- resField.JsonName = proto.String("field")
- reqMsg.Fields = []*descriptor.Field{reqField}
- resMsg.Fields = []*descriptor.Field{resField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String(",my_service.proto"),
- MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{reqMsg, resMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth1,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/c/cpath",
- },
- },
- },
- }, {
- MethodDescriptorProto: meth2,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/b/bpath",
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetPreserveRPCOrder(true)
- err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := result.Paths
- firstRPCPath := file.Services[0].Methods[0].Bindings[0].PathTmpl.Template
- secondRPCPath := file.Services[0].Methods[1].Bindings[0].PathTmpl.Template
- for i, pathData := range paths {
- switch i {
- case 0:
- if got, want := pathData.Path, firstRPCPath; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- case 1:
- if got, want := pathData.Path, secondRPCPath; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- }
- }
- }
- func TestRenderServicesOpenapiPathsOrderPreservedMultipleServices(t *testing.T) {
- reqDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- resDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyResponse"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- meth1 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod1"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- meth2 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod2"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- meth3 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod3"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- meth4 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod4"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- svc1 := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("MyServiceOne"),
- Method: []*descriptorpb.MethodDescriptorProto{meth1, meth2},
- }
- svc2 := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("MyServiceTwo"),
- Method: []*descriptorpb.MethodDescriptorProto{meth3, meth4},
- }
- reqMsg := &descriptor.Message{
- DescriptorProto: reqDesc,
- }
- resMsg := &descriptor.Message{
- DescriptorProto: resDesc,
- }
- reqField := &descriptor.Field{
- Message: reqMsg,
- FieldDescriptorProto: reqMsg.GetField()[0],
- }
- resField := &descriptor.Field{
- Message: resMsg,
- FieldDescriptorProto: resMsg.GetField()[0],
- }
- reqField.JsonName = proto.String("field")
- resField.JsonName = proto.String("field")
- reqMsg.Fields = []*descriptor.Field{reqField}
- resMsg.Fields = []*descriptor.Field{resField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String(",my_service.proto"),
- MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc1, svc2},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{reqMsg, resMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc1,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth1,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/g/gpath",
- },
- },
- },
- }, {
- MethodDescriptorProto: meth2,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/f/fpath",
- },
- },
- },
- },
- },
- }, {
- ServiceDescriptorProto: svc1,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth3,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/c/cpath",
- },
- },
- },
- }, {
- MethodDescriptorProto: meth4,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/b/bpath",
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetPreserveRPCOrder(true)
- reg.SetUseJSONNamesForFields(true)
- err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := result.Paths
- firstRPCPath := file.Services[0].Methods[0].Bindings[0].PathTmpl.Template
- secondRPCPath := file.Services[0].Methods[1].Bindings[0].PathTmpl.Template
- thirdRPCPath := file.Services[1].Methods[0].Bindings[0].PathTmpl.Template
- fourthRPCPath := file.Services[1].Methods[1].Bindings[0].PathTmpl.Template
- for i, pathData := range paths {
- switch i {
- case 0:
- if got, want := pathData.Path, firstRPCPath; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- case 1:
- if got, want := pathData.Path, secondRPCPath; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- case 2:
- if got, want := pathData.Path, thirdRPCPath; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- case 3:
- if got, want := pathData.Path, fourthRPCPath; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- }
- }
- }
- func TestRenderServicesOpenapiPathsOrderPreservedAdditionalBindings(t *testing.T) {
- reqDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyRequest"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- resDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("MyResponse"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("field"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- },
- }
- meth1 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod1"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- meth2 := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("MyMethod2"),
- InputType: proto.String("MyRequest"),
- OutputType: proto.String("MyResponse"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("MyService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth1, meth2},
- }
- reqMsg := &descriptor.Message{
- DescriptorProto: reqDesc,
- }
- resMsg := &descriptor.Message{
- DescriptorProto: resDesc,
- }
- reqField := &descriptor.Field{
- Message: reqMsg,
- FieldDescriptorProto: reqMsg.GetField()[0],
- }
- resField := &descriptor.Field{
- Message: resMsg,
- FieldDescriptorProto: resMsg.GetField()[0],
- }
- reqField.JsonName = proto.String("field")
- resField.JsonName = proto.String("field")
- reqMsg.Fields = []*descriptor.Field{reqField}
- resMsg.Fields = []*descriptor.Field{resField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Package: proto.String("example"),
- Name: proto.String(",my_service.proto"),
- MessageType: []*descriptorpb.DescriptorProto{reqDesc, resDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{reqMsg, resMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth1,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/c/cpath",
- },
- }, {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/additionalbinding",
- },
- },
- },
- }, {
- MethodDescriptorProto: meth2,
- RequestType: reqMsg,
- ResponseType: resMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/b/bpath",
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetPreserveRPCOrder(true)
- reg.SetUseJSONNamesForFields(true)
- err := reg.Load(&pluginpb.CodeGeneratorRequest{ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto}})
- if err != nil {
- t.Fatalf("failed to reg.Load(): %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- paths := result.Paths
- if err != nil {
- t.Fatalf("failed to obtain extension paths: %v", err)
- }
- firstRPCPath := file.Services[0].Methods[0].Bindings[0].PathTmpl.Template
- firstRPCPathAdditionalBinding := file.Services[0].Methods[0].Bindings[1].PathTmpl.Template
- secondRPCPath := file.Services[0].Methods[1].Bindings[0].PathTmpl.Template
- for i, pathData := range paths {
- switch i {
- case 0:
- if got, want := pathData.Path, firstRPCPath; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- case 1:
- if got, want := pathData.Path, firstRPCPathAdditionalBinding; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- case 2:
- if got, want := pathData.Path, secondRPCPath; got != want {
- t.Fatalf("RPC path order not preserved, got %s want %s", got, want)
- }
- }
- }
- }
- func TestArrayMessageItemsType(t *testing.T) {
- msgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("children"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.ExampleMessage"),
- Number: proto.Int32(1),
- JsonName: proto.String("children"),
- },
- },
- }
- nestDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("NestDescMessage"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("children"),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.ExampleMessage"),
- Number: proto.Int32(1),
- JsonName: proto.String("children"),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("NestDescMessage"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgDesc,
- }
- nsg := &descriptor.Message{
- DescriptorProto: nestDesc,
- }
- msg.Fields = []*descriptor.Field{
- {
- Message: msg,
- FieldDescriptorProto: msg.GetField()[0],
- },
- }
- nsg.Fields = []*descriptor.Field{
- {
- Message: nsg,
- FieldDescriptorProto: nsg.GetField()[0],
- },
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgDesc, nestDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg, nsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: nsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- Body: &descriptor.Body{
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{}),
- },
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(true)
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(&file)
- if err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{
- {
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("acme/example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgDesc, nestDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("acme/example"),
- },
- },
- },
- }); err != nil {
- t.Errorf("reg.Load(%#v) failed with %v; want success", reg, err)
- return
- }
- expect := openapiDefinitionsObject{
- "rpcStatus": openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "object",
- },
- Properties: &openapiSchemaObjectProperties{
- keyVal{
- Key: "code",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "integer",
- Format: "int32",
- },
- },
- },
- keyVal{
- Key: "message",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- keyVal{
- Key: "details",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{
- schemaCore: schemaCore{
- Type: "object",
- Ref: "#/definitions/protobufAny",
- },
- },
- },
- },
- },
- },
- },
- "exampleExampleMessage": openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "object",
- },
- Properties: &openapiSchemaObjectProperties{
- keyVal{
- Key: "children",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{
- schemaCore: schemaCore{
- Type: "object",
- Ref: "#/definitions/exampleExampleMessage",
- },
- },
- },
- },
- },
- },
- },
- "exampleNestDescMessage": openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "object",
- },
- Properties: &openapiSchemaObjectProperties{
- keyVal{
- Key: "children",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "array",
- Items: &openapiItemsObject{
- schemaCore: schemaCore{
- Type: "object",
- Ref: "#/definitions/exampleExampleMessage",
- },
- },
- },
- },
- },
- },
- },
- "protobufAny": openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "object",
- },
- Properties: &openapiSchemaObjectProperties{
- keyVal{
- Key: "@type",
- Value: openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- },
- },
- },
- AdditionalProperties: &openapiSchemaObject{},
- },
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", reg, err)
- return
- }
- if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := expect, result.Definitions, "Produces"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, is, want)
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestQueryParameterType(t *testing.T) {
- ntDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("AddressEntry"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("key"),
- Number: proto.Int32(1),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- JsonName: proto.String("key"),
- },
- {
- Name: proto.String("value"),
- Number: proto.Int32(2),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
- JsonName: proto.String("value"),
- },
- },
- Options: &descriptorpb.MessageOptions{
- MapEntry: proto.Bool(true),
- },
- }
- msgDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("Person"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("Address"),
- Number: proto.Int32(1),
- Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".example.com.Person.AddressEntry"),
- JsonName: proto.String("Address"),
- },
- },
- NestedType: []*descriptorpb.DescriptorProto{
- ntDesc,
- },
- }
- nesteDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleResponse"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("Key"),
- Number: proto.Int32(1),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- JsonName: proto.String("Key"),
- },
- {
- Name: proto.String("Value"),
- Number: proto.Int32(2),
- Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
- Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
- JsonName: proto.String("Value"),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("Person"),
- OutputType: proto.String("ExampleResponse"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- msg := &descriptor.Message{
- DescriptorProto: msgDesc,
- }
- nt := &descriptor.Message{
- DescriptorProto: ntDesc,
- }
- nest := &descriptor.Message{
- DescriptorProto: nesteDesc,
- }
- msg.Fields = []*descriptor.Field{
- {
- Message: msg,
- FieldDescriptorProto: msg.GetField()[0],
- },
- }
- nt.Fields = []*descriptor.Field{
- {
- Message: nt,
- FieldDescriptorProto: msg.GetField()[0],
- },
- }
- nest.Fields = []*descriptor.Field{
- {
- Message: nest,
- FieldDescriptorProto: nest.GetField()[0],
- },
- {
- Message: nest,
- FieldDescriptorProto: nest.GetField()[1],
- },
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("person.proto"),
- Package: proto.String("example.com"),
- MessageType: []*descriptorpb.DescriptorProto{msgDesc, nesteDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg, nest},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: nest,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo",
- },
- },
- },
- },
- },
- },
- },
- }
- expect := openapiPathsObject{{
- Path: "/v1/echo",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Parameters: openapiParametersObject{
- {
- Name: "Address[string]",
- 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`,
- In: "query",
- Type: "integer",
- },
- },
- },
- },
- }}
- reg := descriptor.NewRegistry()
- reg.SetUseJSONNamesForFields(false)
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- fileCL := crossLinkFixture(&file)
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{
- {
- Name: proto.String("person.proto"),
- Package: proto.String("example.com"),
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- MessageType: []*descriptorpb.DescriptorProto{msgDesc, nesteDesc},
- Service: []*descriptorpb.ServiceDescriptorProto{},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("person.proto"),
- },
- },
- },
- })
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- result, err := applyTemplate(param{File: fileCL, reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, is, name := []string{"application/json"}, result.Produces, "Produces"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, is, want)
- }
- if want, is, name := expect[0].PathItemObject.Get.Parameters, result.getPathItemObject("/v1/echo").Get.Parameters, "Produces"; !reflect.DeepEqual(is, want) {
- t.Errorf("applyTemplate(%#v).%s = %v want to be %v", file, name, is, want)
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- func TestApplyTemplateRequestWithServerStreamingHttpBody(t *testing.T) {
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Echo"),
- InputType: proto.String(".google.api.HttpBody"),
- OutputType: proto.String(".google.api.HttpBody"),
- ClientStreaming: proto.Bool(false),
- ServerStreaming: proto.Bool(true),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- httpBodyFile, err := protoregistry.GlobalFiles.FindFileByPath("google/api/httpbody.proto")
- if err != nil {
- t.Fatal(err)
- }
- httpBodyFile.SourceLocations()
- desc, err := protoregistry.GlobalFiles.FindDescriptorByName("google.api.HttpBody")
- if err != nil {
- t.Fatal(err)
- }
- msg := &descriptor.Message{
- DescriptorProto: protodesc.ToDescriptorProto(desc.(protoreflect.MessageDescriptor)),
- File: &descriptor.File{
- FileDescriptorProto: protodesc.ToFileDescriptorProto(httpBodyFile),
- },
- }
- anyFile, err := protoregistry.GlobalFiles.FindFileByPath("google/protobuf/any.proto")
- if err != nil {
- t.Fatal(err)
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- Dependency: []string{
- "google/api/httpbody.proto",
- },
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo",
- },
- },
- },
- },
- },
- },
- },
- }
- reg := descriptor.NewRegistry()
- if err := AddErrorDefs(reg); err != nil {
- t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err)
- return
- }
- err = reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{
- protodesc.ToFileDescriptorProto(anyFile),
- protodesc.ToFileDescriptorProto(httpBodyFile),
- file.FileDescriptorProto,
- },
- })
- if err != nil {
- t.Fatalf("failed to load code generator request: %v", err)
- }
- result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err)
- return
- }
- if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want)
- }
- if _, ok := result.getPathItemObject("/v1/echo").Post.Responses["200"]; !ok {
- t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.getPathItemObject("/v1/echo").Post.Responses["200"]`)
- } else {
- if want, got, name := "A successful response.(streaming responses)", result.getPathItemObject("/v1/echo").Post.Responses["200"].Description, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- streamExampleExampleMessage := result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema
- if want, got, name := "string", streamExampleExampleMessage.Type, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- if want, got, name := "binary", streamExampleExampleMessage.Format, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Format`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- if want, got, name := "Free form byte stream", streamExampleExampleMessage.Title, `result.getPathItemObject("/v1/echo").Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) {
- t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want)
- }
- if len(*streamExampleExampleMessage.Properties) != 0 {
- t.Errorf("applyTemplate(%#v).Properties should be empty", file)
- }
- }
- // If there was a failure, print out the input and the json result for debugging.
- if t.Failed() {
- t.Errorf("had: %s", file)
- t.Errorf("got: %s", fmt.Sprint(result))
- }
- }
- // Returns the openapiPathItemObject associated with a path.
- func (so openapiSwaggerObject) getPathItemObject(path string) openapiPathItemObject {
- for _, pathData := range so.Paths {
- if pathData.Path == path {
- return pathData.PathItemObject
- }
- }
- return openapiPathItemObject{}
- }
- func TestGetPathItemObjectSwaggerObjectMethod(t *testing.T) {
- testCases := [...]struct {
- testName string
- swaggerObject openapiSwaggerObject
- path string
- expectedPathItemObject openapiPathItemObject
- }{
- {
- testName: "Path present in swagger object",
- swaggerObject: openapiSwaggerObject{Paths: openapiPathsObject{{
- Path: "a/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }}},
- path: "a/path",
- expectedPathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }, {
- testName: "Path not present in swaggerObject",
- swaggerObject: openapiSwaggerObject{Paths: openapiPathsObject{{
- Path: "a/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }}},
- path: "b/path",
- expectedPathItemObject: openapiPathItemObject{},
- }, {
- testName: "Path present in swaggerPathsObject with multiple paths",
- swaggerObject: openapiSwaggerObject{Paths: openapiPathsObject{{
- Path: "a/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }, {
- Path: "another/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "Another testful description",
- },
- },
- }}},
- path: "another/path",
- expectedPathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "Another testful description",
- },
- },
- }, {
- testName: "Path not present in swaggerObject with no paths",
- swaggerObject: openapiSwaggerObject{},
- path: "b/path",
- expectedPathItemObject: openapiPathItemObject{},
- },
- }
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.testName, func(t *testing.T) {
- actualPathItemObject := tc.swaggerObject.getPathItemObject(tc.path)
- if isEqual := reflect.DeepEqual(actualPathItemObject, tc.expectedPathItemObject); !isEqual {
- t.Fatalf("Got pathItemObject: %#v, want pathItemObject: %#v", actualPathItemObject, tc.expectedPathItemObject)
- }
- })
- }
- }
- func TestGetPathItemObjectFunction(t *testing.T) {
- testCases := [...]struct {
- testName string
- paths openapiPathsObject
- path string
- expectedPathItemObject openapiPathItemObject
- expectedIsPathPresent bool
- }{
- {
- testName: "Path present in openapiPathsObject",
- paths: openapiPathsObject{{
- Path: "a/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }},
- path: "a/path",
- expectedPathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- expectedIsPathPresent: true,
- }, {
- testName: "Path not present in openapiPathsObject",
- paths: openapiPathsObject{{
- Path: "a/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }},
- path: "b/path",
- expectedPathItemObject: openapiPathItemObject{},
- expectedIsPathPresent: false,
- }, {
- testName: "Path present in openapiPathsObject with multiple paths",
- paths: openapiPathsObject{{
- Path: "a/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }, {
- Path: "another/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "Another testful description",
- },
- },
- }},
- path: "another/path",
- expectedPathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "Another testful description",
- },
- },
- expectedIsPathPresent: true,
- }, {
- testName: "Path not present in empty openapiPathsObject",
- paths: openapiPathsObject{},
- path: "b/path",
- expectedPathItemObject: openapiPathItemObject{},
- expectedIsPathPresent: false,
- },
- }
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.testName, func(t *testing.T) {
- actualPathItemObject, actualIsPathPresent := getPathItemObject(tc.paths, tc.path)
- if isEqual := reflect.DeepEqual(actualPathItemObject, tc.expectedPathItemObject); !isEqual {
- t.Fatalf("Got pathItemObject: %#v, want pathItemObject: %#v", actualPathItemObject, tc.expectedPathItemObject)
- }
- if actualIsPathPresent != tc.expectedIsPathPresent {
- t.Fatalf("Got isPathPresent bool: %t, want isPathPresent bool: %t", actualIsPathPresent, tc.expectedIsPathPresent)
- }
- })
- }
- }
- func TestUpdatePaths(t *testing.T) {
- testCases := [...]struct {
- testName string
- paths openapiPathsObject
- pathToUpdate string
- newPathItemObject openapiPathItemObject
- expectedUpdatedPaths openapiPathsObject
- }{
- {
- testName: "Path present in openapiPathsObject, pathItemObject updated.",
- paths: openapiPathsObject{{
- Path: "a/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }},
- pathToUpdate: "a/path",
- newPathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A newly updated testful description",
- },
- },
- expectedUpdatedPaths: openapiPathsObject{{
- Path: "a/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A newly updated testful description",
- },
- },
- }},
- }, {
- testName: "Path not present in openapiPathsObject, new path data appended.",
- paths: openapiPathsObject{{
- Path: "c/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }},
- pathToUpdate: "b/path",
- newPathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A new testful description to add",
- },
- },
- expectedUpdatedPaths: openapiPathsObject{{
- Path: "c/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A testful description",
- },
- },
- }, {
- Path: "b/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A new testful description to add",
- },
- },
- }},
- }, {
- testName: "No paths present in openapiPathsObject, new path data appended.",
- paths: openapiPathsObject{},
- pathToUpdate: "b/path",
- newPathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A new testful description to add",
- },
- },
- expectedUpdatedPaths: openapiPathsObject{{
- Path: "b/path",
- PathItemObject: openapiPathItemObject{
- Get: &openapiOperationObject{
- Description: "A new testful description to add",
- },
- },
- }},
- },
- }
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.testName, func(t *testing.T) {
- updatePaths(&tc.paths, tc.pathToUpdate, tc.newPathItemObject)
- if pathsCorrectlyUpdated := reflect.DeepEqual(tc.paths, tc.expectedUpdatedPaths); !pathsCorrectlyUpdated {
- t.Fatalf("Paths not correctly updated. Want %#v, got %#v", tc.expectedUpdatedPaths, tc.paths)
- }
- })
- }
- }
- // Test that enum values have internal comments removed
- func TestEnumValueProtoComments(t *testing.T) {
- reg := descriptor.NewRegistry()
- name := "kind"
- comments := "(-- this is a comment --)"
- enum := &descriptor.Enum{
- EnumDescriptorProto: &descriptorpb.EnumDescriptorProto{
- Name: &name,
- },
- File: &descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- Name: new(string),
- Package: new(string),
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{
- Location: []*descriptorpb.SourceCodeInfo_Location{
- &descriptorpb.SourceCodeInfo_Location{
- LeadingComments: &comments,
- },
- },
- },
- },
- },
- }
- comments = enumValueProtoComments(reg, enum)
- if comments != "" {
- t.Errorf("expected '', got '%v'", comments)
- }
- }
- func MustMarshal(v interface{}) []byte {
- b, err := json.Marshal(v)
- if err != nil {
- panic(err)
- }
- return b
- }
- func TestMergeTags(t *testing.T) {
- testCases := [...]struct {
- testName string
- existingTags []openapiTagObject
- newTags []openapiTagObject
- expectedMergedTags []openapiTagObject
- }{
- {
- testName: "Simple merge.",
- existingTags: []openapiTagObject{{
- Name: "tag1",
- Description: "tag1 description",
- }},
- newTags: []openapiTagObject{{
- Name: "tag2",
- Description: "tag2 description",
- }},
- expectedMergedTags: []openapiTagObject{{
- Name: "tag1",
- Description: "tag1 description",
- }, {
- Name: "tag2",
- Description: "tag2 description",
- }},
- },
- {
- testName: "Merge description",
- existingTags: []openapiTagObject{{
- Name: "tag1",
- Description: "tag1 description",
- }, {
- Name: "tag2",
- }, {
- Name: "tag3",
- Description: "tag3 description",
- }},
- newTags: []openapiTagObject{{
- Name: "tag2",
- Description: "tag2 description",
- }},
- expectedMergedTags: []openapiTagObject{{
- Name: "tag1",
- Description: "tag1 description",
- }, {
- Name: "tag2",
- Description: "tag2 description",
- }, {
- Name: "tag3",
- Description: "tag3 description",
- }},
- },
- {
- testName: "Merge external docs",
- existingTags: []openapiTagObject{{
- Name: "tag1",
- ExternalDocs: &openapiExternalDocumentationObject{},
- }, {
- Name: "tag2",
- }, {
- Name: "tag3",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "tag3 description",
- },
- }, {
- Name: "tag4",
- ExternalDocs: &openapiExternalDocumentationObject{
- URL: "tag4 url",
- },
- }},
- newTags: []openapiTagObject{{
- Name: "tag1",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "tag1 description",
- },
- }, {
- Name: "tag2",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "tag2 description",
- URL: "tag2 url",
- },
- }, {
- Name: "tag3",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "ignored tag3 description",
- URL: "tag3 url",
- },
- }, {
- Name: "tag4",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "tag4 description",
- },
- }},
- expectedMergedTags: []openapiTagObject{{
- Name: "tag1",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "tag1 description",
- },
- }, {
- Name: "tag2",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "tag2 description",
- URL: "tag2 url",
- },
- }, {
- Name: "tag3",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "tag3 description",
- URL: "tag3 url",
- },
- }, {
- Name: "tag4",
- ExternalDocs: &openapiExternalDocumentationObject{
- Description: "tag4 description",
- URL: "tag4 url",
- },
- }},
- },
- {
- testName: "Merge extensions",
- existingTags: []openapiTagObject{{
- Name: "tag1",
- extensions: []extension{{key: "x-key1", value: MustMarshal("key1 extension")}},
- }, {
- Name: "tag2",
- extensions: []extension{
- {key: "x-key1", value: MustMarshal("key1 extension")},
- {key: "x-key2", value: MustMarshal("key2 extension")},
- },
- }, {
- Name: "tag3",
- extensions: []extension{
- {key: "x-key1", value: MustMarshal("key1 extension")},
- },
- }, {
- Name: "tag4",
- extensions: nil,
- }},
- newTags: []openapiTagObject{{
- Name: "tag1",
- extensions: []extension{{key: "x-key2", value: MustMarshal("key2 extension")}},
- }, {
- Name: "tag2",
- extensions: []extension{
- {key: "x-key1", value: MustMarshal("key1 extension")},
- {key: "x-key2", value: MustMarshal("ignored key2 extension")},
- {key: "x-key3", value: MustMarshal("key3 extension")},
- },
- }, {
- Name: "tag3",
- extensions: nil,
- }, {
- Name: "tag4",
- extensions: []extension{
- {key: "x-key1", value: MustMarshal("key1 extension")},
- },
- }},
- expectedMergedTags: []openapiTagObject{{
- Name: "tag1",
- extensions: []extension{
- {key: "x-key1", value: MustMarshal("key1 extension")},
- {key: "x-key2", value: MustMarshal("key2 extension")},
- },
- }, {
- Name: "tag2",
- extensions: []extension{
- {key: "x-key1", value: MustMarshal("key1 extension")},
- {key: "x-key2", value: MustMarshal("key2 extension")},
- {key: "x-key3", value: MustMarshal("key3 extension")},
- },
- }, {
- Name: "tag3",
- extensions: []extension{
- {key: "x-key1", value: MustMarshal("key1 extension")},
- },
- }, {
- Name: "tag4",
- extensions: []extension{
- {key: "x-key1", value: MustMarshal("key1 extension")},
- },
- }},
- },
- }
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.testName, func(t *testing.T) {
- mergedTags := mergeTags(tc.existingTags, tc.newTags)
- if !reflect.DeepEqual(tc.expectedMergedTags, mergedTags) {
- t.Fatalf("%s: Tags not correctly merged. Want %#v, got %#v", tc.testName, tc.expectedMergedTags, mergedTags)
- }
- })
- }
- }
- func TestApiVisibilityOption(t *testing.T) {
- reg := descriptor.NewRegistry()
- msgdesc := &descriptorpb.DescriptorProto{
- Name: proto.String("ExampleMessage"),
- }
- msg := &descriptor.Message{
- DescriptorProto: msgdesc,
- }
- methodExample := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("Example"),
- InputType: proto.String("ExampleMessage"),
- OutputType: proto.String("ExampleMessage"),
- }
- serviceOptions := &descriptorpb.ServiceOptions{}
- proto.SetExtension(serviceOptions, visibility.E_ApiVisibility, &visibility.VisibilityRule{
- Restriction: "INTERNAL",
- })
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("ExampleService"),
- Options: serviceOptions,
- Method: []*descriptorpb.MethodDescriptorProto{methodExample},
- }
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("example.proto"),
- Package: proto.String("example"),
- MessageType: []*descriptorpb.DescriptorProto{msgdesc},
- Service: []*descriptorpb.ServiceDescriptorProto{svc},
- Options: &descriptorpb.FileOptions{
- GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
- },
- },
- GoPkg: descriptor.GoPackage{
- Path: "example.com/path/to/example/example.pb",
- Name: "example_pb",
- },
- Messages: []*descriptor.Message{msg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: methodExample,
- RequestType: msg,
- ResponseType: msg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "GET",
- Body: &descriptor.Body{FieldPath: nil},
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/example",
- },
- },
- },
- },
- },
- },
- },
- }
- err := reg.Load(&pluginpb.CodeGeneratorRequest{
- ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
- })
- if err != nil {
- t.Errorf("failed to reg.Load(req): %v", err)
- }
- actual, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg})
- if err != nil {
- t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
- }
- if len(actual.Definitions) != 0 {
- t.Fatal("Definition should be excluded by api visibility option")
- }
- }
|