12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200 |
- 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 previously 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 ensure 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 itself 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
- }
- requiredField := []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
- 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.Error(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.Error(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(int64(math.MaxInt32) + 1),
- "int32",
- errors.New("the provided default value \"2147483648\" does not match provided format \"int32\""),
- },
- {
- "integer",
- fmt.Sprint(int64(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) {
- 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) {
- 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) {
- 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 getParameters(names []string) []descriptor.Parameter {
- params := make([]descriptor.Parameter, 0)
- for _, name := range names {
- params = append(params, descriptor.Parameter{
- Target: &descriptor.Field{
- FieldDescriptorProto: &descriptorpb.FieldDescriptorProto{
- Name: proto.String(name),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- },
- Message: &descriptor.Message{
- File: &descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{},
- },
- DescriptorProto: &descriptorpb.DescriptorProto{
- Name: proto.String(""),
- },
- },
- FieldMessage: nil,
- ForcePrefixedName: false,
- },
- FieldPath: []descriptor.FieldPathComponent{{
- Name: name,
- Target: nil,
- }},
- Method: nil,
- })
- }
- return params
- }
- func TestTemplateToOpenAPIPathExpandSlashed(t *testing.T) {
- tests := []struct {
- input string
- expected string
- pathParams []descriptor.Parameter
- expectedPathParams []string
- useJSONNames bool
- }{
- {"/v1/{name=projects/*/documents/*}:exportResults", "/v1/projects/{project}/documents/{document}:exportResults", getParameters([]string{"name"}), []string{"project", "document"}, true},
- {"/test/{name=*}", "/test/{name}", getParameters([]string{"name"}), []string{"name"}, true},
- {"/test/{name=*}/", "/test/{name}/", getParameters([]string{"name"}), []string{"name"}, true},
- {"/test/{name=test_cases/*}/", "/test/test_cases/{testCase}/", getParameters([]string{"name"}), []string{"testCase"}, true},
- {"/test/{name=test_cases/*}/", "/test/test_cases/{test_case}/", getParameters([]string{"name"}), []string{"test_case"}, false},
- {"/test/{test_type.name=test_cases/*}/", "/test/test_cases/{testCase}/", getParameters([]string{"test_type.name"}), []string{"testCase"}, true},
- {"/test/{test_type.name=test_cases/*}/", "/test/test_cases/{test_case}/", getParameters([]string{"test_type.name"}), []string{"test_case"}, false},
- }
- reg := descriptor.NewRegistry()
- reg.SetExpandSlashedPathPatterns(true)
- for _, data := range tests {
- reg.SetUseJSONNamesForFields(data.useJSONNames)
- actualParts, actualParams := templateToExpandedPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), data.pathParams)
- if data.expected != actualParts {
- t.Errorf("Expected templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expected, actualParts)
- }
- pathParamsNames := make([]string, 0)
- for _, param := range actualParams {
- pathParamsNames = append(pathParamsNames, param.FieldPath[0].Name)
- }
- if !reflect.DeepEqual(data.expectedPathParams, pathParamsNames) {
- t.Errorf("Expected mutated path params in templateToOpenAPIPath(%v) = %v, actual: %v", data.input, data.expectedPathParams, data.pathParams)
- }
- }
- }
- func TestExpandedPathParametersStringType(t *testing.T) {
- tests := []struct {
- input string
- }{
- {"/test/{name=test_cases/*}/"}, {"/v1/{name=projects/*/documents/*}:exportResults"},
- }
- reg := descriptor.NewRegistry()
- reg.SetExpandSlashedPathPatterns(true)
- expectedParamType := openapiSchemaObject{
- schemaCore: schemaCore{
- Type: "string",
- },
- }
- for _, data := range tests {
- _, actualParams := templateToExpandedPath(data.input, reg, generateFieldsForJSONReservedName(), generateMsgsForJSONReservedName(), getParameters([]string{"name"}))
- for _, param := range actualParams {
- refs := make(refMap)
- actualParamType := schemaOfField(param.Target, reg, refs)
- if !reflect.DeepEqual(actualParamType, expectedParamType) {
- t.Errorf("Expected all path parameters to be type of 'string', actual: %#+v", actualParamType)
- }
- }
- }
- }
- 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) {
- 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 templateToExpandedPath(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message, pathParams []descriptor.Parameter) (string, []descriptor.Parameter) {
- pathParts, pathParams := expandPathPatterns(templateToParts(path, reg, fields, msgs), pathParams, reg)
- return partsToOpenAPIPath(pathParts, make(map[string]string)), pathParams
- }
- func TestFQMNToRegexpMap(t *testing.T) {
- 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) {
- 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",
- }
- fieldOptions := new(descriptorpb.FieldOptions)
- proto.SetExtension(fieldOptions, openapi_options.E_Openapiv2Field, jsonSchema)
- requiredField := []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
- requiredFieldOptions := new(descriptorpb.FieldOptions)
- proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, requiredField)
- outputOnlyField := []annotations.FieldBehavior{annotations.FieldBehavior_OUTPUT_ONLY}
- 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]+",
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- },
- },
- Title: "field title",
- Description: "field description",
- UniqueItems: true,
- 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]+",
- MaxProperties: 33,
- MinProperties: 22,
- Required: []string{"req"},
- ReadOnly: true,
- },
- },
- Title: "field title",
- Description: "field description",
- UniqueItems: true,
- 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"},
- }
- requiredField := new(descriptorpb.FieldOptions)
- proto.SetExtension(requiredField, openapi_options.E_Openapiv2Field, jsonSchema)
- fieldBehaviorRequired := []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
- requiredFieldOptions := new(descriptorpb.FieldOptions)
- proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, fieldBehaviorRequired)
- fieldBehaviorOutputOnlyField := []annotations.FieldBehavior{annotations.FieldBehavior_OUTPUT_ONLY}
- fieldBehaviorOutputOnlyOptions := new(descriptorpb.FieldOptions)
- proto.SetExtension(fieldBehaviorOutputOnlyOptions, annotations.E_FieldBehavior, fieldBehaviorOutputOnlyField)
- fieldVisibilityFieldInternal := &visibility.VisibilityRule{Restriction: "INTERNAL"}
- fieldVisibilityInternalOption := new(descriptorpb.FieldOptions)
- proto.SetExtension(fieldVisibilityInternalOption, visibility.E_FieldVisibility, fieldVisibilityFieldInternal)
- fieldVisibilityFieldPreview := &visibility.VisibilityRule{Restriction: "INTERNAL,PREVIEW"}
- 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)
- }
- 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)
- }
- 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",
- },
- }
- 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)
- }
- 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)
- }
- 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)
- }
- 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)
- }
- 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 TestRenderServicesOpenapiRequiredBodyFieldContainingPathParam(t *testing.T) {
- fieldBehaviorRequired := []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED}
- requiredFieldOptions := new(descriptorpb.FieldOptions)
- proto.SetExtension(requiredFieldOptions, annotations.E_FieldBehavior, fieldBehaviorRequired)
- bookDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("Book"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("name"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(1),
- },
- {
- Name: proto.String("type"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
- Number: proto.Int32(2),
- },
- },
- }
- addBookReqDesc := &descriptorpb.DescriptorProto{
- Name: proto.String("AddBookReq"),
- Field: []*descriptorpb.FieldDescriptorProto{
- {
- Name: proto.String("book"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
- TypeName: proto.String(".Book"),
- Number: proto.Int32(1),
- Options: requiredFieldOptions,
- },
- {
- Name: proto.String("libraryId"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_UINT32.Enum(),
- Number: proto.Int32(2),
- Options: requiredFieldOptions,
- },
- {
- Name: proto.String("isLatestEdition"),
- Type: descriptorpb.FieldDescriptorProto_TYPE_BOOL.Enum(),
- Number: proto.Int32(3),
- },
- },
- }
- meth := &descriptorpb.MethodDescriptorProto{
- Name: proto.String("AddBook"),
- InputType: proto.String("AddBookReq"),
- OutputType: proto.String("Book"),
- }
- svc := &descriptorpb.ServiceDescriptorProto{
- Name: proto.String("BookService"),
- Method: []*descriptorpb.MethodDescriptorProto{meth},
- }
- bookMsg := &descriptor.Message{
- DescriptorProto: bookDesc,
- }
- addBookReqMsg := &descriptor.Message{
- DescriptorProto: addBookReqDesc,
- }
- nameField := &descriptor.Field{
- Message: bookMsg,
- FieldDescriptorProto: bookMsg.GetField()[0],
- }
- typeField := &descriptor.Field{
- Message: bookMsg,
- FieldDescriptorProto: bookMsg.GetField()[1],
- }
- bookMsg.Fields = []*descriptor.Field{nameField, typeField}
- bookField := &descriptor.Field{
- Message: addBookReqMsg,
- FieldMessage: bookMsg,
- FieldDescriptorProto: addBookReqMsg.GetField()[0],
- }
- libraryIdField := &descriptor.Field{
- Message: addBookReqMsg,
- FieldDescriptorProto: addBookReqMsg.GetField()[1],
- }
- isLatestEditionField := &descriptor.Field{
- Message: addBookReqMsg,
- FieldDescriptorProto: addBookReqMsg.GetField()[2],
- }
- addBookReqMsg.Fields = []*descriptor.Field{bookField, libraryIdField, isLatestEditionField}
- file := descriptor.File{
- FileDescriptorProto: &descriptorpb.FileDescriptorProto{
- SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
- Name: proto.String("book.proto"),
- MessageType: []*descriptorpb.DescriptorProto{bookDesc, addBookReqDesc},
- 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{bookMsg, addBookReqMsg},
- Services: []*descriptor.Service{
- {
- ServiceDescriptorProto: svc,
- Methods: []*descriptor.Method{
- {
- MethodDescriptorProto: meth,
- RequestType: addBookReqMsg,
- ResponseType: bookMsg,
- Bindings: []*descriptor.Binding{
- {
- HTTPMethod: "POST",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/books/{book.type}",
- },
- PathParams: []descriptor.Parameter{
- {
- FieldPath: descriptor.FieldPath([]descriptor.FieldPathComponent{
- {
- Name: "book",
- },
- {
- Name: "type",
- },
- }),
- Target: typeField,
- },
- },
- Body: &descriptor.Body{
- FieldPath: []descriptor.FieldPathComponent{},
- },
- },
- },
- },
- },
- },
- },
- }
- 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)
- return
- }
- result, err := applyTemplate(param{File: fileCL, 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/books/{book.type}"; got != want {
- t.Fatalf("Wrong results path, got %s want %s", got, want)
- }
- operation := *result.getPathItemObject("/v1/books/{book.type}").Post
- if got, want := operation.Parameters[0].Name, "book.type"; got != want {
- t.Fatalf("Wrong parameter name 0, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[0].In, "path"; got != want {
- t.Fatalf("Wrong parameter location 0, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].Name, "body"; got != want {
- t.Fatalf("Wrong parameter name 1, got %s want %s", got, want)
- }
- if got, want := operation.Parameters[1].In, "body"; got != want {
- t.Fatalf("Wrong parameter location 1, got %s want %s", got, want)
- }
- if want, is, name := "#/definitions/BookServiceAddBookBody", operation.Parameters[1].Schema.schemaCore.Ref, "operation.Parameters[1].Schema.schemaCore.Ref"; !reflect.DeepEqual(is, want) {
- t.Fatalf("%s = %s want to be %s", name, want, is)
- }
- definition, found := result.Definitions["BookServiceAddBookBody"]
- if !found {
- t.Fatalf("expecting definition to contain BookServiceAddBookBody")
- }
- if want, is, name := 3, len(*definition.Properties), "len(*definition.Properties)"; !reflect.DeepEqual(is, want) {
- t.Fatalf("%s = %d want to be %d", name, want, is)
- }
- for index, keyValue := range []string{"book", "libraryId", "isLatestEdition"} {
- if got, want := (*definition.Properties)[index].Key, keyValue; got != want {
- t.Fatalf("Wrong definition property %d, got %s want %s", index, got, want)
- }
- }
- correctRequiredFields := []string{"book", "libraryId"}
- if got, want := definition.Required, correctRequiredFields; !reflect.DeepEqual(got, want) {
- t.Fatalf("Wrong required fields in body definition, 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{
- {
- 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")
- }
- }
- func TestRenderServicesOptionDeprecated(t *testing.T) {
- testCases := [...]struct {
- testName string
- methodOptions descriptorpb.MethodOptions
- openapiOperation *openapi_options.Operation
- expectedDeprecated bool
- }{
- {
- testName: "method option",
- methodOptions: descriptorpb.MethodOptions{
- Deprecated: proto.Bool(true),
- },
- expectedDeprecated: true,
- },
- {
- testName: "openapi option",
- openapiOperation: &openapi_options.Operation{
- Deprecated: true,
- },
- expectedDeprecated: true,
- },
- {
- testName: "empty openapi doesn't override method option",
- methodOptions: descriptorpb.MethodOptions{
- Deprecated: proto.Bool(true),
- },
- openapiOperation: &openapi_options.Operation{},
- expectedDeprecated: true,
- },
- }
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.testName, func(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"),
- Options: &tc.methodOptions,
- }
- 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",
- PathTmpl: httprule.Template{
- Version: 1,
- OpCodes: []int{0, 0},
- Template: "/v1/echo",
- },
- },
- },
- },
- },
- },
- },
- }
- if tc.openapiOperation != nil {
- proto.SetExtension(
- proto.Message(file.Services[0].Methods[0].Options),
- openapi_options.E_Openapiv2Operation,
- tc.openapiOperation,
- )
- }
- reg := descriptor.NewRegistry()
- reg.SetEnableRpcDeprecation(true)
- fileCL := crossLinkFixture(&file)
- if err := reg.Load(reqFromFile(fileCL)); 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)
- }
- got := result.getPathItemObject("/v1/echo").Get.Deprecated
- if got != tc.expectedDeprecated {
- t.Fatalf("Wrong deprecated field, got %v want %v", got, tc.expectedDeprecated)
- }
- })
- }
- }
|